Merge git://git.kernel.org/pub/scm/linux/kernel/git/lethal/fbdev-2.6
[pandora-kernel.git] / drivers / video / sh_mobile_hdmi.c
index 8c59cc8..74d9f54 100644 (file)
@@ -22,6 +22,7 @@
 #include <linux/slab.h>
 #include <linux/types.h>
 #include <linux/workqueue.h>
+#include <sound/soc.h>
 #include <sound/soc-dapm.h>
 #include <sound/initval.h>
 
@@ -221,6 +222,7 @@ struct sh_hdmi {
        struct delayed_work edid_work;
        struct fb_var_screeninfo var;
        struct fb_monspecs monspec;
+       struct notifier_block notifier;
 };
 
 static void hdmi_write(struct sh_hdmi *hdmi, u8 data, u8 reg)
@@ -737,7 +739,7 @@ static int sh_hdmi_read_edid(struct sh_hdmi *hdmi, unsigned long *hdmi_rate,
        struct fb_modelist *modelist = NULL;
        unsigned int f_width = 0, f_height = 0, f_refresh = 0;
        unsigned long found_rate_error = ULONG_MAX; /* silly compiler... */
-       bool exact_match = false;
+       bool scanning = false, preferred_bad = false;
        u8 edid[128];
        char *forced;
        int i;
@@ -800,6 +802,9 @@ static int sh_hdmi_read_edid(struct sh_hdmi *hdmi, unsigned long *hdmi_rate,
                if (i < 2) {
                        f_width = 0;
                        f_height = 0;
+               } else {
+                       /* The user wants us to use the EDID data */
+                       scanning = true;
                }
                dev_dbg(hdmi->dev, "Forced mode %ux%u@%uHz\n",
                        f_width, f_height, f_refresh);
@@ -807,37 +812,56 @@ static int sh_hdmi_read_edid(struct sh_hdmi *hdmi, unsigned long *hdmi_rate,
 
        /* Walk monitor modes to find the best or the exact match */
        for (i = 0, mode = hdmi->monspec.modedb;
-            f_width && f_height && i < hdmi->monspec.modedb_len && !exact_match;
+            i < hdmi->monspec.modedb_len && scanning;
             i++, mode++) {
                unsigned long rate_error;
 
-               /* No interest in unmatching modes */
-               if (f_width != mode->xres || f_height != mode->yres)
+               if (!f_width && !f_height) {
+                       /*
+                        * A parameter string "video=sh_mobile_lcdc:0x0" means
+                        * use the preferred EDID mode. If it is rejected by
+                        * .fb_check_var(), keep looking, until an acceptable
+                        * one is found.
+                        */
+                       if ((mode->flag & FB_MODE_IS_FIRST) || preferred_bad)
+                               scanning = false;
+                       else
+                               continue;
+               } else if (f_width != mode->xres || f_height != mode->yres) {
+                       /* No interest in unmatching modes */
                        continue;
+               }
 
                rate_error = sh_hdmi_rate_error(hdmi, mode, hdmi_rate, parent_rate);
 
-               if (f_refresh == mode->refresh || (!f_refresh && !rate_error))
-                       /*
-                        * Exact match if either the refresh rate matches or it
-                        * hasn't been specified and we've found a mode, for
-                        * which we can configure the clock precisely
-                        */
-                       exact_match = true;
-               else if (found && found_rate_error <= rate_error)
-                       /*
-                        * We otherwise search for the closest matching clock
-                        * rate - either if no refresh rate has been specified
-                        * or we cannot find an exactly matching one
-                        */
-                       continue;
+               if (scanning) {
+                       if (f_refresh == mode->refresh || (!f_refresh && !rate_error))
+                               /*
+                                * Exact match if either the refresh rate
+                                * matches or it hasn't been specified and we've
+                                * found a mode, for which we can configure the
+                                * clock precisely
+                                */
+                               scanning = false;
+                       else if (found && found_rate_error <= rate_error)
+                               /*
+                                * We otherwise search for the closest matching
+                                * clock rate - either if no refresh rate has
+                                * been specified or we cannot find an exactly
+                                * matching one
+                                */
+                               continue;
+               }
 
                /* Check if supported: sufficient fb memory, supported clock-rate */
                fb_videomode_to_var(var, mode);
 
+               var->bits_per_pixel = info->var.bits_per_pixel;
+
                if (info && info->fbops->fb_check_var &&
                    info->fbops->fb_check_var(var, info)) {
-                       exact_match = false;
+                       scanning = true;
+                       preferred_bad = true;
                        continue;
                }
 
@@ -855,9 +879,9 @@ static int sh_hdmi_read_edid(struct sh_hdmi *hdmi, unsigned long *hdmi_rate,
         * driver, and passing ->info with HDMI platform data.
         */
        if (info && !found) {
-               modelist = hdmi->info->modelist.next &&
-                       !list_empty(&hdmi->info->modelist) ?
-                       list_entry(hdmi->info->modelist.next,
+               modelist = info->modelist.next &&
+                       !list_empty(&info->modelist) ?
+                       list_entry(info->modelist.next,
                                   struct fb_modelist, list) :
                        NULL;
 
@@ -1100,6 +1124,7 @@ static void sh_hdmi_edid_work_fn(struct work_struct *work)
        mutex_lock(&hdmi->mutex);
 
        if (hdmi->hp_state == HDMI_HOTPLUG_CONNECTED) {
+               struct fb_info *info = hdmi->info;
                unsigned long parent_rate = 0, hdmi_rate;
 
                /* A device has been plugged in */
@@ -1121,22 +1146,21 @@ static void sh_hdmi_edid_work_fn(struct work_struct *work)
                /* Switched to another (d) power-save mode */
                msleep(10);
 
-               if (!hdmi->info)
+               if (!info)
                        goto out;
 
-               ch = hdmi->info->par;
+               ch = info->par;
 
                acquire_console_sem();
 
                /* HDMI plug in */
                if (!sh_hdmi_must_reconfigure(hdmi) &&
-                   hdmi->info->state == FBINFO_STATE_RUNNING) {
+                   info->state == FBINFO_STATE_RUNNING) {
                        /*
                         * First activation with the default monitor - just turn
                         * on, if we run a resume here, the logo disappears
                         */
-                       if (lock_fb_info(hdmi->info)) {
-                               struct fb_info *info = hdmi->info;
+                       if (lock_fb_info(info)) {
                                info->var.width = hdmi->var.width;
                                info->var.height = hdmi->var.height;
                                sh_hdmi_display_on(hdmi, info);
@@ -1144,7 +1168,7 @@ static void sh_hdmi_edid_work_fn(struct work_struct *work)
                        }
                } else {
                        /* New monitor or have to wake up */
-                       fb_set_suspend(hdmi->info, 0);
+                       fb_set_suspend(info, 0);
                }
 
                release_console_sem();
@@ -1174,13 +1198,6 @@ out:
        dev_dbg(hdmi->dev, "%s(%p): end\n", __func__, pdata->lcd_dev);
 }
 
-static int sh_hdmi_notify(struct notifier_block *nb,
-                         unsigned long action, void *data);
-
-static struct notifier_block sh_hdmi_notifier = {
-       .notifier_call = sh_hdmi_notify,
-};
-
 static int sh_hdmi_notify(struct notifier_block *nb,
                          unsigned long action, void *data)
 {
@@ -1190,7 +1207,7 @@ static int sh_hdmi_notify(struct notifier_block *nb,
        struct sh_mobile_lcdc_board_cfg *board_cfg = &ch->cfg.board_cfg;
        struct sh_hdmi *hdmi = board_cfg->board_data;
 
-       if (nb != &sh_hdmi_notifier || !hdmi || hdmi->info != info)
+       if (!hdmi || nb != &hdmi->notifier || hdmi->info != info)
                return NOTIFY_DONE;
 
        switch(action) {
@@ -1209,11 +1226,11 @@ static int sh_hdmi_notify(struct notifier_block *nb,
                 * temporarily, synchronise with the work queue and re-acquire
                 * the info->lock.
                 */
-               unlock_fb_info(hdmi->info);
+               unlock_fb_info(info);
                mutex_lock(&hdmi->mutex);
                hdmi->info = NULL;
                mutex_unlock(&hdmi->mutex);
-               lock_fb_info(hdmi->info);
+               lock_fb_info(info);
                return NOTIFY_OK;
        }
        return NOTIFY_DONE;
@@ -1311,6 +1328,9 @@ static int __init sh_hdmi_probe(struct platform_device *pdev)
                goto ecodec;
        }
 
+       hdmi->notifier.notifier_call = sh_hdmi_notify;
+       fb_register_client(&hdmi->notifier);
+
        return 0;
 
 ecodec:
@@ -1341,6 +1361,8 @@ static int __exit sh_hdmi_remove(struct platform_device *pdev)
 
        snd_soc_unregister_codec(&pdev->dev);
 
+       fb_unregister_client(&hdmi->notifier);
+
        board_cfg->display_on = NULL;
        board_cfg->display_off = NULL;
        board_cfg->board_data = NULL;