xref: /haiku/src/add-ons/accelerants/radeon/ProposeDisplayMode.c (revision 67bce78b48ed6d01b5a8eef89f5694c372b7e0a1)
1 /*
2 	Copyright (c) 2002, Thomas Kurschel
3 
4 
5 	Part of Radeon accelerant
6 
7 	Everything concerning getting/testing display modes
8 */
9 
10 #include "radeon_accelerant.h"
11 #include "generic.h"
12 #include <string.h>
13 #include <sys/ioctl.h>
14 #include "GlobalData.h"
15 
16 #include "crtc_regs.h"
17 #include "utils.h"
18 
19 // standard mode list
20 // all drivers contain this list - this should really be moved to
21 // something like the screen preferences panel
22 
23 #define	T_POSITIVE_SYNC	(B_POSITIVE_HSYNC | B_POSITIVE_VSYNC)
24 #define MODE_FLAGS	(B_8_BIT_DAC | B_HARDWARE_CURSOR | B_PARALLEL_ACCESS | B_DPMS | B_SUPPORTS_OVERLAYS)
25 //#define MODE_COUNT (sizeof (mode_list) / sizeof (display_mode))
26 
27 static const display_mode base_mode_list[] = {
28 { { 25175, 640, 656, 752, 800, 480, 490, 492, 525, 0}, B_CMAP8, 640, 480, 0, 0, MODE_FLAGS}, /* Vesa_Monitor_@60Hz_(640X480X8.Z1) */
29 { { 27500, 640, 672, 768, 864, 480, 488, 494, 530, 0}, B_CMAP8, 640, 480, 0, 0, MODE_FLAGS}, /* 640X480X60Hz */
30 { { 30500, 640, 672, 768, 864, 480, 517, 523, 588, 0}, B_CMAP8, 640, 480, 0, 0, MODE_FLAGS}, /* SVGA_640X480X60HzNI */
31 { { 31500, 640, 664, 704, 832, 480, 489, 492, 520, 0}, B_CMAP8, 640, 480, 0, 0, MODE_FLAGS}, /* Vesa_Monitor_@70-72Hz_(640X480X8.Z1) */
32 { { 31500, 640, 656, 720, 840, 480, 481, 484, 500, 0}, B_CMAP8, 640, 480, 0, 0, MODE_FLAGS}, /* Vesa_Monitor_@75Hz_(640X480X8.Z1) */
33 { { 36000, 640, 696, 752, 832, 480, 481, 484, 509, 0}, B_CMAP8, 640, 480, 0, 0, MODE_FLAGS}, /* Vesa_Monitor_@85Hz_(640X480X8.Z1) */
34 { { 25175, 640, 656, 752, 800, 400, 412, 414, 449, B_POSITIVE_VSYNC}, B_CMAP8, 640, 400, 0, 0, MODE_FLAGS}, /* 640x400 - www.epanorama.net/documents/pc/vga_timing.html) */
35 { { 25175, 640, 656, 752, 800, 350, 387, 389, 449, B_POSITIVE_HSYNC}, B_CMAP8, 640, 350, 0, 0, MODE_FLAGS}, /* 640x350 - www.epanorama.net/documents/pc/vga_timing.html) */
36 { { 38100, 800, 832, 960, 1088, 600, 602, 606, 620, 0}, B_CMAP8, 800, 600, 0, 0, MODE_FLAGS}, /* SVGA_800X600X56HzNI */
37 { { 40000, 800, 840, 968, 1056, 600, 601, 605, 628, T_POSITIVE_SYNC}, B_CMAP8, 800, 600, 0, 0, MODE_FLAGS}, /* Vesa_Monitor_@60Hz_(800X600X8.Z1) */
38 { { 49500, 800, 816, 896, 1056, 600, 601, 604, 625, T_POSITIVE_SYNC}, B_CMAP8, 800, 600, 0, 0, MODE_FLAGS}, /* Vesa_Monitor_@75Hz_(800X600X8.Z1) */
39 { { 50000, 800, 856, 976, 1040, 600, 637, 643, 666, T_POSITIVE_SYNC}, B_CMAP8, 800, 600, 0, 0, MODE_FLAGS}, /* Vesa_Monitor_@70-72Hz_(800X600X8.Z1) */
40 { { 56250, 800, 832, 896, 1048, 600, 601, 604, 631, T_POSITIVE_SYNC}, B_CMAP8, 800, 600, 0, 0, MODE_FLAGS}, /* Vesa_Monitor_@85Hz_(800X600X8.Z1) */
41 { { 65000, 1024, 1048, 1184, 1344, 768, 771, 777, 806, 0}, B_CMAP8, 1024, 768, 0, 0, MODE_FLAGS}, /* Vesa_Monitor_@60Hz_(1024X768X8.Z1) */
42 { { 75000, 1024, 1048, 1184, 1328, 768, 771, 777, 806, 0}, B_CMAP8, 1024, 768, 0, 0, MODE_FLAGS}, /* Vesa_Monitor_@70-72Hz_(1024X768X8.Z1) */
43 { { 78750, 1024, 1040, 1136, 1312, 768, 769, 772, 800, T_POSITIVE_SYNC}, B_CMAP8, 1024, 768, 0, 0, MODE_FLAGS}, /* Vesa_Monitor_@75Hz_(1024X768X8.Z1) */
44 { { 94500, 1024, 1072, 1168, 1376, 768, 769, 772, 808, T_POSITIVE_SYNC}, B_CMAP8, 1024, 768, 0, 0, MODE_FLAGS}, /* Vesa_Monitor_@85Hz_(1024X768X8.Z1) */
45 { { 94200, 1152, 1184, 1280, 1472, 864, 865, 868, 914, T_POSITIVE_SYNC}, B_CMAP8, 1152, 864, 0, 0, MODE_FLAGS}, /* Vesa_Monitor_@70Hz_(1152X864X8.Z1) */
46 { { 108000, 1152, 1216, 1344, 1600, 864, 865, 868, 900, T_POSITIVE_SYNC}, B_CMAP8, 1152, 864, 0, 0, MODE_FLAGS}, /* Vesa_Monitor_@75Hz_(1152X864X8.Z1) */
47 { { 121500, 1152, 1216, 1344, 1568, 864, 865, 868, 911, T_POSITIVE_SYNC}, B_CMAP8, 1152, 864, 0, 0, MODE_FLAGS}, /* Vesa_Monitor_@85Hz_(1152X864X8.Z1) */
48 { { 108000, 1280, 1328, 1440, 1688, 1024, 1025, 1028, 1066, T_POSITIVE_SYNC}, B_CMAP8, 1280, 1024, 0, 0, MODE_FLAGS}, /* Vesa_Monitor_@60Hz_(1280X1024X8.Z1) */
49 { { 135000, 1280, 1296, 1440, 1688, 1024, 1025, 1028, 1066, T_POSITIVE_SYNC}, B_CMAP8, 1280, 1024, 0, 0, MODE_FLAGS}, /* Vesa_Monitor_@75Hz_(1280X1024X8.Z1) */
50 { { 157500, 1280, 1344, 1504, 1728, 1024, 1025, 1028, 1072, T_POSITIVE_SYNC}, B_CMAP8, 1280, 1024, 0, 0, MODE_FLAGS}, /* Vesa_Monitor_@85Hz_(1280X1024X8.Z1) */
51 { { 162000, 1600, 1664, 1856, 2160, 1200, 1201, 1204, 1250, T_POSITIVE_SYNC}, B_CMAP8, 1600, 1200, 0, 0, MODE_FLAGS}, /* Vesa_Monitor_@60Hz_(1600X1200X8.Z1) */
52 { { 175500, 1600, 1664, 1856, 2160, 1200, 1201, 1204, 1250, T_POSITIVE_SYNC}, B_CMAP8, 1600, 1200, 0, 0, MODE_FLAGS}, /* Vesa_Monitor_@65Hz_(1600X1200X8.Z1) */
53 { { 189000, 1600, 1664, 1856, 2160, 1200, 1201, 1204, 1250, T_POSITIVE_SYNC}, B_CMAP8, 1600, 1200, 0, 0, MODE_FLAGS}, /* Vesa_Monitor_@70Hz_(1600X1200X8.Z1) */
54 { { 202500, 1600, 1664, 1856, 2160, 1200, 1201, 1204, 1250, T_POSITIVE_SYNC}, B_CMAP8, 1600, 1200, 0, 0, MODE_FLAGS}, /* Vesa_Monitor_@75Hz_(1600X1200X8.Z1) */
55 { { 216000, 1600, 1664, 1856, 2160, 1200, 1201, 1204, 1250, T_POSITIVE_SYNC}, B_CMAP8, 1600, 1200, 0, 0, MODE_FLAGS}, /* Vesa_Monitor_@80Hz_(1600X1200X8.Z1) */
56 { { 229500, 1600, 1664, 1856, 2160, 1200, 1201, 1204, 1250, T_POSITIVE_SYNC}, B_CMAP8, 1600, 1200, 0, 0, MODE_FLAGS}  /* Vesa_Monitor_@85Hz_(1600X1200X8.Z1) */
57 };
58 
59 status_t Radeon_ProposeDisplayMode( shared_info *si, physical_port *port,
60 	pll_info *pll, display_mode *target,
61 	const display_mode *low, const display_mode *high );
62 void Radeon_DisposeModeList( shared_info *si );
63 
64 
65 // convert Be colour space in Radeon data type
66 // returns true, if supported colour space
67 //	space - Be colour space
68 //	format - (out) Radeon data type
69 //	bpp - (out) bytes per pixel
70 bool Radeon_GetFormat( int space, int *format, int *bpp )
71 {
72 	switch( space ) {
73     /*case 4:  format = 1; bytpp = 0; break;*/
74     case B_CMAP8:  *format = 2; *bpp = 1; break;
75     case B_RGB15_LITTLE: *format = 3; *bpp = 2; break;      /*  555 */
76     case B_RGB16_LITTLE: *format = 4; *bpp = 2; break;      /*  565 */
77     case B_RGB24_LITTLE: *format = 5; *bpp = 3; break;      /*  RGB */
78     case B_RGB32_LITTLE: *format = 6; *bpp = 4; break;      /* xRGB */
79     default:
80 		SHOW_ERROR( 1, "Unsupported color space (%d)", space );
81 		return false;
82     }
83 
84     return true;
85 }
86 
87 
88 // macros to convert between register values and pixels
89 #define H_DISPLAY_2REG( a ) ((a) / 8 - 1)
90 #define H_DISPLAY_2PIX( a ) (((a) + 1) * 8)
91 #define H_TOTAL_2REG( a ) ((a) / 8 - 1)
92 #define H_TOTAL_2PIX( a ) (((a) + 1) * 8)
93 #define H_SSTART_2REG( a ) ((a) - 8 + h_sync_fudge)
94 #define H_SSTART_2PIX( a ) ((a) + 8 - h_sync_fudge)
95 #define H_SWID_2REG( a ) ((a) / 8)
96 #define H_SWID_2PIX( a ) ((a) * 8)
97 
98 #define V_2REG( a ) ((a) - 1)
99 #define V_2PIX( a ) ((a) + 1)
100 
101 /*
102 	Validate a target display mode is both
103 		a) a valid display mode for this device and
104 		b) falls between the contraints imposed by "low" and "high"
105 
106 	If the mode is not (or cannot) be made valid for this device, return B_ERROR.
107 	If a valid mode can be constructed, but it does not fall within the limits,
108 	 return B_BAD_VALUE.
109 	If the mode is both valid AND falls within the limits, return B_OK.
110 */
111 status_t Radeon_ProposeDisplayMode( shared_info *si, physical_port *port,
112 	pll_info *pll, display_mode *target,
113 	const display_mode *low, const display_mode *high )
114 {
115 	status_t result = B_OK;
116 
117 	uint64 target_refresh;
118 	bool want_same_width, want_same_height;
119 	int format, bpp;
120 	uint32 row_bytes;
121 	int eff_virtual_width;
122 //	display_type_e disp_type;
123 
124 	// save refresh rate - we want to leave this (artifical) value untouched
125 	// don't use floating point, we are in kernel mode
126 	target_refresh =
127 		(((uint64)target->timing.pixel_clock * 1000) << FIX_SHIFT) /
128 		((uint64)target->timing.h_total * target->timing.v_total);
129 
130 	want_same_width = target->timing.h_display == target->virtual_width;
131 	want_same_height = target->timing.v_display == target->virtual_height;
132 
133 	if( !Radeon_GetFormat( target->space, &format, &bpp ))
134 		return B_ERROR;
135 
136 	// for flat panels, check maximum resolution;
137 	// all the other tricks (like fixed resolution and resulting scaling)
138 	// are done automagically by set_display_mode
139     if( port->disp_type == dt_dvi_1 || port->disp_type == dt_lvds ) {
140         if( target->timing.h_display > si->fp_port.panel_xres )
141             target->timing.h_display = si->fp_port.panel_xres;
142 
143         if(	target->timing.v_display > si->fp_port.panel_yres )
144             target->timing.v_display = si->fp_port.panel_yres;
145 	}
146 
147 	// validate horizontal timings
148 	{
149 		int h_sync_fudge, h_display, h_sync_start, h_sync_wid, h_total;
150 
151 		h_display = target->timing.h_display;
152 		h_sync_fudge = Radeon_GetHSyncFudge( si, port, format );
153 		h_sync_start = target->timing.h_sync_start;
154 		h_sync_wid = target->timing.h_sync_end - target->timing.h_sync_start;
155 		h_total = target->timing.h_total;
156 
157 		// make sure, display is not too small
158 		// (I reckon Radeon doesn't care, but your monitor probably does)
159 		if( h_display < 320 )
160 			h_display = 320;
161 		// apply hardware restrictions
162 		// as h_display is the smallest register, it's always possible
163 		// to adjust other values to keep them in supported range
164 		if( h_display > H_DISPLAY_2PIX( RADEON_CRTC_H_DISP >> RADEON_CRTC_H_DISP_SHIFT ) )
165 			h_display = H_DISPLAY_2PIX( RADEON_CRTC_H_DISP >> RADEON_CRTC_H_DISP_SHIFT );
166 		// round properly
167 		h_display = H_DISPLAY_2PIX( H_DISPLAY_2REG( h_display ));
168 
169 		// ensure minimum time before sync
170 		if( h_sync_start < h_display + 2*8 )
171 			h_sync_start = h_display + 2*8;
172 		// sync has wider range than display are, so we won't collide there,
173 		// but total width has same range as sync start, so leave some space
174 		if( h_sync_start > H_SSTART_2PIX( RADEON_CRTC_H_SYNC_STRT_CHAR | RADEON_CRTC_H_SYNC_STRT_PIX ) - 4*8 )
175 			h_sync_start = H_SSTART_2PIX( RADEON_CRTC_H_SYNC_STRT_CHAR | RADEON_CRTC_H_SYNC_STRT_PIX ) - 4*8;
176 
177 		// ensure minimum sync length
178 		if( h_sync_wid < H_SWID_2PIX( 3 ))
179 			h_sync_wid = H_SWID_2PIX( 3 );
180 		// allowed range is quite small, so make sure sync isn't too long
181 		if( h_sync_wid > H_SWID_2PIX( RADEON_CRTC_H_SYNC_WID >> RADEON_CRTC_H_SYNC_WID_SHIFT ) )
182 			h_sync_wid = H_SWID_2PIX( RADEON_CRTC_H_SYNC_WID >> RADEON_CRTC_H_SYNC_WID_SHIFT );
183 		// round properly
184 		h_sync_wid = H_SWID_2PIX( H_SWID_2REG( h_sync_wid ));
185 
186 		// last but not least adapt total width
187 		// "+7" is needed for rounding up: sync_start isn't rounded, but h_total is
188 		if( h_total < h_sync_start + h_sync_wid + 1*8 + 7 )
189 			h_total = h_sync_start + h_sync_wid + 1*8 + 7;
190 		// we may get a too long total width; this can only happen
191 		// because sync is too long, so truncate sync accordingly
192 		if( h_total > H_TOTAL_2PIX( RADEON_CRTC_H_TOTAL ) ) {
193 			h_total = H_TOTAL_2PIX( RADEON_CRTC_H_TOTAL );
194 			h_sync_wid = min( h_sync_wid, h_total - h_sync_start );
195 			h_sync_wid = H_SWID_2PIX( H_SWID_2REG( h_sync_wid ));
196 		}
197 		// round properly
198 		h_total = H_TOTAL_2PIX( H_TOTAL_2REG( h_total ));
199 
200 		target->timing.h_display = h_display;
201 		target->timing.h_sync_start = h_sync_start;
202 		target->timing.h_sync_end = h_sync_start + h_sync_wid;
203 		target->timing.h_total = h_total;
204 	}
205 
206 	// did we fall out of one of the limits?
207 	if( target->timing.h_display < low->timing.h_display ||
208 		target->timing.h_display > high->timing.h_display ||
209 		target->timing.h_sync_start < low->timing.h_sync_start ||
210 		target->timing.h_sync_start > high->timing.h_sync_start ||
211 		target->timing.h_sync_end < low->timing.h_sync_end ||
212 		target->timing.h_sync_end > high->timing.h_sync_end ||
213 		target->timing.h_total < low->timing.h_total ||
214 		target->timing.h_total > high->timing.h_total)
215 	{
216 		SHOW_FLOW0( 4, "out of horizontal limits" );
217 		result = B_BAD_VALUE;
218 	}
219 
220 	// validate vertical timings
221 	{
222 		int v_display, v_sync_start, v_sync_wid, v_total;
223 
224 		v_display = target->timing.v_display;
225 		v_sync_start = target->timing.v_sync_start;
226 		v_sync_wid = target->timing.v_sync_end - target->timing.v_sync_start;
227 		v_total = target->timing.v_total;
228 
229 		// apply a reasonable minimal height to make monitor happy
230 		if( v_display < 200 )
231 			v_display = 200;
232 		// apply limits but make sure we have enough lines left for blank and sync
233 		if( v_display > V_2PIX(RADEON_CRTC_V_DISP >> RADEON_CRTC_V_DISP_SHIFT) - 5)
234 			v_display = V_2PIX(RADEON_CRTC_V_DISP >> RADEON_CRTC_V_DISP_SHIFT) - 5;
235 
236 		// leave at least one line before sync
237 		// (some flat panel have zero gap here; probably, this leads to
238 		// the infamous bright line at top of screen)
239 		if( v_sync_start < v_display + 1 )
240 			v_sync_start = v_display + 1;
241 		// apply hardware limit and leave some lines for sync
242 		if( v_sync_start > V_2PIX(RADEON_CRTC_V_SYNC_STRT) - 4)
243 			v_sync_start = V_2PIX(RADEON_CRTC_V_SYNC_STRT) - 4;
244 
245 		// don't make sync too short
246 		if( v_sync_wid < 2 )
247 			v_sync_wid = 2;
248 		// sync width is quite restricted
249 		if( v_sync_wid > (RADEON_CRTC_V_SYNC_WID >> RADEON_CRTC_V_SYNC_WID_SHIFT))
250 			v_sync_wid = (RADEON_CRTC_V_SYNC_WID >> RADEON_CRTC_V_SYNC_WID_SHIFT);
251 
252 		// leave a gap of at least 1 line
253 		if( v_total < v_sync_start + v_sync_wid + 1 )
254 			v_total = v_sync_start + v_sync_wid + 1;
255 		// if too long, truncate it and adapt sync len
256 		if( v_total > V_2PIX( RADEON_CRTC_V_TOTAL ) ) {
257 			v_total = V_2PIX( RADEON_CRTC_V_TOTAL );
258 			v_sync_wid = min( v_sync_wid, v_total - v_sync_start - 4 );
259 		}
260 
261 		target->timing.v_display = v_display;
262 		target->timing.v_sync_start = v_sync_start;
263 		target->timing.v_sync_end = v_sync_start + v_sync_wid;
264 		target->timing.v_total = v_total;
265 	}
266 
267 	// did we fall out of one of the limits?
268 	if(	target->timing.v_display < low->timing.v_display ||
269 		target->timing.v_display > high->timing.v_display ||
270 		target->timing.v_sync_start < low->timing.v_sync_start ||
271 		target->timing.v_sync_start > high->timing.h_sync_start ||
272 		target->timing.v_sync_end < low->timing.v_sync_end ||
273 		target->timing.v_sync_end > high->timing.v_sync_end ||
274 		target->timing.v_total < low->timing.v_total ||
275 		target->timing.v_total > high->timing.v_total )
276 	{
277 		SHOW_FLOW0( 4, "out of vertical limits" );
278 		result = B_BAD_VALUE;
279 	}
280 
281 	// restore whished refresh rate
282 	target->timing.pixel_clock =
283 		((uint64)target_refresh / 1000 * target->timing.h_total * target->timing.v_total + FIX_SCALE / 2)
284 		>> FIX_SHIFT;
285 
286 	// apply PLL restrictions
287 	if( target->timing.pixel_clock / 10 > pll->max_pll_freq ||
288 		target->timing.pixel_clock / 10 * 12 < pll->min_pll_freq )
289 	{
290 		SHOW_ERROR( 2, "pixel_clock (%ld) out of range (%d, %d)", target->timing.pixel_clock,
291 			pll->max_pll_freq * 10, pll->min_pll_freq / 12 );
292 		return B_ERROR;
293 	}
294 
295 	// make sure virtual_size > visible_size
296 	// additionally, restore virtual_size == visible_size if it was so on entry
297 	if ((target->timing.h_display > target->virtual_width) || want_same_width)
298 		target->virtual_width = target->timing.h_display;
299 	if ((target->timing.v_display > target->virtual_height) || want_same_height)
300 		target->virtual_height = target->timing.v_display;
301 
302 	// TBD: limit is taken from XFree86
303 	// this is probably a CRTC limit; don't know about the accelerator limit (if any)
304 	// h_display can be at most 512*8, so we don't risk h_virtual < h_display
305 	// after applying this restriction
306 	if (target->virtual_width > 1024*8)
307 		target->virtual_width = 1024*8;
308 
309 	if (target->virtual_width < low->virtual_width ||
310 		target->virtual_width > high->virtual_width )
311 	{
312 		SHOW_FLOW0( 4, "out of virtual horizontal limits" );
313 		result = B_BAD_VALUE;
314 	}
315 
316 	// we may have to use a larger virtual width -
317 	// take care of that when calculating memory consumption
318 	eff_virtual_width = Radeon_RoundVWidth( target->virtual_height, bpp );
319 
320 	// calculate rowbytes after we've nailed the virtual width
321 	row_bytes = eff_virtual_width * bpp;
322 
323 	// if we haven't enough memory, reduce virtual height
324 	// (some programs create back buffers by asking for a huge
325 	// virtual screen; they actually want to know what is possible
326 	// to adjust the number of back buffers according to amount
327 	// of graphics memory)
328 
329 	// careful about additionally required memory:
330 	// 1024 bytes are needed for hardware cursor
331 	if ((row_bytes * target->virtual_height) > si->local_mem_size - 1024 )
332 		target->virtual_height = (si->local_mem_size - 1024) / row_bytes;
333 
334 	// make sure we haven't shrunk virtual height too much
335 	if (target->virtual_height < target->timing.v_display) {
336 		SHOW_ERROR( 2, "not enough memory for this mode (could show only %d of %d lines)",
337 			target->virtual_height, target->timing.v_display );
338 		return B_ERROR;
339 	}
340 
341 	if (target->virtual_height < low->virtual_height ||
342 		target->virtual_height > high->virtual_height )
343 	{
344 		SHOW_FLOW0( 4, "out of virtual vertical limits" );
345 		result = B_BAD_VALUE;
346 	}
347 
348 	// we ignore flags - in the sample driver, they did the same,
349 	// so why bother?
350 	return result;
351 }
352 
353 // public function: return number of display modes returned by get_mode_list
354 uint32 ACCELERANT_MODE_COUNT( void )
355 {
356 	return ai->si->mode_count;
357 }
358 
359 // public function: get list of standard display modes
360 //	dm - modes are copied to here (to be allocated by caller)
361 status_t GET_MODE_LIST( display_mode *dm )
362 {
363 	memcpy( dm, ai->mode_list, ai->si->mode_count * sizeof(display_mode) );
364 
365 	return B_OK;
366 }
367 
368 
369 static const color_space spaces[4] = {
370 	B_CMAP8, B_RGB15_LITTLE, B_RGB16_LITTLE, B_RGB32_LITTLE
371 };
372 
373 // if given mode is possible on this card, add it to standard mode list
374 //	mode - mode to add (colourspace is ignored but replaced
375 //	       by each officially supported colour space in turn)
376 //	ignore_timing - don't care if timing has to be modified to make mode valid
377 //	                (used for fp modes - we just want their resolution)
378 static void checkAndAddMode( accelerator_info *ai, const display_mode *mode, bool ignore_timing )
379 {
380 	shared_info *si = ai->si;
381 	uint i;
382 	display_mode low, high;
383 	uint32 pix_clk_range;
384 	display_mode *dst;
385 
386 	if( ignore_timing ) {
387 		// for fp modes: don't add mode if its resolution is already in official mode list
388 		for( i = 0; i < si->mode_count; ++i ) {
389 			if( ai->mode_list[i].timing.h_display == mode->timing.h_display &&
390 				ai->mode_list[i].timing.v_display == mode->timing.v_display &&
391 				ai->mode_list[i].virtual_width == mode->virtual_width &&
392 				ai->mode_list[i].virtual_height == mode->virtual_height )
393 				return;
394 		}
395 	}
396 
397 	// set ranges for acceptable values
398 	low = high = *mode;
399 
400 	// range is 6.25% of default clock: arbitrarily picked
401 	pix_clk_range = low.timing.pixel_clock >> 5;
402 	low.timing.pixel_clock -= pix_clk_range;
403 	high.timing.pixel_clock += pix_clk_range;
404 
405 	if( ignore_timing ) {
406 		low.timing.h_total = 0;
407 		low.timing.h_sync_start = 0;
408 		low.timing.h_sync_end = 0;
409 		low.timing.v_total = 0;
410 		low.timing.v_sync_start = 0;
411 		low.timing.v_sync_end = 0;
412 		high.timing.h_total = 0xffff;
413 		high.timing.h_sync_start = 0xffff;
414 		high.timing.h_sync_end = 0xffff;
415 		high.timing.v_total = 0xffff;
416 		high.timing.v_sync_start = 0xffff;
417 		high.timing.v_sync_end = 0xffff;
418 	}
419 
420 	dst = &ai->mode_list[si->mode_count];
421 
422 	// iterator through all colour spaces
423 	for( i = 0; i < (sizeof(spaces) / sizeof(color_space)); i++ ) {
424 		// check whether first port can handle it
425 		*dst = *mode;
426 		dst->space = low.space = high.space = spaces[i];
427 
428 		if( Radeon_ProposeDisplayMode( si, &si->ports[0],
429 			&si->pll, dst, &low, &high ) == B_OK )
430 		{
431 			si->mode_count++;
432 			++dst;
433 
434 		} else {
435 			// it can't, so try second port
436 			*dst = *mode;
437 			dst->space = spaces[i];
438 
439 			if( Radeon_ProposeDisplayMode( si, &si->ports[1],
440 				&si->pll, dst, &low, &high ) == B_OK )
441 			{
442 				si->mode_count++;
443 				++dst;
444 
445 			} else
446 				SHOW_FLOW( 4, "%ld, %ld not supported", dst->virtual_width, dst->virtual_height );
447 		}
448 	}
449 }
450 
451 
452 // add display mode including span mode variations to offical list
453 static void checkAndAddMultiMode( accelerator_info *ai, const display_mode *mode,
454 	bool ignore_timing )
455 {
456 	display_mode wide_mode;
457 
458 	SHOW_FLOW( 4, "%ld, %ld", mode->virtual_width, mode->virtual_height );
459 
460 	// plain mode
461 	checkAndAddMode( ai, mode, ignore_timing );
462 
463 	// double width mode
464 	wide_mode = *mode;
465 	wide_mode.virtual_width *= 2;
466 	wide_mode.flags |= B_SCROLL;
467 	checkAndAddMode( ai, &wide_mode, ignore_timing );
468 
469 	// double height mode
470 	wide_mode = *mode;
471 	wide_mode.virtual_height *= 2;
472 	wide_mode.flags |= B_SCROLL;
473 	checkAndAddMode( ai, &wide_mode, ignore_timing );
474 }
475 
476 // add display mode of flat panel to official list
477 static void addFPMode( accelerator_info *ai, fp_info *fp_info )
478 {
479     if( fp_info->disp_type == dt_dvi_1 || fp_info->disp_type == dt_lvds ) {
480     	display_mode mode;
481 
482 		mode.virtual_width = mode.timing.h_display = fp_info->panel_xres;
483 		mode.virtual_height = mode.timing.v_display = fp_info->panel_yres;
484 
485 		mode.timing.h_total = mode.timing.h_display + fp_info->h_blank;
486 		mode.timing.h_sync_start = mode.timing.h_display + fp_info->h_over_plus;
487 		mode.timing.h_sync_end = mode.timing.h_sync_start + fp_info->h_sync_width;
488 		mode.timing.v_total = mode.timing.v_display + fp_info->v_blank;
489 		mode.timing.v_sync_start = mode.timing.v_display + fp_info->v_over_plus;
490 		mode.timing.v_sync_end = mode.timing.v_sync_start + fp_info->v_sync_width;
491 
492 		mode.timing.pixel_clock = fp_info->dot_clock;
493 
494 		// if we have no pixel clock, assume 60 Hz
495 		// (as we don't program PLL in this case, it doesn't matter
496 		// if it's wrong, we just want this resolution in the mode list)
497 		if( mode.timing.pixel_clock == 0 ) {
498 			// devide by 1000 as clock is in kHz
499 			mode.timing.pixel_clock =
500 				((uint32)mode.timing.h_total * mode.timing.v_total * 60) / 1000;
501 		}
502 
503 		mode.flags = MODE_FLAGS;
504 		mode.h_display_start = 0;
505 		mode.v_display_start = 0;
506 
507 		SHOW_FLOW( 2, "H: %4d %4d %4d %4d (v=%4d)",
508 			mode.timing.h_display, mode.timing.h_sync_start,
509 			mode.timing.h_sync_end, mode.timing.h_total, mode.virtual_width );
510 		SHOW_FLOW( 2, "V: %4d %4d %4d %4d (h=%4d)",
511 			mode.timing.v_display, mode.timing.v_sync_start,
512 			mode.timing.v_sync_end, mode.timing.v_total, mode.virtual_height );
513 		SHOW_FLOW( 2, "clk: %ld", mode.timing.pixel_clock );
514 
515 		// flat panels seem to have strange timings;
516 		// as we ignore user-supplied timing for FPs anyway,
517 		// the mode can (and usually has to) be modified to be
518 		// used for normal CRTs
519 		checkAndAddMultiMode( ai, &mode, true );
520 	}
521 }
522 
523 // create list of officially supported modes
524 status_t Radeon_CreateModeList( shared_info *si )
525 {
526 	size_t max_size;
527 	uint i;
528 	uint max_num_modes;
529 
530 	// maximum number of official modes:
531 	// (predefined-modes + fp-modes) * number-of-colour-spaces * number-of-(non)-span-modes
532 	max_num_modes = ((sizeof( base_mode_list ) / sizeof( base_mode_list[0] ) + 1) * 4 * 3);
533 
534 	max_size = (max_num_modes * sizeof(display_mode) + (B_PAGE_SIZE-1)) & ~(B_PAGE_SIZE-1);
535 
536 	si->mode_list_area = create_area("Radeon accelerant mode info",
537 		(void **)&ai->mode_list, B_ANY_ADDRESS,
538 		max_size, B_NO_LOCK, B_READ_AREA | B_WRITE_AREA);
539 
540 	if( si->mode_list_area < B_OK )
541 		return si->mode_list_area;
542 
543 	si->mode_count = 0;
544 
545 	// check standard modes
546 	for( i = 0; i < sizeof( base_mode_list ) / sizeof( base_mode_list[0] ); i++ )
547 		checkAndAddMultiMode( ai, &base_mode_list[i], false );
548 
549 	// plus fp mode
550 	addFPMode( ai, &si->fp_port );
551 
552 	// as we've created the list ourself, we don't clone it
553 	ai->mode_list_area = si->mode_list_area;
554 
555 	return B_OK;
556 }
557 
558 
559 // cleanup official display mode list
560 void Radeon_DisposeModeList( shared_info *si )
561 {
562 	delete_area( si->mode_list_area );
563 }
564 
565 
566 // public function: wraps for internal propose_display_mode
567 status_t PROPOSE_DISPLAY_MODE( display_mode *target, const display_mode *low,
568 	const display_mode *high )
569 {
570 	virtual_card *vc = ai->vc;
571 	shared_info *si = ai->si;
572 	status_t result1, result2;
573 	bool isTunneled;
574 	status_t result;
575 
576 	// check whether we got a tunneled settings command
577 	result = Radeon_CheckMultiMonTunnel( vc, target, low, high, &isTunneled );
578 	if( isTunneled )
579 		return result;
580 
581 	// transform to multi-screen mode first
582 	Radeon_DetectMultiMode( vc, target );
583 	Radeon_VerifyMultiMode( vc, si, target );
584 
585 	SHOW_FLOW0( 2, "wished:" );
586 	SHOW_FLOW( 2, "H: %4d %4d %4d %4d (v=%4d)",
587 		target->timing.h_display, target->timing.h_sync_start,
588 		target->timing.h_sync_end, target->timing.h_total, target->virtual_width );
589 	SHOW_FLOW( 2, "V: %4d %4d %4d %4d (h=%4d)",
590 		target->timing.v_display, target->timing.v_sync_start,
591 		target->timing.v_sync_end, target->timing.v_total, target->virtual_height );
592 	SHOW_FLOW( 2, "clk: %ld", target->timing.pixel_clock );
593 
594 	// we must assure that each ProposeMode call doesn't tweak the mode in
595 	// a way that it cannot be handled by the other port anymore
596 	result1 = Radeon_ProposeDisplayMode( si, &si->ports[vc->ports[0].physical_port],
597 		&si->pll, target, low, high );
598 
599 	if( result1 == B_ERROR )
600 		return B_ERROR;
601 
602 	if( Radeon_NeedsSecondPort( target )) {
603 		// if both ports are used, make sure both can handle mode
604 		result2 = Radeon_ProposeDisplayMode( si, &si->ports[vc->ports[1].physical_port],
605 			&si->pll, target, low, high );
606 
607 		if( result2 == B_ERROR )
608 			return B_ERROR;
609 	} else {
610 		result2 = B_OK;
611 	}
612 
613 	SHOW_INFO0( 2, "got:" );
614 	SHOW_INFO( 2, "H: %4d %4d %4d %4d (v=%4d)",
615 		target->timing.h_display, target->timing.h_sync_start,
616 		target->timing.h_sync_end, target->timing.h_total, target->virtual_width );
617 	SHOW_INFO( 2, "V: %4d %4d %4d %4d (h=%4d)",
618 		target->timing.v_display, target->timing.v_sync_start,
619 		target->timing.v_sync_end, target->timing.v_total, target->virtual_height );
620 	SHOW_INFO( 2, "clk: %ld", target->timing.pixel_clock );
621 
622 	Radeon_HideMultiMode( vc, target );
623 
624 	if( result1 == B_OK && result2 == B_OK )
625 		return B_OK;
626 	else
627 		return B_BAD_VALUE;
628 }
629