From f1198d1ea1c1fe7a0d619c5cf7049efe6205e527 Mon Sep 17 00:00:00 2001 From: Guennadi Liakhovetski Date: Fri, 15 Oct 2010 07:54:04 +0000 Subject: [PATCH] fbdev: sh_mobile_hdmi: add support for the "video=" kernel command line option Add support for specifying video modes on the kernel command line. Mode selection priorities are also changed such, that only exact matches of specified modes with monitor modes from EDID are accepted, at least in width and height. If none found - fall back to framebuffer default setting, if available. Signed-off-by: Guennadi Liakhovetski Signed-off-by: Paul Mundt --- drivers/video/sh_mobile_hdmi.c | 142 +++++++++++++++++++++------------ 1 file changed, 90 insertions(+), 52 deletions(-) diff --git a/drivers/video/sh_mobile_hdmi.c b/drivers/video/sh_mobile_hdmi.c index 94a94fde2c67..f0ff2848700c 100644 --- a/drivers/video/sh_mobile_hdmi.c +++ b/drivers/video/sh_mobile_hdmi.c @@ -611,14 +611,39 @@ static void sh_hdmi_configure(struct sh_hdmi *hdmi) hdmi_write(hdmi, 0x40, HDMI_SYSTEM_CTRL); } +static unsigned long sh_hdmi_rate_error(struct sh_hdmi *hdmi, + const struct fb_videomode *mode) +{ + long target = PICOS2KHZ(mode->pixclock) * 1000, + rate = clk_round_rate(hdmi->hdmi_clk, target); + unsigned long rate_error = rate > 0 ? abs(rate - target) : ULONG_MAX; + + dev_dbg(hdmi->dev, "%u-%u-%u-%u x %u-%u-%u-%u\n", + mode->left_margin, mode->xres, + mode->right_margin, mode->hsync_len, + mode->upper_margin, mode->yres, + mode->lower_margin, mode->vsync_len); + + dev_dbg(hdmi->dev, "\t@%lu(+/-%lu)Hz, e=%lu / 1000, r=%uHz\n", target, + rate_error, rate_error ? 10000 / (10 * target / rate_error) : 0, + mode->refresh); + + return rate_error; +} + static int sh_hdmi_read_edid(struct sh_hdmi *hdmi) { struct fb_var_screeninfo tmpvar; - /* TODO: When we are ready to use EDID, use this to fill &hdmi->var */ struct fb_var_screeninfo *var = &tmpvar; const struct fb_videomode *mode, *found = NULL; - int i; + struct fb_info *info = hdmi->info; + 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; u8 edid[128]; + char *forced; + int i; /* Read EDID */ dev_dbg(hdmi->dev, "Read back EDID code:"); @@ -639,69 +664,82 @@ static int sh_hdmi_read_edid(struct sh_hdmi *hdmi) fb_edid_to_monspecs(edid, &hdmi->monspec); - /* First look for an exact match */ - for (i = 0, mode = hdmi->monspec.modedb; i < hdmi->monspec.modedb_len; - i++, mode++) { - dev_dbg(hdmi->dev, "%u-%u-%u-%u x %u-%u-%u-%u @ %lu kHz monitor detected\n", - mode->left_margin, mode->xres, - mode->right_margin, mode->hsync_len, - mode->upper_margin, mode->yres, - mode->lower_margin, mode->vsync_len, - PICOS2KHZ(mode->pixclock)); - if (!found && hdmi->info) { - fb_videomode_to_var(var, mode); - found = fb_match_mode(var, &hdmi->info->modelist); - /* - * If an exact match found, we're good to bail out, but - * continue to print out all modes - */ + fb_get_options("sh_mobile_lcdc", &forced); + if (forced && *forced) { + /* Only primitive parsing so far */ + i = sscanf(forced, "%ux%u@%u", + &f_width, &f_height, &f_refresh); + if (i < 2) { + f_width = 0; + f_height = 0; } + dev_dbg(hdmi->dev, "Forced mode %ux%u@%uHz\n", + f_width, f_height, f_refresh); + } + + /* 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++, mode++) { + unsigned long rate_error = sh_hdmi_rate_error(hdmi, mode); + + /* No interest in unmatching modes */ + if (f_width != mode->xres || f_height != mode->yres) + continue; + 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; + + /* Check if supported: sufficient fb memory, supported clock-rate */ + fb_videomode_to_var(var, mode); + + if (info && info->fbops->fb_check_var && + info->fbops->fb_check_var(var, info)) { + exact_match = false; + continue; + } + + found = mode; + found_rate_error = rate_error; } /* - * The monitor might also work with a mode, that is smaller, than one of - * its modes, use the first (default) one for this + * TODO 1: if no ->info is present, postpone running the config until + * after ->info first gets registered. + * TODO 2: consider registering the HDMI platform device from the LCDC + * driver, and passing ->info with HDMI platform data. */ - if (!found && hdmi->info && hdmi->monspec.modedb_len) { - struct fb_modelist *modelist; - unsigned int min_err = UINT_MAX, err; - const struct fb_videomode *mon_mode = hdmi->monspec.modedb; + if (info && !found) { + modelist = hdmi->info->modelist.next && + !list_empty(&hdmi->info->modelist) ? + list_entry(hdmi->info->modelist.next, + struct fb_modelist, list) : + NULL; - list_for_each_entry(modelist, &hdmi->info->modelist, list) { - mode = &modelist->mode; - dev_dbg(hdmi->dev, "matching %ux%u to %ux%u\n", mode->xres, mode->yres, - mon_mode->xres, mon_mode->yres); - if (mode->xres <= mon_mode->xres && mode->yres <= mon_mode->yres) { - err = mon_mode->xres - mode->xres + mon_mode->yres - mode->yres; - if (!err) { - found = mode; - break; - } - if (err < min_err) { - found = mode; - min_err = err; - } - } + if (modelist) { + found = &modelist->mode; + found_rate_error = sh_hdmi_rate_error(hdmi, found); } } - /* Nothing suitable specified by the platform: use monitor's first mode */ - if (!found && hdmi->monspec.modedb_len) - found = hdmi->monspec.modedb; - - /* No valid timing info in EDID - last resort: use platform default mode */ - if (!found && hdmi->info) { - struct fb_modelist *modelist = list_entry(hdmi->info->modelist.next, - struct fb_modelist, list); - found = &modelist->mode; - } - /* No cookie today */ if (!found) return -ENXIO; - dev_dbg(hdmi->dev, "best \"%s\" %ux%u@%ups\n", found->name, - found->xres, found->yres, found->pixclock); + dev_info(hdmi->dev, "Using %s mode %ux%u@%uHz (%luHz), clock error %luHz\n", + modelist ? "default" : "EDID", found->xres, found->yres, + found->refresh, PICOS2KHZ(found->pixclock) * 1000, found_rate_error); if ((found->xres == 720 && found->yres == 480) || (found->xres == 1280 && found->yres == 720) ||