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