drm/exynos: Add 1024x768/1280x800 HDMI resolutions
authorSean Paul <seanpaul@chromium.org>
Mon, 22 Apr 2013 17:00:11 +0000 (13:00 -0400)
committerChromeBot <chrome-bot@google.com>
Wed, 24 Apr 2013 19:05:11 +0000 (12:05 -0700)
This patch adds 1024x768 and 1280x800 HDMI resolutions by redistributing
vertical lines to the blanking period from the active area. The result
is that userspace is exposed resolutions which fit into the mixer's
scanning guidelines (720 lines), and the monitor thinks it's getting the
full monty. For example, 1024x768 looks like 1024x720 from userspace's
perspective, but 1024x768 from the monitor's perspective.

BUG=chromium:221411, chrome-os-partner:14238
TEST=Tested by hand with 1024x768

Change-Id: I6dcfe6260504e38aa604c5b19abc2bd716bbf729
Signed-off-by: Sean Paul <seanpaul@chromium.org>
Reviewed-on: https://gerrit.chromium.org/gerrit/48830
Reviewed-by: Mark Hayter <mdhayter@chromium.org>
Reviewed-by: Stéphane Marchesin <marcheu@chromium.org>
drivers/gpu/drm/exynos/exynos_drm_connector.c
drivers/gpu/drm/exynos/exynos_drm_display.h
drivers/gpu/drm/exynos/exynos_hdmi.c
drivers/gpu/drm/exynos/exynos_mixer.c

