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