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