xref: /haiku/src/add-ons/accelerants/intel_extreme/Pipes.cpp (revision 1e60bdeab63fa7a57bc9a55b032052e95a18bd2c)
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 
216 	// Set the image size for both pipes, just in case.
217 	write32(INTEL_DISPLAY_A_IMAGE_SIZE,
218 		((uint32)(target->virtual_width - 1) << 16)
219 			| ((uint32)target->virtual_height - 1));
220 	write32(INTEL_DISPLAY_B_IMAGE_SIZE,
221 		((uint32)(target->virtual_width - 1) << 16)
222 			| ((uint32)target->virtual_height - 1));
223 
224 	write32(INTEL_DISPLAY_A_PIPE_SIZE + fPipeOffset,
225 		((uint32)(target->timing.v_display - 1) << 16)
226 			| ((uint32)target->timing.h_display - 1));
227 
228 	// This is useful for debugging: it sets the border to red, so you
229 	// can see what is border and what is porch (black area around the
230 	// sync)
231 	//write32(INTEL_DISPLAY_A_RED + fPipeOffset, 0x00FF0000);
232 
233 	if (fHasTranscoder)
234 		_ConfigureTranscoder(target);
235 }
236 
237 
238 void
239 Pipe::ConfigureClocks(const pll_divisors& divisors, uint32 pixelClock,
240 	uint32 extraFlags)
241 {
242 	CALLED();
243 
244 	addr_t pllDivisorA = INTEL_DISPLAY_A_PLL_DIVISOR_0;
245 	addr_t pllDivisorB = INTEL_DISPLAY_A_PLL_DIVISOR_1;
246 	addr_t pllControl = INTEL_DISPLAY_A_PLL;
247 	addr_t pllMD = INTEL_DISPLAY_A_PLL_MD;
248 
249 	if (fPipeIndex == INTEL_PIPE_B) {
250 		pllDivisorA = INTEL_DISPLAY_B_PLL_DIVISOR_0;
251 		pllDivisorB = INTEL_DISPLAY_B_PLL_DIVISOR_1;
252 		pllControl = INTEL_DISPLAY_B_PLL;
253 		pllMD = INTEL_DISPLAY_B_PLL_MD;
254 	}
255 
256 	float refFreq = gInfo->shared_info->pll_info.reference_frequency / 1000.0f;
257 
258 	if (gInfo->shared_info->device_type.InGroup(INTEL_GROUP_96x)) {
259 		float adjusted = ((refFreq * divisors.m) / divisors.n) / divisors.p;
260 		uint32 pixelMultiply = uint32(adjusted / (pixelClock / 1000.0f));
261 		write32(pllMD, (0 << 24) | ((pixelMultiply - 1) << 8));
262 	}
263 
264 	// XXX: For now we assume no LVDS downclocking and program the same divisor
265 	// value to both divisor 0 (standard) and 1 (reduced divisor)
266 	if (gInfo->shared_info->device_type.InGroup(INTEL_GROUP_PIN)) {
267 		write32(pllDivisorA, (((1 << divisors.n) << DISPLAY_PLL_N_DIVISOR_SHIFT)
268 				& DISPLAY_PLL_IGD_N_DIVISOR_MASK)
269 			| (((divisors.m2 - 2) << DISPLAY_PLL_M2_DIVISOR_SHIFT)
270 				& DISPLAY_PLL_IGD_M2_DIVISOR_MASK));
271 		write32(pllDivisorB, (((1 << divisors.n) << DISPLAY_PLL_N_DIVISOR_SHIFT)
272 				& DISPLAY_PLL_IGD_N_DIVISOR_MASK)
273 			| (((divisors.m2 - 2) << DISPLAY_PLL_M2_DIVISOR_SHIFT)
274 				& DISPLAY_PLL_IGD_M2_DIVISOR_MASK));
275 	} else {
276 		write32(pllDivisorA, (((divisors.n - 2) << DISPLAY_PLL_N_DIVISOR_SHIFT)
277 				& DISPLAY_PLL_N_DIVISOR_MASK)
278 			| (((divisors.m1 - 2) << DISPLAY_PLL_M1_DIVISOR_SHIFT)
279 				& DISPLAY_PLL_M1_DIVISOR_MASK)
280 			| (((divisors.m2 - 2) << DISPLAY_PLL_M2_DIVISOR_SHIFT)
281 				& DISPLAY_PLL_M2_DIVISOR_MASK));
282 		write32(pllDivisorB, (((divisors.n - 2) << DISPLAY_PLL_N_DIVISOR_SHIFT)
283 				& DISPLAY_PLL_N_DIVISOR_MASK)
284 			| (((divisors.m1 - 2) << DISPLAY_PLL_M1_DIVISOR_SHIFT)
285 				& DISPLAY_PLL_M1_DIVISOR_MASK)
286 			| (((divisors.m2 - 2) << DISPLAY_PLL_M2_DIVISOR_SHIFT)
287 				& DISPLAY_PLL_M2_DIVISOR_MASK));
288 	}
289 
290 	uint32 pll = DISPLAY_PLL_ENABLED | DISPLAY_PLL_NO_VGA_CONTROL | extraFlags;
291 
292 	if (gInfo->shared_info->device_type.Generation() >= 3) {
293 		// p1 divisor << 1 , 1-8
294 		if (gInfo->shared_info->device_type.InGroup(INTEL_GROUP_PIN)) {
295 			pll |= ((1 << (divisors.p1 - 1))
296 					<< DISPLAY_PLL_IGD_POST1_DIVISOR_SHIFT)
297 				& DISPLAY_PLL_IGD_POST1_DIVISOR_MASK;
298 		} else {
299 			pll |= ((1 << (divisors.p1 - 1))
300 					<< DISPLAY_PLL_POST1_DIVISOR_SHIFT)
301 				& DISPLAY_PLL_9xx_POST1_DIVISOR_MASK;
302 		//	pll |= ((divisors.p1 - 1) << DISPLAY_PLL_POST1_DIVISOR_SHIFT)
303 		//		& DISPLAY_PLL_9xx_POST1_DIVISOR_MASK;
304 		}
305 
306 		if (divisors.p2 == 5 || divisors.p2 == 7)
307 			pll |= DISPLAY_PLL_DIVIDE_HIGH;
308 
309 		if (gInfo->shared_info->device_type.InGroup(INTEL_GROUP_96x))
310 			pll |= 6 << DISPLAY_PLL_PULSE_PHASE_SHIFT;
311 	} else {
312 		if (divisors.p2 != 5 && divisors.p2 != 7)
313 			pll |= DISPLAY_PLL_DIVIDE_4X;
314 
315 		pll |= DISPLAY_PLL_2X_CLOCK;
316 
317 		// TODO: Is this supposed to be DISPLAY_PLL_IGD_POST1_DIVISOR_MASK??
318 		if (divisors.p1 > 2) {
319 			pll |= ((divisors.p1 - 2) << DISPLAY_PLL_POST1_DIVISOR_SHIFT)
320 				& DISPLAY_PLL_POST1_DIVISOR_MASK;
321 		} else
322 			pll |= DISPLAY_PLL_POST1_DIVIDE_2;
323 	}
324 
325 	// Allow the PLL to warm up by masking its bit.
326 	write32(pllControl, pll & ~DISPLAY_PLL_NO_VGA_CONTROL);
327 	read32(pllControl);
328 	spin(150);
329 	write32(pllControl, pll);
330 	read32(pllControl);
331 	spin(150);
332 }
333 
334 
335 void
336 Pipe::Enable(bool enable)
337 {
338 	CALLED();
339 
340 	addr_t pipeReg = INTEL_DISPLAY_A_PIPE_CONTROL + fPipeOffset;
341 	addr_t planeReg = INTEL_DISPLAY_A_CONTROL + fPlaneOffset;
342 
343 	// Planes always have to operate on an enabled pipe
344 
345 	if (enable) {
346 		write32(pipeReg, read32(pipeReg) | INTEL_PIPE_ENABLED);
347 		wait_for_vblank();
348 		write32(planeReg, read32(planeReg) | DISPLAY_CONTROL_ENABLED);
349 	} else {
350 		write32(planeReg, read32(planeReg) & ~DISPLAY_CONTROL_ENABLED);
351 		wait_for_vblank();
352 		write32(pipeReg, read32(pipeReg) & ~INTEL_PIPE_ENABLED);
353 	}
354 
355 	read32(INTEL_DISPLAY_A_BASE);
356 		// flush the eventually cached PCI bus writes
357 }
358