diff options
Diffstat (limited to 'drivers/video/fbdev/core/fbmon.c')
-rw-r--r-- | drivers/video/fbdev/core/fbmon.c | 1592 |
1 files changed, 1592 insertions, 0 deletions
diff --git a/drivers/video/fbdev/core/fbmon.c b/drivers/video/fbdev/core/fbmon.c new file mode 100644 index 000000000000..c204ebe6187e --- /dev/null +++ b/drivers/video/fbdev/core/fbmon.c | |||
@@ -0,0 +1,1592 @@ | |||
1 | /* | ||
2 | * linux/drivers/video/fbmon.c | ||
3 | * | ||
4 | * Copyright (C) 2002 James Simmons <jsimmons@users.sf.net> | ||
5 | * | ||
6 | * Credits: | ||
7 | * | ||
8 | * The EDID Parser is a conglomeration from the following sources: | ||
9 | * | ||
10 | * 1. SciTech SNAP Graphics Architecture | ||
11 | * Copyright (C) 1991-2002 SciTech Software, Inc. All rights reserved. | ||
12 | * | ||
13 | * 2. XFree86 4.3.0, interpret_edid.c | ||
14 | * Copyright 1998 by Egbert Eich <Egbert.Eich@Physik.TU-Darmstadt.DE> | ||
15 | * | ||
16 | * 3. John Fremlin <vii@users.sourceforge.net> and | ||
17 | * Ani Joshi <ajoshi@unixbox.com> | ||
18 | * | ||
19 | * Generalized Timing Formula is derived from: | ||
20 | * | ||
21 | * GTF Spreadsheet by Andy Morrish (1/5/97) | ||
22 | * available at http://www.vesa.org | ||
23 | * | ||
24 | * This file is subject to the terms and conditions of the GNU General Public | ||
25 | * License. See the file COPYING in the main directory of this archive | ||
26 | * for more details. | ||
27 | * | ||
28 | */ | ||
29 | #include <linux/fb.h> | ||
30 | #include <linux/module.h> | ||
31 | #include <linux/pci.h> | ||
32 | #include <linux/slab.h> | ||
33 | #include <video/edid.h> | ||
34 | #include <video/of_videomode.h> | ||
35 | #include <video/videomode.h> | ||
36 | #ifdef CONFIG_PPC_OF | ||
37 | #include <asm/prom.h> | ||
38 | #include <asm/pci-bridge.h> | ||
39 | #endif | ||
40 | #include "../edid.h" | ||
41 | |||
42 | /* | ||
43 | * EDID parser | ||
44 | */ | ||
45 | |||
46 | #undef DEBUG /* define this for verbose EDID parsing output */ | ||
47 | |||
48 | #ifdef DEBUG | ||
49 | #define DPRINTK(fmt, args...) printk(fmt,## args) | ||
50 | #else | ||
51 | #define DPRINTK(fmt, args...) | ||
52 | #endif | ||
53 | |||
54 | #define FBMON_FIX_HEADER 1 | ||
55 | #define FBMON_FIX_INPUT 2 | ||
56 | #define FBMON_FIX_TIMINGS 3 | ||
57 | |||
58 | #ifdef CONFIG_FB_MODE_HELPERS | ||
59 | struct broken_edid { | ||
60 | u8 manufacturer[4]; | ||
61 | u32 model; | ||
62 | u32 fix; | ||
63 | }; | ||
64 | |||
65 | static const struct broken_edid brokendb[] = { | ||
66 | /* DEC FR-PCXAV-YZ */ | ||
67 | { | ||
68 | .manufacturer = "DEC", | ||
69 | .model = 0x073a, | ||
70 | .fix = FBMON_FIX_HEADER, | ||
71 | }, | ||
72 | /* ViewSonic PF775a */ | ||
73 | { | ||
74 | .manufacturer = "VSC", | ||
75 | .model = 0x5a44, | ||
76 | .fix = FBMON_FIX_INPUT, | ||
77 | }, | ||
78 | /* Sharp UXGA? */ | ||
79 | { | ||
80 | .manufacturer = "SHP", | ||
81 | .model = 0x138e, | ||
82 | .fix = FBMON_FIX_TIMINGS, | ||
83 | }, | ||
84 | }; | ||
85 | |||
86 | static const unsigned char edid_v1_header[] = { 0x00, 0xff, 0xff, 0xff, | ||
87 | 0xff, 0xff, 0xff, 0x00 | ||
88 | }; | ||
89 | |||
90 | static void copy_string(unsigned char *c, unsigned char *s) | ||
91 | { | ||
92 | int i; | ||
93 | c = c + 5; | ||
94 | for (i = 0; (i < 13 && *c != 0x0A); i++) | ||
95 | *(s++) = *(c++); | ||
96 | *s = 0; | ||
97 | while (i-- && (*--s == 0x20)) *s = 0; | ||
98 | } | ||
99 | |||
100 | static int edid_is_serial_block(unsigned char *block) | ||
101 | { | ||
102 | if ((block[0] == 0x00) && (block[1] == 0x00) && | ||
103 | (block[2] == 0x00) && (block[3] == 0xff) && | ||
104 | (block[4] == 0x00)) | ||
105 | return 1; | ||
106 | else | ||
107 | return 0; | ||
108 | } | ||
109 | |||
110 | static int edid_is_ascii_block(unsigned char *block) | ||
111 | { | ||
112 | if ((block[0] == 0x00) && (block[1] == 0x00) && | ||
113 | (block[2] == 0x00) && (block[3] == 0xfe) && | ||
114 | (block[4] == 0x00)) | ||
115 | return 1; | ||
116 | else | ||
117 | return 0; | ||
118 | } | ||
119 | |||
120 | static int edid_is_limits_block(unsigned char *block) | ||
121 | { | ||
122 | if ((block[0] == 0x00) && (block[1] == 0x00) && | ||
123 | (block[2] == 0x00) && (block[3] == 0xfd) && | ||
124 | (block[4] == 0x00)) | ||
125 | return 1; | ||
126 | else | ||
127 | return 0; | ||
128 | } | ||
129 | |||
130 | static int edid_is_monitor_block(unsigned char *block) | ||
131 | { | ||
132 | if ((block[0] == 0x00) && (block[1] == 0x00) && | ||
133 | (block[2] == 0x00) && (block[3] == 0xfc) && | ||
134 | (block[4] == 0x00)) | ||
135 | return 1; | ||
136 | else | ||
137 | return 0; | ||
138 | } | ||
139 | |||
140 | static int edid_is_timing_block(unsigned char *block) | ||
141 | { | ||
142 | if ((block[0] != 0x00) || (block[1] != 0x00) || | ||
143 | (block[2] != 0x00) || (block[4] != 0x00)) | ||
144 | return 1; | ||
145 | else | ||
146 | return 0; | ||
147 | } | ||
148 | |||
149 | static int check_edid(unsigned char *edid) | ||
150 | { | ||
151 | unsigned char *block = edid + ID_MANUFACTURER_NAME, manufacturer[4]; | ||
152 | unsigned char *b; | ||
153 | u32 model; | ||
154 | int i, fix = 0, ret = 0; | ||
155 | |||
156 | manufacturer[0] = ((block[0] & 0x7c) >> 2) + '@'; | ||
157 | manufacturer[1] = ((block[0] & 0x03) << 3) + | ||
158 | ((block[1] & 0xe0) >> 5) + '@'; | ||
159 | manufacturer[2] = (block[1] & 0x1f) + '@'; | ||
160 | manufacturer[3] = 0; | ||
161 | model = block[2] + (block[3] << 8); | ||
162 | |||
163 | for (i = 0; i < ARRAY_SIZE(brokendb); i++) { | ||
164 | if (!strncmp(manufacturer, brokendb[i].manufacturer, 4) && | ||
165 | brokendb[i].model == model) { | ||
166 | fix = brokendb[i].fix; | ||
167 | break; | ||
168 | } | ||
169 | } | ||
170 | |||
171 | switch (fix) { | ||
172 | case FBMON_FIX_HEADER: | ||
173 | for (i = 0; i < 8; i++) { | ||
174 | if (edid[i] != edid_v1_header[i]) { | ||
175 | ret = fix; | ||
176 | break; | ||
177 | } | ||
178 | } | ||
179 | break; | ||
180 | case FBMON_FIX_INPUT: | ||
181 | b = edid + EDID_STRUCT_DISPLAY; | ||
182 | /* Only if display is GTF capable will | ||
183 | the input type be reset to analog */ | ||
184 | if (b[4] & 0x01 && b[0] & 0x80) | ||
185 | ret = fix; | ||
186 | break; | ||
187 | case FBMON_FIX_TIMINGS: | ||
188 | b = edid + DETAILED_TIMING_DESCRIPTIONS_START; | ||
189 | ret = fix; | ||
190 | |||
191 | for (i = 0; i < 4; i++) { | ||
192 | if (edid_is_limits_block(b)) { | ||
193 | ret = 0; | ||
194 | break; | ||
195 | } | ||
196 | |||
197 | b += DETAILED_TIMING_DESCRIPTION_SIZE; | ||
198 | } | ||
199 | |||
200 | break; | ||
201 | } | ||
202 | |||
203 | if (ret) | ||
204 | printk("fbmon: The EDID Block of " | ||
205 | "Manufacturer: %s Model: 0x%x is known to " | ||
206 | "be broken,\n", manufacturer, model); | ||
207 | |||
208 | return ret; | ||
209 | } | ||
210 | |||
211 | static void fix_edid(unsigned char *edid, int fix) | ||
212 | { | ||
213 | int i; | ||
214 | unsigned char *b, csum = 0; | ||
215 | |||
216 | switch (fix) { | ||
217 | case FBMON_FIX_HEADER: | ||
218 | printk("fbmon: trying a header reconstruct\n"); | ||
219 | memcpy(edid, edid_v1_header, 8); | ||
220 | break; | ||
221 | case FBMON_FIX_INPUT: | ||
222 | printk("fbmon: trying to fix input type\n"); | ||
223 | b = edid + EDID_STRUCT_DISPLAY; | ||
224 | b[0] &= ~0x80; | ||
225 | edid[127] += 0x80; | ||
226 | break; | ||
227 | case FBMON_FIX_TIMINGS: | ||
228 | printk("fbmon: trying to fix monitor timings\n"); | ||
229 | b = edid + DETAILED_TIMING_DESCRIPTIONS_START; | ||
230 | for (i = 0; i < 4; i++) { | ||
231 | if (!(edid_is_serial_block(b) || | ||
232 | edid_is_ascii_block(b) || | ||
233 | edid_is_monitor_block(b) || | ||
234 | edid_is_timing_block(b))) { | ||
235 | b[0] = 0x00; | ||
236 | b[1] = 0x00; | ||
237 | b[2] = 0x00; | ||
238 | b[3] = 0xfd; | ||
239 | b[4] = 0x00; | ||
240 | b[5] = 60; /* vfmin */ | ||
241 | b[6] = 60; /* vfmax */ | ||
242 | b[7] = 30; /* hfmin */ | ||
243 | b[8] = 75; /* hfmax */ | ||
244 | b[9] = 17; /* pixclock - 170 MHz*/ | ||
245 | b[10] = 0; /* GTF */ | ||
246 | break; | ||
247 | } | ||
248 | |||
249 | b += DETAILED_TIMING_DESCRIPTION_SIZE; | ||
250 | } | ||
251 | |||
252 | for (i = 0; i < EDID_LENGTH - 1; i++) | ||
253 | csum += edid[i]; | ||
254 | |||
255 | edid[127] = 256 - csum; | ||
256 | break; | ||
257 | } | ||
258 | } | ||
259 | |||
260 | static int edid_checksum(unsigned char *edid) | ||
261 | { | ||
262 | unsigned char csum = 0, all_null = 0; | ||
263 | int i, err = 0, fix = check_edid(edid); | ||
264 | |||
265 | if (fix) | ||
266 | fix_edid(edid, fix); | ||
267 | |||
268 | for (i = 0; i < EDID_LENGTH; i++) { | ||
269 | csum += edid[i]; | ||
270 | all_null |= edid[i]; | ||
271 | } | ||
272 | |||
273 | if (csum == 0x00 && all_null) { | ||
274 | /* checksum passed, everything's good */ | ||
275 | err = 1; | ||
276 | } | ||
277 | |||
278 | return err; | ||
279 | } | ||
280 | |||
281 | static int edid_check_header(unsigned char *edid) | ||
282 | { | ||
283 | int i, err = 1, fix = check_edid(edid); | ||
284 | |||
285 | if (fix) | ||
286 | fix_edid(edid, fix); | ||
287 | |||
288 | for (i = 0; i < 8; i++) { | ||
289 | if (edid[i] != edid_v1_header[i]) | ||
290 | err = 0; | ||
291 | } | ||
292 | |||
293 | return err; | ||
294 | } | ||
295 | |||
296 | static void parse_vendor_block(unsigned char *block, struct fb_monspecs *specs) | ||
297 | { | ||
298 | specs->manufacturer[0] = ((block[0] & 0x7c) >> 2) + '@'; | ||
299 | specs->manufacturer[1] = ((block[0] & 0x03) << 3) + | ||
300 | ((block[1] & 0xe0) >> 5) + '@'; | ||
301 | specs->manufacturer[2] = (block[1] & 0x1f) + '@'; | ||
302 | specs->manufacturer[3] = 0; | ||
303 | specs->model = block[2] + (block[3] << 8); | ||
304 | specs->serial = block[4] + (block[5] << 8) + | ||
305 | (block[6] << 16) + (block[7] << 24); | ||
306 | specs->year = block[9] + 1990; | ||
307 | specs->week = block[8]; | ||
308 | DPRINTK(" Manufacturer: %s\n", specs->manufacturer); | ||
309 | DPRINTK(" Model: %x\n", specs->model); | ||
310 | DPRINTK(" Serial#: %u\n", specs->serial); | ||
311 | DPRINTK(" Year: %u Week %u\n", specs->year, specs->week); | ||
312 | } | ||
313 | |||
314 | static void get_dpms_capabilities(unsigned char flags, | ||
315 | struct fb_monspecs *specs) | ||
316 | { | ||
317 | specs->dpms = 0; | ||
318 | if (flags & DPMS_ACTIVE_OFF) | ||
319 | specs->dpms |= FB_DPMS_ACTIVE_OFF; | ||
320 | if (flags & DPMS_SUSPEND) | ||
321 | specs->dpms |= FB_DPMS_SUSPEND; | ||
322 | if (flags & DPMS_STANDBY) | ||
323 | specs->dpms |= FB_DPMS_STANDBY; | ||
324 | DPRINTK(" DPMS: Active %s, Suspend %s, Standby %s\n", | ||
325 | (flags & DPMS_ACTIVE_OFF) ? "yes" : "no", | ||
326 | (flags & DPMS_SUSPEND) ? "yes" : "no", | ||
327 | (flags & DPMS_STANDBY) ? "yes" : "no"); | ||
328 | } | ||
329 | |||
330 | static void get_chroma(unsigned char *block, struct fb_monspecs *specs) | ||
331 | { | ||
332 | int tmp; | ||
333 | |||
334 | DPRINTK(" Chroma\n"); | ||
335 | /* Chromaticity data */ | ||
336 | tmp = ((block[5] & (3 << 6)) >> 6) | (block[0x7] << 2); | ||
337 | tmp *= 1000; | ||
338 | tmp += 512; | ||
339 | specs->chroma.redx = tmp/1024; | ||
340 | DPRINTK(" RedX: 0.%03d ", specs->chroma.redx); | ||
341 | |||
342 | tmp = ((block[5] & (3 << 4)) >> 4) | (block[0x8] << 2); | ||
343 | tmp *= 1000; | ||
344 | tmp += 512; | ||
345 | specs->chroma.redy = tmp/1024; | ||
346 | DPRINTK("RedY: 0.%03d\n", specs->chroma.redy); | ||
347 | |||
348 | tmp = ((block[5] & (3 << 2)) >> 2) | (block[0x9] << 2); | ||
349 | tmp *= 1000; | ||
350 | tmp += 512; | ||
351 | specs->chroma.greenx = tmp/1024; | ||
352 | DPRINTK(" GreenX: 0.%03d ", specs->chroma.greenx); | ||
353 | |||
354 | tmp = (block[5] & 3) | (block[0xa] << 2); | ||
355 | tmp *= 1000; | ||
356 | tmp += 512; | ||
357 | specs->chroma.greeny = tmp/1024; | ||
358 | DPRINTK("GreenY: 0.%03d\n", specs->chroma.greeny); | ||
359 | |||
360 | tmp = ((block[6] & (3 << 6)) >> 6) | (block[0xb] << 2); | ||
361 | tmp *= 1000; | ||
362 | tmp += 512; | ||
363 | specs->chroma.bluex = tmp/1024; | ||
364 | DPRINTK(" BlueX: 0.%03d ", specs->chroma.bluex); | ||
365 | |||
366 | tmp = ((block[6] & (3 << 4)) >> 4) | (block[0xc] << 2); | ||
367 | tmp *= 1000; | ||
368 | tmp += 512; | ||
369 | specs->chroma.bluey = tmp/1024; | ||
370 | DPRINTK("BlueY: 0.%03d\n", specs->chroma.bluey); | ||
371 | |||
372 | tmp = ((block[6] & (3 << 2)) >> 2) | (block[0xd] << 2); | ||
373 | tmp *= 1000; | ||
374 | tmp += 512; | ||
375 | specs->chroma.whitex = tmp/1024; | ||
376 | DPRINTK(" WhiteX: 0.%03d ", specs->chroma.whitex); | ||
377 | |||
378 | tmp = (block[6] & 3) | (block[0xe] << 2); | ||
379 | tmp *= 1000; | ||
380 | tmp += 512; | ||
381 | specs->chroma.whitey = tmp/1024; | ||
382 | DPRINTK("WhiteY: 0.%03d\n", specs->chroma.whitey); | ||
383 | } | ||
384 | |||
385 | static void calc_mode_timings(int xres, int yres, int refresh, | ||
386 | struct fb_videomode *mode) | ||
387 | { | ||
388 | struct fb_var_screeninfo *var; | ||
389 | |||
390 | var = kzalloc(sizeof(struct fb_var_screeninfo), GFP_KERNEL); | ||
391 | |||
392 | if (var) { | ||
393 | var->xres = xres; | ||
394 | var->yres = yres; | ||
395 | fb_get_mode(FB_VSYNCTIMINGS | FB_IGNOREMON, | ||
396 | refresh, var, NULL); | ||
397 | mode->xres = xres; | ||
398 | mode->yres = yres; | ||
399 | mode->pixclock = var->pixclock; | ||
400 | mode->refresh = refresh; | ||
401 | mode->left_margin = var->left_margin; | ||
402 | mode->right_margin = var->right_margin; | ||
403 | mode->upper_margin = var->upper_margin; | ||
404 | mode->lower_margin = var->lower_margin; | ||
405 | mode->hsync_len = var->hsync_len; | ||
406 | mode->vsync_len = var->vsync_len; | ||
407 | mode->vmode = 0; | ||
408 | mode->sync = 0; | ||
409 | kfree(var); | ||
410 | } | ||
411 | } | ||
412 | |||
413 | static int get_est_timing(unsigned char *block, struct fb_videomode *mode) | ||
414 | { | ||
415 | int num = 0; | ||
416 | unsigned char c; | ||
417 | |||
418 | c = block[0]; | ||
419 | if (c&0x80) { | ||
420 | calc_mode_timings(720, 400, 70, &mode[num]); | ||
421 | mode[num++].flag = FB_MODE_IS_CALCULATED; | ||
422 | DPRINTK(" 720x400@70Hz\n"); | ||
423 | } | ||
424 | if (c&0x40) { | ||
425 | calc_mode_timings(720, 400, 88, &mode[num]); | ||
426 | mode[num++].flag = FB_MODE_IS_CALCULATED; | ||
427 | DPRINTK(" 720x400@88Hz\n"); | ||
428 | } | ||
429 | if (c&0x20) { | ||
430 | mode[num++] = vesa_modes[3]; | ||
431 | DPRINTK(" 640x480@60Hz\n"); | ||
432 | } | ||
433 | if (c&0x10) { | ||
434 | calc_mode_timings(640, 480, 67, &mode[num]); | ||
435 | mode[num++].flag = FB_MODE_IS_CALCULATED; | ||
436 | DPRINTK(" 640x480@67Hz\n"); | ||
437 | } | ||
438 | if (c&0x08) { | ||
439 | mode[num++] = vesa_modes[4]; | ||
440 | DPRINTK(" 640x480@72Hz\n"); | ||
441 | } | ||
442 | if (c&0x04) { | ||
443 | mode[num++] = vesa_modes[5]; | ||
444 | DPRINTK(" 640x480@75Hz\n"); | ||
445 | } | ||
446 | if (c&0x02) { | ||
447 | mode[num++] = vesa_modes[7]; | ||
448 | DPRINTK(" 800x600@56Hz\n"); | ||
449 | } | ||
450 | if (c&0x01) { | ||
451 | mode[num++] = vesa_modes[8]; | ||
452 | DPRINTK(" 800x600@60Hz\n"); | ||
453 | } | ||
454 | |||
455 | c = block[1]; | ||
456 | if (c&0x80) { | ||
457 | mode[num++] = vesa_modes[9]; | ||
458 | DPRINTK(" 800x600@72Hz\n"); | ||
459 | } | ||
460 | if (c&0x40) { | ||
461 | mode[num++] = vesa_modes[10]; | ||
462 | DPRINTK(" 800x600@75Hz\n"); | ||
463 | } | ||
464 | if (c&0x20) { | ||
465 | calc_mode_timings(832, 624, 75, &mode[num]); | ||
466 | mode[num++].flag = FB_MODE_IS_CALCULATED; | ||
467 | DPRINTK(" 832x624@75Hz\n"); | ||
468 | } | ||
469 | if (c&0x10) { | ||
470 | mode[num++] = vesa_modes[12]; | ||
471 | DPRINTK(" 1024x768@87Hz Interlaced\n"); | ||
472 | } | ||
473 | if (c&0x08) { | ||
474 | mode[num++] = vesa_modes[13]; | ||
475 | DPRINTK(" 1024x768@60Hz\n"); | ||
476 | } | ||
477 | if (c&0x04) { | ||
478 | mode[num++] = vesa_modes[14]; | ||
479 | DPRINTK(" 1024x768@70Hz\n"); | ||
480 | } | ||
481 | if (c&0x02) { | ||
482 | mode[num++] = vesa_modes[15]; | ||
483 | DPRINTK(" 1024x768@75Hz\n"); | ||
484 | } | ||
485 | if (c&0x01) { | ||
486 | mode[num++] = vesa_modes[21]; | ||
487 | DPRINTK(" 1280x1024@75Hz\n"); | ||
488 | } | ||
489 | c = block[2]; | ||
490 | if (c&0x80) { | ||
491 | mode[num++] = vesa_modes[17]; | ||
492 | DPRINTK(" 1152x870@75Hz\n"); | ||
493 | } | ||
494 | DPRINTK(" Manufacturer's mask: %x\n",c&0x7F); | ||
495 | return num; | ||
496 | } | ||
497 | |||
498 | static int get_std_timing(unsigned char *block, struct fb_videomode *mode, | ||
499 | int ver, int rev) | ||
500 | { | ||
501 | int xres, yres = 0, refresh, ratio, i; | ||
502 | |||
503 | xres = (block[0] + 31) * 8; | ||
504 | if (xres <= 256) | ||
505 | return 0; | ||
506 | |||
507 | ratio = (block[1] & 0xc0) >> 6; | ||
508 | switch (ratio) { | ||
509 | case 0: | ||
510 | /* in EDID 1.3 the meaning of 0 changed to 16:10 (prior 1:1) */ | ||
511 | if (ver < 1 || (ver == 1 && rev < 3)) | ||
512 | yres = xres; | ||
513 | else | ||
514 | yres = (xres * 10)/16; | ||
515 | break; | ||
516 | case 1: | ||
517 | yres = (xres * 3)/4; | ||
518 | break; | ||
519 | case 2: | ||
520 | yres = (xres * 4)/5; | ||
521 | break; | ||
522 | case 3: | ||
523 | yres = (xres * 9)/16; | ||
524 | break; | ||
525 | } | ||
526 | refresh = (block[1] & 0x3f) + 60; | ||
527 | |||
528 | DPRINTK(" %dx%d@%dHz\n", xres, yres, refresh); | ||
529 | for (i = 0; i < VESA_MODEDB_SIZE; i++) { | ||
530 | if (vesa_modes[i].xres == xres && | ||
531 | vesa_modes[i].yres == yres && | ||
532 | vesa_modes[i].refresh == refresh) { | ||
533 | *mode = vesa_modes[i]; | ||
534 | mode->flag |= FB_MODE_IS_STANDARD; | ||
535 | return 1; | ||
536 | } | ||
537 | } | ||
538 | calc_mode_timings(xres, yres, refresh, mode); | ||
539 | return 1; | ||
540 | } | ||
541 | |||
542 | static int get_dst_timing(unsigned char *block, | ||
543 | struct fb_videomode *mode, int ver, int rev) | ||
544 | { | ||
545 | int j, num = 0; | ||
546 | |||
547 | for (j = 0; j < 6; j++, block += STD_TIMING_DESCRIPTION_SIZE) | ||
548 | num += get_std_timing(block, &mode[num], ver, rev); | ||
549 | |||
550 | return num; | ||
551 | } | ||
552 | |||
553 | static void get_detailed_timing(unsigned char *block, | ||
554 | struct fb_videomode *mode) | ||
555 | { | ||
556 | mode->xres = H_ACTIVE; | ||
557 | mode->yres = V_ACTIVE; | ||
558 | mode->pixclock = PIXEL_CLOCK; | ||
559 | mode->pixclock /= 1000; | ||
560 | mode->pixclock = KHZ2PICOS(mode->pixclock); | ||
561 | mode->right_margin = H_SYNC_OFFSET; | ||
562 | mode->left_margin = (H_ACTIVE + H_BLANKING) - | ||
563 | (H_ACTIVE + H_SYNC_OFFSET + H_SYNC_WIDTH); | ||
564 | mode->upper_margin = V_BLANKING - V_SYNC_OFFSET - | ||
565 | V_SYNC_WIDTH; | ||
566 | mode->lower_margin = V_SYNC_OFFSET; | ||
567 | mode->hsync_len = H_SYNC_WIDTH; | ||
568 | mode->vsync_len = V_SYNC_WIDTH; | ||
569 | if (HSYNC_POSITIVE) | ||
570 | mode->sync |= FB_SYNC_HOR_HIGH_ACT; | ||
571 | if (VSYNC_POSITIVE) | ||
572 | mode->sync |= FB_SYNC_VERT_HIGH_ACT; | ||
573 | mode->refresh = PIXEL_CLOCK/((H_ACTIVE + H_BLANKING) * | ||
574 | (V_ACTIVE + V_BLANKING)); | ||
575 | if (INTERLACED) { | ||
576 | mode->yres *= 2; | ||
577 | mode->upper_margin *= 2; | ||
578 | mode->lower_margin *= 2; | ||
579 | mode->vsync_len *= 2; | ||
580 | mode->vmode |= FB_VMODE_INTERLACED; | ||
581 | } | ||
582 | mode->flag = FB_MODE_IS_DETAILED; | ||
583 | |||
584 | DPRINTK(" %d MHz ", PIXEL_CLOCK/1000000); | ||
585 | DPRINTK("%d %d %d %d ", H_ACTIVE, H_ACTIVE + H_SYNC_OFFSET, | ||
586 | H_ACTIVE + H_SYNC_OFFSET + H_SYNC_WIDTH, H_ACTIVE + H_BLANKING); | ||
587 | DPRINTK("%d %d %d %d ", V_ACTIVE, V_ACTIVE + V_SYNC_OFFSET, | ||
588 | V_ACTIVE + V_SYNC_OFFSET + V_SYNC_WIDTH, V_ACTIVE + V_BLANKING); | ||
589 | DPRINTK("%sHSync %sVSync\n\n", (HSYNC_POSITIVE) ? "+" : "-", | ||
590 | (VSYNC_POSITIVE) ? "+" : "-"); | ||
591 | } | ||
592 | |||
593 | /** | ||
594 | * fb_create_modedb - create video mode database | ||
595 | * @edid: EDID data | ||
596 | * @dbsize: database size | ||
597 | * | ||
598 | * RETURNS: struct fb_videomode, @dbsize contains length of database | ||
599 | * | ||
600 | * DESCRIPTION: | ||
601 | * This function builds a mode database using the contents of the EDID | ||
602 | * data | ||
603 | */ | ||
604 | static struct fb_videomode *fb_create_modedb(unsigned char *edid, int *dbsize) | ||
605 | { | ||
606 | struct fb_videomode *mode, *m; | ||
607 | unsigned char *block; | ||
608 | int num = 0, i, first = 1; | ||
609 | int ver, rev; | ||
610 | |||
611 | ver = edid[EDID_STRUCT_VERSION]; | ||
612 | rev = edid[EDID_STRUCT_REVISION]; | ||
613 | |||
614 | mode = kzalloc(50 * sizeof(struct fb_videomode), GFP_KERNEL); | ||
615 | if (mode == NULL) | ||
616 | return NULL; | ||
617 | |||
618 | if (edid == NULL || !edid_checksum(edid) || | ||
619 | !edid_check_header(edid)) { | ||
620 | kfree(mode); | ||
621 | return NULL; | ||
622 | } | ||
623 | |||
624 | *dbsize = 0; | ||
625 | |||
626 | DPRINTK(" Detailed Timings\n"); | ||
627 | block = edid + DETAILED_TIMING_DESCRIPTIONS_START; | ||
628 | for (i = 0; i < 4; i++, block+= DETAILED_TIMING_DESCRIPTION_SIZE) { | ||
629 | if (!(block[0] == 0x00 && block[1] == 0x00)) { | ||
630 | get_detailed_timing(block, &mode[num]); | ||
631 | if (first) { | ||
632 | mode[num].flag |= FB_MODE_IS_FIRST; | ||
633 | first = 0; | ||
634 | } | ||
635 | num++; | ||
636 | } | ||
637 | } | ||
638 | |||
639 | DPRINTK(" Supported VESA Modes\n"); | ||
640 | block = edid + ESTABLISHED_TIMING_1; | ||
641 | num += get_est_timing(block, &mode[num]); | ||
642 | |||
643 | DPRINTK(" Standard Timings\n"); | ||
644 | block = edid + STD_TIMING_DESCRIPTIONS_START; | ||
645 | for (i = 0; i < STD_TIMING; i++, block += STD_TIMING_DESCRIPTION_SIZE) | ||
646 | num += get_std_timing(block, &mode[num], ver, rev); | ||
647 | |||
648 | block = edid + DETAILED_TIMING_DESCRIPTIONS_START; | ||
649 | for (i = 0; i < 4; i++, block+= DETAILED_TIMING_DESCRIPTION_SIZE) { | ||
650 | if (block[0] == 0x00 && block[1] == 0x00 && block[3] == 0xfa) | ||
651 | num += get_dst_timing(block + 5, &mode[num], ver, rev); | ||
652 | } | ||
653 | |||
654 | /* Yikes, EDID data is totally useless */ | ||
655 | if (!num) { | ||
656 | kfree(mode); | ||
657 | return NULL; | ||
658 | } | ||
659 | |||
660 | *dbsize = num; | ||
661 | m = kmalloc(num * sizeof(struct fb_videomode), GFP_KERNEL); | ||
662 | if (!m) | ||
663 | return mode; | ||
664 | memmove(m, mode, num * sizeof(struct fb_videomode)); | ||
665 | kfree(mode); | ||
666 | return m; | ||
667 | } | ||
668 | |||
669 | /** | ||
670 | * fb_destroy_modedb - destroys mode database | ||
671 | * @modedb: mode database to destroy | ||
672 | * | ||
673 | * DESCRIPTION: | ||
674 | * Destroy mode database created by fb_create_modedb | ||
675 | */ | ||
676 | void fb_destroy_modedb(struct fb_videomode *modedb) | ||
677 | { | ||
678 | kfree(modedb); | ||
679 | } | ||
680 | |||
681 | static int fb_get_monitor_limits(unsigned char *edid, struct fb_monspecs *specs) | ||
682 | { | ||
683 | int i, retval = 1; | ||
684 | unsigned char *block; | ||
685 | |||
686 | block = edid + DETAILED_TIMING_DESCRIPTIONS_START; | ||
687 | |||
688 | DPRINTK(" Monitor Operating Limits: "); | ||
689 | |||
690 | for (i = 0; i < 4; i++, block += DETAILED_TIMING_DESCRIPTION_SIZE) { | ||
691 | if (edid_is_limits_block(block)) { | ||
692 | specs->hfmin = H_MIN_RATE * 1000; | ||
693 | specs->hfmax = H_MAX_RATE * 1000; | ||
694 | specs->vfmin = V_MIN_RATE; | ||
695 | specs->vfmax = V_MAX_RATE; | ||
696 | specs->dclkmax = MAX_PIXEL_CLOCK * 1000000; | ||
697 | specs->gtf = (GTF_SUPPORT) ? 1 : 0; | ||
698 | retval = 0; | ||
699 | DPRINTK("From EDID\n"); | ||
700 | break; | ||
701 | } | ||
702 | } | ||
703 | |||
704 | /* estimate monitor limits based on modes supported */ | ||
705 | if (retval) { | ||
706 | struct fb_videomode *modes, *mode; | ||
707 | int num_modes, hz, hscan, pixclock; | ||
708 | int vtotal, htotal; | ||
709 | |||
710 | modes = fb_create_modedb(edid, &num_modes); | ||
711 | if (!modes) { | ||
712 | DPRINTK("None Available\n"); | ||
713 | return 1; | ||
714 | } | ||
715 | |||
716 | retval = 0; | ||
717 | for (i = 0; i < num_modes; i++) { | ||
718 | mode = &modes[i]; | ||
719 | pixclock = PICOS2KHZ(modes[i].pixclock) * 1000; | ||
720 | htotal = mode->xres + mode->right_margin + mode->hsync_len | ||
721 | + mode->left_margin; | ||
722 | vtotal = mode->yres + mode->lower_margin + mode->vsync_len | ||
723 | + mode->upper_margin; | ||
724 | |||
725 | if (mode->vmode & FB_VMODE_INTERLACED) | ||
726 | vtotal /= 2; | ||
727 | |||
728 | if (mode->vmode & FB_VMODE_DOUBLE) | ||
729 | vtotal *= 2; | ||
730 | |||
731 | hscan = (pixclock + htotal / 2) / htotal; | ||
732 | hscan = (hscan + 500) / 1000 * 1000; | ||
733 | hz = (hscan + vtotal / 2) / vtotal; | ||
734 | |||
735 | if (specs->dclkmax == 0 || specs->dclkmax < pixclock) | ||
736 | specs->dclkmax = pixclock; | ||
737 | |||
738 | if (specs->dclkmin == 0 || specs->dclkmin > pixclock) | ||
739 | specs->dclkmin = pixclock; | ||
740 | |||
741 | if (specs->hfmax == 0 || specs->hfmax < hscan) | ||
742 | specs->hfmax = hscan; | ||
743 | |||
744 | if (specs->hfmin == 0 || specs->hfmin > hscan) | ||
745 | specs->hfmin = hscan; | ||
746 | |||
747 | if (specs->vfmax == 0 || specs->vfmax < hz) | ||
748 | specs->vfmax = hz; | ||
749 | |||
750 | if (specs->vfmin == 0 || specs->vfmin > hz) | ||
751 | specs->vfmin = hz; | ||
752 | } | ||
753 | DPRINTK("Extrapolated\n"); | ||
754 | fb_destroy_modedb(modes); | ||
755 | } | ||
756 | DPRINTK(" H: %d-%dKHz V: %d-%dHz DCLK: %dMHz\n", | ||
757 | specs->hfmin/1000, specs->hfmax/1000, specs->vfmin, | ||
758 | specs->vfmax, specs->dclkmax/1000000); | ||
759 | return retval; | ||
760 | } | ||
761 | |||
762 | static void get_monspecs(unsigned char *edid, struct fb_monspecs *specs) | ||
763 | { | ||
764 | unsigned char c, *block; | ||
765 | |||
766 | block = edid + EDID_STRUCT_DISPLAY; | ||
767 | |||
768 | fb_get_monitor_limits(edid, specs); | ||
769 | |||
770 | c = block[0] & 0x80; | ||
771 | specs->input = 0; | ||
772 | if (c) { | ||
773 | specs->input |= FB_DISP_DDI; | ||
774 | DPRINTK(" Digital Display Input"); | ||
775 | } else { | ||
776 | DPRINTK(" Analog Display Input: Input Voltage - "); | ||
777 | switch ((block[0] & 0x60) >> 5) { | ||
778 | case 0: | ||
779 | DPRINTK("0.700V/0.300V"); | ||
780 | specs->input |= FB_DISP_ANA_700_300; | ||
781 | break; | ||
782 | case 1: | ||
783 | DPRINTK("0.714V/0.286V"); | ||
784 | specs->input |= FB_DISP_ANA_714_286; | ||
785 | break; | ||
786 | case 2: | ||
787 | DPRINTK("1.000V/0.400V"); | ||
788 | specs->input |= FB_DISP_ANA_1000_400; | ||
789 | break; | ||
790 | case 3: | ||
791 | DPRINTK("0.700V/0.000V"); | ||
792 | specs->input |= FB_DISP_ANA_700_000; | ||
793 | break; | ||
794 | } | ||
795 | } | ||
796 | DPRINTK("\n Sync: "); | ||
797 | c = block[0] & 0x10; | ||
798 | if (c) | ||
799 | DPRINTK(" Configurable signal level\n"); | ||
800 | c = block[0] & 0x0f; | ||
801 | specs->signal = 0; | ||
802 | if (c & 0x10) { | ||
803 | DPRINTK("Blank to Blank "); | ||
804 | specs->signal |= FB_SIGNAL_BLANK_BLANK; | ||
805 | } | ||
806 | if (c & 0x08) { | ||
807 | DPRINTK("Separate "); | ||
808 | specs->signal |= FB_SIGNAL_SEPARATE; | ||
809 | } | ||
810 | if (c & 0x04) { | ||
811 | DPRINTK("Composite "); | ||
812 | specs->signal |= FB_SIGNAL_COMPOSITE; | ||
813 | } | ||
814 | if (c & 0x02) { | ||
815 | DPRINTK("Sync on Green "); | ||
816 | specs->signal |= FB_SIGNAL_SYNC_ON_GREEN; | ||
817 | } | ||
818 | if (c & 0x01) { | ||
819 | DPRINTK("Serration on "); | ||
820 | specs->signal |= FB_SIGNAL_SERRATION_ON; | ||
821 | } | ||
822 | DPRINTK("\n"); | ||
823 | specs->max_x = block[1]; | ||
824 | specs->max_y = block[2]; | ||
825 | DPRINTK(" Max H-size in cm: "); | ||
826 | if (specs->max_x) | ||
827 | DPRINTK("%d\n", specs->max_x); | ||
828 | else | ||
829 | DPRINTK("variable\n"); | ||
830 | DPRINTK(" Max V-size in cm: "); | ||
831 | if (specs->max_y) | ||
832 | DPRINTK("%d\n", specs->max_y); | ||
833 | else | ||
834 | DPRINTK("variable\n"); | ||
835 | |||
836 | c = block[3]; | ||
837 | specs->gamma = c+100; | ||
838 | DPRINTK(" Gamma: "); | ||
839 | DPRINTK("%d.%d\n", specs->gamma/100, specs->gamma % 100); | ||
840 | |||
841 | get_dpms_capabilities(block[4], specs); | ||
842 | |||
843 | switch ((block[4] & 0x18) >> 3) { | ||
844 | case 0: | ||
845 | DPRINTK(" Monochrome/Grayscale\n"); | ||
846 | specs->input |= FB_DISP_MONO; | ||
847 | break; | ||
848 | case 1: | ||
849 | DPRINTK(" RGB Color Display\n"); | ||
850 | specs->input |= FB_DISP_RGB; | ||
851 | break; | ||
852 | case 2: | ||
853 | DPRINTK(" Non-RGB Multicolor Display\n"); | ||
854 | specs->input |= FB_DISP_MULTI; | ||
855 | break; | ||
856 | default: | ||
857 | DPRINTK(" Unknown\n"); | ||
858 | specs->input |= FB_DISP_UNKNOWN; | ||
859 | break; | ||
860 | } | ||
861 | |||
862 | get_chroma(block, specs); | ||
863 | |||
864 | specs->misc = 0; | ||
865 | c = block[4] & 0x7; | ||
866 | if (c & 0x04) { | ||
867 | DPRINTK(" Default color format is primary\n"); | ||
868 | specs->misc |= FB_MISC_PRIM_COLOR; | ||
869 | } | ||
870 | if (c & 0x02) { | ||
871 | DPRINTK(" First DETAILED Timing is preferred\n"); | ||
872 | specs->misc |= FB_MISC_1ST_DETAIL; | ||
873 | } | ||
874 | if (c & 0x01) { | ||
875 | printk(" Display is GTF capable\n"); | ||
876 | specs->gtf = 1; | ||
877 | } | ||
878 | } | ||
879 | |||
880 | int fb_parse_edid(unsigned char *edid, struct fb_var_screeninfo *var) | ||
881 | { | ||
882 | int i; | ||
883 | unsigned char *block; | ||
884 | |||
885 | if (edid == NULL || var == NULL) | ||
886 | return 1; | ||
887 | |||
888 | if (!(edid_checksum(edid))) | ||
889 | return 1; | ||
890 | |||
891 | if (!(edid_check_header(edid))) | ||
892 | return 1; | ||
893 | |||
894 | block = edid + DETAILED_TIMING_DESCRIPTIONS_START; | ||
895 | |||
896 | for (i = 0; i < 4; i++, block += DETAILED_TIMING_DESCRIPTION_SIZE) { | ||
897 | if (edid_is_timing_block(block)) { | ||
898 | var->xres = var->xres_virtual = H_ACTIVE; | ||
899 | var->yres = var->yres_virtual = V_ACTIVE; | ||
900 | var->height = var->width = 0; | ||
901 | var->right_margin = H_SYNC_OFFSET; | ||
902 | var->left_margin = (H_ACTIVE + H_BLANKING) - | ||
903 | (H_ACTIVE + H_SYNC_OFFSET + H_SYNC_WIDTH); | ||
904 | var->upper_margin = V_BLANKING - V_SYNC_OFFSET - | ||
905 | V_SYNC_WIDTH; | ||
906 | var->lower_margin = V_SYNC_OFFSET; | ||
907 | var->hsync_len = H_SYNC_WIDTH; | ||
908 | var->vsync_len = V_SYNC_WIDTH; | ||
909 | var->pixclock = PIXEL_CLOCK; | ||
910 | var->pixclock /= 1000; | ||
911 | var->pixclock = KHZ2PICOS(var->pixclock); | ||
912 | |||
913 | if (HSYNC_POSITIVE) | ||
914 | var->sync |= FB_SYNC_HOR_HIGH_ACT; | ||
915 | if (VSYNC_POSITIVE) | ||
916 | var->sync |= FB_SYNC_VERT_HIGH_ACT; | ||
917 | return 0; | ||
918 | } | ||
919 | } | ||
920 | return 1; | ||
921 | } | ||
922 | |||
923 | void fb_edid_to_monspecs(unsigned char *edid, struct fb_monspecs *specs) | ||
924 | { | ||
925 | unsigned char *block; | ||
926 | int i, found = 0; | ||
927 | |||
928 | if (edid == NULL) | ||
929 | return; | ||
930 | |||
931 | if (!(edid_checksum(edid))) | ||
932 | return; | ||
933 | |||
934 | if (!(edid_check_header(edid))) | ||
935 | return; | ||
936 | |||
937 | memset(specs, 0, sizeof(struct fb_monspecs)); | ||
938 | |||
939 | specs->version = edid[EDID_STRUCT_VERSION]; | ||
940 | specs->revision = edid[EDID_STRUCT_REVISION]; | ||
941 | |||
942 | DPRINTK("========================================\n"); | ||
943 | DPRINTK("Display Information (EDID)\n"); | ||
944 | DPRINTK("========================================\n"); | ||
945 | DPRINTK(" EDID Version %d.%d\n", (int) specs->version, | ||
946 | (int) specs->revision); | ||
947 | |||
948 | parse_vendor_block(edid + ID_MANUFACTURER_NAME, specs); | ||
949 | |||
950 | block = edid + DETAILED_TIMING_DESCRIPTIONS_START; | ||
951 | for (i = 0; i < 4; i++, block += DETAILED_TIMING_DESCRIPTION_SIZE) { | ||
952 | if (edid_is_serial_block(block)) { | ||
953 | copy_string(block, specs->serial_no); | ||
954 | DPRINTK(" Serial Number: %s\n", specs->serial_no); | ||
955 | } else if (edid_is_ascii_block(block)) { | ||
956 | copy_string(block, specs->ascii); | ||
957 | DPRINTK(" ASCII Block: %s\n", specs->ascii); | ||
958 | } else if (edid_is_monitor_block(block)) { | ||
959 | copy_string(block, specs->monitor); | ||
960 | DPRINTK(" Monitor Name: %s\n", specs->monitor); | ||
961 | } | ||
962 | } | ||
963 | |||
964 | DPRINTK(" Display Characteristics:\n"); | ||
965 | get_monspecs(edid, specs); | ||
966 | |||
967 | specs->modedb = fb_create_modedb(edid, &specs->modedb_len); | ||
968 | |||
969 | /* | ||
970 | * Workaround for buggy EDIDs that sets that the first | ||
971 | * detailed timing is preferred but has not detailed | ||
972 | * timing specified | ||
973 | */ | ||
974 | for (i = 0; i < specs->modedb_len; i++) { | ||
975 | if (specs->modedb[i].flag & FB_MODE_IS_DETAILED) { | ||
976 | found = 1; | ||
977 | break; | ||
978 | } | ||
979 | } | ||
980 | |||
981 | if (!found) | ||
982 | specs->misc &= ~FB_MISC_1ST_DETAIL; | ||
983 | |||
984 | DPRINTK("========================================\n"); | ||
985 | } | ||
986 | |||
987 | /** | ||
988 | * fb_edid_add_monspecs() - add monitor video modes from E-EDID data | ||
989 | * @edid: 128 byte array with an E-EDID block | ||
990 | * @spacs: monitor specs to be extended | ||
991 | */ | ||
992 | void fb_edid_add_monspecs(unsigned char *edid, struct fb_monspecs *specs) | ||
993 | { | ||
994 | unsigned char *block; | ||
995 | struct fb_videomode *m; | ||
996 | int num = 0, i; | ||
997 | u8 svd[64], edt[(128 - 4) / DETAILED_TIMING_DESCRIPTION_SIZE]; | ||
998 | u8 pos = 4, svd_n = 0; | ||
999 | |||
1000 | if (!edid) | ||
1001 | return; | ||
1002 | |||
1003 | if (!edid_checksum(edid)) | ||
1004 | return; | ||
1005 | |||
1006 | if (edid[0] != 0x2 || | ||
1007 | edid[2] < 4 || edid[2] > 128 - DETAILED_TIMING_DESCRIPTION_SIZE) | ||
1008 | return; | ||
1009 | |||
1010 | DPRINTK(" Short Video Descriptors\n"); | ||
1011 | |||
1012 | while (pos < edid[2]) { | ||
1013 | u8 len = edid[pos] & 0x1f, type = (edid[pos] >> 5) & 7; | ||
1014 | pr_debug("Data block %u of %u bytes\n", type, len); | ||
1015 | if (type == 2) | ||
1016 | for (i = pos; i < pos + len; i++) { | ||
1017 | u8 idx = edid[pos + i] & 0x7f; | ||
1018 | svd[svd_n++] = idx; | ||
1019 | pr_debug("N%sative mode #%d\n", | ||
1020 | edid[pos + i] & 0x80 ? "" : "on-n", idx); | ||
1021 | } | ||
1022 | pos += len + 1; | ||
1023 | } | ||
1024 | |||
1025 | block = edid + edid[2]; | ||
1026 | |||
1027 | DPRINTK(" Extended Detailed Timings\n"); | ||
1028 | |||
1029 | for (i = 0; i < (128 - edid[2]) / DETAILED_TIMING_DESCRIPTION_SIZE; | ||
1030 | i++, block += DETAILED_TIMING_DESCRIPTION_SIZE) | ||
1031 | if (PIXEL_CLOCK) | ||
1032 | edt[num++] = block - edid; | ||
1033 | |||
1034 | /* Yikes, EDID data is totally useless */ | ||
1035 | if (!(num + svd_n)) | ||
1036 | return; | ||
1037 | |||
1038 | m = kzalloc((specs->modedb_len + num + svd_n) * | ||
1039 | sizeof(struct fb_videomode), GFP_KERNEL); | ||
1040 | |||
1041 | if (!m) | ||
1042 | return; | ||
1043 | |||
1044 | memcpy(m, specs->modedb, specs->modedb_len * sizeof(struct fb_videomode)); | ||
1045 | |||
1046 | for (i = specs->modedb_len; i < specs->modedb_len + num; i++) { | ||
1047 | get_detailed_timing(edid + edt[i - specs->modedb_len], &m[i]); | ||
1048 | if (i == specs->modedb_len) | ||
1049 | m[i].flag |= FB_MODE_IS_FIRST; | ||
1050 | pr_debug("Adding %ux%u@%u\n", m[i].xres, m[i].yres, m[i].refresh); | ||
1051 | } | ||
1052 | |||
1053 | for (i = specs->modedb_len + num; i < specs->modedb_len + num + svd_n; i++) { | ||
1054 | int idx = svd[i - specs->modedb_len - num]; | ||
1055 | if (!idx || idx > 63) { | ||
1056 | pr_warning("Reserved SVD code %d\n", idx); | ||
1057 | } else if (idx > ARRAY_SIZE(cea_modes) || !cea_modes[idx].xres) { | ||
1058 | pr_warning("Unimplemented SVD code %d\n", idx); | ||
1059 | } else { | ||
1060 | memcpy(&m[i], cea_modes + idx, sizeof(m[i])); | ||
1061 | pr_debug("Adding SVD #%d: %ux%u@%u\n", idx, | ||
1062 | m[i].xres, m[i].yres, m[i].refresh); | ||
1063 | } | ||
1064 | } | ||
1065 | |||
1066 | kfree(specs->modedb); | ||
1067 | specs->modedb = m; | ||
1068 | specs->modedb_len = specs->modedb_len + num + svd_n; | ||
1069 | } | ||
1070 | |||
1071 | /* | ||
1072 | * VESA Generalized Timing Formula (GTF) | ||
1073 | */ | ||
1074 | |||
1075 | #define FLYBACK 550 | ||
1076 | #define V_FRONTPORCH 1 | ||
1077 | #define H_OFFSET 40 | ||
1078 | #define H_SCALEFACTOR 20 | ||
1079 | #define H_BLANKSCALE 128 | ||
1080 | #define H_GRADIENT 600 | ||
1081 | #define C_VAL 30 | ||
1082 | #define M_VAL 300 | ||
1083 | |||
1084 | struct __fb_timings { | ||
1085 | u32 dclk; | ||
1086 | u32 hfreq; | ||
1087 | u32 vfreq; | ||
1088 | u32 hactive; | ||
1089 | u32 vactive; | ||
1090 | u32 hblank; | ||
1091 | u32 vblank; | ||
1092 | u32 htotal; | ||
1093 | u32 vtotal; | ||
1094 | }; | ||
1095 | |||
1096 | /** | ||
1097 | * fb_get_vblank - get vertical blank time | ||
1098 | * @hfreq: horizontal freq | ||
1099 | * | ||
1100 | * DESCRIPTION: | ||
1101 | * vblank = right_margin + vsync_len + left_margin | ||
1102 | * | ||
1103 | * given: right_margin = 1 (V_FRONTPORCH) | ||
1104 | * vsync_len = 3 | ||
1105 | * flyback = 550 | ||
1106 | * | ||
1107 | * flyback * hfreq | ||
1108 | * left_margin = --------------- - vsync_len | ||
1109 | * 1000000 | ||
1110 | */ | ||
1111 | static u32 fb_get_vblank(u32 hfreq) | ||
1112 | { | ||
1113 | u32 vblank; | ||
1114 | |||
1115 | vblank = (hfreq * FLYBACK)/1000; | ||
1116 | vblank = (vblank + 500)/1000; | ||
1117 | return (vblank + V_FRONTPORCH); | ||
1118 | } | ||
1119 | |||
1120 | /** | ||
1121 | * fb_get_hblank_by_freq - get horizontal blank time given hfreq | ||
1122 | * @hfreq: horizontal freq | ||
1123 | * @xres: horizontal resolution in pixels | ||
1124 | * | ||
1125 | * DESCRIPTION: | ||
1126 | * | ||
1127 | * xres * duty_cycle | ||
1128 | * hblank = ------------------ | ||
1129 | * 100 - duty_cycle | ||
1130 | * | ||
1131 | * duty cycle = percent of htotal assigned to inactive display | ||
1132 | * duty cycle = C - (M/Hfreq) | ||
1133 | * | ||
1134 | * where: C = ((offset - scale factor) * blank_scale) | ||
1135 | * -------------------------------------- + scale factor | ||
1136 | * 256 | ||
1137 | * M = blank_scale * gradient | ||
1138 | * | ||
1139 | */ | ||
1140 | static u32 fb_get_hblank_by_hfreq(u32 hfreq, u32 xres) | ||
1141 | { | ||
1142 | u32 c_val, m_val, duty_cycle, hblank; | ||
1143 | |||
1144 | c_val = (((H_OFFSET - H_SCALEFACTOR) * H_BLANKSCALE)/256 + | ||
1145 | H_SCALEFACTOR) * 1000; | ||
1146 | m_val = (H_BLANKSCALE * H_GRADIENT)/256; | ||
1147 | m_val = (m_val * 1000000)/hfreq; | ||
1148 | duty_cycle = c_val - m_val; | ||
1149 | hblank = (xres * duty_cycle)/(100000 - duty_cycle); | ||
1150 | return (hblank); | ||
1151 | } | ||
1152 | |||
1153 | /** | ||
1154 | * fb_get_hblank_by_dclk - get horizontal blank time given pixelclock | ||
1155 | * @dclk: pixelclock in Hz | ||
1156 | * @xres: horizontal resolution in pixels | ||
1157 | * | ||
1158 | * DESCRIPTION: | ||
1159 | * | ||
1160 | * xres * duty_cycle | ||
1161 | * hblank = ------------------ | ||
1162 | * 100 - duty_cycle | ||
1163 | * | ||
1164 | * duty cycle = percent of htotal assigned to inactive display | ||
1165 | * duty cycle = C - (M * h_period) | ||
1166 | * | ||
1167 | * where: h_period = SQRT(100 - C + (0.4 * xres * M)/dclk) + C - 100 | ||
1168 | * ----------------------------------------------- | ||
1169 | * 2 * M | ||
1170 | * M = 300; | ||
1171 | * C = 30; | ||
1172 | |||
1173 | */ | ||
1174 | static u32 fb_get_hblank_by_dclk(u32 dclk, u32 xres) | ||
1175 | { | ||
1176 | u32 duty_cycle, h_period, hblank; | ||
1177 | |||
1178 | dclk /= 1000; | ||
1179 | h_period = 100 - C_VAL; | ||
1180 | h_period *= h_period; | ||
1181 | h_period += (M_VAL * xres * 2 * 1000)/(5 * dclk); | ||
1182 | h_period *= 10000; | ||
1183 | |||
1184 | h_period = int_sqrt(h_period); | ||
1185 | h_period -= (100 - C_VAL) * 100; | ||
1186 | h_period *= 1000; | ||
1187 | h_period /= 2 * M_VAL; | ||
1188 | |||
1189 | duty_cycle = C_VAL * 1000 - (M_VAL * h_period)/100; | ||
1190 | hblank = (xres * duty_cycle)/(100000 - duty_cycle) + 8; | ||
1191 | hblank &= ~15; | ||
1192 | return (hblank); | ||
1193 | } | ||
1194 | |||
1195 | /** | ||
1196 | * fb_get_hfreq - estimate hsync | ||
1197 | * @vfreq: vertical refresh rate | ||
1198 | * @yres: vertical resolution | ||
1199 | * | ||
1200 | * DESCRIPTION: | ||
1201 | * | ||
1202 | * (yres + front_port) * vfreq * 1000000 | ||
1203 | * hfreq = ------------------------------------- | ||
1204 | * (1000000 - (vfreq * FLYBACK) | ||
1205 | * | ||
1206 | */ | ||
1207 | |||
1208 | static u32 fb_get_hfreq(u32 vfreq, u32 yres) | ||
1209 | { | ||
1210 | u32 divisor, hfreq; | ||
1211 | |||
1212 | divisor = (1000000 - (vfreq * FLYBACK))/1000; | ||
1213 | hfreq = (yres + V_FRONTPORCH) * vfreq * 1000; | ||
1214 | return (hfreq/divisor); | ||
1215 | } | ||
1216 | |||
1217 | static void fb_timings_vfreq(struct __fb_timings *timings) | ||
1218 | { | ||
1219 | timings->hfreq = fb_get_hfreq(timings->vfreq, timings->vactive); | ||
1220 | timings->vblank = fb_get_vblank(timings->hfreq); | ||
1221 | timings->vtotal = timings->vactive + timings->vblank; | ||
1222 | timings->hblank = fb_get_hblank_by_hfreq(timings->hfreq, | ||
1223 | timings->hactive); | ||
1224 | timings->htotal = timings->hactive + timings->hblank; | ||
1225 | timings->dclk = timings->htotal * timings->hfreq; | ||
1226 | } | ||
1227 | |||
1228 | static void fb_timings_hfreq(struct __fb_timings *timings) | ||
1229 | { | ||
1230 | timings->vblank = fb_get_vblank(timings->hfreq); | ||
1231 | timings->vtotal = timings->vactive + timings->vblank; | ||
1232 | timings->vfreq = timings->hfreq/timings->vtotal; | ||
1233 | timings->hblank = fb_get_hblank_by_hfreq(timings->hfreq, | ||
1234 | timings->hactive); | ||
1235 | timings->htotal = timings->hactive + timings->hblank; | ||
1236 | timings->dclk = timings->htotal * timings->hfreq; | ||
1237 | } | ||
1238 | |||
1239 | static void fb_timings_dclk(struct __fb_timings *timings) | ||
1240 | { | ||
1241 | timings->hblank = fb_get_hblank_by_dclk(timings->dclk, | ||
1242 | timings->hactive); | ||
1243 | timings->htotal = timings->hactive + timings->hblank; | ||
1244 | timings->hfreq = timings->dclk/timings->htotal; | ||
1245 | timings->vblank = fb_get_vblank(timings->hfreq); | ||
1246 | timings->vtotal = timings->vactive + timings->vblank; | ||
1247 | timings->vfreq = timings->hfreq/timings->vtotal; | ||
1248 | } | ||
1249 | |||
1250 | /* | ||
1251 | * fb_get_mode - calculates video mode using VESA GTF | ||
1252 | * @flags: if: 0 - maximize vertical refresh rate | ||
1253 | * 1 - vrefresh-driven calculation; | ||
1254 | * 2 - hscan-driven calculation; | ||
1255 | * 3 - pixelclock-driven calculation; | ||
1256 | * @val: depending on @flags, ignored, vrefresh, hsync or pixelclock | ||
1257 | * @var: pointer to fb_var_screeninfo | ||
1258 | * @info: pointer to fb_info | ||
1259 | * | ||
1260 | * DESCRIPTION: | ||
1261 | * Calculates video mode based on monitor specs using VESA GTF. | ||
1262 | * The GTF is best for VESA GTF compliant monitors but is | ||
1263 | * specifically formulated to work for older monitors as well. | ||
1264 | * | ||
1265 | * If @flag==0, the function will attempt to maximize the | ||
1266 | * refresh rate. Otherwise, it will calculate timings based on | ||
1267 | * the flag and accompanying value. | ||
1268 | * | ||
1269 | * If FB_IGNOREMON bit is set in @flags, monitor specs will be | ||
1270 | * ignored and @var will be filled with the calculated timings. | ||
1271 | * | ||
1272 | * All calculations are based on the VESA GTF Spreadsheet | ||
1273 | * available at VESA's public ftp (http://www.vesa.org). | ||
1274 | * | ||
1275 | * NOTES: | ||
1276 | * The timings generated by the GTF will be different from VESA | ||
1277 | * DMT. It might be a good idea to keep a table of standard | ||
1278 | * VESA modes as well. The GTF may also not work for some displays, | ||
1279 | * such as, and especially, analog TV. | ||
1280 | * | ||
1281 | * REQUIRES: | ||
1282 | * A valid info->monspecs, otherwise 'safe numbers' will be used. | ||
1283 | */ | ||
1284 | int fb_get_mode(int flags, u32 val, struct fb_var_screeninfo *var, struct fb_info *info) | ||
1285 | { | ||
1286 | struct __fb_timings *timings; | ||
1287 | u32 interlace = 1, dscan = 1; | ||
1288 | u32 hfmin, hfmax, vfmin, vfmax, dclkmin, dclkmax, err = 0; | ||
1289 | |||
1290 | |||
1291 | timings = kzalloc(sizeof(struct __fb_timings), GFP_KERNEL); | ||
1292 | |||
1293 | if (!timings) | ||
1294 | return -ENOMEM; | ||
1295 | |||
1296 | /* | ||
1297 | * If monspecs are invalid, use values that are enough | ||
1298 | * for 640x480@60 | ||
1299 | */ | ||
1300 | if (!info || !info->monspecs.hfmax || !info->monspecs.vfmax || | ||
1301 | !info->monspecs.dclkmax || | ||
1302 | info->monspecs.hfmax < info->monspecs.hfmin || | ||
1303 | info->monspecs.vfmax < info->monspecs.vfmin || | ||
1304 | info->monspecs.dclkmax < info->monspecs.dclkmin) { | ||
1305 | hfmin = 29000; hfmax = 30000; | ||
1306 | vfmin = 60; vfmax = 60; | ||
1307 | dclkmin = 0; dclkmax = 25000000; | ||
1308 | } else { | ||
1309 | hfmin = info->monspecs.hfmin; | ||
1310 | hfmax = info->monspecs.hfmax; | ||
1311 | vfmin = info->monspecs.vfmin; | ||
1312 | vfmax = info->monspecs.vfmax; | ||
1313 | dclkmin = info->monspecs.dclkmin; | ||
1314 | dclkmax = info->monspecs.dclkmax; | ||
1315 | } | ||
1316 | |||
1317 | timings->hactive = var->xres; | ||
1318 | timings->vactive = var->yres; | ||
1319 | if (var->vmode & FB_VMODE_INTERLACED) { | ||
1320 | timings->vactive /= 2; | ||
1321 | interlace = 2; | ||
1322 | } | ||
1323 | if (var->vmode & FB_VMODE_DOUBLE) { | ||
1324 | timings->vactive *= 2; | ||
1325 | dscan = 2; | ||
1326 | } | ||
1327 | |||
1328 | switch (flags & ~FB_IGNOREMON) { | ||
1329 | case FB_MAXTIMINGS: /* maximize refresh rate */ | ||
1330 | timings->hfreq = hfmax; | ||
1331 | fb_timings_hfreq(timings); | ||
1332 | if (timings->vfreq > vfmax) { | ||
1333 | timings->vfreq = vfmax; | ||
1334 | fb_timings_vfreq(timings); | ||
1335 | } | ||
1336 | if (timings->dclk > dclkmax) { | ||
1337 | timings->dclk = dclkmax; | ||
1338 | fb_timings_dclk(timings); | ||
1339 | } | ||
1340 | break; | ||
1341 | case FB_VSYNCTIMINGS: /* vrefresh driven */ | ||
1342 | timings->vfreq = val; | ||
1343 | fb_timings_vfreq(timings); | ||
1344 | break; | ||
1345 | case FB_HSYNCTIMINGS: /* hsync driven */ | ||
1346 | timings->hfreq = val; | ||
1347 | fb_timings_hfreq(timings); | ||
1348 | break; | ||
1349 | case FB_DCLKTIMINGS: /* pixelclock driven */ | ||
1350 | timings->dclk = PICOS2KHZ(val) * 1000; | ||
1351 | fb_timings_dclk(timings); | ||
1352 | break; | ||
1353 | default: | ||
1354 | err = -EINVAL; | ||
1355 | |||
1356 | } | ||
1357 | |||
1358 | if (err || (!(flags & FB_IGNOREMON) && | ||
1359 | (timings->vfreq < vfmin || timings->vfreq > vfmax || | ||
1360 | timings->hfreq < hfmin || timings->hfreq > hfmax || | ||
1361 | timings->dclk < dclkmin || timings->dclk > dclkmax))) { | ||
1362 | err = -EINVAL; | ||
1363 | } else { | ||
1364 | var->pixclock = KHZ2PICOS(timings->dclk/1000); | ||
1365 | var->hsync_len = (timings->htotal * 8)/100; | ||
1366 | var->right_margin = (timings->hblank/2) - var->hsync_len; | ||
1367 | var->left_margin = timings->hblank - var->right_margin - | ||
1368 | var->hsync_len; | ||
1369 | var->vsync_len = (3 * interlace)/dscan; | ||
1370 | var->lower_margin = (1 * interlace)/dscan; | ||
1371 | var->upper_margin = (timings->vblank * interlace)/dscan - | ||
1372 | (var->vsync_len + var->lower_margin); | ||
1373 | } | ||
1374 | |||
1375 | kfree(timings); | ||
1376 | return err; | ||
1377 | } | ||
1378 | |||
1379 | #ifdef CONFIG_VIDEOMODE_HELPERS | ||
1380 | int fb_videomode_from_videomode(const struct videomode *vm, | ||
1381 | struct fb_videomode *fbmode) | ||
1382 | { | ||
1383 | unsigned int htotal, vtotal; | ||
1384 | |||
1385 | fbmode->xres = vm->hactive; | ||
1386 | fbmode->left_margin = vm->hback_porch; | ||
1387 | fbmode->right_margin = vm->hfront_porch; | ||
1388 | fbmode->hsync_len = vm->hsync_len; | ||
1389 | |||
1390 | fbmode->yres = vm->vactive; | ||
1391 | fbmode->upper_margin = vm->vback_porch; | ||
1392 | fbmode->lower_margin = vm->vfront_porch; | ||
1393 | fbmode->vsync_len = vm->vsync_len; | ||
1394 | |||
1395 | /* prevent division by zero in KHZ2PICOS macro */ | ||
1396 | fbmode->pixclock = vm->pixelclock ? | ||
1397 | KHZ2PICOS(vm->pixelclock / 1000) : 0; | ||
1398 | |||
1399 | fbmode->sync = 0; | ||
1400 | fbmode->vmode = 0; | ||
1401 | if (vm->flags & DISPLAY_FLAGS_HSYNC_HIGH) | ||
1402 | fbmode->sync |= FB_SYNC_HOR_HIGH_ACT; | ||
1403 | if (vm->flags & DISPLAY_FLAGS_VSYNC_HIGH) | ||
1404 | fbmode->sync |= FB_SYNC_VERT_HIGH_ACT; | ||
1405 | if (vm->flags & DISPLAY_FLAGS_INTERLACED) | ||
1406 | fbmode->vmode |= FB_VMODE_INTERLACED; | ||
1407 | if (vm->flags & DISPLAY_FLAGS_DOUBLESCAN) | ||
1408 | fbmode->vmode |= FB_VMODE_DOUBLE; | ||
1409 | fbmode->flag = 0; | ||
1410 | |||
1411 | htotal = vm->hactive + vm->hfront_porch + vm->hback_porch + | ||
1412 | vm->hsync_len; | ||
1413 | vtotal = vm->vactive + vm->vfront_porch + vm->vback_porch + | ||
1414 | vm->vsync_len; | ||
1415 | /* prevent division by zero */ | ||
1416 | if (htotal && vtotal) { | ||
1417 | fbmode->refresh = vm->pixelclock / (htotal * vtotal); | ||
1418 | /* a mode must have htotal and vtotal != 0 or it is invalid */ | ||
1419 | } else { | ||
1420 | fbmode->refresh = 0; | ||
1421 | return -EINVAL; | ||
1422 | } | ||
1423 | |||
1424 | return 0; | ||
1425 | } | ||
1426 | EXPORT_SYMBOL_GPL(fb_videomode_from_videomode); | ||
1427 | |||
1428 | #ifdef CONFIG_OF | ||
1429 | static inline void dump_fb_videomode(const struct fb_videomode *m) | ||
1430 | { | ||
1431 | pr_debug("fb_videomode = %ux%u@%uHz (%ukHz) %u %u %u %u %u %u %u %u %u\n", | ||
1432 | m->xres, m->yres, m->refresh, m->pixclock, m->left_margin, | ||
1433 | m->right_margin, m->upper_margin, m->lower_margin, | ||
1434 | m->hsync_len, m->vsync_len, m->sync, m->vmode, m->flag); | ||
1435 | } | ||
1436 | |||
1437 | /** | ||
1438 | * of_get_fb_videomode - get a fb_videomode from devicetree | ||
1439 | * @np: device_node with the timing specification | ||
1440 | * @fb: will be set to the return value | ||
1441 | * @index: index into the list of display timings in devicetree | ||
1442 | * | ||
1443 | * DESCRIPTION: | ||
1444 | * This function is expensive and should only be used, if only one mode is to be | ||
1445 | * read from DT. To get multiple modes start with of_get_display_timings ond | ||
1446 | * work with that instead. | ||
1447 | */ | ||
1448 | int of_get_fb_videomode(struct device_node *np, struct fb_videomode *fb, | ||
1449 | int index) | ||
1450 | { | ||
1451 | struct videomode vm; | ||
1452 | int ret; | ||
1453 | |||
1454 | ret = of_get_videomode(np, &vm, index); | ||
1455 | if (ret) | ||
1456 | return ret; | ||
1457 | |||
1458 | fb_videomode_from_videomode(&vm, fb); | ||
1459 | |||
1460 | pr_debug("%s: got %dx%d display mode from %s\n", | ||
1461 | of_node_full_name(np), vm.hactive, vm.vactive, np->name); | ||
1462 | dump_fb_videomode(fb); | ||
1463 | |||
1464 | return 0; | ||
1465 | } | ||
1466 | EXPORT_SYMBOL_GPL(of_get_fb_videomode); | ||
1467 | #endif /* CONFIG_OF */ | ||
1468 | #endif /* CONFIG_VIDEOMODE_HELPERS */ | ||
1469 | |||
1470 | #else | ||
1471 | int fb_parse_edid(unsigned char *edid, struct fb_var_screeninfo *var) | ||
1472 | { | ||
1473 | return 1; | ||
1474 | } | ||
1475 | void fb_edid_to_monspecs(unsigned char *edid, struct fb_monspecs *specs) | ||
1476 | { | ||
1477 | specs = NULL; | ||
1478 | } | ||
1479 | void fb_edid_add_monspecs(unsigned char *edid, struct fb_monspecs *specs) | ||
1480 | { | ||
1481 | } | ||
1482 | void fb_destroy_modedb(struct fb_videomode *modedb) | ||
1483 | { | ||
1484 | } | ||
1485 | int fb_get_mode(int flags, u32 val, struct fb_var_screeninfo *var, | ||
1486 | struct fb_info *info) | ||
1487 | { | ||
1488 | return -EINVAL; | ||
1489 | } | ||
1490 | #endif /* CONFIG_FB_MODE_HELPERS */ | ||
1491 | |||
1492 | /* | ||
1493 | * fb_validate_mode - validates var against monitor capabilities | ||
1494 | * @var: pointer to fb_var_screeninfo | ||
1495 | * @info: pointer to fb_info | ||
1496 | * | ||
1497 | * DESCRIPTION: | ||
1498 | * Validates video mode against monitor capabilities specified in | ||
1499 | * info->monspecs. | ||
1500 | * | ||
1501 | * REQUIRES: | ||
1502 | * A valid info->monspecs. | ||
1503 | */ | ||
1504 | int fb_validate_mode(const struct fb_var_screeninfo *var, struct fb_info *info) | ||
1505 | { | ||
1506 | u32 hfreq, vfreq, htotal, vtotal, pixclock; | ||
1507 | u32 hfmin, hfmax, vfmin, vfmax, dclkmin, dclkmax; | ||
1508 | |||
1509 | /* | ||
1510 | * If monspecs are invalid, use values that are enough | ||
1511 | * for 640x480@60 | ||
1512 | */ | ||
1513 | if (!info->monspecs.hfmax || !info->monspecs.vfmax || | ||
1514 | !info->monspecs.dclkmax || | ||
1515 | info->monspecs.hfmax < info->monspecs.hfmin || | ||
1516 | info->monspecs.vfmax < info->monspecs.vfmin || | ||
1517 | info->monspecs.dclkmax < info->monspecs.dclkmin) { | ||
1518 | hfmin = 29000; hfmax = 30000; | ||
1519 | vfmin = 60; vfmax = 60; | ||
1520 | dclkmin = 0; dclkmax = 25000000; | ||
1521 | } else { | ||
1522 | hfmin = info->monspecs.hfmin; | ||
1523 | hfmax = info->monspecs.hfmax; | ||
1524 | vfmin = info->monspecs.vfmin; | ||
1525 | vfmax = info->monspecs.vfmax; | ||
1526 | dclkmin = info->monspecs.dclkmin; | ||
1527 | dclkmax = info->monspecs.dclkmax; | ||
1528 | } | ||
1529 | |||
1530 | if (!var->pixclock) | ||
1531 | return -EINVAL; | ||
1532 | pixclock = PICOS2KHZ(var->pixclock) * 1000; | ||
1533 | |||
1534 | htotal = var->xres + var->right_margin + var->hsync_len + | ||
1535 | var->left_margin; | ||
1536 | vtotal = var->yres + var->lower_margin + var->vsync_len + | ||
1537 | var->upper_margin; | ||
1538 | |||
1539 | if (var->vmode & FB_VMODE_INTERLACED) | ||
1540 | vtotal /= 2; | ||
1541 | if (var->vmode & FB_VMODE_DOUBLE) | ||
1542 | vtotal *= 2; | ||
1543 | |||
1544 | hfreq = pixclock/htotal; | ||
1545 | hfreq = (hfreq + 500) / 1000 * 1000; | ||
1546 | |||
1547 | vfreq = hfreq/vtotal; | ||
1548 | |||
1549 | return (vfreq < vfmin || vfreq > vfmax || | ||
1550 | hfreq < hfmin || hfreq > hfmax || | ||
1551 | pixclock < dclkmin || pixclock > dclkmax) ? | ||
1552 | -EINVAL : 0; | ||
1553 | } | ||
1554 | |||
1555 | #if defined(CONFIG_FIRMWARE_EDID) && defined(CONFIG_X86) | ||
1556 | |||
1557 | /* | ||
1558 | * We need to ensure that the EDID block is only returned for | ||
1559 | * the primary graphics adapter. | ||
1560 | */ | ||
1561 | |||
1562 | const unsigned char *fb_firmware_edid(struct device *device) | ||
1563 | { | ||
1564 | struct pci_dev *dev = NULL; | ||
1565 | struct resource *res = NULL; | ||
1566 | unsigned char *edid = NULL; | ||
1567 | |||
1568 | if (device) | ||
1569 | dev = to_pci_dev(device); | ||
1570 | |||
1571 | if (dev) | ||
1572 | res = &dev->resource[PCI_ROM_RESOURCE]; | ||
1573 | |||
1574 | if (res && res->flags & IORESOURCE_ROM_SHADOW) | ||
1575 | edid = edid_info.dummy; | ||
1576 | |||
1577 | return edid; | ||
1578 | } | ||
1579 | #else | ||
1580 | const unsigned char *fb_firmware_edid(struct device *device) | ||
1581 | { | ||
1582 | return NULL; | ||
1583 | } | ||
1584 | #endif | ||
1585 | EXPORT_SYMBOL(fb_firmware_edid); | ||
1586 | |||
1587 | EXPORT_SYMBOL(fb_parse_edid); | ||
1588 | EXPORT_SYMBOL(fb_edid_to_monspecs); | ||
1589 | EXPORT_SYMBOL(fb_edid_add_monspecs); | ||
1590 | EXPORT_SYMBOL(fb_get_mode); | ||
1591 | EXPORT_SYMBOL(fb_validate_mode); | ||
1592 | EXPORT_SYMBOL(fb_destroy_modedb); | ||