xref: /haiku/src/add-ons/accelerants/intel_extreme/mode.cpp (revision 9e19c5aea45313875b22104ca2f00fd392e3233c)
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 	} else if (gInfo->shared_info->pch_info >= INTEL_PCH_SPT)
590 		return BLC_PWM_PCH_CTL2;
591 
592 	if (gInfo->shared_info->pch_info == INTEL_PCH_NONE)
593 		return MCH_BLC_PWM_CTL;
594 
595 	// FIXME this mixup of south and north registers seems very strange; it should either be
596 	// a single register with both period and duty in it, or two separate registers.
597 	if (period)
598 		return PCH_SOUTH_BLC_PWM_PERIOD;
599 	else
600 		return PCH_BLC_PWM_CTL;
601 }
602 
603 
604 status_t
605 intel_set_brightness(float brightness)
606 {
607 	CALLED();
608 
609 	if (brightness < 0 || brightness > 1)
610 		return B_BAD_VALUE;
611 
612 	// The "duty cycle" is a proportion of the period (0 = backlight off,
613 	// period = maximum brightness).
614 	// Additionally we don't want it to be completely 0 here, because then
615 	// it becomes hard to turn the display on again (at least until we get
616 	// working ACPI keyboard shortcuts for this). So always keep the backlight
617 	// at least a little bit on for now.
618 
619 	if (gInfo->shared_info->pch_info >= INTEL_PCH_CNP) {
620 		uint32_t period = read32(intel_get_backlight_register(true));
621 
622 		uint32_t duty = (uint32_t)(period * brightness);
623 		duty = std::max(duty, (uint32_t)gInfo->shared_info->min_brightness);
624 
625 		write32(intel_get_backlight_register(false), duty);
626 	} else 	if (gInfo->shared_info->pch_info >= INTEL_PCH_SPT) {
627 		uint32_t period = read32(intel_get_backlight_register(true)) >> 16;
628 
629 		uint32_t duty = (uint32_t)(period * brightness) & 0xffff;
630 		duty = std::max(duty, (uint32_t)gInfo->shared_info->min_brightness);
631 
632 		write32(intel_get_backlight_register(false), duty | (period << 16));
633 	} else {
634 		// On older devices there is a single register with both period and duty cycle
635 		uint32 tmp = read32(intel_get_backlight_register(true));
636 		bool legacyMode = false;
637 		if (gInfo->shared_info->device_type.Generation() == 2
638 			|| gInfo->shared_info->device_type.IsModel(INTEL_MODEL_915M)
639 			|| gInfo->shared_info->device_type.IsModel(INTEL_MODEL_945M)) {
640 			legacyMode = (tmp & BLM_LEGACY_MODE) != 0;
641 		}
642 
643 		uint32_t period = tmp >> 16;
644 
645 		uint32_t mask = 0xffff;
646 		uint32_t shift = 0;
647 		if (gInfo->shared_info->device_type.Generation() < 4) {
648 			// The low bit must be masked out because
649 			// it is apparently used for something else on some Atom machines (no
650 			// reference to that in the documentation that I know of).
651 			mask = 0xfffe;
652 			shift = 1;
653 			period = tmp >> 17;
654 		}
655 		if (legacyMode)
656 			period *= 0xfe;
657 		uint32_t duty = (uint32_t)(period * brightness);
658 		if (legacyMode) {
659 			uint8 lpc = duty / 0xff + 1;
660 			duty /= lpc;
661 
662 			// set pci config reg with lpc
663 			intel_brightness_legacy brightnessLegacy;
664 			brightnessLegacy.magic = INTEL_PRIVATE_DATA_MAGIC;
665 			brightnessLegacy.lpc = lpc;
666 			ioctl(gInfo->device, INTEL_SET_BRIGHTNESS_LEGACY, &brightnessLegacy,
667 				sizeof(brightnessLegacy));
668 		}
669 
670 		duty = std::max(duty, (uint32_t)gInfo->shared_info->min_brightness);
671 		duty <<= shift;
672 
673 		write32(intel_get_backlight_register(false), (duty & mask) | (tmp & ~mask));
674 	}
675 
676 	return B_OK;
677 }
678 
679 
680 status_t
681 intel_get_brightness(float* brightness)
682 {
683 	CALLED();
684 
685 	if (brightness == NULL)
686 		return B_BAD_VALUE;
687 
688 	uint32_t duty;
689 	uint32_t period;
690 
691 	if (gInfo->shared_info->pch_info >= INTEL_PCH_CNP) {
692 		period = read32(intel_get_backlight_register(true));
693 		duty = read32(intel_get_backlight_register(false));
694 	} else {
695 		uint32 tmp = read32(intel_get_backlight_register(true));
696 		bool legacyMode = false;
697 		if (gInfo->shared_info->device_type.Generation() == 2
698 			|| gInfo->shared_info->device_type.IsModel(INTEL_MODEL_915M)
699 			|| gInfo->shared_info->device_type.IsModel(INTEL_MODEL_945M)) {
700 			legacyMode = (tmp & BLM_LEGACY_MODE) != 0;
701 		}
702 		period = tmp >> 16;
703 		duty = read32(intel_get_backlight_register(false)) & 0xffff;
704 		if (legacyMode) {
705 			period *= 0xff;
706 
707 			// get lpc from pci config reg
708 			intel_brightness_legacy brightnessLegacy;
709 			brightnessLegacy.magic = INTEL_PRIVATE_DATA_MAGIC;
710 			ioctl(gInfo->device, INTEL_GET_BRIGHTNESS_LEGACY, &brightnessLegacy,
711 				sizeof(brightnessLegacy));
712 			duty *= brightnessLegacy.lpc;
713 		}
714 		if (gInfo->shared_info->device_type.Generation() < 4) {
715 			period >>= 1;
716 			duty >>= 1;
717 		}
718 	}
719 	*brightness = (float)duty / period;
720 
721 	return B_OK;
722 }
723 
724 
725 status_t
726 intel_get_frame_buffer_config(frame_buffer_config* config)
727 {
728 	CALLED();
729 
730 	uint32 offset = gInfo->shared_info->frame_buffer_offset;
731 
732 	config->frame_buffer = gInfo->shared_info->graphics_memory + offset;
733 	config->frame_buffer_dma
734 		= (uint8*)gInfo->shared_info->physical_graphics_memory + offset;
735 	config->bytes_per_row = gInfo->shared_info->bytes_per_row;
736 
737 	return B_OK;
738 }
739 
740 
741 status_t
742 intel_get_pixel_clock_limits(display_mode* mode, uint32* _low, uint32* _high)
743 {
744 	CALLED();
745 
746 	if (_low != NULL) {
747 		// lower limit of about 48Hz vertical refresh
748 		uint32 totalClocks = (uint32)mode->timing.h_total
749 			* (uint32)mode->timing.v_total;
750 		uint32 low = (totalClocks * 48L) / 1000L;
751 		if (low < gInfo->shared_info->pll_info.min_frequency)
752 			low = gInfo->shared_info->pll_info.min_frequency;
753 		else if (low > gInfo->shared_info->pll_info.max_frequency)
754 			return B_ERROR;
755 
756 		*_low = low;
757 	}
758 
759 	if (_high != NULL)
760 		*_high = gInfo->shared_info->pll_info.max_frequency;
761 
762 	return B_OK;
763 }
764 
765 
766 status_t
767 intel_move_display(uint16 horizontalStart, uint16 verticalStart)
768 {
769 	intel_shared_info &sharedInfo = *gInfo->shared_info;
770 	Autolock locker(sharedInfo.accelerant_lock);
771 
772 	display_mode &mode = sharedInfo.current_mode;
773 
774 	if (horizontalStart + mode.timing.h_display > mode.virtual_width
775 		|| verticalStart + mode.timing.v_display > mode.virtual_height)
776 		return B_BAD_VALUE;
777 
778 	mode.h_display_start = horizontalStart;
779 	mode.v_display_start = verticalStart;
780 
781 	set_frame_buffer_base();
782 
783 	return B_OK;
784 }
785 
786 
787 status_t
788 intel_get_timing_constraints(display_timing_constraints* constraints)
789 {
790 	CALLED();
791 	return B_ERROR;
792 }
793 
794 
795 void
796 intel_set_indexed_colors(uint count, uint8 first, uint8* colors, uint32 flags)
797 {
798 	TRACE("%s(colors = %p, first = %u)\n", __func__, colors, first);
799 
800 	if (colors == NULL)
801 		return;
802 
803 	Autolock locker(gInfo->shared_info->accelerant_lock);
804 
805 	for (; count-- > 0; first++) {
806 		uint32 color = colors[0] << 16 | colors[1] << 8 | colors[2];
807 		colors += 3;
808 
809 		write32(INTEL_DISPLAY_A_PALETTE + first * sizeof(uint32), color);
810 		write32(INTEL_DISPLAY_B_PALETTE + first * sizeof(uint32), color);
811 	}
812 }
813