xref: /haiku/src/add-ons/accelerants/nvidia/SetDisplayMode.c (revision d29f332ad4210e2e2b5ef42231531dd937ebc52e)
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);
88 	if (si->ps.secondary_head) head2_dpms(false, false, false);
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 
308 	/* set up acceleration for this mode */
309 	/* note:
310 	 * Maybe later we can forget about non-DMA mode (depends on 3D acceleration
311 	 * attempts). */
312 	if (!si->settings.dma_acc)
313 		nv_acc_init();
314 	else
315 		nv_acc_init_dma();
316 	/* set up overlay unit for this mode */
317 	nv_bes_init();
318 
319 	/* note freemem range */
320 	/* first free adress follows hardcursor and workspace */
321 	si->engine.threeD.mem_low = si->fbc.bytes_per_row * si->dm.virtual_height;
322 	if (si->settings.hardcursor) si->engine.threeD.mem_low += 2048;
323 	/* last free adress is end-of-ram minus max space needed for overlay bitmaps */
324 	//fixme possible:
325 	//if overlay buffers are allocated subtract buffersize from mem_high;
326 	//only allocate overlay buffers if 3D is not in use. (block overlay during 3D)
327 	si->engine.threeD.mem_high = si->ps.memory_size - 1;
328 	/* don't touch the DMA acceleration engine command buffer if it exists */
329 	/* note:
330 	 * the buffer is 32kB in size. Keep some extra distance for safety (faulty apps). */
331 	if (si->settings.dma_acc)
332 	{
333 		if (si->ps.card_arch < NV40A)
334 		{
335 			/* keeping 32kB distance from the DMA buffer */
336 			si->engine.threeD.mem_high -= (64 * 1024);
337 		}
338 		else
339 		{
340 			/* 416kB distance is just OK: keeping another 64kB distance for safety;
341 			 * confirmed for NV43. */
342 			/* note:
343 			 * if you get too close to the DMA command buffer on NV40 and NV43 at
344 			 * least (both confirmed), the source DMA instance will mess-up for
345 			 * at least engine cmd NV_IMAGE_BLIT and NV12_IMAGE_BLIT. */
346 			si->engine.threeD.mem_high -= (512 * 1024);
347 		}
348 	}
349 	si->engine.threeD.mem_high -= (MAXBUFFERS * 1024 * 1024 * 2); /* see overlay.c file */
350 
351 	/* restore screen(s) output state(s) */
352 	SET_DPMS_MODE(si->dpms_flags);
353 
354 	/* enable interrupts using the kernel driver */
355 	interrupt_enable(true);
356 
357 	/* make sure a possible 3D add-on will re-initialize itself by signalling ready */
358 	si->engine.threeD.mode_changing = false;
359 
360 	/* optimize memory-access if needed */
361 //	head1_mem_priority(colour_depth1);
362 
363 	/* Tune RAM CAS-latency if needed. Must be done *here*! */
364 	nv_set_cas_latency();
365 
366 	LOG(1,("SETMODE: booted since %f mS\n", system_time()/1000.0));
367 
368 	return B_OK;
369 }
370 
371 /*
372 	Set which pixel of the virtual frame buffer will show up in the
373 	top left corner of the display device.  Used for page-flipping
374 	games and virtual desktops.
375 */
376 status_t MOVE_DISPLAY(uint16 h_display_start, uint16 v_display_start) {
377 	uint8 colour_depth;
378 	uint32 startadd,startadd_right;
379 
380 	LOG(4,("MOVE_DISPLAY: h %d, v %d\n", h_display_start, v_display_start));
381 
382 	/* nVidia cards support pixelprecise panning on both heads in all modes:
383 	 * No stepping granularity needed! */
384 
385 	/* determine bits used for the colordepth */
386 	switch(si->dm.space)
387 	{
388 	case B_CMAP8:
389 		colour_depth=8;
390 		break;
391 	case B_RGB15_LITTLE:
392 	case B_RGB16_LITTLE:
393 		colour_depth=16;
394 		break;
395 	case B_RGB24_LITTLE:
396 		colour_depth=24;
397 		break;
398 	case B_RGB32_LITTLE:
399 		colour_depth=32;
400 		break;
401 	default:
402 		return B_ERROR;
403 	}
404 
405 	/* do not run past end of display */
406 	switch (si->dm.flags & DUALHEAD_BITS)
407 	{
408 	case DUALHEAD_ON:
409 	case DUALHEAD_SWITCH:
410 		if (((si->dm.timing.h_display * 2) + h_display_start) > si->dm.virtual_width)
411 			return B_ERROR;
412 		break;
413 	default:
414 		if ((si->dm.timing.h_display + h_display_start) > si->dm.virtual_width)
415 			return B_ERROR;
416 		break;
417 	}
418 	if ((si->dm.timing.v_display + v_display_start) > si->dm.virtual_height)
419 		return B_ERROR;
420 
421 	/* everybody remember where we parked... */
422 	si->dm.h_display_start = h_display_start;
423 	si->dm.v_display_start = v_display_start;
424 
425 	/* actually set the registers */
426 	//fixme: seperate both heads: we need a secondary si->fbc!
427 	startadd = v_display_start * si->fbc.bytes_per_row;
428 	startadd += h_display_start * (colour_depth >> 3);
429 	startadd += (uint8*)si->fbc.frame_buffer - (uint8*)si->framebuffer;
430 	startadd_right = startadd + si->dm.timing.h_display * (colour_depth >> 3);
431 
432 	interrupt_enable(false);
433 
434 	switch (si->dm.flags & DUALHEAD_BITS)
435 	{
436 		case DUALHEAD_ON:
437 		case DUALHEAD_SWITCH:
438 			head1_set_display_start(startadd,colour_depth);
439 			head2_set_display_start(startadd_right,colour_depth);
440 			break;
441 		case DUALHEAD_OFF:
442 			head1_set_display_start(startadd,colour_depth);
443 			break;
444 		case DUALHEAD_CLONE:
445 			head1_set_display_start(startadd,colour_depth);
446 			head2_set_display_start(startadd,colour_depth);
447 			break;
448 	}
449 
450 	interrupt_enable(true);
451 	return B_OK;
452 }
453 
454 /* Set the indexed color palette */
455 void SET_INDEXED_COLORS(uint count, uint8 first, uint8 *color_data, uint32 flags) {
456 	int i;
457 	uint8 *r,*g,*b;
458 
459 	/* Protect gamma correction when not in CMAP8 */
460 	if (si->dm.space != B_CMAP8) return;
461 
462 	r=si->color_data;
463 	g=r+256;
464 	b=g+256;
465 
466 	i=first;
467 	while (count--)
468 	{
469 		r[i]=*color_data++;
470 		g[i]=*color_data++;
471 		b[i]=*color_data++;
472 		i++;
473 	}
474 	head1_palette(r,g,b);
475 	if (si->dm.flags & DUALHEAD_BITS) head2_palette(r,g,b);
476 }
477 
478 /* Put the display into one of the Display Power Management modes. */
479 status_t SET_DPMS_MODE(uint32 dpms_flags)
480 {
481 	bool display, h1h, h1v, h2h, h2v;
482 
483 	interrupt_enable(false);
484 
485 	LOG(4,("SET_DPMS_MODE: 0x%08x\n", dpms_flags));
486 
487 	/* note current DPMS state for our reference */
488 	si->dpms_flags = dpms_flags;
489 
490 	/* determine signals to send to head(s) */
491 	display = h1h = h1v = h2h = h2v = true;
492 	switch(dpms_flags)
493 	{
494 	case B_DPMS_ON:	/* H: on, V: on, display on */
495 		break;
496 	case B_DPMS_STAND_BY:
497 		display = h1h = h2h = false;
498 		break;
499 	case B_DPMS_SUSPEND:
500 		display = h1v = h2v = false;
501 		break;
502 	case B_DPMS_OFF: /* H: off, V: off, display off */
503 		display = h1h = h1v = h2h = h2v = false;
504 		break;
505 	default:
506 		LOG(8,("SET: Invalid DPMS settings (DH) 0x%08x\n", dpms_flags));
507 		interrupt_enable(true);
508 		return B_ERROR;
509 	}
510 
511 	/* CRTC used for TVout needs specific DPMS programming */
512 	if (si->dm.flags & TV_BITS)
513 	{
514 		/* TV_PRIMARY tells us that the head to be used with TVout is the head that's
515 		 * actually assigned as being the primary head at powerup:
516 		 * so non dualhead-mode-dependant, and not 'fixed' CRTC1! */
517 		if (si->dm.flags & TV_PRIMARY)
518 		{
519 			LOG(4,("SET_DPMS_MODE: tuning primary head DPMS settings for TVout compatibility\n"));
520 
521 			if ((si->dm.flags & DUALHEAD_BITS) != DUALHEAD_SWITCH)
522 			{
523 				if (!(si->settings.vga_on_tv))
524 				{
525 					/* block VGA output on head displaying on TV */
526 					/* Note:
527 					 * this specific sync setting is required: Vsync is used to keep TVout
528 					 * synchronized to the CRTC 'vertically' (otherwise 'rolling' occurs).
529 					 * This leaves Hsync only for shutting off the VGA screen. */
530 					h1h = false;
531 					h1v = true;
532 				}
533 				else
534 				{
535 					/* when concurrent VGA is used alongside TVout on a head, DPMS is safest
536 					 * applied this way: Vsync is needed for stopping TVout successfully when
537 					 * a (new) modeswitch occurs.
538 					 * (see routine BT_stop_tvout() in nv_brooktreetv.c) */
539 					/* Note:
540 					 * applying 'normal' DPMS here and forcing Vsync on in the above mentioned
541 					 * routine seems to not always be enough: sometimes image generation will
542 					 * not resume in that case. */
543 					h1h = display;
544 					h1v = true;
545 				}
546 			}
547 			else
548 			{
549 				if (!(si->settings.vga_on_tv))
550 				{
551 					h2h = false;
552 					h2v = true;
553 				}
554 				else
555 				{
556 					h2h = display;
557 					h2v = true;
558 				}
559 			}
560 		}
561 		else
562 		{
563 			LOG(4,("SET_DPMS_MODE: tuning secondary head DPMS settings for TVout compatibility\n"));
564 
565 			if ((si->dm.flags & DUALHEAD_BITS) != DUALHEAD_SWITCH)
566 			{
567 				if (!(si->settings.vga_on_tv))
568 				{
569 					h2h = false;
570 					h2v = true;
571 				}
572 				else
573 				{
574 					h2h = display;
575 					h2v = true;
576 				}
577 			}
578 			else
579 			{
580 				if (!(si->settings.vga_on_tv))
581 				{
582 					h1h = false;
583 					h1v = true;
584 				}
585 				else
586 				{
587 					h1h = display;
588 					h1v = true;
589 				}
590 			}
591 		}
592 	}
593 
594 	/* issue actual DPMS commands as far as applicable */
595 	head1_dpms(display, h1h, h1v);
596 	if ((si->ps.secondary_head) && (si->dm.flags & DUALHEAD_BITS))
597 		head2_dpms(display, h2h, h2v);
598 	if (si->dm.flags & TV_BITS)
599 		BT_dpms(display);
600 
601 	interrupt_enable(true);
602 	return B_OK;
603 }
604 
605 /* Report device DPMS capabilities */
606 uint32 DPMS_CAPABILITIES(void)
607 {
608 	return 	(B_DPMS_ON | B_DPMS_STAND_BY | B_DPMS_SUSPEND | B_DPMS_OFF);
609 }
610 
611 /* Return the current DPMS mode */
612 uint32 DPMS_MODE(void)
613 {
614 	return si->dpms_flags;
615 }
616