xref: /haiku/src/add-ons/accelerants/intel_extreme/mode.cpp (revision 6f80a9801fedbe7355c4360bd204ba746ec3ec2d)
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 			break;
207 		}
208 	}
209 	// use EDID found at boot time if there since we don't have any ourselves
210 	if (!gInfo->has_edid && gInfo->shared_info->has_vesa_edid_info) {
211 		TRACE("%s: Using VESA edid info\n", __func__);
212 		memcpy(&gInfo->edid_info, &gInfo->shared_info->vesa_edid_info,
213 			sizeof(edid1_info));
214 		// show in log what we got
215 		edid_dump(&gInfo->edid_info);
216 		gInfo->has_edid = true;
217 	}
218 
219 	display_mode* list;
220 	uint32 count = 0;
221 
222 	const color_space kSupportedSpaces[] = {B_RGB32_LITTLE, B_RGB16_LITTLE,
223 		B_CMAP8};
224 	const color_space* supportedSpaces;
225 	int colorSpaceCount;
226 
227 	if (gInfo->shared_info->device_type.Generation() >= 4) {
228 		// No B_RGB15, use our custom colorspace list
229 		supportedSpaces = kSupportedSpaces;
230 		colorSpaceCount = B_COUNT_OF(kSupportedSpaces);
231 	} else {
232 		supportedSpaces = NULL;
233 		colorSpaceCount = 0;
234 	}
235 
236 	// If no EDID, but have vbt from driver, use that mode
237 	if (!gInfo->has_edid && gInfo->shared_info->got_vbt) {
238 		// We could not read any EDID info. Fallback to creating a list with
239 		// only the mode set up by the BIOS.
240 
241 		check_display_mode_hook limitModes = NULL;
242 		if (gInfo->shared_info->device_type.Generation() < 4)
243 			limitModes = limit_modes_for_gen3_lvds;
244 
245 		display_mode mode;
246 		mode.timing = gInfo->shared_info->panel_timing;
247 		mode.space = B_RGB32;
248 		mode.virtual_width = mode.timing.h_display;
249 		mode.virtual_height = mode.timing.v_display;
250 		mode.h_display_start = 0;
251 		mode.v_display_start = 0;
252 		mode.flags = 0;
253 
254 		// TODO: support lower modes via scaling and windowing
255 		gInfo->mode_list_area = create_display_modes("intel extreme modes", NULL, &mode, 1,
256 			supportedSpaces, colorSpaceCount, limitModes, &list, &count);
257 	} else {
258 		// Otherwise return the 'real' list of modes
259 		gInfo->mode_list_area = create_display_modes("intel extreme modes",
260 			gInfo->has_edid ? &gInfo->edid_info : NULL, NULL, 0,
261 			supportedSpaces, colorSpaceCount, NULL, &list, &count);
262 	}
263 
264 	if (gInfo->mode_list_area < B_OK)
265 		return gInfo->mode_list_area;
266 
267 	gInfo->mode_list = list;
268 	gInfo->shared_info->mode_list_area = gInfo->mode_list_area;
269 	gInfo->shared_info->mode_count = count;
270 
271 	return B_OK;
272 }
273 
274 
275 void
276 wait_for_vblank(void)
277 {
278 	acquire_sem_etc(gInfo->shared_info->vblank_sem, 1, B_RELATIVE_TIMEOUT,
279 		21000);
280 		// With the output turned off via DPMS, we might not get any interrupts
281 		// anymore that's why we don't wait forever for it. At 50Hz, we're sure
282 		// to get a vblank in at most 20ms, so there is no need to wait longer
283 		// than that.
284 }
285 
286 
287 //	#pragma mark -
288 
289 
290 uint32
291 intel_accelerant_mode_count(void)
292 {
293 	CALLED();
294 	return gInfo->shared_info->mode_count;
295 }
296 
297 
298 status_t
299 intel_get_mode_list(display_mode* modeList)
300 {
301 	CALLED();
302 	memcpy(modeList, gInfo->mode_list,
303 		gInfo->shared_info->mode_count * sizeof(display_mode));
304 	return B_OK;
305 }
306 
307 
308 status_t
309 intel_propose_display_mode(display_mode* target, const display_mode* low,
310 	const display_mode* high)
311 {
312 	CALLED();
313 
314 	display_mode mode = *target;
315 
316 	if (sanitize_display_mode(*target)) {
317 		TRACE("Video mode was adjusted by sanitize_display_mode\n");
318 		TRACE("Initial mode: Hd %d Hs %d He %d Ht %d Vd %d Vs %d Ve %d Vt %d\n",
319 			mode.timing.h_display, mode.timing.h_sync_start,
320 			mode.timing.h_sync_end, mode.timing.h_total,
321 			mode.timing.v_display, mode.timing.v_sync_start,
322 			mode.timing.v_sync_end, mode.timing.v_total);
323 		TRACE("Sanitized: Hd %d Hs %d He %d Ht %d Vd %d Vs %d Ve %d Vt %d\n",
324 			target->timing.h_display, target->timing.h_sync_start,
325 			target->timing.h_sync_end, target->timing.h_total,
326 			target->timing.v_display, target->timing.v_sync_start,
327 			target->timing.v_sync_end, target->timing.v_total);
328 	}
329 	// (most) modeflags are outputs from us (the driver). So we should
330 	// set them depending on the mode and the current hardware config
331 	target->flags |= B_SCROLL;
332 
333 	return is_display_mode_within_bounds(*target, *low, *high)
334 		? B_OK : B_BAD_VALUE;
335 }
336 
337 
338 status_t
339 intel_set_display_mode(display_mode* mode)
340 {
341 	if (mode == NULL)
342 		return B_BAD_VALUE;
343 
344 	TRACE("%s(%" B_PRIu16 "x%" B_PRIu16 ", virtual: %" B_PRIu16 "x%" B_PRIu16 ")\n", __func__,
345 		mode->timing.h_display, mode->timing.v_display, mode->virtual_width, mode->virtual_height);
346 
347 	display_mode target = *mode;
348 
349 	if (intel_propose_display_mode(&target, &target, &target) != B_OK)
350 		return B_BAD_VALUE;
351 
352 	uint32 colorMode, bytesPerRow, bitsPerPixel;
353 	get_color_space_format(target, colorMode, bytesPerRow, bitsPerPixel);
354 
355 	// TODO: do not go further if the mode is identical to the current one.
356 	// This would avoid the screen being off when switching workspaces when they
357 	// have the same resolution.
358 
359 	intel_shared_info &sharedInfo = *gInfo->shared_info;
360 	Autolock locker(sharedInfo.accelerant_lock);
361 
362 	// First register dump
363 	//dump_registers();
364 
365 	// TODO: This may not be neccesary
366 	set_display_power_mode(B_DPMS_OFF);
367 
368 	// free old and allocate new frame buffer in graphics memory
369 
370 	intel_free_memory(sharedInfo.frame_buffer);
371 
372 	addr_t base;
373 	if (intel_allocate_memory(bytesPerRow * target.virtual_height, 0,
374 			base) < B_OK) {
375 		// oh, how did that happen? Unfortunately, there is no really good way
376 		// back. Try to restore a framebuffer for the previous mode, at least.
377 		if (intel_allocate_memory(sharedInfo.current_mode.virtual_height
378 				* sharedInfo.bytes_per_row, 0, base) == B_OK) {
379 			sharedInfo.frame_buffer = base;
380 			sharedInfo.frame_buffer_offset = base
381 				- (addr_t)sharedInfo.graphics_memory;
382 			set_frame_buffer_base();
383 		}
384 
385 		ERROR("%s: Failed to allocate framebuffer !\n", __func__);
386 		return B_NO_MEMORY;
387 	}
388 
389 	// clear frame buffer before using it
390 	memset((uint8*)base, 0, bytesPerRow * target.virtual_height);
391 	sharedInfo.frame_buffer = base;
392 	sharedInfo.frame_buffer_offset = base - (addr_t)sharedInfo.graphics_memory;
393 
394 #if 0
395 	if ((gInfo->head_mode & HEAD_MODE_TESTING) != 0) {
396 		// 1. Enable panel power as needed to retrieve panel configuration
397 		// (use AUX VDD enable bit)
398 			// skip, did detection already, might need that before that though
399 
400 		// 2. Enable PCH clock reference source and PCH SSC modulator,
401 		// wait for warmup (Can be done anytime before enabling port)
402 			// skip, most certainly already set up by bios to use other ports,
403 			// will need for coldstart though
404 
405 		// 3. If enabling CPU embedded DisplayPort A: (Can be done anytime
406 		// before enabling CPU pipe or port)
407 		//	a.	Enable PCH 120MHz clock source output to CPU, wait for DMI
408 		//		latency
409 		//	b.	Configure and enable CPU DisplayPort PLL in the DisplayPort A
410 		//		register, wait for warmup
411 			// skip, not doing eDP right now, should go into
412 			// EmbeddedDisplayPort class though
413 
414 		// 4. If enabling port on PCH: (Must be done before enabling CPU pipe
415 		// or FDI)
416 		//	a.	Enable PCH FDI Receiver PLL, wait for warmup plus DMI latency
417 		//	b.	Switch from Rawclk to PCDclk in FDI Receiver (FDI A OR FDI B)
418 		//	c.	[DevSNB] Enable CPU FDI Transmitter PLL, wait for warmup
419 		//	d.	[DevILK] CPU FDI PLL is always on and does not need to be
420 		//		enabled
421 		FDILink* link = pipe->FDILink();
422 		if (link != NULL) {
423 			link->Receiver().EnablePLL();
424 			link->Receiver().SwitchClock(true);
425 			link->Transmitter().EnablePLL();
426 		}
427 
428 		// 5. Enable CPU panel fitter if needed for hires, required for VGA
429 		// (Can be done anytime before enabling CPU pipe)
430 		PanelFitter* fitter = pipe->PanelFitter();
431 		if (fitter != NULL)
432 			fitter->Enable(mode);
433 
434 		// 6. Configure CPU pipe timings, M/N/TU, and other pipe settings
435 		// (Can be done anytime before enabling CPU pipe)
436 		pll_divisors divisors;
437 		compute_pll_divisors(target, divisors, false);
438 		pipe->ConfigureTimings(divisors);
439 
440 		// 7. Enable CPU pipe
441 		pipe->Enable();
442 
443 8. Configure and enable CPU planes (VGA or hires)
444 9. If enabling port on PCH:
445 		//	a.   Program PCH FDI Receiver TU size same as Transmitter TU size for TU error checking
446 		//	b.   Train FDI
447 		//		i. Set pre-emphasis and voltage (iterate if training steps fail)
448                     ii. Enable CPU FDI Transmitter and PCH FDI Receiver with Training Pattern 1 enabled.
449                    iii. Wait for FDI training pattern 1 time
450                    iv. Read PCH FDI Receiver ISR ([DevIBX-B+] IIR) for bit lock in bit 8 (retry at least once if no lock)
451                     v. Enable training pattern 2 on CPU FDI Transmitter and PCH FDI Receiver
452                    vi.  Wait for FDI training pattern 2 time
453                   vii. Read PCH FDI Receiver ISR ([DevIBX-B+] IIR) for symbol lock in bit 9 (retry at least once if no
454                         lock)
455                   viii. Enable normal pixel output on CPU FDI Transmitter and PCH FDI Receiver
456                    ix.  Wait for FDI idle pattern time for link to become active
457          c.   Configure and enable PCH DPLL, wait for PCH DPLL warmup (Can be done anytime before enabling
458               PCH transcoder)
459          d.   [DevCPT] Configure DPLL SEL to set the DPLL to transcoder mapping and enable DPLL to the
460               transcoder.
461          e.   [DevCPT] Configure DPLL_CTL DPLL_HDMI_multipler.
462          f.   Configure PCH transcoder timings, M/N/TU, and other transcoder settings (should match CPU settings).
463          g.   [DevCPT] Configure and enable Transcoder DisplayPort Control if DisplayPort will be used
464          h.   Enable PCH transcoder
465 10. Enable ports (DisplayPort must enable in training pattern 1)
466 11. Enable panel power through panel power sequencing
467 12. Wait for panel power sequencing to reach enabled steady state
468 13. Disable panel power override
469 14. If DisplayPort, complete link training
470 15. Enable panel backlight
471 	}
472 #endif
473 
474 	// make sure VGA display is disabled
475 	write32(INTEL_VGA_DISPLAY_CONTROL, VGA_DISPLAY_DISABLED);
476 	read32(INTEL_VGA_DISPLAY_CONTROL);
477 
478 	// Go over each port and set the display mode
479 	for (uint32 i = 0; i < gInfo->port_count; i++) {
480 		if (gInfo->ports[i] == NULL)
481 			continue;
482 		if (!gInfo->ports[i]->IsConnected())
483 			continue;
484 
485 		status_t status = gInfo->ports[i]->SetDisplayMode(&target, colorMode);
486 		if (status != B_OK)
487 			ERROR("%s: Unable to set display mode!\n", __func__);
488 	}
489 
490 	TRACE("%s: Port configuration completed successfully!\n", __func__);
491 
492 	// We set the same color mode across all pipes
493 	program_pipe_color_modes(colorMode);
494 
495 	// TODO: This may not be neccesary (see DPMS OFF at top)
496 	set_display_power_mode(sharedInfo.dpms_mode);
497 
498 	// Changing bytes per row seems to be ignored if the plane/pipe is turned
499 	// off
500 
501 	// Always set both pipes, just in case
502 	// TODO rework this when we get multiple head support with different
503 	// resolutions
504 	if (sharedInfo.device_type.InFamily(INTEL_FAMILY_LAKE)) {
505 		write32(INTEL_DISPLAY_A_BYTES_PER_ROW, bytesPerRow >> 6);
506 		write32(INTEL_DISPLAY_B_BYTES_PER_ROW, bytesPerRow >> 6);
507 	} else {
508 		write32(INTEL_DISPLAY_A_BYTES_PER_ROW, bytesPerRow);
509 		write32(INTEL_DISPLAY_B_BYTES_PER_ROW, bytesPerRow);
510 	}
511 
512 	// update shared info
513 	sharedInfo.current_mode = target;
514 	sharedInfo.bytes_per_row = bytesPerRow;
515 	sharedInfo.bits_per_pixel = bitsPerPixel;
516 
517 	set_frame_buffer_base();
518 		// triggers writing back double-buffered registers
519 		// which is INTEL_DISPLAY_X_BYTES_PER_ROW only apparantly
520 
521 	// Second register dump
522 	//dump_registers();
523 
524 	return B_OK;
525 }
526 
527 
528 status_t
529 intel_get_display_mode(display_mode* _currentMode)
530 {
531 	CALLED();
532 
533 	*_currentMode = gInfo->shared_info->current_mode;
534 
535 	// This seems unreliable. We should always know the current_mode
536 	//retrieve_current_mode(*_currentMode, INTEL_DISPLAY_A_PLL);
537 	return B_OK;
538 }
539 
540 
541 status_t
542 intel_get_preferred_mode(display_mode* preferredMode)
543 {
544 	TRACE("%s\n", __func__);
545 	display_mode mode;
546 
547 	if (gInfo->has_edid || !gInfo->shared_info->got_vbt
548 			|| !gInfo->shared_info->device_type.IsMobile()) {
549 		return B_ERROR;
550 	}
551 
552 	mode.timing = gInfo->shared_info->panel_timing;
553 	mode.space = B_RGB32;
554 	mode.virtual_width = mode.timing.h_display;
555 	mode.virtual_height = mode.timing.v_display;
556 	mode.h_display_start = 0;
557 	mode.v_display_start = 0;
558 	mode.flags = 0;
559 	memcpy(preferredMode, &mode, sizeof(mode));
560 	return B_OK;
561 }
562 
563 
564 status_t
565 intel_get_edid_info(void* info, size_t size, uint32* _version)
566 {
567 	if (!gInfo->has_edid)
568 		return B_ERROR;
569 	if (size < sizeof(struct edid1_info))
570 		return B_BUFFER_OVERFLOW;
571 
572 	memcpy(info, &gInfo->edid_info, sizeof(struct edid1_info));
573 	*_version = EDID_VERSION_1;
574 	return B_OK;
575 }
576 
577 
578 // Get the backlight registers. We need the backlight frequency (we never write it, but we ned to
579 // know it's value as the duty cycle/brihtness level is proportional to it), and the duty cycle
580 // register (read to get the current backlight value, written to set it). On older generations,
581 // the two values are in the same register (16 bits each), on newer ones there are two separate
582 // registers.
583 static int32_t
584 intel_get_backlight_register(bool period)
585 {
586 	if (gInfo->shared_info->pch_info >= INTEL_PCH_CNP) {
587 		if (period)
588 			return PCH_SOUTH_BLC_PWM_PERIOD;
589 		else
590 			return PCH_SOUTH_BLC_PWM_DUTY_CYCLE;
591 	} else if (gInfo->shared_info->pch_info >= INTEL_PCH_SPT)
592 		return BLC_PWM_PCH_CTL2;
593 
594 	if (gInfo->shared_info->pch_info == INTEL_PCH_NONE)
595 		return MCH_BLC_PWM_CTL;
596 
597 	// FIXME this mixup of south and north registers seems very strange; it should either be
598 	// a single register with both period and duty in it, or two separate registers.
599 	if (period)
600 		return PCH_SOUTH_BLC_PWM_PERIOD;
601 	else
602 		return PCH_BLC_PWM_CTL;
603 }
604 
605 
606 status_t
607 intel_set_brightness(float brightness)
608 {
609 	CALLED();
610 
611 	if (brightness < 0 || brightness > 1)
612 		return B_BAD_VALUE;
613 
614 	// The "duty cycle" is a proportion of the period (0 = backlight off,
615 	// period = maximum brightness).
616 	// Additionally we don't want it to be completely 0 here, because then
617 	// it becomes hard to turn the display on again (at least until we get
618 	// working ACPI keyboard shortcuts for this). So always keep the backlight
619 	// at least a little bit on for now.
620 
621 	if (gInfo->shared_info->pch_info >= INTEL_PCH_CNP) {
622 		uint32_t period = read32(intel_get_backlight_register(true));
623 
624 		uint32_t duty = (uint32_t)(period * brightness);
625 		duty = std::max(duty, (uint32_t)gInfo->shared_info->min_brightness);
626 
627 		write32(intel_get_backlight_register(false), duty);
628 	} else 	if (gInfo->shared_info->pch_info >= INTEL_PCH_SPT) {
629 		uint32_t period = read32(intel_get_backlight_register(true)) >> 16;
630 
631 		uint32_t duty = (uint32_t)(period * brightness) & 0xffff;
632 		duty = std::max(duty, (uint32_t)gInfo->shared_info->min_brightness);
633 
634 		write32(intel_get_backlight_register(false), duty | (period << 16));
635 	} else {
636 		// On older devices there is a single register with both period and duty cycle
637 		uint32 tmp = read32(intel_get_backlight_register(true));
638 		bool legacyMode = false;
639 		if (gInfo->shared_info->device_type.Generation() == 2
640 			|| gInfo->shared_info->device_type.IsModel(INTEL_MODEL_915M)
641 			|| gInfo->shared_info->device_type.IsModel(INTEL_MODEL_945M)) {
642 			legacyMode = (tmp & BLM_LEGACY_MODE) != 0;
643 		}
644 
645 		uint32_t period = tmp >> 16;
646 
647 		uint32_t mask = 0xffff;
648 		uint32_t shift = 0;
649 		if (gInfo->shared_info->device_type.Generation() < 4) {
650 			// The low bit must be masked out because
651 			// it is apparently used for something else on some Atom machines (no
652 			// reference to that in the documentation that I know of).
653 			mask = 0xfffe;
654 			shift = 1;
655 			period = tmp >> 17;
656 		}
657 		if (legacyMode)
658 			period *= 0xfe;
659 		uint32_t duty = (uint32_t)(period * brightness);
660 		if (legacyMode) {
661 			uint8 lpc = duty / 0xff + 1;
662 			duty /= lpc;
663 
664 			// set pci config reg with lpc
665 			intel_brightness_legacy brightnessLegacy;
666 			brightnessLegacy.magic = INTEL_PRIVATE_DATA_MAGIC;
667 			brightnessLegacy.lpc = lpc;
668 			ioctl(gInfo->device, INTEL_SET_BRIGHTNESS_LEGACY, &brightnessLegacy,
669 				sizeof(brightnessLegacy));
670 		}
671 
672 		duty = std::max(duty, (uint32_t)gInfo->shared_info->min_brightness);
673 		duty <<= shift;
674 
675 		write32(intel_get_backlight_register(false), (duty & mask) | (tmp & ~mask));
676 	}
677 
678 	return B_OK;
679 }
680 
681 
682 status_t
683 intel_get_brightness(float* brightness)
684 {
685 	CALLED();
686 
687 	if (brightness == NULL)
688 		return B_BAD_VALUE;
689 
690 	uint32_t duty;
691 	uint32_t period;
692 
693 	if (gInfo->shared_info->pch_info >= INTEL_PCH_CNP) {
694 		period = read32(intel_get_backlight_register(true));
695 		duty = read32(intel_get_backlight_register(false));
696 	} else {
697 		uint32 tmp = read32(intel_get_backlight_register(true));
698 		bool legacyMode = false;
699 		if (gInfo->shared_info->device_type.Generation() == 2
700 			|| gInfo->shared_info->device_type.IsModel(INTEL_MODEL_915M)
701 			|| gInfo->shared_info->device_type.IsModel(INTEL_MODEL_945M)) {
702 			legacyMode = (tmp & BLM_LEGACY_MODE) != 0;
703 		}
704 		period = tmp >> 16;
705 		duty = read32(intel_get_backlight_register(false)) & 0xffff;
706 		if (legacyMode) {
707 			period *= 0xff;
708 
709 			// get lpc from pci config reg
710 			intel_brightness_legacy brightnessLegacy;
711 			brightnessLegacy.magic = INTEL_PRIVATE_DATA_MAGIC;
712 			ioctl(gInfo->device, INTEL_GET_BRIGHTNESS_LEGACY, &brightnessLegacy,
713 				sizeof(brightnessLegacy));
714 			duty *= brightnessLegacy.lpc;
715 		}
716 		if (gInfo->shared_info->device_type.Generation() < 4) {
717 			period >>= 1;
718 			duty >>= 1;
719 		}
720 	}
721 	*brightness = (float)duty / period;
722 
723 	return B_OK;
724 }
725 
726 
727 status_t
728 intel_get_frame_buffer_config(frame_buffer_config* config)
729 {
730 	CALLED();
731 
732 	uint32 offset = gInfo->shared_info->frame_buffer_offset;
733 
734 	config->frame_buffer = gInfo->shared_info->graphics_memory + offset;
735 	config->frame_buffer_dma
736 		= (uint8*)gInfo->shared_info->physical_graphics_memory + offset;
737 	config->bytes_per_row = gInfo->shared_info->bytes_per_row;
738 
739 	return B_OK;
740 }
741 
742 
743 status_t
744 intel_get_pixel_clock_limits(display_mode* mode, uint32* _low, uint32* _high)
745 {
746 	CALLED();
747 
748 	if (_low != NULL) {
749 		// lower limit of about 48Hz vertical refresh
750 		uint32 totalClocks = (uint32)mode->timing.h_total
751 			* (uint32)mode->timing.v_total;
752 		uint32 low = (totalClocks * 48L) / 1000L;
753 		if (low < gInfo->shared_info->pll_info.min_frequency)
754 			low = gInfo->shared_info->pll_info.min_frequency;
755 		else if (low > gInfo->shared_info->pll_info.max_frequency)
756 			return B_ERROR;
757 
758 		*_low = low;
759 	}
760 
761 	if (_high != NULL)
762 		*_high = gInfo->shared_info->pll_info.max_frequency;
763 
764 	return B_OK;
765 }
766 
767 
768 status_t
769 intel_move_display(uint16 horizontalStart, uint16 verticalStart)
770 {
771 	intel_shared_info &sharedInfo = *gInfo->shared_info;
772 	Autolock locker(sharedInfo.accelerant_lock);
773 
774 	display_mode &mode = sharedInfo.current_mode;
775 
776 	if (horizontalStart + mode.timing.h_display > mode.virtual_width
777 		|| verticalStart + mode.timing.v_display > mode.virtual_height)
778 		return B_BAD_VALUE;
779 
780 	mode.h_display_start = horizontalStart;
781 	mode.v_display_start = verticalStart;
782 
783 	set_frame_buffer_base();
784 
785 	return B_OK;
786 }
787 
788 
789 status_t
790 intel_get_timing_constraints(display_timing_constraints* constraints)
791 {
792 	CALLED();
793 	return B_ERROR;
794 }
795 
796 
797 void
798 intel_set_indexed_colors(uint count, uint8 first, uint8* colors, uint32 flags)
799 {
800 	TRACE("%s(colors = %p, first = %u)\n", __func__, colors, first);
801 
802 	if (colors == NULL)
803 		return;
804 
805 	Autolock locker(gInfo->shared_info->accelerant_lock);
806 
807 	for (; count-- > 0; first++) {
808 		uint32 color = colors[0] << 16 | colors[1] << 8 | colors[2];
809 		colors += 3;
810 
811 		write32(INTEL_DISPLAY_A_PALETTE + first * sizeof(uint32), color);
812 		write32(INTEL_DISPLAY_B_PALETTE + first * sizeof(uint32), color);
813 	}
814 }
815