xref: /haiku/src/add-ons/accelerants/intel_extreme/Pipes.cpp (revision 6c2abee2f5e73c3fc81c33da51ac610f8bf1117a)
1 /*
2  * Copyright 2011-2015, Haiku, Inc. All Rights Reserved.
3  * Distributed under the terms of the MIT License.
4  *
5  * Authors:
6  *		Michael Lotz, mmlr@mlotz.ch
7  *		Alexander von Gluck IV, kallisti5@unixzen.com
8  */
9 #include "Pipes.h"
10 
11 #include "accelerant.h"
12 #include "intel_extreme.h"
13 #include <KernelExport.h>
14 
15 #include <stdlib.h>
16 #include <string.h>
17 
18 #include <new>
19 
20 
21 #define TRACE_PIPE
22 #ifdef TRACE_PIPE
23 extern "C" void _sPrintf(const char* format, ...);
24 #	define TRACE(x...) _sPrintf("intel_extreme: " x)
25 #else
26 #	define TRACE(x...) ;
27 #endif
28 
29 #define ERROR(x...) _sPrintf("intel_extreme: " x)
30 #define CALLED(x...) TRACE("CALLED %s\n", __PRETTY_FUNCTION__)
31 
32 
33 // PIPE: 6
34 // PLANE: 7
35 
36 
37 void
38 program_pipe_color_modes(uint32 colorMode)
39 {
40 	// All pipes get the same color mode
41 	write32(INTEL_DISPLAY_A_CONTROL, (read32(INTEL_DISPLAY_A_CONTROL)
42 			& ~(DISPLAY_CONTROL_COLOR_MASK | DISPLAY_CONTROL_GAMMA))
43         | colorMode);
44 	write32(INTEL_DISPLAY_B_CONTROL, (read32(INTEL_DISPLAY_B_CONTROL)
45 			& ~(DISPLAY_CONTROL_COLOR_MASK | DISPLAY_CONTROL_GAMMA))
46 		| colorMode);
47 }
48 
49 
50 // #pragma mark - Pipe
51 
52 
53 Pipe::Pipe(pipe_index pipeIndex)
54 	:
55 	fHasTranscoder(false),
56 	fFDILink(NULL),
57 //	fPanelFitter(NULL),
58 	fPipeIndex(pipeIndex),
59 	fPipeOffset(0),
60 	fPlaneOffset(0)
61 {
62 	if (pipeIndex == INTEL_PIPE_B) {
63 		fPipeOffset = INTEL_DISPLAY_OFFSET;
64 		fPlaneOffset = INTEL_PLANE_OFFSET;
65 	}
66 
67 	// IvyBridge: Analog + Digital Ports behind FDI (on northbridge)
68 	// Haswell: Only VGA behind FDI (on northbridge)
69 	// SkyLake: FDI gone. No more northbridge video.
70 	if (gInfo->shared_info->pch_info != INTEL_PCH_NONE) {
71 		TRACE("%s: Pipe %s routed through FDI\n", __func__,
72 			(pipeIndex == INTEL_PIPE_A) ? "A" : "B");
73 
74 		fHasTranscoder = true;
75 
76 		// Program FDILink if PCH
77 		fFDILink = new(std::nothrow) FDILink(pipeIndex);
78 	}
79 
80 	TRACE("Pipe %s. Pipe Base: 0x%" B_PRIxADDR
81 		" Plane Base: 0x% " B_PRIxADDR "\n", (pipeIndex == INTEL_PIPE_A)
82 			? "A" : "B", fPipeOffset, fPlaneOffset);
83 }
84 
85 
86 Pipe::~Pipe()
87 {
88 }
89 
90 
91 bool
92 Pipe::IsEnabled()
93 {
94 	CALLED();
95 
96 	return (read32(INTEL_DISPLAY_A_PIPE_CONTROL + fPipeOffset)
97 		& INTEL_PIPE_ENABLED) != 0;
98 }
99 
100 
101 void
102 Pipe::Configure(display_mode* mode)
103 {
104 	uint32 pipeControl = read32(INTEL_DISPLAY_A_PIPE_CONTROL + fPipeOffset);
105 
106 	// TODO: Haswell+ dithering changes.
107 	if (gInfo->shared_info->device_type.Generation() >= 4) {
108 		pipeControl |= (INTEL_PIPE_DITHER_EN | INTEL_PIPE_DITHER_TYPE_SP);
109 		switch (mode->space) {
110 			case B_CMAP8:
111 			case B_RGB15_LITTLE:
112 			case B_RGB16_LITTLE:
113 				pipeControl |= INTEL_PIPE_6BPC;
114 				break;
115 			case B_RGB24_LITTLE:
116 				pipeControl |= INTEL_PIPE_8BPC;
117 				break;
118 			case B_RGB32_LITTLE:
119 			default:
120 				pipeControl |= INTEL_PIPE_10BPC;
121 				break;
122 		}
123 	}
124 
125 	// TODO: CxSR downclocking?
126 
127 	// TODO: Interlaced modes
128 	pipeControl |= INTEL_PIPE_PROGRESSIVE;
129 
130 	write32(INTEL_DISPLAY_A_PIPE_CONTROL + fPipeOffset, pipeControl);
131 	read32(INTEL_DISPLAY_A_PIPE_CONTROL + fPipeOffset);
132 }
133 
134 
135 void
136 Pipe::_ConfigureTranscoder(display_mode* target)
137 {
138 	CALLED();
139 
140 	TRACE("%s: fPipeOffset: 0x%" B_PRIx32"\n", __func__, fPipeOffset);
141 
142 	// update timing (fPipeOffset bumps the DISPLAY_A to B when needed)
143 	write32(INTEL_TRANSCODER_A_HTOTAL + fPipeOffset,
144 		((uint32)(target->timing.h_total - 1) << 16)
145 		| ((uint32)target->timing.h_display - 1));
146 	write32(INTEL_TRANSCODER_A_HBLANK + fPipeOffset,
147 		((uint32)(target->timing.h_total - 1) << 16)
148 		| ((uint32)target->timing.h_display - 1));
149 	write32(INTEL_TRANSCODER_A_HSYNC + fPipeOffset,
150 		((uint32)(target->timing.h_sync_end - 1) << 16)
151 		| ((uint32)target->timing.h_sync_start - 1));
152 
153 	write32(INTEL_TRANSCODER_A_VTOTAL + fPipeOffset,
154 		((uint32)(target->timing.v_total - 1) << 16)
155 		| ((uint32)target->timing.v_display - 1));
156 	write32(INTEL_TRANSCODER_A_VBLANK + fPipeOffset,
157 		((uint32)(target->timing.v_total - 1) << 16)
158 		| ((uint32)target->timing.v_display - 1));
159 	write32(INTEL_TRANSCODER_A_VSYNC + fPipeOffset,
160 		((uint32)(target->timing.v_sync_end - 1) << 16)
161 		| ((uint32)target->timing.v_sync_start - 1));
162 
163 	#if 0
164 	// XXX: Is it ok to do these on non-digital?
165 	write32(INTEL_TRANSCODER_A_POS + fPipeOffset, 0);
166 	write32(INTEL_TRANSCODER_A_IMAGE_SIZE + fPipeOffset,
167 		((uint32)(target->virtual_width - 1) << 16)
168 			| ((uint32)target->virtual_height - 1));
169 	#endif
170 }
171 
172 
173 void
174 Pipe::ConfigureTimings(display_mode* target)
175 {
176 	CALLED();
177 
178 	TRACE("%s: fPipeOffset: 0x%" B_PRIx32"\n", __func__, fPipeOffset);
179 
180 	if (target == NULL) {
181 		ERROR("%s: Invalid display mode!\n", __func__);
182 		return;
183 	}
184 
185 	/* If there is a transcoder, leave the display at its native resolution,
186 	 * and configure only the transcoder (panel fitting will match them
187 	 * together). */
188 	if (!fHasTranscoder)
189 	{
190 		// update timing (fPipeOffset bumps the DISPLAY_A to B when needed)
191 		write32(INTEL_DISPLAY_A_HTOTAL + fPipeOffset,
192 			((uint32)(target->timing.h_total - 1) << 16)
193 			| ((uint32)target->timing.h_display - 1));
194 		write32(INTEL_DISPLAY_A_HBLANK + fPipeOffset,
195 			((uint32)(target->timing.h_total - 1) << 16)
196 			| ((uint32)target->timing.h_display - 1));
197 		write32(INTEL_DISPLAY_A_HSYNC + fPipeOffset,
198 			((uint32)(target->timing.h_sync_end - 1) << 16)
199 			| ((uint32)target->timing.h_sync_start - 1));
200 
201 		write32(INTEL_DISPLAY_A_VTOTAL + fPipeOffset,
202 			((uint32)(target->timing.v_total - 1) << 16)
203 			| ((uint32)target->timing.v_display - 1));
204 		write32(INTEL_DISPLAY_A_VBLANK + fPipeOffset,
205 			((uint32)(target->timing.v_total - 1) << 16)
206 			| ((uint32)target->timing.v_display - 1));
207 		write32(INTEL_DISPLAY_A_VSYNC + fPipeOffset,
208 			((uint32)(target->timing.v_sync_end - 1) << 16)
209 			| ((uint32)target->timing.v_sync_start - 1));
210 	}
211 
212 	// XXX: Is it ok to do these on non-digital?
213 
214 	write32(INTEL_DISPLAY_A_POS + fPipeOffset, 0);
215 	write32(INTEL_DISPLAY_A_IMAGE_SIZE,
216 		((uint32)(target->virtual_width - 1) << 16)
217 			| ((uint32)target->virtual_height - 1));
218 
219 	write32(INTEL_DISPLAY_A_PIPE_SIZE + fPipeOffset,
220 		((uint32)(target->timing.v_display - 1) << 16)
221 			| ((uint32)target->timing.h_display - 1));
222 
223 	// This is useful for debugging: it sets the border to red, so you
224 	// can see what is border and what is porch (black area around the
225 	// sync)
226 	//write32(INTEL_DISPLAY_A_RED + fPipeOffset, 0x00FF0000);
227 
228 	if (fHasTranscoder)
229 		_ConfigureTranscoder(target);
230 }
231 
232 
233 void
234 Pipe::ConfigureClocks(const pll_divisors& divisors, uint32 pixelClock,
235 	uint32 extraFlags)
236 {
237 	CALLED();
238 
239 	addr_t pllDivisorA = INTEL_DISPLAY_A_PLL_DIVISOR_0;
240 	addr_t pllDivisorB = INTEL_DISPLAY_A_PLL_DIVISOR_1;
241 	addr_t pllControl = INTEL_DISPLAY_A_PLL;
242 	addr_t pllMD = INTEL_DISPLAY_A_PLL_MD;
243 
244 	if (fPipeIndex == INTEL_PIPE_B) {
245 		pllDivisorA = INTEL_DISPLAY_B_PLL_DIVISOR_0;
246 		pllDivisorB = INTEL_DISPLAY_B_PLL_DIVISOR_1;
247 		pllControl = INTEL_DISPLAY_B_PLL;
248 		pllMD = INTEL_DISPLAY_B_PLL_MD;
249 	}
250 
251 	float refFreq = gInfo->shared_info->pll_info.reference_frequency / 1000.0f;
252 
253 	if (gInfo->shared_info->device_type.InGroup(INTEL_GROUP_96x)) {
254 		float adjusted = ((refFreq * divisors.m) / divisors.n) / divisors.p;
255 		uint32 pixelMultiply = uint32(adjusted / (pixelClock / 1000.0f));
256 		write32(pllMD, (0 << 24) | ((pixelMultiply - 1) << 8));
257 	}
258 
259 	// XXX: For now we assume no LVDS downclocking and program the same divisor
260 	// value to both divisor 0 (standard) and 1 (reduced divisor)
261 	if (gInfo->shared_info->device_type.InGroup(INTEL_GROUP_PIN)) {
262 		write32(pllDivisorA, (((1 << divisors.n) << DISPLAY_PLL_N_DIVISOR_SHIFT)
263 				& DISPLAY_PLL_IGD_N_DIVISOR_MASK)
264 			| (((divisors.m2 - 2) << DISPLAY_PLL_M2_DIVISOR_SHIFT)
265 				& DISPLAY_PLL_IGD_M2_DIVISOR_MASK));
266 		write32(pllDivisorB, (((1 << divisors.n) << DISPLAY_PLL_N_DIVISOR_SHIFT)
267 				& DISPLAY_PLL_IGD_N_DIVISOR_MASK)
268 			| (((divisors.m2 - 2) << DISPLAY_PLL_M2_DIVISOR_SHIFT)
269 				& DISPLAY_PLL_IGD_M2_DIVISOR_MASK));
270 	} else {
271 		write32(pllDivisorA, (((divisors.n - 2) << DISPLAY_PLL_N_DIVISOR_SHIFT)
272 				& DISPLAY_PLL_N_DIVISOR_MASK)
273 			| (((divisors.m1 - 2) << DISPLAY_PLL_M1_DIVISOR_SHIFT)
274 				& DISPLAY_PLL_M1_DIVISOR_MASK)
275 			| (((divisors.m2 - 2) << DISPLAY_PLL_M2_DIVISOR_SHIFT)
276 				& DISPLAY_PLL_M2_DIVISOR_MASK));
277 		write32(pllDivisorB, (((divisors.n - 2) << DISPLAY_PLL_N_DIVISOR_SHIFT)
278 				& DISPLAY_PLL_N_DIVISOR_MASK)
279 			| (((divisors.m1 - 2) << DISPLAY_PLL_M1_DIVISOR_SHIFT)
280 				& DISPLAY_PLL_M1_DIVISOR_MASK)
281 			| (((divisors.m2 - 2) << DISPLAY_PLL_M2_DIVISOR_SHIFT)
282 				& DISPLAY_PLL_M2_DIVISOR_MASK));
283 	}
284 
285 	uint32 pll = DISPLAY_PLL_ENABLED | DISPLAY_PLL_NO_VGA_CONTROL | extraFlags;
286 
287 	if (gInfo->shared_info->device_type.Generation() >= 3) {
288 		// p1 divisor << 1 , 1-8
289 		if (gInfo->shared_info->device_type.InGroup(INTEL_GROUP_PIN)) {
290 			pll |= ((1 << (divisors.p1 - 1))
291 					<< DISPLAY_PLL_IGD_POST1_DIVISOR_SHIFT)
292 				& DISPLAY_PLL_IGD_POST1_DIVISOR_MASK;
293 		} else {
294 			pll |= ((1 << (divisors.p1 - 1))
295 					<< DISPLAY_PLL_POST1_DIVISOR_SHIFT)
296 				& DISPLAY_PLL_9xx_POST1_DIVISOR_MASK;
297 		//	pll |= ((divisors.p1 - 1) << DISPLAY_PLL_POST1_DIVISOR_SHIFT)
298 		//		& DISPLAY_PLL_9xx_POST1_DIVISOR_MASK;
299 		}
300 
301 		if (divisors.p2 == 5 || divisors.p2 == 7)
302 			pll |= DISPLAY_PLL_DIVIDE_HIGH;
303 
304 		if (gInfo->shared_info->device_type.InGroup(INTEL_GROUP_96x))
305 			pll |= 6 << DISPLAY_PLL_PULSE_PHASE_SHIFT;
306 	} else {
307 		if (divisors.p2 != 5 && divisors.p2 != 7)
308 			pll |= DISPLAY_PLL_DIVIDE_4X;
309 
310 		pll |= DISPLAY_PLL_2X_CLOCK;
311 
312 		// TODO: Is this supposed to be DISPLAY_PLL_IGD_POST1_DIVISOR_MASK??
313 		if (divisors.p1 > 2) {
314 			pll |= ((divisors.p1 - 2) << DISPLAY_PLL_POST1_DIVISOR_SHIFT)
315 				& DISPLAY_PLL_POST1_DIVISOR_MASK;
316 		} else
317 			pll |= DISPLAY_PLL_POST1_DIVIDE_2;
318 	}
319 
320 	// Allow the PLL to warm up by masking its bit.
321 	write32(pllControl, pll & ~DISPLAY_PLL_NO_VGA_CONTROL);
322 	read32(pllControl);
323 	spin(150);
324 	write32(pllControl, pll);
325 	read32(pllControl);
326 	spin(150);
327 }
328 
329 
330 void
331 Pipe::Enable(bool enable)
332 {
333 	CALLED();
334 
335 	addr_t pipeReg = INTEL_DISPLAY_A_PIPE_CONTROL + fPipeOffset;
336 	addr_t planeReg = INTEL_DISPLAY_A_CONTROL + fPlaneOffset;
337 
338 	// Planes always have to operate on an enabled pipe
339 
340 	if (enable) {
341 		write32(pipeReg, read32(pipeReg) | INTEL_PIPE_ENABLED);
342 		wait_for_vblank();
343 		write32(planeReg, read32(planeReg) | DISPLAY_CONTROL_ENABLED);
344 	} else {
345 		write32(planeReg, read32(planeReg) & ~DISPLAY_CONTROL_ENABLED);
346 		wait_for_vblank();
347 		write32(pipeReg, read32(pipeReg) & ~INTEL_PIPE_ENABLED);
348 	}
349 
350 	read32(INTEL_DISPLAY_A_BASE);
351 		// flush the eventually cached PCI bus writes
352 }
353