Re: [PATCH] drm, drm/i915/lvds: Honour video= parameter to override LVDS fixed mode

Dave Airlie
2011-02-09T23:41:46Z

On Wed, 2011-02-09 at 15:01 +0000, Chris Wilson wrote:
> The LVDS code ignores any connector for which it cannot find a fixed
> mode (through an EDID, vBIOS tables or the current active mode). Some
> platforms may include an LVDS header on the board and this may then be
> partnered with a panel without an EDID. This results in us ignoring the
> connector and not lighting up the panel.

Yeah not like this.

you want to make the command line the *last* option we use, the final
fallback. LVDS panels have EDID and VBT hardcoded modes for a good
reason, they don't work with other modes that well. You always want to
use a scaler on the LVDS panel to do modes not the native mode. So if I
have a VBT or EDID and you set video= I should get a scaled mode, not
garbage.

So what I suspect you really want is to leave video= alone or enhance it
somehow, or maybe add i915.lvds_native_mode= parameter.

Dave.

>
> Under UMS, it was possible to override this by specifying the mode
> through the Xorg.conf. For KMS, one specifies the modeline through the
> video= parameter. So we need to include this user modeline when checking
> for panel fixed modes.
>
> The machinery to parse the video= modes and generate the appropriate
> drm_mode is already built into drm_fb_herlper and so we can just
> extract, move it to the core and also use it from intel_lvds.c
>
> Reported-by: Oliver Seitz <info@vtnd.de>
> Signed-off-by: Chris Wilson <chris@chris-wilson.co.uk>
> Cc: Dave Airlie <airlied@redhat.com>
> ---
> drivers/gpu/drm/drm_fb_helper.c | 207 +++++++------------------------------
> drivers/gpu/drm/drm_modes.c | 154 +++++++++++++++++++++++++++
> drivers/gpu/drm/i915/intel_lvds.c | 17 +++
> include/drm/drmP.h | 25 +++++
> include/drm/drm_fb_helper.h | 16 +---
> 5 files changed, 233 insertions(+), 186 deletions(-)
>
> diff --git a/drivers/gpu/drm/drm_fb_helper.c b/drivers/gpu/drm/drm_fb_helper.c
> index 6977a1c..5a80412 100644
> --- a/drivers/gpu/drm/drm_fb_helper.c
> +++ b/drivers/gpu/drm/drm_fb_helper.c
> @@ -70,174 +70,50 @@ fail:
> }
> EXPORT_SYMBOL(drm_fb_helper_single_add_all_connectors);
>
> -/**
> - * drm_fb_helper_connector_parse_command_line - parse command line for connector
> - * @connector - connector to parse line for
> - * @mode_option - per connector mode option
> - *
> - * This parses the connector specific then generic command lines for
> - * modes and options to configure the connector.
> - *
> - * This uses the same parameters as the fb modedb.c, except for extra
> - * <xres>x<yres>[M][R][-<bpp>][@<refresh>][i][m][eDd]
> - *
> - * enable/enable Digital/disable bit at the end
> - */
> -static bool drm_fb_helper_connector_parse_command_line(struct drm_fb_helper_connector *fb_helper_conn,
> - const char *mode_option)
> -{
> - const char *name;
> - unsigned int namelen;
> - int res_specified = 0, bpp_specified = 0, refresh_specified = 0;
> - unsigned int xres = 0, yres = 0, bpp = 32, refresh = 0;
> - int yres_specified = 0, cvt = 0, rb = 0, interlace = 0, margins = 0;
> - int i;
> - enum drm_connector_force force = DRM_FORCE_UNSPECIFIED;
> - struct drm_fb_helper_cmdline_mode *cmdline_mode;
> - struct drm_connector *connector;
> -
> - if (!fb_helper_conn)
> - return false;
> - connector = fb_helper_conn->connector;
> -
> - cmdline_mode = &fb_helper_conn->cmdline_mode;
> - if (!mode_option)
> - mode_option = fb_mode_option;
> -
> - if (!mode_option) {
> - cmdline_mode->specified = false;
> - return false;
> - }
> -
> - name = mode_option;
> - namelen = strlen(name);
> - for (i = namelen-1; i >= 0; i--) {
> - switch (name[i]) {
> - case '@':
> - namelen = i;
> - if (!refresh_specified && !bpp_specified &&
> - !yres_specified) {
> - refresh = simple_strtol(&name[i+1], NULL, 10);
> - refresh_specified = 1;
> - if (cvt || rb)
> - cvt = 0;
> - } else
> - goto done;
> - break;
> - case '-':
> - namelen = i;
> - if (!bpp_specified && !yres_specified) {
> - bpp = simple_strtol(&name[i+1], NULL, 10);
> - bpp_specified = 1;
> - if (cvt || rb)
> - cvt = 0;
> - } else
> - goto done;
> - break;
> - case 'x':
> - if (!yres_specified) {
> - yres = simple_strtol(&name[i+1], NULL, 10);
> - yres_specified = 1;
> - } else
> - goto done;
> - 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;
> - case 'e':
> - force = DRM_FORCE_ON;
> - break;
> - case 'D':
> - if ((connector->connector_type != DRM_MODE_CONNECTOR_DVII) &&
> - (connector->connector_type != DRM_MODE_CONNECTOR_HDMIB))
> - force = DRM_FORCE_ON;
> - else
> - force = DRM_FORCE_ON_DIGITAL;
> - break;
> - case 'd':
> - force = DRM_FORCE_OFF;
> - break;
> - default:
> - goto done;
> - }
> - }
> - if (i < 0 && yres_specified) {
> - xres = simple_strtol(name, NULL, 10);
> - res_specified = 1;
> - }
> -done:
> -
> - DRM_DEBUG_KMS("cmdline mode for connector %s %dx%d@%dHz%s%s%s\n",
> - drm_get_connector_name(connector), xres, yres,
> - (refresh) ? refresh : 60, (rb) ? " reduced blanking" :
> - "", (margins) ? " with margins" : "", (interlace) ?
> - " interlaced" : "");
> -
> - if (force) {
> - const char *s;
> - switch (force) {
> - case DRM_FORCE_OFF: s = "OFF"; break;
> - case DRM_FORCE_ON_DIGITAL: s = "ON - dig"; break;
> - default:
> - case DRM_FORCE_ON: s = "ON"; break;
> - }
> -
> - DRM_INFO("forcing %s connector %s\n",
> - drm_get_connector_name(connector), s);
> - connector->force = force;
> - }
> -
> - if (res_specified) {
> - cmdline_mode->specified = true;
> - cmdline_mode->xres = xres;
> - cmdline_mode->yres = yres;
> - }
> -
> - if (refresh_specified) {
> - cmdline_mode->refresh_specified = true;
> - cmdline_mode->refresh = refresh;
> - }
> -
> - if (bpp_specified) {
> - cmdline_mode->bpp_specified = true;
> - cmdline_mode->bpp = bpp;
> - }
> - cmdline_mode->rb = rb ? true : false;
> - cmdline_mode->cvt = cvt ? true : false;
> - cmdline_mode->interlace = interlace ? true : false;
> -
> - return true;
> -}
> -
> static int drm_fb_helper_parse_command_line(struct drm_fb_helper *fb_helper)
> {
> struct drm_fb_helper_connector *fb_helper_conn;
> int i;
>
> for (i = 0; i < fb_helper->connector_count; i++) {
> + struct drm_cmdline_mode *mode;
> + struct drm_connector *connector;
> char *option = NULL;
>
> fb_helper_conn = fb_helper->connector_info[i];
> + connector = fb_helper_conn->connector;
> + mode = &fb_helper_conn->cmdline_mode;
>
> /* do something on return - turn off connector maybe */
> - if (fb_get_options(drm_get_connector_name(fb_helper_conn->connector), &option))
> + if (fb_get_options(drm_get_connector_name(connector), &option))
> continue;
>
> - drm_fb_helper_connector_parse_command_line(fb_helper_conn, option);
> + if (drm_mode_parse_command_line_for_connector(option,
> + connector,
> + mode)) {
> + if (mode->force) {
> + const char *s;
> + switch (mode->force) {
> + case DRM_FORCE_OFF: s = "OFF"; break;
> + case DRM_FORCE_ON_DIGITAL: s = "ON - dig"; break;
> + default:
> + case DRM_FORCE_ON: s = "ON"; break;
> + }
> +
> + DRM_INFO("forcing %s connector %s\n",
> + drm_get_connector_name(connector), s);
> + connector->force = mode->force;
> + }
> +
> + DRM_DEBUG_KMS("cmdline mode for connector %s %dx%d@%dHz%s%s%s\n",
> + drm_get_connector_name(connector),
> + mode->xres, mode->yres,
> + mode->refresh_specified ? mode->refresh : 60,
> + mode->rb ? " reduced blanking" : "",
> + mode->margins ? " with margins" : "",
> + mode->interlace ? " interlaced" : "");
> + }
> +
> }
> return 0;
> }
> @@ -883,7 +759,7 @@ int drm_fb_helper_single_fb_probe(struct drm_fb_helper *fb_helper,
> /* first up get a count of crtcs now in use and new min/maxes width/heights */
> for (i = 0; i < fb_helper->connector_count; i++) {
> struct drm_fb_helper_connector *fb_helper_conn = fb_helper->connector_info[i];
> - struct drm_fb_helper_cmdline_mode *cmdline_mode;
> + struct drm_cmdline_mode *cmdline_mode;
>
> cmdline_mode = &fb_helper_conn->cmdline_mode;
>
> @@ -1105,7 +981,7 @@ static struct drm_display_mode *drm_has_preferred_mode(struct drm_fb_helper_conn
>
> static bool drm_has_cmdline_mode(struct drm_fb_helper_connector *fb_connector)
> {
> - struct drm_fb_helper_cmdline_mode *cmdline_mode;
> + struct drm_cmdline_mode *cmdline_mode;
> cmdline_mode = &fb_connector->cmdline_mode;
> return cmdline_mode->specified;
> }
> @@ -1113,7 +989,7 @@ static bool drm_has_cmdline_mode(struct drm_fb_helper_connector *fb_connector)
> static struct drm_display_mode *drm_pick_cmdline_mode(struct drm_fb_helper_connector *fb_helper_conn,
> int width, int height)
> {
> - struct drm_fb_helper_cmdline_mode *cmdline_mode;
> + struct drm_cmdline_mode *cmdline_mode;
> struct drm_display_mode *mode = NULL;
>
> cmdline_mode = &fb_helper_conn->cmdline_mode;
> @@ -1145,19 +1021,8 @@ static struct drm_display_mode *drm_pick_cmdline_mode(struct drm_fb_helper_conne
> }
>
> create_mode:
> - if (cmdline_mode->cvt)
> - mode = drm_cvt_mode(fb_helper_conn->connector->dev,
> - cmdline_mode->xres, cmdline_mode->yres,
> - cmdline_mode->refresh_specified ? cmdline_mode->refresh : 60,
> - cmdline_mode->rb, cmdline_mode->interlace,
> - cmdline_mode->margins);
> - else
> - mode = drm_gtf_mode(fb_helper_conn->connector->dev,
> - cmdline_mode->xres, cmdline_mode->yres,
> - cmdline_mode->refresh_specified ? cmdline_mode->refresh : 60,
> - cmdline_mode->interlace,
> - cmdline_mode->margins);
> - drm_mode_set_crtcinfo(mode, CRTC_INTERLACE_HALVE_V);
> + mode = drm_mode_create_from_cmdline_mode(fb_helper_conn->connector->dev,
> + cmdline_mode);
> list_add(&mode->head, &fb_helper_conn->connector->modes);
> return mode;
> }
> diff --git a/drivers/gpu/drm/drm_modes.c b/drivers/gpu/drm/drm_modes.c
> index 58e65f9..f9da47e 100644
> --- a/drivers/gpu/drm/drm_modes.c
> +++ b/drivers/gpu/drm/drm_modes.c
> @@ -974,3 +974,157 @@ void drm_mode_connector_list_update(struct drm_connector *connector)
> }
> }
> EXPORT_SYMBOL(drm_mode_connector_list_update);
> +
> +/**
> + * drm_mode_parse_command_line_for_connector - parse command line for connector
> + * @mode_option - per connector mode option
> + * @connector - connector to parse line for
> + *
> + * This parses the connector specific then generic command lines for
> + * modes and options to configure the connector.
> + *
> + * This uses the same parameters as the fb modedb.c, except for extra
> + * <xres>x<yres>[M][R][-<bpp>][@<refresh>][i][m][eDd]
> + *
> + * enable/enable Digital/disable bit at the end
> + */
> +bool drm_mode_parse_command_line_for_connector(const char *mode_option,
> + struct drm_connector *connector,
> + struct drm_cmdline_mode *mode)
> +{
> + const char *name;
> + unsigned int namelen;
> + int res_specified = 0, bpp_specified = 0, refresh_specified = 0;
> + unsigned int xres = 0, yres = 0, bpp = 32, refresh = 0;
> + int yres_specified = 0, cvt = 0, rb = 0, interlace = 0, margins = 0;
> + int i;
> + enum drm_connector_force force = DRM_FORCE_UNSPECIFIED;
> +
> + if (!mode_option)
> + mode_option = fb_mode_option;
> +
> + if (!mode_option) {
> + mode->specified = false;
> + return false;
> + }
> +
> + name = mode_option;
> + namelen = strlen(name);
> + for (i = namelen-1; i >= 0; i--) {
> + switch (name[i]) {
> + case '@':
> + namelen = i;
> + if (!refresh_specified && !bpp_specified &&
> + !yres_specified) {
> + refresh = simple_strtol(&name[i+1], NULL, 10);
> + refresh_specified = 1;
> + if (cvt || rb)
> + cvt = 0;
> + } else
> + goto done;
> + break;
> + case '-':
> + namelen = i;
> + if (!bpp_specified && !yres_specified) {
> + bpp = simple_strtol(&name[i+1], NULL, 10);
> + bpp_specified = 1;
> + if (cvt || rb)
> + cvt = 0;
> + } else
> + goto done;
> + break;
> + case 'x':
> + if (!yres_specified) {
> + yres = simple_strtol(&name[i+1], NULL, 10);
> + yres_specified = 1;
> + } else
> + goto done;
> + 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;
> + case 'e':
> + force = DRM_FORCE_ON;
> + break;
> + case 'D':
> + if ((connector->connector_type != DRM_MODE_CONNECTOR_DVII) &&
> + (connector->connector_type != DRM_MODE_CONNECTOR_HDMIB))
> + force = DRM_FORCE_ON;
> + else
> + force = DRM_FORCE_ON_DIGITAL;
> + break;
> + case 'd':
> + force = DRM_FORCE_OFF;
> + break;
> + default:
> + goto done;
> + }
> + }
> + if (i < 0 && yres_specified) {
> + xres = simple_strtol(name, NULL, 10);
> + res_specified = 1;
> + }
> +done:
> + if (res_specified) {
> + mode->specified = true;
> + mode->xres = xres;
> + mode->yres = yres;
> + }
> +
> + if (refresh_specified) {
> + mode->refresh_specified = true;
> + mode->refresh = refresh;
> + }
> +
> + if (bpp_specified) {
> + mode->bpp_specified = true;
> + mode->bpp = bpp;
> + }
> + mode->rb = rb ? true : false;
> + mode->cvt = cvt ? true : false;
> + mode->interlace = interlace ? true : false;
> + mode->force = force;
> +
> + return true;
> +}
> +EXPORT_SYMBOL(drm_mode_parse_command_line_for_connector);
> +
> +struct drm_display_mode *
> +drm_mode_create_from_cmdline_mode(struct drm_device *dev,
> + struct drm_cmdline_mode *cmd)
> +{
> + struct drm_display_mode *mode;
> +
> + if (cmd->cvt)
> + mode = drm_cvt_mode(dev,
> + cmd->xres, cmd->yres,
> + cmd->refresh_specified ? cmd->refresh : 60,
> + cmd->rb, cmd->interlace,
> + cmd->margins);
> + else
> + mode = drm_gtf_mode(dev,
> + cmd->xres, cmd->yres,
> + cmd->refresh_specified ? cmd->refresh : 60,
> + cmd->interlace,
> + cmd->margins);
> + if (!mode)
> + return NULL;
> +
> + drm_mode_set_crtcinfo(mode, CRTC_INTERLACE_HALVE_V);
> + return mode;
> +}
> +EXPORT_SYMBOL(drm_mode_create_from_cmdline_mode);
> diff --git a/drivers/gpu/drm/i915/intel_lvds.c b/drivers/gpu/drm/i915/intel_lvds.c
> index cd08960..af4ef17 100644
> --- a/drivers/gpu/drm/i915/intel_lvds.c
> +++ b/drivers/gpu/drm/i915/intel_lvds.c
> @@ -874,6 +874,8 @@ bool intel_lvds_init(struct drm_device *dev)
> struct drm_encoder *encoder;
> struct drm_display_mode *scan; /* *modes, *bios_mode; */
> struct drm_crtc *crtc;
> + char *cmdline_option = NULL;
> + struct drm_cmdline_mode cmdline_mode;
> u32 lvds;
> int pipe;
> u8 pin;
> @@ -951,6 +953,7 @@ bool intel_lvds_init(struct drm_device *dev)
> intel_lvds->fitting_mode = DRM_MODE_SCALE_ASPECT;
> /*
> * LVDS discovery:
> + * 0) user override
> * 1) check for EDID on DDC
> * 2) check for VBT data
> * 3) check to see if LVDS is already on
> @@ -959,6 +962,20 @@ bool intel_lvds_init(struct drm_device *dev)
> * if closed, act like it's not there for now
> */
>
> + if (fb_get_options(drm_get_connector_name(connector),
> + &cmdline_option) == 0 &&
> + drm_mode_parse_command_line_for_connector(cmdline_option,
> + connector,
> + &cmdline_mode)) {
> + intel_lvds->fixed_mode =
> + drm_mode_create_from_cmdline_mode(dev, &cmdline_mode);
> + if (intel_lvds->fixed_mode) {
> + intel_lvds->fixed_mode->type |=
> + DRM_MODE_TYPE_PREFERRED;
> + goto out;
> + }
> + }
> +
> /*
> * Attempt to get the fixed panel mode from DDC. Assume that the
> * preferred mode is the right one.
> diff --git a/include/drm/drmP.h b/include/drm/drmP.h
> index fe29aad..bf01108 100644
> --- a/include/drm/drmP.h
> +++ b/include/drm/drmP.h
> @@ -968,6 +968,22 @@ struct drm_minor {
> struct drm_mode_group mode_group;
> };
>
> +/* mode specified on the command line */
> +struct drm_cmdline_mode {
> + bool specified;
> + bool refresh_specified;
> + bool bpp_specified;
> + int xres, yres;
> + int bpp;
> + int refresh;
> + bool rb;
> + bool interlace;
> + bool cvt;
> + bool margins;
> + enum drm_connector_force force;
> +};
> +
> +
> struct drm_pending_vblank_event {
> struct drm_pending_event base;
> int pipe;
> @@ -1381,6 +1397,15 @@ extern int drm_calc_vbltimestamp_from_scanoutpos(struct drm_device *dev,
> struct drm_crtc *refcrtc);
> extern void drm_calc_timestamping_constants(struct drm_crtc *crtc);
>
> +extern bool
> +drm_mode_parse_command_line_for_connector(const char *mode_option,
> + struct drm_connector *connector,
> + struct drm_cmdline_mode *mode);
> +
> +extern struct drm_display_mode *
> +drm_mode_create_from_cmdline_mode(struct drm_device *dev,
> + struct drm_cmdline_mode *cmd);
> +
> /* Modesetting support */
> extern void drm_vblank_pre_modeset(struct drm_device *dev, int crtc);
> extern void drm_vblank_post_modeset(struct drm_device *dev, int crtc);
> diff --git a/include/drm/drm_fb_helper.h b/include/drm/drm_fb_helper.h
> index f22e7fe..4e66488 100644
> --- a/include/drm/drm_fb_helper.h
> +++ b/include/drm/drm_fb_helper.h
> @@ -40,20 +40,6 @@ struct drm_fb_helper_crtc {
> struct drm_display_mode *desired_mode;
> };
>
> -/* mode specified on the command line */
> -struct drm_fb_helper_cmdline_mode {
> - bool specified;
> - bool refresh_specified;
> - bool bpp_specified;
> - int xres, yres;
> - int bpp;
> - int refresh;
> - bool rb;
> - bool interlace;
> - bool cvt;
> - bool margins;
> -};
> -
> struct drm_fb_helper_surface_size {
> u32 fb_width;
> u32 fb_height;
> @@ -74,8 +60,8 @@ struct drm_fb_helper_funcs {
> };
>
> struct drm_fb_helper_connector {
> - struct drm_fb_helper_cmdline_mode cmdline_mode;
> struct drm_connector *connector;
> + struct drm_cmdline_mode cmdline_mode;
> };
>
> struct drm_fb_helper {