Fedora kernel-2.6.17-1.2142_FC4 patched with stable patch-2.6.17.4-vs2.0.2-rc26.diff
[linux-2.6.git] / drivers / video / modedb.c
index d9b0a2f..26a1c61 100644 (file)
     ((v).xres == (x) && (v).yres == (y))
 
 #ifdef DEBUG
-#define DPRINTK(fmt, args...)  printk(KERN_DEBUG "%s: " fmt, __FUNCTION__ , ## args)
+#define DPRINTK(fmt, args...)  printk("modedb %s: " fmt, __FUNCTION__ , ## args)
 #else
 #define DPRINTK(fmt, args...)
 #endif
 
-
-const char *global_mode_option = NULL;
-
+const char *global_mode_option;
 
     /*
      *  Standard video mode definitions (taken from XFree86)
@@ -184,6 +182,10 @@ static const struct fb_videomode modedb[] = {
        /* 1600x1200 @ 75 Hz, 93.75 kHz hsync */
        NULL, 75, 1600, 1200, 4938, 304, 64, 46, 1, 192, 3,
        FB_SYNC_HOR_HIGH_ACT|FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED
+    }, {
+       /* 1680x1050 @ 60 Hz, 65.191 kHz hsync */
+       NULL, 60, 1680, 1050, 6848, 280, 104, 30, 3, 176, 6,
+       FB_SYNC_HOR_HIGH_ACT|FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED
     }, {
        /* 1600x1200 @ 85 Hz, 105.77 kHz hsync */
        NULL, 85, 1600, 1200, 4545, 272, 16, 37, 4, 192, 3,
@@ -248,9 +250,19 @@ static const struct fb_videomode modedb[] = {
        /* 480x300 @ 72 Hz, 48.0 kHz hsync */
        NULL, 72, 480, 300, 33386, 40, 24, 11, 19, 80, 3,
        0, FB_VMODE_DOUBLE
+    }, {
+       /* 1920x1200 @ 60 Hz, 74.5 Khz hsync */
+       NULL, 60, 1920, 1200, 5177, 128, 336, 1, 38, 208, 3,
+       FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
+       FB_VMODE_NONINTERLACED
+    }, {
+       /* 1152x768, 60 Hz, PowerBook G4 Titanium I and II */
+       NULL, 60, 1152, 768, 15386, 158, 26, 29, 3, 136, 6,
+       FB_SYNC_HOR_HIGH_ACT|FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED
     },
 };
 
