xref: /haiku/src/add-ons/accelerants/nvidia/SetDisplayMode.c (revision e6b30aee0fd7a23d6a6baab9f3718945a0cd838a)
1 
2 /*
3 	Copyright 1999, Be Incorporated.   All Rights Reserved.
4 	This file may be used under the terms of the Be Sample Code License.
5 
6 	Other authors:
7 	Mark Watson,
8 	Apsed,
9 	Rudolf Cornelissen 11/2002-10/2007
10 */
11 
12 #define MODULE_BIT 0x00200000
13 
14 #include "acc_std.h"
15 
16 /* First validate the mode, then call lots of bit banging stuff to set the mode(s)! */
17 status_t SET_DISPLAY_MODE(display_mode *mode_to_set)
18 {
19 	/* BOUNDS WARNING:
20 	 * It's impossible to deviate whatever small amount in a display_mode if the lower
21 	 * and upper limits are the same!
22 	 * Besides:
23 	 * BeOS (tested R5.0.3PE) is failing BWindowScreen::SetFrameBuffer() if PROPOSEMODE
24 	 * returns B_BAD_VALUE!
25 	 * Which means PROPOSEMODE should not return that on anything except on
26 	 * deviations for:
27 	 * display_mode.virtual_width;
28 	 * display_mode.virtual_height;
29 	 * display_mode.timing.h_display;
30 	 * display_mode.timing.v_display;
31 	 * So:
32 	 * We don't use bounds here by making sure bounds and target are the same struct!
33 	 * (See the call to PROPOSE_DISPLAY_MODE below) */
34 	display_mode /*bounds,*/ target;
35 
36 	uint8 colour_depth1 = 32;
37 	uint32 startadd,startadd_right;
38 //	bool crt1, crt2, cross;
39 
40 	/* Adjust mode to valid one and fail if invalid */
41 	target /*= bounds*/ = *mode_to_set;
42 	/* show the mode bits */
43 	LOG(1, ("SETMODE: (ENTER) initial modeflags: $%08x\n", target.flags));
44 	LOG(1, ("SETMODE: requested target pixelclock %dkHz\n",  target.timing.pixel_clock));
45 	LOG(1, ("SETMODE: requested virtual_width %d, virtual_height %d\n",
46 										target.virtual_width, target.virtual_height));
47 
48 	/* See BOUNDS WARNING above... */
49 	if (PROPOSE_DISPLAY_MODE(&target, &target, &target) == B_ERROR)	return B_ERROR;
50 
51 	/* make sure a possible 3D add-on will block rendering and re-initialize itself.
52 	 * note: update in _this_ order only */
53 	/* SET_DISPLAY_MODE will reset this flag when it's done. */
54 	si->engine.threeD.mode_changing = true;
55 	/* every 3D add-on will reset this bit-flag when it's done. */
56 	si->engine.threeD.newmode = 0xffffffff;
57 	/* every 3D clone needs to reclaim a slot.
58 	 * note: this also cleans up reserved channels for killed 3D clones.. */
59 	si->engine.threeD.clones = 0x00000000;
60 
61 	/* disable interrupts using the kernel driver */
62 	head1_interrupt_enable(false);
63 	if (si->ps.secondary_head) head2_interrupt_enable(false);
64 
65 	/* disable TVout if supported */
66 	if (si->ps.tvout) BT_stop_tvout();
67 
68 	/* turn off screen(s) _after_ TVout is disabled (if applicable) */
69 	head1_dpms(false, false, false, true);
70 	if (si->ps.secondary_head) head2_dpms(false, false, false, true);
71 	if (si->ps.tvout) BT_dpms(false);
72 
73 	/*where in framebuffer the screen is (should this be dependant on previous MOVEDISPLAY?)*/
74 	startadd = (uint8*)si->fbc.frame_buffer - (uint8*)si->framebuffer;
75 
76 	/* calculate and set new mode bytes_per_row */
77 	nv_general_validate_pic_size (&target, &si->fbc.bytes_per_row, &si->acc_mode);
78 
79 	/*Perform the very long mode switch!*/
80 	if (target.flags & DUALHEAD_BITS) /*if some dualhead mode*/
81 	{
82 		uint8 colour_depth2 = colour_depth1;
83 
84 		/* init display mode for secondary head */
85 		display_mode target2 = target;
86 
87 		LOG(1,("SETMODE: setting DUALHEAD mode\n"));
88 
89 		/* validate flags for secondary TVout */
90 		//fixme: remove or block on autodetect fail. (is now shutoff)
91 		if ((0) && (target2.flags & TV_BITS))
92 		{
93 			target.flags &= ~TV_BITS;//still needed for some routines...
94 			target2.flags &= ~TV_BITS;
95 			LOG(1,("SETMODE: blocking TVout: no TVout cable connected!\n"));
96 		}
97 
98 		/* detect which connectors have a CRT connected */
99 		//fixme: 'hot-plugging' for analog monitors removed: remove code as well;
100 		//or make it work with digital panels connected as well.
101 //		crt1 = nv_dac_crt_connected();
102 //		crt2 = nv_dac2_crt_connected();
103 		/* connect outputs 'straight-through' */
104 //		if (crt1)
105 //		{
106 			/* connector1 is used as primary output */
107 //			cross = false;
108 //		}
109 //		else
110 //		{
111 //			if (crt2)
112 				/* connector2 is used as primary output */
113 //				cross = true;
114 //			else
115 				/* no CRT detected: assume connector1 is used as primary output */
116 //				cross = false;
117 //		}
118 		/* set output connectors assignment if possible */
119 		if ((target.flags & DUALHEAD_BITS) == DUALHEAD_SWITCH)
120 			/* invert output assignment in switch mode */
121 			nv_general_head_select(true);
122 		else
123 			nv_general_head_select(false);
124 
125 		/* set the pixel clock PLL(s) */
126 		LOG(8,("SETMODE: target clock %dkHz\n",target.timing.pixel_clock));
127 		if (head1_set_pix_pll(target) == B_ERROR)
128 			LOG(8,("SETMODE: error setting pixel clock (internal DAC)\n"));
129 
130 		LOG(8,("SETMODE: target2 clock %dkHz\n",target2.timing.pixel_clock));
131 		if (head2_set_pix_pll(target2) == B_ERROR)
132 			LOG(8,("SETMODE: error setting pixel clock (DAC2)\n"));
133 
134 		/*set the colour depth for CRTC1 and the DAC */
135 		switch(target.space)
136 		{
137 		case B_CMAP8:
138 			colour_depth1 =  8;
139 			head1_mode(BPP8, 1.0);
140 			head1_depth(BPP8);
141 			break;
142 		case B_RGB15_LITTLE:
143 			colour_depth1 = 16;
144 			head1_mode(BPP15, 1.0);
145 			head1_depth(BPP15);
146 			break;
147 		case B_RGB16_LITTLE:
148 			colour_depth1 = 16;
149 			head1_mode(BPP16, 1.0);
150 			head1_depth(BPP16);
151 			break;
152 		case B_RGB32_LITTLE:
153 			colour_depth1 = 32;
154 			head1_mode(BPP32, 1.0);
155 			head1_depth(BPP32);
156 			break;
157 		}
158 		/*set the colour depth for CRTC2 and DAC2 */
159 		switch(target2.space)
160 		{
161 		case B_CMAP8:
162 			colour_depth2 =  8;
163 			head2_mode(BPP8, 1.0);
164 			head2_depth(BPP8);
165 			break;
166 		case B_RGB15_LITTLE:
167 			colour_depth2 = 16;
168 			head2_mode(BPP15, 1.0);
169 			head2_depth(BPP15);
170 			break;
171 		case B_RGB16_LITTLE:
172 			colour_depth2 = 16;
173 			head2_mode(BPP16, 1.0);
174 			head2_depth(BPP16);
175 			break;
176 		case B_RGB32_LITTLE:
177 			colour_depth2 = 32;
178 			head2_mode(BPP32, 1.0);
179 			head2_depth(BPP32);
180 			break;
181 		}
182 
183 		/* check if we are doing interlaced TVout mode */
184 		//fixme: we don't support interlaced mode?
185 		si->interlaced_tv_mode = false;
186 
187 		/*set the display(s) pitches*/
188 		head1_set_display_pitch ();
189 		//fixme: seperate for real dualhead modes:
190 		//we need a secondary si->fbc!
191 		head2_set_display_pitch ();
192 
193 		/*work out where the "right" screen starts*/
194 		startadd_right = startadd + (target.timing.h_display * (colour_depth1 >> 3));
195 
196 		/* Tell card what memory to display */
197 		switch (target.flags & DUALHEAD_BITS)
198 		{
199 		case DUALHEAD_ON:
200 		case DUALHEAD_SWITCH:
201 			head1_set_display_start(startadd,colour_depth1);
202 			head2_set_display_start(startadd_right,colour_depth2);
203 			break;
204 		case DUALHEAD_CLONE:
205 			head1_set_display_start(startadd,colour_depth1);
206 			head2_set_display_start(startadd,colour_depth2);
207 			break;
208 		}
209 
210 		/* set the timing */
211 		head1_set_timing(target);
212 		head2_set_timing(target2);
213 
214 		/* TVout support: program TVout encoder and modify CRTC timing */
215 		if (si->ps.tvout && (target2.flags & TV_BITS)) BT_setmode(target2);
216 	}
217 	else /* single head mode */
218 	{
219 		int colour_mode = BPP32;
220 
221 		/* connect output */
222 		if (si->ps.secondary_head)
223 		{
224 			/* detect which connectors have a CRT connected */
225 			//fixme: 'hot-plugging' for analog monitors removed: remove code as well;
226 			//or make it work with digital panels connected as well.
227 //			crt1 = nv_dac_crt_connected();
228 //			crt2 = nv_dac2_crt_connected();
229 			/* connect outputs 'straight-through' */
230 //			if (crt1)
231 //			{
232 				/* connector1 is used as primary output */
233 //				cross = false;
234 //			}
235 //			else
236 //			{
237 //				if (crt2)
238 					/* connector2 is used as primary output */
239 //					cross = true;
240 //				else
241 					/* no CRT detected: assume connector1 is used as primary output */
242 //					cross = false;
243 //			}
244 			/* set output connectors assignment if possible */
245 			nv_general_head_select(false);
246 		}
247 
248 		switch(target.space)
249 		{
250 		case B_CMAP8:        colour_depth1 =  8; colour_mode = BPP8;  break;
251 		case B_RGB15_LITTLE: colour_depth1 = 16; colour_mode = BPP15; break;
252 		case B_RGB16_LITTLE: colour_depth1 = 16; colour_mode = BPP16; break;
253 		case B_RGB32_LITTLE: colour_depth1 = 32; colour_mode = BPP32; break;
254 		default:
255 			LOG(8,("SETMODE: Invalid singlehead colour depth 0x%08x\n", target.space));
256 			return B_ERROR;
257 		}
258 
259 		/* set the pixel clock PLL */
260 		if (head1_set_pix_pll(target) == B_ERROR)
261 			LOG(8,("CRTC: error setting pixel clock (internal DAC)\n"));
262 
263 		/* set the colour depth for CRTC1 and the DAC */
264 		/* first set the colordepth */
265 		head1_depth(colour_mode);
266 		/* then(!) program the PAL (<8bit colordepth does not support 8bit PAL) */
267 		head1_mode(colour_mode,1.0);
268 
269 		/* set the display pitch */
270 		head1_set_display_pitch();
271 
272 		/* tell the card what memory to display */
273 		head1_set_display_start(startadd,colour_depth1);
274 
275 		/* set the timing */
276 		head1_set_timing(target);
277 
278 		/* TVout support: program TVout encoder and modify CRTC timing */
279 		if (si->ps.tvout && (target.flags & TV_BITS)) BT_setmode(target);
280 
281 		//fixme: shut-off the videoPLL if it exists...
282 	}
283 
284 	/* update driver's mode store */
285 	si->dm = target;
286 
287 	/* update FIFO data fetching according to mode */
288 	nv_crtc_update_fifo();
289 	if (si->ps.secondary_head) nv_crtc2_update_fifo();
290 
291 	/* set up acceleration for this mode */
292 	/* note:
293 	 * Maybe later we can forget about non-DMA mode (depends on 3D acceleration
294 	 * attempts). */
295 //no acc support for G8x yet!
296 if (si->ps.card_arch < NV50A)
297 {
298 	if (!si->settings.dma_acc)
299 		nv_acc_init();
300 	else
301 		nv_acc_init_dma();
302 }
303 	/* set up overlay unit for this mode */
304 	nv_bes_init();
305 
306 	/* note freemem range */
307 	/* first free adress follows hardcursor and workspace */
308 	si->engine.threeD.mem_low = si->fbc.bytes_per_row * si->dm.virtual_height;
309 	if (si->settings.hardcursor) si->engine.threeD.mem_low += 2048;
310 	/* last free adress is end-of-ram minus max space needed for overlay bitmaps */
311 	//fixme possible:
312 	//if overlay buffers are allocated subtract buffersize from mem_high;
313 	//only allocate overlay buffers if 3D is not in use. (block overlay during 3D)
314 	si->engine.threeD.mem_high = si->ps.memory_size - 1;
315 	/* Keep some extra distance as a workaround for certain bugs (see
316 	 * DriverInterface.h for an explanation). */
317 	if (si->ps.card_arch < NV40A)
318 		si->engine.threeD.mem_high -= PRE_NV40_OFFSET;
319 	else
320 		si->engine.threeD.mem_high -= NV40_PLUS_OFFSET;
321 
322 	si->engine.threeD.mem_high -= (MAXBUFFERS * 1024 * 1024 * 2); /* see overlay.c file */
323 
324 	/* restore screen(s) output state(s) */
325 	SET_DPMS_MODE(si->dpms_flags);
326 
327 	/* enable interrupts using the kernel driver */
328 	//fixme:
329 	//add head2 once we use one driver instance 'per head' (instead of 'per card')
330 	head1_interrupt_enable(true);
331 
332 	/* make sure a possible 3D add-on will re-initialize itself by signalling ready */
333 	si->engine.threeD.mode_changing = false;
334 
335 	/* optimize memory-access if needed */
336 //	head1_mem_priority(colour_depth1);
337 
338 	/* Tune RAM CAS-latency if needed. Must be done *here*! */
339 	nv_set_cas_latency();
340 
341 	LOG(1,("SETMODE: booted since %f mS\n", system_time()/1000.0));
342 
343 	return B_OK;
344 }
345 
346 /*
347 	Set which pixel of the virtual frame buffer will show up in the
348 	top left corner of the display device.  Used for page-flipping
349 	games and virtual desktops.
350 */
351 status_t MOVE_DISPLAY(uint16 h_display_start, uint16 v_display_start) {
352 	uint8 colour_depth;
353 	uint32 startadd,startadd_right;
354 
355 	LOG(4,("MOVE_DISPLAY: h %d, v %d\n", h_display_start, v_display_start));
356 
357 	/* nVidia cards support pixelprecise panning on both heads in all modes:
358 	 * No stepping granularity needed! */
359 
360 	/* determine bits used for the colordepth */
361 	switch(si->dm.space)
362 	{
363 	case B_CMAP8:
364 		colour_depth=8;
365 		break;
366 	case B_RGB15_LITTLE:
367 	case B_RGB16_LITTLE:
368 		colour_depth=16;
369 		break;
370 	case B_RGB24_LITTLE:
371 		colour_depth=24;
372 		break;
373 	case B_RGB32_LITTLE:
374 		colour_depth=32;
375 		break;
376 	default:
377 		return B_ERROR;
378 	}
379 
380 	/* do not run past end of display */
381 	switch (si->dm.flags & DUALHEAD_BITS)
382 	{
383 	case DUALHEAD_ON:
384 	case DUALHEAD_SWITCH:
385 		if (((si->dm.timing.h_display * 2) + h_display_start) > si->dm.virtual_width)
386 			return B_ERROR;
387 		break;
388 	default:
389 		if ((si->dm.timing.h_display + h_display_start) > si->dm.virtual_width)
390 			return B_ERROR;
391 		break;
392 	}
393 	if ((si->dm.timing.v_display + v_display_start) > si->dm.virtual_height)
394 		return B_ERROR;
395 
396 	/* everybody remember where we parked... */
397 	si->dm.h_display_start = h_display_start;
398 	si->dm.v_display_start = v_display_start;
399 
400 	/* actually set the registers */
401 	//fixme: seperate both heads: we need a secondary si->fbc!
402 	startadd = v_display_start * si->fbc.bytes_per_row;
403 	startadd += h_display_start * (colour_depth >> 3);
404 	startadd += (uint8*)si->fbc.frame_buffer - (uint8*)si->framebuffer;
405 	startadd_right = startadd + si->dm.timing.h_display * (colour_depth >> 3);
406 
407 	/* disable interrupts using the kernel driver */
408 	head1_interrupt_enable(false);
409 	if (si->ps.secondary_head) head2_interrupt_enable(false);
410 
411 	switch (si->dm.flags & DUALHEAD_BITS)
412 	{
413 		case DUALHEAD_ON:
414 		case DUALHEAD_SWITCH:
415 			head1_set_display_start(startadd,colour_depth);
416 			head2_set_display_start(startadd_right,colour_depth);
417 			break;
418 		case DUALHEAD_OFF:
419 			head1_set_display_start(startadd,colour_depth);
420 			break;
421 		case DUALHEAD_CLONE:
422 			head1_set_display_start(startadd,colour_depth);
423 			head2_set_display_start(startadd,colour_depth);
424 			break;
425 	}
426 
427 	//fixme:
428 	//add head2 once we use one driver instance 'per head' (instead of 'per card')
429 	head1_interrupt_enable(true);
430 
431 	return B_OK;
432 }
433 
434 /* Set the indexed color palette */
435 void SET_INDEXED_COLORS(uint count, uint8 first, uint8 *color_data, uint32 flags) {
436 	int i;
437 	uint8 *r,*g,*b;
438 
439 	/* Protect gamma correction when not in CMAP8 */
440 	if (si->dm.space != B_CMAP8) return;
441 
442 	r=si->color_data;
443 	g=r+256;
444 	b=g+256;
445 
446 	i=first;
447 	while (count--)
448 	{
449 		r[i]=*color_data++;
450 		g[i]=*color_data++;
451 		b[i]=*color_data++;
452 		i++;
453 	}
454 	head1_palette(r,g,b);
455 	if (si->dm.flags & DUALHEAD_BITS) head2_palette(r,g,b);
456 }
457 
458 /* Put the display into one of the Display Power Management modes. */
459 status_t SET_DPMS_MODE(uint32 dpms_flags)
460 {
461 	bool display, h1h, h1v, h2h, h2v, do_p1, do_p2;
462 
463 	/* disable interrupts using the kernel driver */
464 	head1_interrupt_enable(false);
465 	if (si->ps.secondary_head) head2_interrupt_enable(false);
466 
467 	LOG(4,("SET_DPMS_MODE: $%08x\n", dpms_flags));
468 
469 	/* note current DPMS state for our reference */
470 	si->dpms_flags = dpms_flags;
471 
472 	/* preset: DPMS for panels should be executed */
473 	do_p1 = do_p2 = true;
474 
475 	/* determine signals to send to head(s) */
476 	display = h1h = h1v = h2h = h2v = true;
477 	switch(dpms_flags)
478 	{
479 	case B_DPMS_ON:	/* H: on, V: on, display on */
480 		break;
481 	case B_DPMS_STAND_BY:
482 		display = h1h = h2h = false;
483 		break;
484 	case B_DPMS_SUSPEND:
485 		display = h1v = h2v = false;
486 		break;
487 	case B_DPMS_OFF: /* H: off, V: off, display off */
488 		display = h1h = h1v = h2h = h2v = false;
489 		break;
490 	default:
491 		LOG(8,("SET: Invalid DPMS settings $%08x\n", dpms_flags));
492 		//fixme:
493 		//add head2 once we use one driver instance 'per head' (instead of 'per card')
494 		head1_interrupt_enable(true);
495 
496 		return B_ERROR;
497 	}
498 
499 	/* CRTC used for TVout needs specific DPMS programming */
500 	if (si->dm.flags & TV_BITS)
501 	{
502 		/* TV_PRIMARY tells us that the head to be used with TVout is the head that's
503 		 * actually assigned as being the primary head at powerup:
504 		 * so non dualhead-mode-dependant, and not 'fixed' CRTC1! */
505 		if (si->dm.flags & TV_PRIMARY)
506 		{
507 			LOG(4,("SET_DPMS_MODE: tuning primary head DPMS settings for TVout compatibility\n"));
508 
509 			if ((si->dm.flags & DUALHEAD_BITS) != DUALHEAD_SWITCH)
510 			{
511 				if (!(si->settings.vga_on_tv))
512 				{
513 					/* block VGA output on head displaying on TV */
514 					/* Note:
515 					 * this specific sync setting is required: Vsync is used to keep TVout
516 					 * synchronized to the CRTC 'vertically' (otherwise 'rolling' occurs).
517 					 * This leaves Hsync only for shutting off the VGA screen. */
518 					h1h = false;
519 					h1v = true;
520 					/* block panel DPMS updates */
521 					do_p1 = false;
522 				}
523 				else
524 				{
525 					/* when concurrent VGA is used alongside TVout on a head, DPMS is safest
526 					 * applied this way: Vsync is needed for stopping TVout successfully when
527 					 * a (new) modeswitch occurs.
528 					 * (see routine BT_stop_tvout() in nv_brooktreetv.c) */
529 					/* Note:
530 					 * applying 'normal' DPMS here and forcing Vsync on in the above mentioned
531 					 * routine seems to not always be enough: sometimes image generation will
532 					 * not resume in that case. */
533 					h1h = display;
534 					h1v = true;
535 				}
536 			}
537 			else
538 			{
539 				if (!(si->settings.vga_on_tv))
540 				{
541 					h2h = false;
542 					h2v = true;
543 					do_p2 = false;
544 				}
545 				else
546 				{
547 					h2h = display;
548 					h2v = true;
549 				}
550 			}
551 		}
552 		else
553 		{
554 			LOG(4,("SET_DPMS_MODE: tuning secondary head DPMS settings for TVout compatibility\n"));
555 
556 			if ((si->dm.flags & DUALHEAD_BITS) != DUALHEAD_SWITCH)
557 			{
558 				if (!(si->settings.vga_on_tv))
559 				{
560 					h2h = false;
561 					h2v = true;
562 					do_p2 = false;
563 				}
564 				else
565 				{
566 					h2h = display;
567 					h2v = true;
568 				}
569 			}
570 			else
571 			{
572 				if (!(si->settings.vga_on_tv))
573 				{
574 					h1h = false;
575 					h1v = true;
576 					do_p1 = false;
577 				}
578 				else
579 				{
580 					h1h = display;
581 					h1v = true;
582 				}
583 			}
584 		}
585 	}
586 
587 	/* issue actual DPMS commands as far as applicable */
588 	head1_dpms(display, h1h, h1v, do_p1);
589 	if ((si->ps.secondary_head) && (si->dm.flags & DUALHEAD_BITS))
590 		head2_dpms(display, h2h, h2v, do_p2);
591 	if (si->dm.flags & TV_BITS)
592 		BT_dpms(display);
593 
594 	//fixme:
595 	//add head2 once we use one driver instance 'per head' (instead of 'per card')
596 	head1_interrupt_enable(true);
597 
598 	return B_OK;
599 }
600 
601 /* Report device DPMS capabilities */
602 uint32 DPMS_CAPABILITIES(void)
603 {
604 	return 	(B_DPMS_ON | B_DPMS_STAND_BY | B_DPMS_SUSPEND | B_DPMS_OFF);
605 }
606 
607 /* Return the current DPMS mode */
608 uint32 DPMS_MODE(void)
609 {
610 	return si->dpms_flags;
611 }
612