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