+#ifdef CONFIG_FB_MODE_HELPERS
 const struct fb_videomode vesa_modes[] = {
        /* 0 640x350-85 VESA */
        { NULL, 85, 640, 350, 31746,  96, 32, 60, 32, 64, 3,
@@ -374,6 +386,8 @@ const struct fb_videomode vesa_modes[] = {
        { NULL, 60, 1920, 1440, 3367, 352, 144, 56, 1, 224, 3,
          FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA },
 };
+EXPORT_SYMBOL(vesa_modes);
+#endif /* CONFIG_FB_MODE_HELPERS */
 
 static int my_atoi(const char *name)
 {
@@ -391,7 +405,7 @@ static int my_atoi(const char *name)
 }
 
 /**
- *     __fb_try_mode - test a video mode
+ *     fb_try_mode - test a video mode
  *     @var: frame buffer user defined part of display
  *     @info: frame buffer info structure
  *     @mode: frame buffer video mode structure
@@ -403,10 +417,10 @@ static int my_atoi(const char *name)
  *
  */
 
-int __fb_try_mode(struct fb_var_screeninfo *var, struct fb_info *info,
-                 const struct fb_videomode *mode, unsigned int bpp)
+static int fb_try_mode(struct fb_var_screeninfo *var, struct fb_info *info,
+                      const struct fb_videomode *mode, unsigned int bpp)
 {
-    int err = 1;
+    int err = 0;
 
     DPRINTK("Trying mode %s %dx%d-%d@%d\n", mode->name ? mode->name : "noname",
            mode->xres, mode->yres, bpp, mode->refresh);
@@ -430,10 +444,9 @@ int __fb_try_mode(struct fb_var_screeninfo *var, struct fb_info *info,
     if (info->fbops->fb_check_var)
        err = info->fbops->fb_check_var(var, info);
     var->activate &= ~FB_ACTIVATE_TEST;
-    return !err;
+    return err;
 }
 
-
 /**
  *     fb_find_mode - finds a valid video mode
  *     @var: frame buffer user defined part of display
@@ -451,12 +464,22 @@ int __fb_try_mode(struct fb_var_screeninfo *var, struct fb_info *info,
  *
  *     Valid mode specifiers for @mode_option:
  *
- *     <xres>x<yres>[-<bpp>][@<refresh>] or
+ *     <xres>x<yres>[M][R][-<bpp>][@<refresh>][i][m] or
  *     <name>[-<bpp>][@<refresh>]
  *
  *     with <xres>, <yres>, <bpp> and <refresh> decimal numbers and
  *     <name> a string.
  *
+ *      If 'M' is present after yres (and before refresh/bpp if present),
+ *      the function will compute the timings using VESA(tm) Coordinated
+ *      Video Timings (CVT).  If 'R' is present after 'M', will compute with
+ *      reduced blanking (for flatpanels).  If 'i' is present, compute
+ *      interlaced mode.  If 'm' is present, add margins equal to 1.8%
+ *      of xres rounded down to 8 pixels, and 1.8% of yres. The char
+ *      'i' and 'm' must be after 'M' and 'R'. Example:
+ *
+ *      1024x768MR-8@60m - Reduced blank with margins at 60Hz.
+ *
  *     NOTE: The passed struct @var is _not_ cleared!  This allows you
  *     to supply values for e.g. the grayscale and accel_flags fields.
  *
@@ -472,12 +495,12 @@ int fb_find_mode(struct fb_var_screeninfo *var,
                 const struct fb_videomode *default_mode,
                 unsigned int default_bpp)
 {
-    int i, j;
+    int i;
 
     /* Set up defaults */
     if (!db) {
        db = modedb;
-       dbsize = sizeof(modedb)/sizeof(*modedb);
+       dbsize = ARRAY_SIZE(modedb);
     }
     if (!default_mode)
        default_mode = &modedb[DEFAULT_MODEDB_INDEX];
@@ -490,7 +513,8 @@ int fb_find_mode(struct fb_var_screeninfo *var,
        unsigned int namelen = strlen(name);
        int res_specified = 0, bpp_specified = 0, refresh_specified = 0;
        unsigned int xres = 0, yres = 0, bpp = default_bpp, refresh = 0;
-       int yres_specified = 0;
+       int yres_specified = 0, cvt = 0, rb = 0, interlace = 0, margins = 0;
+       u32 best, diff;
 
        for (i = namelen-1; i >= 0; i--) {
            switch (name[i]) {
@@ -500,6 +524,8 @@ int fb_find_mode(struct fb_var_screeninfo *var,
                        !yres_specified) {
                        refresh = my_atoi(&name[i+1]);
                        refresh_specified = 1;
+                       if (cvt || rb)
+                           cvt = 0;
                    } else
                        goto done;
                    break;
@@ -508,6 +534,8 @@ int fb_find_mode(struct fb_var_screeninfo *var,
                    if (!bpp_specified && !yres_specified) {
                        bpp = my_atoi(&name[i+1]);
                        bpp_specified = 1;
+                       if (cvt || rb)
+                           cvt = 0;
                    } else
                        goto done;
                    break;
@@ -520,6 +548,22 @@ int fb_find_mode(struct fb_var_screeninfo *var,
                    break;
                case '0'...'9':
                    break;
+               case 'M':
+                   if (!yres_specified)
+                       cvt = 1;
+                   break;
+               case 'R':
+                   if (!cvt)
+                       rb = 1;
+                   break;
+               case 'm':
+                   if (!cvt)
+                       margins = 1;
+                   break;
+               case 'i':
+                   if (!cvt)
+                       interlace = 1;
+                   break;
                default:
                    goto done;
            }
@@ -529,30 +573,453 @@ int fb_find_mode(struct fb_var_screeninfo *var,
            res_specified = 1;
        }
 done:
-       for (i = refresh_specified; i >= 0; i--) {
-           DPRINTK("Trying specified video mode%s\n",
-                   i ? "" : " (ignoring refresh rate)");
-           for (j = 0; j < dbsize; j++)
-               if ((name_matches(db[j], name, namelen) ||
-                    (res_specified && res_matches(db[j], xres, yres))) &&
-                   (!i || db[j].refresh == refresh) &&
-                   __fb_try_mode(var, info, &db[j], bpp))
-                   return 2-i;
+       if (cvt) {
+           struct fb_videomode cvt_mode;
+           int ret;
+
+           DPRINTK("CVT mode %dx%d@%dHz%s%s%s\n", xres, yres,
+                   (refresh) ? refresh : 60, (rb) ? " reduced blanking" :
+                   "", (margins) ? " with margins" : "", (interlace) ?
+                   " interlaced" : "");
+
+           cvt_mode.xres = xres;
+           cvt_mode.yres = yres;
+           cvt_mode.refresh = (refresh) ? refresh : 60;
+
+           if (interlace)
+               cvt_mode.vmode |= FB_VMODE_INTERLACED;
+           else
+               cvt_mode.vmode &= ~FB_VMODE_INTERLACED;
+
+           ret = fb_find_mode_cvt(&cvt_mode, margins, rb);
+
+           if (!ret && !fb_try_mode(var, info, &cvt_mode, bpp)) {
+               DPRINTK("modedb CVT: CVT mode ok\n");
+               return 1;
+           }
+
+           DPRINTK("CVT mode invalid, getting mode from database\n");
+       }
+
+       DPRINTK("Trying specified video mode%s %ix%i\n",
+           refresh_specified ? "" : " (ignoring refresh rate)", xres, yres);
+
+       diff = refresh;
+       best = -1;
+       for (i = 0; i < dbsize; i++) {
+               if ((name_matches(db[i], name, namelen) &&
+                       !fb_try_mode(var, info, &db[i], bpp)))
+                       return 1;
+               if (res_specified && res_matches(db[i], xres, yres)) {
+                       if(!fb_try_mode(var, info, &db[i], bpp)) {
+                               if(!refresh_specified || db[i].refresh == refresh)
+                                       return 1;
+                               else {
+                                       if(diff > abs(db[i].refresh - refresh)) {
+                                               diff = abs(db[i].refresh - refresh);
+                                               best = i;
+                                       }
+                               }
+                       }
+               }
+       }
+       if (best != -1) {
+               fb_try_mode(var, info, &db[best], bpp);
+               return 2;
+       }
+
+       diff = xres + yres;
+       best = -1;
+       DPRINTK("Trying best-fit modes\n");
+       for (i = 0; i < dbsize; i++) {
+           if (xres <= db[i].xres && yres <= db[i].yres) {
+               DPRINTK("Trying %ix%i\n", db[i].xres, db[i].yres);
+               if (!fb_try_mode(var, info, &db[i], bpp)) {
+                   if (diff > (db[i].xres - xres) + (db[i].yres - yres)) {
+                       diff = (db[i].xres - xres) + (db[i].yres - yres);
+                       best = i;
+                   }
+               }
+           }
+       }
+       if (best != -1) {
+           fb_try_mode(var, info, &db[best], bpp);
+           return 5;
        }
     }
 
     DPRINTK("Trying default video mode\n");
-    if (__fb_try_mode(var, info, default_mode, default_bpp))
+    if (!fb_try_mode(var, info, default_mode, default_bpp))
        return 3;
 
     DPRINTK("Trying all modes\n");
     for (i = 0; i < dbsize; i++)
-       if (__fb_try_mode(var, info, &db[i], default_bpp))
+       if (!fb_try_mode(var, info, &db[i], default_bpp))
            return 4;
 
     DPRINTK("No valid mode found\n");
     return 0;
 }
 
-EXPORT_SYMBOL(vesa_modes);
+/**
+ * fb_var_to_videomode - convert fb_var_screeninfo to fb_videomode
+ * @mode: pointer to struct fb_videomode
+ * @var: pointer to struct fb_var_screeninfo
+ */
+void fb_var_to_videomode(struct fb_videomode *mode,
+                        struct fb_var_screeninfo *var)
+{
+       u32 pixclock, hfreq, htotal, vtotal;
+
+       mode->name = NULL;
+       mode->xres = var->xres;
+       mode->yres = var->yres;
+       mode->pixclock = var->pixclock;
+       mode->hsync_len = var->hsync_len;
+       mode->vsync_len = var->vsync_len;
+       mode->left_margin = var->left_margin;
+       mode->right_margin = var->right_margin;
+       mode->upper_margin = var->upper_margin;
+       mode->lower_margin = var->lower_margin;
+       mode->sync = var->sync;
+       mode->vmode = var->vmode & FB_VMODE_MASK;
+       mode->flag = FB_MODE_IS_FROM_VAR;
+       mode->refresh = 0;
+
+       if (!var->pixclock)
+               return;
+
+       pixclock = PICOS2KHZ(var->pixclock) * 1000;
+
+       htotal = var->xres + var->right_margin + var->hsync_len +
+               var->left_margin;
+       vtotal = var->yres + var->lower_margin + var->vsync_len +
+               var->upper_margin;
+
+       if (var->vmode & FB_VMODE_INTERLACED)
+               vtotal /= 2;
+       if (var->vmode & FB_VMODE_DOUBLE)
+               vtotal *= 2;
+
+       hfreq = pixclock/htotal;
+       mode->refresh = hfreq/vtotal;
+}
+
+/**
+ * fb_videomode_to_var - convert fb_videomode to fb_var_screeninfo
+ * @var: pointer to struct fb_var_screeninfo
+ * @mode: pointer to struct fb_videomode
+ */
+void fb_videomode_to_var(struct fb_var_screeninfo *var,
+                              struct fb_videomode *mode)
+{
+       var->xres = mode->xres;
+       var->yres = mode->yres;
+       var->pixclock = mode->pixclock;
+       var->left_margin = mode->left_margin;
+       var->hsync_len = mode->hsync_len;
+       var->vsync_len = mode->vsync_len;
+       var->right_margin = mode->right_margin;
+       var->upper_margin = mode->upper_margin;
+       var->lower_margin = mode->lower_margin;
+       var->sync = mode->sync;
+       var->vmode = mode->vmode & FB_VMODE_MASK;
+}
+
+/**
+ * fb_mode_is_equal - compare 2 videomodes
+ * @mode1: first videomode
+ * @mode2: second videomode
+ *
+ * RETURNS:
+ * 1 if equal, 0 if not
+ */
+int fb_mode_is_equal(struct fb_videomode *mode1,
+                    struct fb_videomode *mode2)
+{
+       return (mode1->xres         == mode2->xres &&
+               mode1->yres         == mode2->yres &&
+               mode1->pixclock     == mode2->pixclock &&
+               mode1->hsync_len    == mode2->hsync_len &&
+               mode1->vsync_len    == mode2->vsync_len &&
+               mode1->left_margin  == mode2->left_margin &&
+               mode1->right_margin == mode2->right_margin &&
+               mode1->upper_margin == mode2->upper_margin &&
+               mode1->lower_margin == mode2->lower_margin &&
+               mode1->sync         == mode2->sync &&
+               mode1->vmode        == mode2->vmode);
+}
+
+/**
+ * fb_find_best_mode - find best matching videomode
+ * @var: pointer to struct fb_var_screeninfo
+ * @head: pointer to struct list_head of modelist
+ *
+ * RETURNS:
+ * struct fb_videomode, NULL if none found
+ *
+ * IMPORTANT:
+ * This function assumes that all modelist entries in
+ * info->modelist are valid.
+ *
+ * NOTES:
+ * Finds best matching videomode which has an equal or greater dimension than
+ * var->xres and var->yres.  If more than 1 videomode is found, will return
+ * the videomode with the highest refresh rate
+ */
+struct fb_videomode *fb_find_best_mode(struct fb_var_screeninfo *var,
+                                      struct list_head *head)
+{
+       struct list_head *pos;
+       struct fb_modelist *modelist;
+       struct fb_videomode *mode, *best = NULL;
+       u32 diff = -1;
+
+       list_for_each(pos, head) {
+               u32 d;
+
+               modelist = list_entry(pos, struct fb_modelist, list);
+               mode = &modelist->mode;
+
+               if (mode->xres >= var->xres && mode->yres >= var->yres) {
+                       d = (mode->xres - var->xres) +
+                               (mode->yres - var->yres);
+                       if (diff > d) {
+                               diff = d;
+                               best = mode;
+                       } else if (diff == d && mode->refresh > best->refresh)
+                           best = mode;
+               }
+       }
+       return best;
+}
+
+/**
+ * fb_find_nearest_mode - find closest videomode
+ *
+ * @mode: pointer to struct fb_videomode
+ * @head: pointer to modelist
+ *
+ * Finds best matching videomode, smaller or greater in dimension.
+ * If more than 1 videomode is found, will return the videomode with
+ * the closest refresh rate.
+ */
+struct fb_videomode *fb_find_nearest_mode(struct fb_videomode *mode,
+                                         struct list_head *head)
+{
+       struct list_head *pos;
+       struct fb_modelist *modelist;
+       struct fb_videomode *cmode, *best = NULL;
+       u32 diff = -1, diff_refresh = -1;
+
+       list_for_each(pos, head) {
+               u32 d;
+
+               modelist = list_entry(pos, struct fb_modelist, list);
+               cmode = &modelist->mode;
+
+               d = abs(cmode->xres - mode->xres) +
+                       abs(cmode->yres - mode->yres);
+               if (diff > d) {
+                       diff = d;
+                       best = cmode;
+               } else if (diff == d) {
+                       d = abs(cmode->refresh - mode->refresh);
+                       if (diff_refresh > d) {
+                               diff_refresh = d;
+                               best = cmode;
+                       }
+               }
+       }
+
+       return best;
+}
+
+/**
+ * fb_match_mode - find a videomode which exactly matches the timings in var
+ * @var: pointer to struct fb_var_screeninfo
+ * @head: pointer to struct list_head of modelist
+ *
+ * RETURNS:
+ * struct fb_videomode, NULL if none found
+ */
+struct fb_videomode *fb_match_mode(struct fb_var_screeninfo *var,
+                                  struct list_head *head)
+{
+       struct list_head *pos;
+       struct fb_modelist *modelist;
+       struct fb_videomode *m, mode;
+
+       fb_var_to_videomode(&mode, var);
+       list_for_each(pos, head) {
+               modelist = list_entry(pos, struct fb_modelist, list);
+               m = &modelist->mode;
+               if (fb_mode_is_equal(m, &mode))
+                       return m;
+       }
+       return NULL;
+}
+
+/**
+ * fb_add_videomode: adds videomode entry to modelist
+ * @mode: videomode to add
+ * @head: struct list_head of modelist
+ *
+ * NOTES:
+ * Will only add unmatched mode entries
+ */
+int fb_add_videomode(struct fb_videomode *mode, struct list_head *head)
+{
+       struct list_head *pos;
+       struct fb_modelist *modelist;
+       struct fb_videomode *m;
+       int found = 0;
+
+       list_for_each(pos, head) {
+               modelist = list_entry(pos, struct fb_modelist, list);
+               m = &modelist->mode;
+               if (fb_mode_is_equal(m, mode)) {
+                       found = 1;
+                       break;
+               }
+       }
+       if (!found) {
+               modelist = kmalloc(sizeof(struct fb_modelist),
+                                                 GFP_KERNEL);
+
+               if (!modelist)
+                       return -ENOMEM;
+               modelist->mode = *mode;
+               list_add(&modelist->list, head);
+       }
+       return 0;
+}
+
+/**
+ * fb_delete_videomode: removed videomode entry from modelist
+ * @mode: videomode to remove
+ * @head: struct list_head of modelist
+ *
+ * NOTES:
+ * Will remove all matching mode entries
+ */
+void fb_delete_videomode(struct fb_videomode *mode, struct list_head *head)
+{
+       struct list_head *pos, *n;
+       struct fb_modelist *modelist;
+       struct fb_videomode *m;
+
+       list_for_each_safe(pos, n, head) {
+               modelist = list_entry(pos, struct fb_modelist, list);
+               m = &modelist->mode;
+               if (fb_mode_is_equal(m, mode)) {
+                       list_del(pos);
+                       kfree(pos);
+               }
+       }
+}
+
+/**
+ * fb_destroy_modelist: destroy modelist
+ * @head: struct list_head of modelist
+ */
+void fb_destroy_modelist(struct list_head *head)
+{
+       struct list_head *pos, *n;
+
+       list_for_each_safe(pos, n, head) {
+               list_del(pos);
+               kfree(pos);
+       }
+}
+
+/**
+ * fb_videomode_to_modelist: convert mode array to mode list
+ * @modedb: array of struct fb_videomode
+ * @num: number of entries in array
+ * @head: struct list_head of modelist
+ */
+void fb_videomode_to_modelist(struct fb_videomode *modedb, int num,
+                             struct list_head *head)
+{
+       int i;
+
+       INIT_LIST_HEAD(head);
+
+       for (i = 0; i < num; i++) {
+               if (fb_add_videomode(&modedb[i], head))
+                       return;
+       }
+}
+
+struct fb_videomode *fb_find_best_display(struct fb_monspecs *specs,
+                                         struct list_head *head)
+{
+       struct list_head *pos;
+       struct fb_modelist *modelist;
+       struct fb_videomode *m, *m1 = NULL, *md = NULL, *best = NULL;
+       int first = 0;
+
+       if (!head->prev || !head->next || list_empty(head))
+               goto finished;
+
+       /* get the first detailed mode and the very first mode */
+       list_for_each(pos, head) {
+               modelist = list_entry(pos, struct fb_modelist, list);
+               m = &modelist->mode;
+
+               if (!first) {
+                       m1 = m;
+                       first = 1;
+               }
+
+               if (m->flag & FB_MODE_IS_FIRST) {
+                       md = m;
+                       break;
+               }
+       }
+
+       /* first detailed timing is preferred */
+       if (specs->misc & FB_MISC_1ST_DETAIL) {
+               best = md;
+               goto finished;
+       }
+
+       /* find best mode based on display width and height */
+       if (specs->max_x && specs->max_y) {
+               struct fb_var_screeninfo var;
+
+               memset(&var, 0, sizeof(struct fb_var_screeninfo));
+               var.xres = (specs->max_x * 7200)/254;
+               var.yres = (specs->max_y * 7200)/254;
+               m = fb_find_best_mode(&var, head);
+               if (m) {
+                       best = m;
+                       goto finished;
+               }
+       }
+
+       /* use first detailed mode */
+       if (md) {
+               best = md;
+               goto finished;
+       }
+
+       /* last resort, use the very first mode */
+       best = m1;
+finished:
+       return best;
+}
+EXPORT_SYMBOL(fb_find_best_display);
+
+EXPORT_SYMBOL(fb_videomode_to_var);
+EXPORT_SYMBOL(fb_var_to_videomode);
+EXPORT_SYMBOL(fb_mode_is_equal);
+EXPORT_SYMBOL(fb_add_videomode);
+EXPORT_SYMBOL(fb_delete_videomode);
+EXPORT_SYMBOL(fb_destroy_modelist);
+EXPORT_SYMBOL(fb_match_mode);
+EXPORT_SYMBOL(fb_find_best_mode);
+EXPORT_SYMBOL(fb_find_nearest_mode);
+EXPORT_SYMBOL(fb_videomode_to_modelist);
 EXPORT_SYMBOL(fb_find_mode);