index 2bdc678..9f62265 100644 (file)
@@ -267,6 +267,7 @@ static int exynos_drm_connector_fill_modes(struct drm_connector *connector,
                                unsigned int max_width, unsigned int max_height)
 {
        struct exynos_drm_display *display = display_from_connector(connector);
+       int ret;
        unsigned int width, height;
 
        DRM_DEBUG_KMS("[CONNECTOR:%d:%s] %d x %d\n", DRM_BASE_ID(connector),
@@ -284,8 +285,18 @@ static int exynos_drm_connector_fill_modes(struct drm_connector *connector,
                display->panel_ops->get_max_res(display->panel_ctx, &width,
                                &height);
 
-       return drm_helper_probe_single_connector_modes(connector, width,
+       ret = drm_helper_probe_single_connector_modes(connector, width,
                                                        height);
+       if (ret < 0) {
+               DRM_ERROR("Failed to probe connector modes ret=%d\n", ret);
+               return ret;
+       }
+
+       if (display->controller_ops->adjust_modes)
+               display->controller_ops->adjust_modes(display->controller_ctx,
+                               connector);
+
+       return ret;
 }
 
 /* get detection status of display device. */
index dc0a987..7aada9c 100644 (file)
@@ -54,6 +54,7 @@ struct exynos_panel_ops {
  * @commit: Applies controller level settings (as opposed to window level)
  * @win_commit: Commits the changes on only one window
  * @win_disable: Disables one of the controller's windows
+ * @adjust_modes: Allows the controller to rework the connector modes
  */
 struct exynos_controller_ops {
        int (*subdrv_probe)(void *ctx, struct drm_device *drm_dev);
@@ -65,6 +66,7 @@ struct exynos_controller_ops {
        void (*commit)(void *ctx);
        void (*win_commit)(void *ctx, int zpos);
        void (*win_disable)(void *ctx, int zpos);
+       void (*adjust_modes)(void *ctx, struct drm_connector *connector);
 };
 
 /*
index 0f7121e..a884b49 100644 (file)
@@ -862,10 +862,10 @@ static int hdmi_v13_conf_index(struct drm_display_mode *mode)
        int i;
 
        for (i = 0; i < ARRAY_SIZE(hdmi_v13_confs); ++i)
-               if (hdmi_v13_confs[i].width == mode->hdisplay &&
-                               hdmi_v13_confs[i].height == mode->vdisplay &&
-                               hdmi_v13_confs[i].vrefresh == mode->vrefresh &&
-                               hdmi_v13_confs[i].interlace ==
+               if (hdmi_v13_confs[i].width == mode->crtc_hdisplay &&
+                       hdmi_v13_confs[i].height == mode->crtc_vdisplay &&
+                       hdmi_v13_confs[i].vrefresh == mode->vrefresh &&
+                       hdmi_v13_confs[i].interlace ==
                                ((mode->flags & DRM_MODE_FLAG_INTERLACE) ?
                                 true : false))
                        return i;
@@ -1716,7 +1716,7 @@ static void hdmi_mode_fixup(void *ctx, struct drm_connector *connector,
                                struct drm_display_mode *mode,
                                struct drm_display_mode *adjusted_mode)
 {
-       struct drm_display_mode *m;
+       struct drm_display_mode *t, *con_mode;
        struct hdmi_context *hdata = ctx;
        int index;
 
@@ -1725,31 +1725,34 @@ static void hdmi_mode_fixup(void *ctx, struct drm_connector *connector,
                        drm_get_connector_name(connector),
                        DRM_BASE_ID(mode), mode->name);
 
-       drm_mode_set_crtcinfo(adjusted_mode, 0);
-
-       if (hdata->is_v13)
-               index = hdmi_v13_conf_index(adjusted_mode);
-       else
-               index = find_hdmiphy_conf(adjusted_mode->clock * 1000);
-
-       /* just return if user desired mode exists. */
-       if (index >= 0)
-               return;
+       /*
+        * Match the incoming mode to a mode in the connector list and copy it
+        * over. This is important since this might be an adjusted mode from the
+        * mixer and those have differing crtc_* values.
+        */
+       list_for_each_entry_safe(con_mode, t, &connector->modes, head) {
+               if (mode->hdisplay == con_mode->hdisplay &&
+                   mode->vdisplay == con_mode->vdisplay &&
+                   mode->clock == con_mode->clock) {
+                       hdmi_mode_copy(adjusted_mode, con_mode);
+                       return;
+               }
+       }
 
        /*
-        * otherwise, find the most suitable mode among modes and change it
-        * to adjusted_mode.
+        * We didn't find a mode which matched the desired resolution, so just
+        * find something with the same clock.
         */
-       list_for_each_entry(m, &connector->modes, head) {
+       list_for_each_entry_safe(con_mode, t, &connector->modes, head) {
                if (hdata->is_v13)
-                       index = hdmi_v13_conf_index(m);
+                       index = hdmi_v13_conf_index(con_mode);
                else
-                       index = find_hdmiphy_conf(m->clock * 1000);
+                       index = find_hdmiphy_conf(con_mode->clock * 1000);
 
                if (index >= 0) {
                        DRM_INFO("desired mode doesn't exist so\n");
                        DRM_INFO("use the most suitable mode among modes.\n");
-                       hdmi_mode_copy(adjusted_mode, m);
+                       hdmi_mode_copy(adjusted_mode, con_mode);
                        break;
                }
        }
@@ -1774,9 +1777,9 @@ static void hdmi_v14_mode_set(struct hdmi_context *hdata,
        hdata->mode_conf.vic = drm_match_cea_mode(m);
 
        hdata->mode_conf.pixel_clock = m->clock * 1000;
-       hdmi_set_reg(core->h_blank, 2, m->htotal - m->hdisplay);
-       hdmi_set_reg(core->v_line, 2, m->vtotal);
-       hdmi_set_reg(core->h_line, 2, m->htotal);
+       hdmi_set_reg(core->h_blank, 2, m->crtc_htotal - m->crtc_hdisplay);
+       hdmi_set_reg(core->v_line, 2, m->crtc_vtotal);
+       hdmi_set_reg(core->h_line, 2, m->crtc_htotal);
        hdmi_set_reg(core->hsync_pol, 1,
                        (m->flags & DRM_MODE_FLAG_NHSYNC)  ? 1 : 0);
        hdmi_set_reg(core->vsync_pol, 1,
@@ -1794,49 +1797,60 @@ static void hdmi_v14_mode_set(struct hdmi_context *hdata,
        if (m->flags & DRM_MODE_FLAG_INTERLACE) {
                /* Interlaced Mode */
                hdmi_set_reg(core->v_sync_line_bef_2, 2,
-                       (m->vsync_end - m->vdisplay) / 2);
+                       (m->crtc_vsync_end - m->crtc_vdisplay) / 2);
                hdmi_set_reg(core->v_sync_line_bef_1, 2,
-                       (m->vsync_start - m->vdisplay) / 2);
-               hdmi_set_reg(core->v2_blank, 2, m->vtotal / 2);
-               hdmi_set_reg(core->v1_blank, 2, (m->vtotal - m->vdisplay) / 2);
+                       (m->crtc_vsync_start - m->crtc_vdisplay) / 2);
+               hdmi_set_reg(core->v2_blank, 2, m->crtc_vtotal / 2);
+               hdmi_set_reg(core->v1_blank, 2,
+                               (m->crtc_vtotal - m->crtc_vdisplay) / 2);
                hdmi_set_reg(core->v_blank_f0, 2,
-                       (m->vtotal + ((m->vsync_end - m->vsync_start) * 4) + 5) / 2);
-               hdmi_set_reg(core->v_blank_f1, 2, m->vtotal);
-               hdmi_set_reg(core->v_sync_line_aft_2, 2, (m->vtotal / 2) + 7);
-               hdmi_set_reg(core->v_sync_line_aft_1, 2, (m->vtotal / 2) + 2);
+                       (m->crtc_vtotal +
+                       ((m->crtc_vsync_end - m->crtc_vsync_start) * 4) + 5)
+                       / 2);
+               hdmi_set_reg(core->v_blank_f1, 2, m->crtc_vtotal);
+               hdmi_set_reg(core->v_sync_line_aft_2, 2,
+                       (m->crtc_vtotal / 2) + 7);
+               hdmi_set_reg(core->v_sync_line_aft_1, 2,
+                       (m->crtc_vtotal / 2) + 2);
                hdmi_set_reg(core->v_sync_line_aft_pxl_2, 2,
-                       (m->htotal / 2) + (m->hsync_start - m->hdisplay));
+                       (m->crtc_htotal / 2) +
+                       (m->crtc_hsync_start - m->crtc_hdisplay));
                hdmi_set_reg(core->v_sync_line_aft_pxl_1, 2,
-                       (m->htotal / 2) + (m->hsync_start - m->hdisplay));
-               hdmi_set_reg(tg->vact_st, 2, (m->vtotal - m->vdisplay) / 2);
-               hdmi_set_reg(tg->vact_sz, 2, m->vdisplay / 2);
+                       (m->crtc_htotal / 2) +
+                       (m->crtc_hsync_start - m->crtc_hdisplay));
+               hdmi_set_reg(tg->vact_st, 2,
+                       (m->crtc_vtotal - m->crtc_vdisplay) / 2);
+               hdmi_set_reg(tg->vact_sz, 2, m->crtc_vdisplay / 2);
                hdmi_set_reg(tg->vact_st2, 2, 0x249);/* Reset value + 1*/
                hdmi_set_reg(tg->vact_st3, 2, 0x0);
                hdmi_set_reg(tg->vact_st4, 2, 0x0);
        } else {
                /* Progressive Mode */
                hdmi_set_reg(core->v_sync_line_bef_2, 2,
-                       m->vsync_end - m->vdisplay);
+                       m->crtc_vsync_end - m->crtc_vdisplay);
                hdmi_set_reg(core->v_sync_line_bef_1, 2,
-                       m->vsync_start - m->vdisplay);
-               hdmi_set_reg(core->v2_blank, 2, m->vtotal);
-               hdmi_set_reg(core->v1_blank, 2, m->vtotal - m->vdisplay);
+                       m->crtc_vsync_start - m->crtc_vdisplay);
+               hdmi_set_reg(core->v2_blank, 2, m->crtc_vtotal);
+               hdmi_set_reg(core->v1_blank, 2,
+                       m->crtc_vtotal - m->crtc_vdisplay);
                hdmi_set_reg(core->v_blank_f0, 2, 0xffff);
                hdmi_set_reg(core->v_blank_f1, 2, 0xffff);
                hdmi_set_reg(core->v_sync_line_aft_2, 2, 0xffff);
                hdmi_set_reg(core->v_sync_line_aft_1, 2, 0xffff);
                hdmi_set_reg(core->v_sync_line_aft_pxl_2, 2, 0xffff);
                hdmi_set_reg(core->v_sync_line_aft_pxl_1, 2, 0xffff);
-               hdmi_set_reg(tg->vact_st, 2, m->vtotal - m->vdisplay);
-               hdmi_set_reg(tg->vact_sz, 2, m->vdisplay);
+               hdmi_set_reg(tg->vact_st, 2, m->crtc_vtotal - m->crtc_vdisplay);
+               hdmi_set_reg(tg->vact_sz, 2, m->crtc_vdisplay);
                hdmi_set_reg(tg->vact_st2, 2, 0x248); /* Reset value */
                hdmi_set_reg(tg->vact_st3, 2, 0x47b); /* Reset value */
                hdmi_set_reg(tg->vact_st4, 2, 0x6ae); /* Reset value */
        }
 
        /* Following values & calculations are same irrespective of mode type */
-       hdmi_set_reg(core->h_sync_start, 2, m->hsync_start - m->hdisplay - 2);
-       hdmi_set_reg(core->h_sync_end, 2, m->hsync_end - m->hdisplay - 2);
+       hdmi_set_reg(core->h_sync_start, 2,
+               m->crtc_hsync_start - m->crtc_hdisplay - 2);
+       hdmi_set_reg(core->h_sync_end, 2,
+               m->crtc_hsync_end - m->crtc_hdisplay - 2);
        hdmi_set_reg(core->vact_space_1, 2, 0xffff);
        hdmi_set_reg(core->vact_space_2, 2, 0xffff);
        hdmi_set_reg(core->vact_space_3, 2, 0xffff);
@@ -1858,10 +1872,10 @@ static void hdmi_v14_mode_set(struct hdmi_context *hdata,
 
        /* Timing generator registers */
        hdmi_set_reg(tg->cmd, 1, 0x0);
-       hdmi_set_reg(tg->h_fsz, 2, m->htotal);
-       hdmi_set_reg(tg->hact_st, 2, m->htotal - m->hdisplay);
-       hdmi_set_reg(tg->hact_sz, 2, m->hdisplay);
-       hdmi_set_reg(tg->v_fsz, 2, m->vtotal);
+       hdmi_set_reg(tg->h_fsz, 2, m->crtc_htotal);
+       hdmi_set_reg(tg->hact_st, 2, m->crtc_htotal - m->crtc_hdisplay);
+       hdmi_set_reg(tg->hact_sz, 2, m->crtc_hdisplay);
+       hdmi_set_reg(tg->v_fsz, 2, m->crtc_vtotal);
        hdmi_set_reg(tg->vsync, 2, 0x1);
        hdmi_set_reg(tg->vsync2, 2, 0x233); /* Reset value */
        hdmi_set_reg(tg->field_chg, 2, 0x233); /* Reset value */
@@ -1873,9 +1887,11 @@ static void hdmi_v14_mode_set(struct hdmi_context *hdata,
 
        /* Workaround 4 implementation for 1440x900 resolution support */
        if (hdata->is_soc_exynos5) {
-               if (m->hdisplay == 1440 && m->vdisplay == 900 && m->clock == 106500) {
-                       hdmi_set_reg(tg->hact_st, 2, m->htotal - m->hdisplay - 0xe0);
-                       hdmi_set_reg(tg->hact_sz, 2, m->hdisplay + 0xe0);
+               if (m->crtc_hdisplay == 1440 && m->crtc_vdisplay == 900 &&
+                   m->clock == 106500) {
+                       hdmi_set_reg(tg->hact_st, 2,
+                               m->crtc_htotal - m->crtc_hdisplay - 0xe0);
+                       hdmi_set_reg(tg->hact_sz, 2, m->crtc_hdisplay + 0xe0);
                }
        }
 }
index 671057d..1fd60bd 100644 (file)
@@ -101,6 +101,16 @@ struct mixer_context {
        int                     previous_dxy;
 };
 
+struct mixer_scan_range {
+       int min_res[2], max_res[2];
+       enum exynos_mixer_mode_type mode_type;
+};
+
+struct mixer_scan_adjustment {
+       int res[2], new_res[2];
+};
+
+
 /* event flags used  */
 enum mixer_status_flags {
        MXR_EVENT_VSYNC = 1,
@@ -135,6 +145,45 @@ static const u8 filter_cr_horiz_tap4[] = {
        70,     59,     48,     37,     27,     19,     11,     5,
 };
 
+struct mixer_scan_range scan_ranges[] = {
+       {
+               .min_res = { 464, 0 },
+               .max_res = { 720, 480 },
+               .mode_type = EXYNOS_MIXER_MODE_SD_NTSC,
+       },
+       {
+               .min_res = { 464, 481 },
+               .max_res = { 720, 576 },
+               .mode_type = EXYNOS_MIXER_MODE_SD_PAL,
+       },
+       {
+               .min_res = { 1024, 0 },
+               .max_res = { 1280, 720 },
+               .mode_type = EXYNOS_MIXER_MODE_HD_720,
+       },
+       {
+               .min_res = { 1664, 0 },
+               .max_res = { 1920, 1080 },
+               .mode_type = EXYNOS_MIXER_MODE_HD_1080,
+       },
+       {
+               .min_res = { 1440, 900 },
+               .max_res = { 1440, 900 },
+               .mode_type = EXYNOS_MIXER_MODE_HD_1080,
+       },
+};
+
+struct mixer_scan_adjustment scan_adjustments[] = {
+       {
+               .res = { 1024, 768 },
+               .new_res = { 1024, 720 },
+       },
+       {
+               .res = { 1280, 800 },
+               .new_res = { 1280, 720 },
+       },
+};
+
 static void mixer_win_reset(struct mixer_context *mctx);
 
 static inline u32 vp_reg_read(struct mixer_resources *res, u32 reg_id)
@@ -179,17 +228,65 @@ static inline void mixer_reg_writemask(struct mixer_resources *res,
 
 enum exynos_mixer_mode_type exynos_mixer_get_mode_type(int width, int height)
 {
-       if (width >= 464 && width <= 720 && height <= 480)
-               return EXYNOS_MIXER_MODE_SD_NTSC;
-       else if (width >= 464 && width <= 720 && height <= 576)
-               return EXYNOS_MIXER_MODE_SD_PAL;
-       else if (width >= 1024 && width <= 1280 && height <= 720)
-               return EXYNOS_MIXER_MODE_HD_720;
-       else if ((width == 1440 && height == 900) ||
-               (width >= 1664 && width <= 1920 && height <= 1080))
-               return EXYNOS_MIXER_MODE_HD_1080;
-       else
-               return EXYNOS_MIXER_MODE_INVALID;
+       int i;
+
+       /*
+        * If the mode matches an adjustment, adjust it before finding the
+        * mode type
+        */
+       for (i = 0; i < ARRAY_SIZE(scan_adjustments); i++) {
+               struct mixer_scan_adjustment *adj = &scan_adjustments[i];
+
+               if (width == adj->res[0] && height == adj->res[1]) {
+                       width = adj->new_res[0];
+                       height = adj->new_res[1];
+               }
+       }
+
+       for (i = 0; i < ARRAY_SIZE(scan_ranges); i++) {
+               struct mixer_scan_range *range = &scan_ranges[i];
+
+               if (width >= range->min_res[0] && width <= range->max_res[0]
+                && height >= range->min_res[1] && height <= range->max_res[1])
+                       return range->mode_type;
+       }
+       return EXYNOS_MIXER_MODE_INVALID;
+}
+
+static void mixer_adjust_modes(void *ctx, struct drm_connector *connector)
+{
+       int i;
+
+       for (i = 0; i < ARRAY_SIZE(scan_adjustments); i++) {
+               struct mixer_scan_adjustment *adj = &scan_adjustments[i];
+               struct drm_display_mode *t, *mode;
+               bool native_support = false;
+
+               /*
+                * Make sure the mode resulting from the adjustment is not
+                * already natively supported. This might cause us to do
+                * something stupid like choose a chopped 1280x800 resolution
+                * over native 720p.
+                */
+               list_for_each_entry_safe(mode, t, &connector->modes, head) {
+                       if (adj->new_res[0] == mode->hdisplay &&
+                           adj->new_res[1] == mode->vdisplay) {
+                               native_support = true;
+                               break;
+                       }
+               }
+               if (native_support)
+                       continue;
+
+               list_for_each_entry_safe(mode, t, &connector->modes, head) {
+                       if (adj->res[0] == mode->hdisplay &&
+                           adj->res[1] == mode->vdisplay) {
+                               mode->hdisplay = adj->new_res[0];
+                               mode->vdisplay = adj->new_res[1];
+                               break;
+                       }
+               }
+       }
 }
 
 static void mixer_regs_dump(struct mixer_context *mctx)
@@ -1089,6 +1186,7 @@ static int mixer_subdrv_probe(void *ctx, struct drm_device *drm_dev)
 
 static struct exynos_controller_ops mixer_ops = {
        /* manager */
+       .adjust_modes           = mixer_adjust_modes,
        .subdrv_probe           = mixer_subdrv_probe,
        .enable_vblank          = mixer_enable_vblank,
        .disable_vblank         = mixer_disable_vblank,