xref: /haiku/src/add-ons/accelerants/intel_extreme/mode.cpp (revision 9e25244c5e9051f6cd333820d6332397361abd6c)
1 /*
2  * Copyright 2006-2010, Haiku, Inc. All Rights Reserved.
3  * Distributed under the terms of the MIT License.
4  *
5  * Support for i915 chipset and up based on the X driver,
6  * Copyright 2006-2007 Intel Corporation.
7  *
8  * Authors:
9  *		Axel Dörfler, axeld@pinc-software.de
10  */
11 
12 
13 #include <math.h>
14 #include <stdio.h>
15 #include <string.h>
16 
17 #include <Debug.h>
18 
19 #include <create_display_modes.h>
20 #include <ddc.h>
21 #include <edid.h>
22 #include <validate_display_mode.h>
23 
24 #include "accelerant_protos.h"
25 #include "accelerant.h"
26 #include "pll.h"
27 #include "Ports.h"
28 #include "utility.h"
29 
30 
31 #undef TRACE
32 #define TRACE_MODE
33 #ifdef TRACE_MODE
34 #	define TRACE(x...) _sPrintf("intel_extreme: " x)
35 #else
36 #	define TRACE(x...)
37 #endif
38 
39 #define ERROR(x...) _sPrintf("intel_extreme: " x)
40 #define CALLED(x...) TRACE("CALLED %s\n", __PRETTY_FUNCTION__)
41 
42 
43 static void
44 get_color_space_format(const display_mode &mode, uint32 &colorMode,
45 	uint32 &bytesPerRow, uint32 &bitsPerPixel)
46 {
47 	uint32 bytesPerPixel;
48 
49 	switch (mode.space) {
50 		case B_RGB32_LITTLE:
51 			if (gInfo->shared_info->device_type.InFamily(INTEL_FAMILY_LAKE)) {
52 				colorMode = DISPLAY_CONTROL_RGB32_SKY;
53 			} else {
54 				colorMode = DISPLAY_CONTROL_RGB32;
55 			}
56 			bytesPerPixel = 4;
57 			bitsPerPixel = 32;
58 			break;
59 		case B_RGB16_LITTLE:
60 			if (gInfo->shared_info->device_type.InFamily(INTEL_FAMILY_LAKE)) {
61 				colorMode = DISPLAY_CONTROL_RGB16_SKY;
62 			} else {
63 				colorMode = DISPLAY_CONTROL_RGB16;
64 			}
65 			bytesPerPixel = 2;
66 			bitsPerPixel = 16;
67 			break;
68 		case B_RGB15_LITTLE:
69 			if (gInfo->shared_info->device_type.InFamily(INTEL_FAMILY_LAKE)) {
70 				colorMode = DISPLAY_CONTROL_RGB15_SKY;
71 			} else {
72 				colorMode = DISPLAY_CONTROL_RGB15;
73 			}
74 			bytesPerPixel = 2;
75 			bitsPerPixel = 15;
76 			break;
77 		case B_CMAP8:
78 		default:
79 			if (gInfo->shared_info->device_type.InFamily(INTEL_FAMILY_LAKE)) {
80 				colorMode = DISPLAY_CONTROL_CMAP8_SKY;
81 			} else {
82 				colorMode = DISPLAY_CONTROL_CMAP8;
83 			}
84 			bytesPerPixel = 1;
85 			bitsPerPixel = 8;
86 			break;
87 	}
88 
89 	bytesPerRow = mode.virtual_width * bytesPerPixel;
90 
91 	// Make sure bytesPerRow is a multiple of 64
92 	if ((bytesPerRow & 63) != 0)
93 		bytesPerRow = (bytesPerRow + 63) & ~63;
94 }
95 
96 
97 static bool
98 sanitize_display_mode(display_mode& mode)
99 {
100 	uint16 pixelCount = 1;
101 	// Older cards require pixel count to be even
102 	if (gInfo->shared_info->device_type.InGroup(INTEL_GROUP_Gxx)
103 			|| gInfo->shared_info->device_type.InGroup(INTEL_GROUP_96x)
104 			|| gInfo->shared_info->device_type.InGroup(INTEL_GROUP_94x)
105 			|| gInfo->shared_info->device_type.InGroup(INTEL_GROUP_91x)
106 			|| gInfo->shared_info->device_type.InFamily(INTEL_FAMILY_8xx)) {
107 		pixelCount = 2;
108 	}
109 
110 	display_constraints constraints = {
111 		// resolution
112 		320, 4096, 200, 4096,
113 		// pixel clock
114 		gInfo->shared_info->pll_info.min_frequency,
115 		gInfo->shared_info->pll_info.max_frequency,
116 		// horizontal
117 		{pixelCount, 0, 8160, 32, 8192, 0, 8192},
118 		{1, 1, 8190, 2, 8192, 1, 8192}
119 	};
120 
121 	return sanitize_display_mode(mode, constraints,
122 		gInfo->has_edid ? &gInfo->edid_info : NULL);
123 }
124 
125 
126 // #pragma mark -
127 
128 
129 static void
130 set_frame_buffer_registers(uint32 offset)
131 {
132 	intel_shared_info &sharedInfo = *gInfo->shared_info;
133 	display_mode &mode = sharedInfo.current_mode;
134 	uint32 bytes_per_pixel = (sharedInfo.bits_per_pixel + 7) / 8;
135 
136 	if (sharedInfo.device_type.InGroup(INTEL_GROUP_96x)
137 		|| sharedInfo.device_type.InGroup(INTEL_GROUP_G4x)
138 		|| sharedInfo.device_type.InGroup(INTEL_GROUP_ILK)
139 		|| sharedInfo.device_type.InFamily(INTEL_FAMILY_SER5)
140 		|| sharedInfo.device_type.InFamily(INTEL_FAMILY_LAKE)
141 		|| sharedInfo.device_type.InFamily(INTEL_FAMILY_SOC0)) {
142 		if (sharedInfo.device_type.InGroup(INTEL_GROUP_HAS)) {
143 //			|| sharedInfo.device_type.InGroup(INTEL_GROUP_SKY)) {
144 			write32(INTEL_DISPLAY_A_OFFSET_HAS + offset,
145 				((uint32)mode.v_display_start << 16)
146 					| (uint32)mode.h_display_start);
147 			read32(INTEL_DISPLAY_A_OFFSET_HAS + offset);
148 		} else {
149 			write32(INTEL_DISPLAY_A_BASE + offset,
150 				mode.v_display_start * sharedInfo.bytes_per_row
151 				+ mode.h_display_start * bytes_per_pixel);
152 			read32(INTEL_DISPLAY_A_BASE + offset);
153 		}
154 		write32(INTEL_DISPLAY_A_SURFACE + offset, sharedInfo.frame_buffer_offset);
155 		read32(INTEL_DISPLAY_A_SURFACE + offset);
156 	} else {
157 		write32(INTEL_DISPLAY_A_BASE + offset, sharedInfo.frame_buffer_offset
158 			+ mode.v_display_start * sharedInfo.bytes_per_row
159 			+ mode.h_display_start * bytes_per_pixel);
160 		read32(INTEL_DISPLAY_A_BASE + offset);
161 	}
162 }
163 
164 
165 void
166 set_frame_buffer_base()
167 {
168 	// TODO we always set both displays to the same address. When we support
169 	// multiple framebuffers, they should get different addresses here.
170 	set_frame_buffer_registers(0);
171 	set_frame_buffer_registers(INTEL_DISPLAY_OFFSET);
172 }
173 
174 
175 static bool
176 limit_modes_for_gen3_lvds(display_mode* mode)
177 {
178 	// Filter out modes with resolution higher than the internal LCD can
179 	// display.
180 	// FIXME do this only for that display. The whole display mode logic
181 	// needs to be adjusted to know which display we're talking about.
182 	if (gInfo->shared_info->panel_timing.h_display < mode->timing.h_display)
183 		return false;
184 	if (gInfo->shared_info->panel_timing.v_display < mode->timing.v_display)
185 		return false;
186 
187 	return true;
188 }
189 
190 /*!	Creates the initial mode list of the primary accelerant.
191 	It's called from intel_init_accelerant().
192 */
193 status_t
194 create_mode_list(void)
195 {
196 	CALLED();
197 
198 	for (uint32 i = 0; i < gInfo->port_count; i++) {
199 		if (gInfo->ports[i] == NULL)
200 			continue;
201 
202 		status_t status = gInfo->ports[i]->GetEDID(&gInfo->edid_info);
203 		if (status == B_OK)
204 			gInfo->has_edid = true;
205 	}
206 	// use EDID found at boot time if there since we don't have any ourselves
207 	if (!gInfo->has_edid && gInfo->shared_info->has_vesa_edid_info) {
208 		TRACE("%s: Using VESA edid info\n", __func__);
209 		memcpy(&gInfo->edid_info, &gInfo->shared_info->vesa_edid_info,
210 			sizeof(edid1_info));
211 		// show in log what we got
212 		edid_dump(&gInfo->edid_info);
213 		gInfo->has_edid = true;
214 	}
215 
216 	display_mode* list;
217 	uint32 count = 0;
218 
219 	const color_space kSupportedSpaces[] = {B_RGB32_LITTLE, B_RGB16_LITTLE,
220 		B_CMAP8};
221 	const color_space* supportedSpaces;
222 	int colorSpaceCount;
223 
224 	if (gInfo->shared_info->device_type.Generation() >= 4) {
225 		// No B_RGB15, use our custom colorspace list
226 		supportedSpaces = kSupportedSpaces;
227 		colorSpaceCount = B_COUNT_OF(kSupportedSpaces);
228 	} else {
229 		supportedSpaces = NULL;
230 		colorSpaceCount = 0;
231 	}
232 
233 	// If no EDID, but have vbt from driver, use that mode
234 	if (!gInfo->has_edid && gInfo->shared_info->got_vbt) {
235 		// We could not read any EDID info. Fallback to creating a list with
236 		// only the mode set up by the BIOS.
237 
238 		check_display_mode_hook limitModes = NULL;
239 		if (gInfo->shared_info->device_type.Generation() < 4)
240 			limitModes = limit_modes_for_gen3_lvds;
241 
242 		display_mode mode;
243 		mode.timing = gInfo->shared_info->panel_timing;
244 		mode.space = B_RGB32;
245 		mode.virtual_width = mode.timing.h_display;
246 		mode.virtual_height = mode.timing.v_display;
247 		mode.h_display_start = 0;
248 		mode.v_display_start = 0;
249 		mode.flags = 0;
250 
251 		// TODO: support lower modes via scaling and windowing
252 		gInfo->mode_list_area = create_display_modes("intel extreme modes", NULL, &mode, 1,
253 			supportedSpaces, colorSpaceCount, limitModes, &list, &count);
254 	} else {
255 		// Otherwise return the 'real' list of modes
256 		gInfo->mode_list_area = create_display_modes("intel extreme modes",
257 			gInfo->has_edid ? &gInfo->edid_info : NULL, NULL, 0,
258 			supportedSpaces, colorSpaceCount, NULL, &list, &count);
259 	}
260 
261 	if (gInfo->mode_list_area < B_OK)
262 		return gInfo->mode_list_area;
263 
264 	gInfo->mode_list = list;
265 	gInfo->shared_info->mode_list_area = gInfo->mode_list_area;
266 	gInfo->shared_info->mode_count = count;
267 
268 	return B_OK;
269 }
270 
271 
272 void
273 wait_for_vblank(void)
274 {
275 	acquire_sem_etc(gInfo->shared_info->vblank_sem, 1, B_RELATIVE_TIMEOUT,
276 		21000);
277 		// With the output turned off via DPMS, we might not get any interrupts
278 		// anymore that's why we don't wait forever for it. At 50Hz, we're sure
279 		// to get a vblank in at most 20ms, so there is no need to wait longer
280 		// than that.
281 }
282 
283 
284 //	#pragma mark -
285 
286 
287 uint32
288 intel_accelerant_mode_count(void)
289 {
290 	CALLED();
291 	return gInfo->shared_info->mode_count;
292 }
293 
294 
295 status_t
296 intel_get_mode_list(display_mode* modeList)
297 {
298 	CALLED();
299 	memcpy(modeList, gInfo->mode_list,
300 		gInfo->shared_info->mode_count * sizeof(display_mode));
301 	return B_OK;
302 }
303 
304 
305 status_t
306 intel_propose_display_mode(display_mode* target, const display_mode* low,
307 	const display_mode* high)
308 {
309 	CALLED();
310 
311 	display_mode mode = *target;
312 
313 	if (sanitize_display_mode(*target)) {
314 		TRACE("Video mode was adjusted by sanitize_display_mode\n");
315 		TRACE("Initial mode: Hd %d Hs %d He %d Ht %d Vd %d Vs %d Ve %d Vt %d\n",
316 			mode.timing.h_display, mode.timing.h_sync_start,
317 			mode.timing.h_sync_end, mode.timing.h_total,
318 			mode.timing.v_display, mode.timing.v_sync_start,
319 			mode.timing.v_sync_end, mode.timing.v_total);
320 		TRACE("Sanitized: Hd %d Hs %d He %d Ht %d Vd %d Vs %d Ve %d Vt %d\n",
321 			target->timing.h_display, target->timing.h_sync_start,
322 			target->timing.h_sync_end, target->timing.h_total,
323 			target->timing.v_display, target->timing.v_sync_start,
324 			target->timing.v_sync_end, target->timing.v_total);
325 	}
326 	// (most) modeflags are outputs from us (the driver). So we should
327 	// set them depending on the mode and the current hardware config
328 	target->flags |= B_SCROLL;
329 
330 	return is_display_mode_within_bounds(*target, *low, *high)
331 		? B_OK : B_BAD_VALUE;
332 }
333 
334 
335 status_t
336 intel_set_display_mode(display_mode* mode)
337 {
338 	if (mode == NULL)
339 		return B_BAD_VALUE;
340 
341 	TRACE("%s(%" B_PRIu16 "x%" B_PRIu16 ", virtual: %" B_PRIu16 "x%" B_PRIu16 ")\n", __func__,
342 		mode->timing.h_display, mode->timing.v_display, mode->virtual_width, mode->virtual_height);
343 
344 	display_mode target = *mode;
345 
346 	if (intel_propose_display_mode(&target, &target, &target) != B_OK)
347 		return B_BAD_VALUE;
348 
349 	uint32 colorMode, bytesPerRow, bitsPerPixel;
350 	get_color_space_format(target, colorMode, bytesPerRow, bitsPerPixel);
351 
352 	// TODO: do not go further if the mode is identical to the current one.
353 	// This would avoid the screen being off when switching workspaces when they
354 	// have the same resolution.
355 
356 	intel_shared_info &sharedInfo = *gInfo->shared_info;
357 	Autolock locker(sharedInfo.accelerant_lock);
358 
359 	// First register dump
360 	//dump_registers();
361 
362 	// TODO: This may not be neccesary
363 	set_display_power_mode(B_DPMS_OFF);
364 
365 	// free old and allocate new frame buffer in graphics memory
366 
367 	intel_free_memory(sharedInfo.frame_buffer);
368 
369 	addr_t base;
370 	if (intel_allocate_memory(bytesPerRow * target.virtual_height, 0,
371 			base) < B_OK) {
372 		// oh, how did that happen? Unfortunately, there is no really good way
373 		// back. Try to restore a framebuffer for the previous mode, at least.
374 		if (intel_allocate_memory(sharedInfo.current_mode.virtual_height
375 				* sharedInfo.bytes_per_row, 0, base) == B_OK) {
376 			sharedInfo.frame_buffer = base;
377 			sharedInfo.frame_buffer_offset = base
378 				- (addr_t)sharedInfo.graphics_memory;
379 			set_frame_buffer_base();
380 		}
381 
382 		ERROR("%s: Failed to allocate framebuffer !\n", __func__);
383 		return B_NO_MEMORY;
384 	}
385 
386 	// clear frame buffer before using it
387 	memset((uint8*)base, 0, bytesPerRow * target.virtual_height);
388 	sharedInfo.frame_buffer = base;
389 	sharedInfo.frame_buffer_offset = base - (addr_t)sharedInfo.graphics_memory;
390 
391 #if 0
392 	if ((gInfo->head_mode & HEAD_MODE_TESTING) != 0) {
393 		// 1. Enable panel power as needed to retrieve panel configuration
394 		// (use AUX VDD enable bit)
395 			// skip, did detection already, might need that before that though
396 
397 		// 2. Enable PCH clock reference source and PCH SSC modulator,
398 		// wait for warmup (Can be done anytime before enabling port)
399 			// skip, most certainly already set up by bios to use other ports,
400 			// will need for coldstart though
401 
402 		// 3. If enabling CPU embedded DisplayPort A: (Can be done anytime
403 		// before enabling CPU pipe or port)
404 		//	a.	Enable PCH 120MHz clock source output to CPU, wait for DMI
405 		//		latency
406 		//	b.	Configure and enable CPU DisplayPort PLL in the DisplayPort A
407 		//		register, wait for warmup
408 			// skip, not doing eDP right now, should go into
409 			// EmbeddedDisplayPort class though
410 
411 		// 4. If enabling port on PCH: (Must be done before enabling CPU pipe
412 		// or FDI)
413 		//	a.	Enable PCH FDI Receiver PLL, wait for warmup plus DMI latency
414 		//	b.	Switch from Rawclk to PCDclk in FDI Receiver (FDI A OR FDI B)
415 		//	c.	[DevSNB] Enable CPU FDI Transmitter PLL, wait for warmup
416 		//	d.	[DevILK] CPU FDI PLL is always on and does not need to be
417 		//		enabled
418 		FDILink* link = pipe->FDILink();
419 		if (link != NULL) {
420 			link->Receiver().EnablePLL();
421 			link->Receiver().SwitchClock(true);
422 			link->Transmitter().EnablePLL();
423 		}
424 
425 		// 5. Enable CPU panel fitter if needed for hires, required for VGA
426 		// (Can be done anytime before enabling CPU pipe)
427 		PanelFitter* fitter = pipe->PanelFitter();
428 		if (fitter != NULL)
429 			fitter->Enable(mode);
430 
431 		// 6. Configure CPU pipe timings, M/N/TU, and other pipe settings
432 		// (Can be done anytime before enabling CPU pipe)
433 		pll_divisors divisors;
434 		compute_pll_divisors(target, divisors, false);
435 		pipe->ConfigureTimings(divisors);
436 
437 		// 7. Enable CPU pipe
438 		pipe->Enable();
439 
440 8. Configure and enable CPU planes (VGA or hires)
441 9. If enabling port on PCH:
442 		//	a.   Program PCH FDI Receiver TU size same as Transmitter TU size for TU error checking
443 		//	b.   Train FDI
444 		//		i. Set pre-emphasis and voltage (iterate if training steps fail)
445                     ii. Enable CPU FDI Transmitter and PCH FDI Receiver with Training Pattern 1 enabled.
446                    iii. Wait for FDI training pattern 1 time
447                    iv. Read PCH FDI Receiver ISR ([DevIBX-B+] IIR) for bit lock in bit 8 (retry at least once if no lock)
448                     v. Enable training pattern 2 on CPU FDI Transmitter and PCH FDI Receiver
449                    vi.  Wait for FDI training pattern 2 time
450                   vii. Read PCH FDI Receiver ISR ([DevIBX-B+] IIR) for symbol lock in bit 9 (retry at least once if no
451                         lock)
452                   viii. Enable normal pixel output on CPU FDI Transmitter and PCH FDI Receiver
453                    ix.  Wait for FDI idle pattern time for link to become active
454          c.   Configure and enable PCH DPLL, wait for PCH DPLL warmup (Can be done anytime before enabling
455               PCH transcoder)
456          d.   [DevCPT] Configure DPLL SEL to set the DPLL to transcoder mapping and enable DPLL to the
457               transcoder.
458          e.   [DevCPT] Configure DPLL_CTL DPLL_HDMI_multipler.
459          f.   Configure PCH transcoder timings, M/N/TU, and other transcoder settings (should match CPU settings).
460          g.   [DevCPT] Configure and enable Transcoder DisplayPort Control if DisplayPort will be used
461          h.   Enable PCH transcoder
462 10. Enable ports (DisplayPort must enable in training pattern 1)
463 11. Enable panel power through panel power sequencing
464 12. Wait for panel power sequencing to reach enabled steady state
465 13. Disable panel power override
466 14. If DisplayPort, complete link training
467 15. Enable panel backlight
468 	}
469 #endif
470 
471 	// make sure VGA display is disabled
472 	write32(INTEL_VGA_DISPLAY_CONTROL, VGA_DISPLAY_DISABLED);
473 	read32(INTEL_VGA_DISPLAY_CONTROL);
474 
475 	// Go over each port and set the display mode
476 	for (uint32 i = 0; i < gInfo->port_count; i++) {
477 		if (gInfo->ports[i] == NULL)
478 			continue;
479 		if (!gInfo->ports[i]->IsConnected())
480 			continue;
481 
482 		status_t status = gInfo->ports[i]->SetDisplayMode(&target, colorMode);
483 		if (status != B_OK)
484 			ERROR("%s: Unable to set display mode!\n", __func__);
485 	}
486 
487 	TRACE("%s: Port configuration completed successfully!\n", __func__);
488 
489 	// We set the same color mode across all pipes
490 	program_pipe_color_modes(colorMode);
491 
492 	// TODO: This may not be neccesary (see DPMS OFF at top)
493 	set_display_power_mode(sharedInfo.dpms_mode);
494 
495 	// Changing bytes per row seems to be ignored if the plane/pipe is turned
496 	// off
497 
498 	// Always set both pipes, just in case
499 	// TODO rework this when we get multiple head support with different
500 	// resolutions
501 	if (sharedInfo.device_type.InFamily(INTEL_FAMILY_LAKE)) {
502 		write32(INTEL_DISPLAY_A_BYTES_PER_ROW, bytesPerRow >> 6);
503 		write32(INTEL_DISPLAY_B_BYTES_PER_ROW, bytesPerRow >> 6);
504 	} else {
505 		write32(INTEL_DISPLAY_A_BYTES_PER_ROW, bytesPerRow);
506 		write32(INTEL_DISPLAY_B_BYTES_PER_ROW, bytesPerRow);
507 	}
508 
509 	// update shared info
510 	sharedInfo.current_mode = target;
511 	sharedInfo.bytes_per_row = bytesPerRow;
512 	sharedInfo.bits_per_pixel = bitsPerPixel;
513 
514 	set_frame_buffer_base();
515 		// triggers writing back double-buffered registers
516 		// which is INTEL_DISPLAY_X_BYTES_PER_ROW only apparantly
517 
518 	// Second register dump
519 	//dump_registers();
520 
521 	return B_OK;
522 }
523 
524 
525 status_t
526 intel_get_display_mode(display_mode* _currentMode)
527 {
528 	CALLED();
529 
530 	*_currentMode = gInfo->shared_info->current_mode;
531 
532 	// This seems unreliable. We should always know the current_mode
533 	//retrieve_current_mode(*_currentMode, INTEL_DISPLAY_A_PLL);
534 	return B_OK;
535 }
536 
537 
538 status_t
539 intel_get_preferred_mode(display_mode* preferredMode)
540 {
541 	TRACE("%s\n", __func__);
542 	display_mode mode;
543 
544 	if (gInfo->has_edid || !gInfo->shared_info->got_vbt
545 			|| !gInfo->shared_info->device_type.IsMobile()) {
546 		return B_ERROR;
547 	}
548 
549 	mode.timing = gInfo->shared_info->panel_timing;
550 	mode.space = B_RGB32;
551 	mode.virtual_width = mode.timing.h_display;
552 	mode.virtual_height = mode.timing.v_display;
553 	mode.h_display_start = 0;
554 	mode.v_display_start = 0;
555 	mode.flags = 0;
556 	memcpy(preferredMode, &mode, sizeof(mode));
557 	return B_OK;
558 }
559 
560 
561 status_t
562 intel_get_edid_info(void* info, size_t size, uint32* _version)
563 {
564 	if (!gInfo->has_edid)
565 		return B_ERROR;
566 	if (size < sizeof(struct edid1_info))
567 		return B_BUFFER_OVERFLOW;
568 
569 	memcpy(info, &gInfo->edid_info, sizeof(struct edid1_info));
570 	*_version = EDID_VERSION_1;
571 	return B_OK;
572 }
573 
574 
575 static int32_t
576 intel_get_backlight_register(bool read)
577 {
578 	if (gInfo->shared_info->pch_info == INTEL_PCH_NONE)
579 		return MCH_BLC_PWM_CTL;
580 
581 	if (read)
582 		return PCH_SBLC_PWM_CTL2;
583 	else
584 		return PCH_BLC_PWM_CTL;
585 }
586 
587 
588 status_t
589 intel_set_brightness(float brightness)
590 {
591 	CALLED();
592 
593 	if (brightness < 0 || brightness > 1)
594 		return B_BAD_VALUE;
595 
596 	uint32_t period = read32(intel_get_backlight_register(true)) >> 16;
597 
598 	// The "duty cycle" is a proportion of the period (0 = backlight off,
599 	// period = maximum brightness). The low bit must be masked out because
600 	// it is apparently used for something else on some Atom machines (no
601 	// reference to that in the documentation that I know of).
602 	// Additionally we don't want it to be completely 0 here, because then
603 	// it becomes hard to turn the display on again (at least until we get
604 	// working ACPI keyboard shortcuts for this). So always keep the backlight
605 	// at least a little bit on for now.
606 	uint32_t duty = (uint32_t)(period * brightness) & 0xfffe;
607 	if (duty == 0 && period != 0)
608 		duty = 2;
609 
610 	write32(intel_get_backlight_register(false), duty | (period << 16));
611 
612 	return B_OK;
613 }
614 
615 
616 status_t
617 intel_get_brightness(float* brightness)
618 {
619 	CALLED();
620 
621 	if (brightness == NULL)
622 		return B_BAD_VALUE;
623 
624 	uint16_t period = read32(intel_get_backlight_register(true)) >> 16;
625 	uint16_t   duty = read32(intel_get_backlight_register(false)) & 0xffff;
626 	*brightness = (float)duty / period;
627 
628 	return B_OK;
629 }
630 
631 
632 status_t
633 intel_get_frame_buffer_config(frame_buffer_config* config)
634 {
635 	CALLED();
636 
637 	uint32 offset = gInfo->shared_info->frame_buffer_offset;
638 
639 	config->frame_buffer = gInfo->shared_info->graphics_memory + offset;
640 	config->frame_buffer_dma
641 		= (uint8*)gInfo->shared_info->physical_graphics_memory + offset;
642 	config->bytes_per_row = gInfo->shared_info->bytes_per_row;
643 
644 	return B_OK;
645 }
646 
647 
648 status_t
649 intel_get_pixel_clock_limits(display_mode* mode, uint32* _low, uint32* _high)
650 {
651 	CALLED();
652 
653 	if (_low != NULL) {
654 		// lower limit of about 48Hz vertical refresh
655 		uint32 totalClocks = (uint32)mode->timing.h_total
656 			* (uint32)mode->timing.v_total;
657 		uint32 low = (totalClocks * 48L) / 1000L;
658 		if (low < gInfo->shared_info->pll_info.min_frequency)
659 			low = gInfo->shared_info->pll_info.min_frequency;
660 		else if (low > gInfo->shared_info->pll_info.max_frequency)
661 			return B_ERROR;
662 
663 		*_low = low;
664 	}
665 
666 	if (_high != NULL)
667 		*_high = gInfo->shared_info->pll_info.max_frequency;
668 
669 	return B_OK;
670 }
671 
672 
673 status_t
674 intel_move_display(uint16 horizontalStart, uint16 verticalStart)
675 {
676 	intel_shared_info &sharedInfo = *gInfo->shared_info;
677 	Autolock locker(sharedInfo.accelerant_lock);
678 
679 	display_mode &mode = sharedInfo.current_mode;
680 
681 	if (horizontalStart + mode.timing.h_display > mode.virtual_width
682 		|| verticalStart + mode.timing.v_display > mode.virtual_height)
683 		return B_BAD_VALUE;
684 
685 	mode.h_display_start = horizontalStart;
686 	mode.v_display_start = verticalStart;
687 
688 	set_frame_buffer_base();
689 
690 	return B_OK;
691 }
692 
693 
694 status_t
695 intel_get_timing_constraints(display_timing_constraints* constraints)
696 {
697 	CALLED();
698 	return B_ERROR;
699 }
700 
701 
702 void
703 intel_set_indexed_colors(uint count, uint8 first, uint8* colors, uint32 flags)
704 {
705 	TRACE("%s(colors = %p, first = %u)\n", __func__, colors, first);
706 
707 	if (colors == NULL)
708 		return;
709 
710 	Autolock locker(gInfo->shared_info->accelerant_lock);
711 
712 	for (; count-- > 0; first++) {
713 		uint32 color = colors[0] << 16 | colors[1] << 8 | colors[2];
714 		colors += 3;
715 
716 		write32(INTEL_DISPLAY_A_PALETTE + first * sizeof(uint32), color);
717 		write32(INTEL_DISPLAY_B_PALETTE + first * sizeof(uint32), color);
718 	}
719 }
720