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