diff --git a/drivers/gpu/drm/exynos/exynos_drm_connector.c b/drivers/gpu/drm/exynos/exynos_drm_connector.c index 618bd4d87d28..ebdd71d50685 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_connector.c +++ b/drivers/gpu/drm/exynos/exynos_drm_connector.c @@ -225,6 +225,29 @@ static struct drm_connector_helper_funcs exynos_connector_helper_funcs = { .best_encoder = exynos_drm_best_encoder, }; +static int exynos_drm_connector_fill_modes(struct drm_connector *connector, + unsigned int max_width, unsigned int max_height) +{ + struct exynos_drm_connector *exynos_connector = + to_exynos_connector(connector); + struct exynos_drm_manager *manager = exynos_connector->manager; + struct exynos_drm_manager_ops *ops = manager->ops; + unsigned int width, height; + + width = max_width; + height = max_height; + + /* + * if specific driver want to find desired_mode using maxmum + * resolution then get max width and height from that driver. + */ + if (ops && ops->get_max_resol) + ops->get_max_resol(manager->dev, &width, &height); + + return drm_helper_probe_single_connector_modes(connector, width, + height); +} + /* get detection status of display device. */ static enum drm_connector_status exynos_drm_connector_detect(struct drm_connector *connector, bool force) @@ -262,7 +285,7 @@ static void exynos_drm_connector_destroy(struct drm_connector *connector) static struct drm_connector_funcs exynos_connector_funcs = { .dpms = drm_helper_connector_dpms, - .fill_modes = drm_helper_probe_single_connector_modes, + .fill_modes = exynos_drm_connector_fill_modes, .detect = exynos_drm_connector_detect, .destroy = exynos_drm_connector_destroy, }; diff --git a/drivers/gpu/drm/exynos/exynos_drm_crtc.c b/drivers/gpu/drm/exynos/exynos_drm_crtc.c index de818831a511..2d9a0e630d7f 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_crtc.c +++ b/drivers/gpu/drm/exynos/exynos_drm_crtc.c @@ -249,7 +249,11 @@ exynos_drm_crtc_mode_set(struct drm_crtc *crtc, struct drm_display_mode *mode, { DRM_DEBUG_KMS("%s\n", __FILE__); - mode = adjusted_mode; + /* + * copy the mode data adjusted by mode_fixup() into crtc->mode + * so that hardware can be seet to proper mode. + */ + memcpy(&crtc->mode, adjusted_mode, sizeof(*adjusted_mode)); return exynos_drm_crtc_update(crtc); } diff --git a/drivers/gpu/drm/exynos/exynos_drm_drv.h b/drivers/gpu/drm/exynos/exynos_drm_drv.h index 13540de90bfc..4115a9f61d21 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_drv.h +++ b/drivers/gpu/drm/exynos/exynos_drm_drv.h @@ -155,8 +155,10 @@ struct exynos_drm_display_ops { * * @dpms: control device power. * @apply: set timing, vblank and overlay data to registers. + * @mode_fixup: fix mode data comparing to hw specific display mode. * @mode_set: convert drm_display_mode to hw specific display mode and * would be called by encoder->mode_set(). + * @get_max_resol: get maximum resolution to specific hardware. * @commit: set current hw specific display mode to hw. * @enable_vblank: specific driver callback for enabling vblank interrupt. * @disable_vblank: specific driver callback for disabling vblank interrupt. @@ -164,7 +166,13 @@ struct exynos_drm_display_ops { struct exynos_drm_manager_ops { void (*dpms)(struct device *subdrv_dev, int mode); void (*apply)(struct device *subdrv_dev); + void (*mode_fixup)(struct device *subdrv_dev, + struct drm_connector *connector, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode); void (*mode_set)(struct device *subdrv_dev, void *mode); + void (*get_max_resol)(struct device *subdrv_dev, unsigned int *width, + unsigned int *height); void (*commit)(struct device *subdrv_dev); int (*enable_vblank)(struct device *subdrv_dev); void (*disable_vblank)(struct device *subdrv_dev); diff --git a/drivers/gpu/drm/exynos/exynos_drm_encoder.c b/drivers/gpu/drm/exynos/exynos_drm_encoder.c index ef4754f1519b..45ca732858fb 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_encoder.c +++ b/drivers/gpu/drm/exynos/exynos_drm_encoder.c @@ -111,9 +111,19 @@ exynos_drm_encoder_mode_fixup(struct drm_encoder *encoder, struct drm_display_mode *mode, struct drm_display_mode *adjusted_mode) { + struct drm_device *dev = encoder->dev; + struct drm_connector *connector; + struct exynos_drm_manager *manager = exynos_drm_get_manager(encoder); + struct exynos_drm_manager_ops *manager_ops = manager->ops; + DRM_DEBUG_KMS("%s\n", __FILE__); - /* drm framework doesn't check NULL. */ + list_for_each_entry(connector, &dev->mode_config.connector_list, head) { + if (connector->encoder == encoder) + if (manager_ops && manager_ops->mode_fixup) + manager_ops->mode_fixup(manager->dev, connector, + mode, adjusted_mode); + } return true; } @@ -132,12 +142,11 @@ static void exynos_drm_encoder_mode_set(struct drm_encoder *encoder, DRM_DEBUG_KMS("%s\n", __FILE__); - mode = adjusted_mode; - list_for_each_entry(connector, &dev->mode_config.connector_list, head) { if (connector->encoder == encoder) { if (manager_ops && manager_ops->mode_set) - manager_ops->mode_set(manager->dev, mode); + manager_ops->mode_set(manager->dev, + adjusted_mode); if (overlay_ops && overlay_ops->mode_set) overlay_ops->mode_set(manager->dev, overlay); diff --git a/drivers/gpu/drm/exynos/exynos_drm_hdmi.c b/drivers/gpu/drm/exynos/exynos_drm_hdmi.c index ed8a319ed84b..ed86bddf81da 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_hdmi.c +++ b/drivers/gpu/drm/exynos/exynos_drm_hdmi.c @@ -155,6 +155,20 @@ static void drm_hdmi_disable_vblank(struct device *subdrv_dev) return hdmi_overlay_ops->disable_vblank(ctx->mixer_ctx->ctx); } +static void drm_hdmi_mode_fixup(struct device *subdrv_dev, + struct drm_connector *connector, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + struct drm_hdmi_context *ctx = to_context(subdrv_dev); + + DRM_DEBUG_KMS("%s\n", __FILE__); + + if (hdmi_manager_ops && hdmi_manager_ops->mode_fixup) + hdmi_manager_ops->mode_fixup(ctx->hdmi_ctx->ctx, connector, + mode, adjusted_mode); +} + static void drm_hdmi_mode_set(struct device *subdrv_dev, void *mode) { struct drm_hdmi_context *ctx = to_context(subdrv_dev); @@ -165,6 +179,18 @@ static void drm_hdmi_mode_set(struct device *subdrv_dev, void *mode) hdmi_manager_ops->mode_set(ctx->hdmi_ctx->ctx, mode); } +static void drm_hdmi_get_max_resol(struct device *subdrv_dev, + unsigned int *width, unsigned int *height) +{ + struct drm_hdmi_context *ctx = to_context(subdrv_dev); + + DRM_DEBUG_KMS("%s\n", __FILE__); + + if (hdmi_manager_ops && hdmi_manager_ops->get_max_resol) + hdmi_manager_ops->get_max_resol(ctx->hdmi_ctx->ctx, width, + height); +} + static void drm_hdmi_commit(struct device *subdrv_dev) { struct drm_hdmi_context *ctx = to_context(subdrv_dev); @@ -200,7 +226,9 @@ static struct exynos_drm_manager_ops drm_hdmi_manager_ops = { .dpms = drm_hdmi_dpms, .enable_vblank = drm_hdmi_enable_vblank, .disable_vblank = drm_hdmi_disable_vblank, + .mode_fixup = drm_hdmi_mode_fixup, .mode_set = drm_hdmi_mode_set, + .get_max_resol = drm_hdmi_get_max_resol, .commit = drm_hdmi_commit, }; diff --git a/drivers/gpu/drm/exynos/exynos_drm_hdmi.h b/drivers/gpu/drm/exynos/exynos_drm_hdmi.h index 3c29f790ee45..44497cfb6c74 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_hdmi.h +++ b/drivers/gpu/drm/exynos/exynos_drm_hdmi.h @@ -47,7 +47,12 @@ struct exynos_hdmi_display_ops { }; struct exynos_hdmi_manager_ops { + void (*mode_fixup)(void *ctx, struct drm_connector *connector, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode); void (*mode_set)(void *ctx, void *mode); + void (*get_max_resol)(void *ctx, unsigned int *width, + unsigned int *height); void (*commit)(void *ctx); void (*disable)(void *ctx); }; diff --git a/drivers/gpu/drm/exynos/exynos_hdmi.c b/drivers/gpu/drm/exynos/exynos_hdmi.c index 1cfe86e5d10e..6fe1e8993c92 100644 --- a/drivers/gpu/drm/exynos/exynos_hdmi.c +++ b/drivers/gpu/drm/exynos/exynos_hdmi.c @@ -41,6 +41,8 @@ #include "exynos_hdmi.h" #define HDMI_OVERLAY_NUMBER 3 +#define MAX_WIDTH 1920 +#define MAX_HEIGHT 1080 #define get_hdmi_context(dev) platform_get_drvdata(to_platform_device(dev)) /* HDMI Version 1.3 */ @@ -1126,7 +1128,7 @@ static int hdmi_v13_conf_index(struct drm_display_mode *mode) true : false)) return i; - return -1; + return -EINVAL; } static int hdmi_v14_conf_index(struct drm_display_mode *mode) @@ -1142,7 +1144,7 @@ static int hdmi_v14_conf_index(struct drm_display_mode *mode) true : false)) return i; - return -1; + return -EINVAL; } static int hdmi_conf_index(struct hdmi_context *hdata, @@ -1150,8 +1152,8 @@ static int hdmi_conf_index(struct hdmi_context *hdata, { if (hdata->is_v13) return hdmi_v13_conf_index(mode); - else - return hdmi_v14_conf_index(mode); + + return hdmi_v14_conf_index(mode); } static bool hdmi_is_connected(void *ctx) @@ -1193,6 +1195,11 @@ static int hdmi_v13_check_timing(struct fb_videomode *check_timing) { int i; + DRM_DEBUG_KMS("valid mode : xres=%d, yres=%d, refresh=%d, intl=%d\n", + check_timing->xres, check_timing->yres, + check_timing->refresh, (check_timing->vmode & + FB_VMODE_INTERLACED) ? true : false); + for (i = 0; i < ARRAY_SIZE(hdmi_v13_confs); ++i) if (hdmi_v13_confs[i].width == check_timing->xres && hdmi_v13_confs[i].height == check_timing->yres && @@ -1200,7 +1207,9 @@ static int hdmi_v13_check_timing(struct fb_videomode *check_timing) hdmi_v13_confs[i].interlace == ((check_timing->vmode & FB_VMODE_INTERLACED) ? true : false)) - return 0; + return 0; + + /* TODO */ return -EINVAL; } @@ -1209,14 +1218,21 @@ static int hdmi_v14_check_timing(struct fb_videomode *check_timing) { int i; - for (i = 0; i < ARRAY_SIZE(hdmi_confs); ++i) + DRM_DEBUG_KMS("valid mode : xres=%d, yres=%d, refresh=%d, intl=%d\n", + check_timing->xres, check_timing->yres, + check_timing->refresh, (check_timing->vmode & + FB_VMODE_INTERLACED) ? true : false); + + for (i = 0; i < ARRAY_SIZE(hdmi_confs); i++) if (hdmi_confs[i].width == check_timing->xres && hdmi_confs[i].height == check_timing->yres && hdmi_confs[i].vrefresh == check_timing->refresh && hdmi_confs[i].interlace == ((check_timing->vmode & FB_VMODE_INTERLACED) ? true : false)) - return 0; + return 0; + + /* TODO */ return -EINVAL; } @@ -1692,6 +1708,46 @@ static void hdmi_conf_apply(struct hdmi_context *hdata) hdmi_regs_dump(hdata, "start"); } +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 hdmi_context *hdata = (struct hdmi_context *)ctx; + int index; + + DRM_DEBUG_KMS("[%d] %s\n", __LINE__, __func__); + + drm_mode_set_crtcinfo(adjusted_mode, 0); + + if (hdata->is_v13) + index = hdmi_v13_conf_index(adjusted_mode); + else + index = hdmi_v14_conf_index(adjusted_mode); + + /* just return if user desired mode exists. */ + if (index >= 0) + return; + + /* + * otherwise, find the most suitable mode among modes and change it + * to adjusted_mode. + */ + list_for_each_entry(m, &connector->modes, head) { + if (hdata->is_v13) + index = hdmi_v13_conf_index(m); + else + index = hdmi_v14_conf_index(m); + + if (index >= 0) { + DRM_INFO("desired mode doesn't exist so\n"); + DRM_INFO("use the most suitable mode among modes.\n"); + memcpy(adjusted_mode, m, sizeof(*m)); + break; + } + } +} + static void hdmi_mode_set(void *ctx, void *mode) { struct hdmi_context *hdata = (struct hdmi_context *)ctx; @@ -1706,6 +1762,15 @@ static void hdmi_mode_set(void *ctx, void *mode) DRM_DEBUG_KMS("not supported mode\n"); } +static void hdmi_get_max_resol(void *ctx, unsigned int *width, + unsigned int *height) +{ + DRM_DEBUG_KMS("[%d] %s\n", __LINE__, __func__); + + *width = MAX_WIDTH; + *height = MAX_HEIGHT; +} + static void hdmi_commit(void *ctx) { struct hdmi_context *hdata = (struct hdmi_context *)ctx; @@ -1730,7 +1795,9 @@ static void hdmi_disable(void *ctx) } static struct exynos_hdmi_manager_ops manager_ops = { + .mode_fixup = hdmi_mode_fixup, .mode_set = hdmi_mode_set, + .get_max_resol = hdmi_get_max_resol, .commit = hdmi_commit, .disable = hdmi_disable, };