diff options
52 files changed, 2267 insertions, 1426 deletions
diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c index 5dda07cf709..fadcd44ff19 100644 --- a/drivers/gpu/drm/drm_edid.c +++ b/drivers/gpu/drm/drm_edid.c @@ -395,13 +395,14 @@ out: * \param adapter : i2c device adaptor * \return 1 on success */ -static bool +bool drm_probe_ddc(struct i2c_adapter *adapter) { unsigned char out; return (drm_do_probe_ddc_edid(adapter, &out, 0, 1) == 0); } +EXPORT_SYMBOL(drm_probe_ddc); /** * drm_get_edid - get EDID data, if available diff --git a/drivers/gpu/drm/exynos/exynos_ddc.c b/drivers/gpu/drm/exynos/exynos_ddc.c index 961a1806a24..37e6ec704e1 100644 --- a/drivers/gpu/drm/exynos/exynos_ddc.c +++ b/drivers/gpu/drm/exynos/exynos_ddc.c @@ -26,29 +26,41 @@ static int s5p_ddc_probe(struct i2c_client *client, { hdmi_attach_ddc_client(client); - dev_info(&client->adapter->dev, "attached s5p_ddc " - "into i2c adapter successfully\n"); + dev_info(&client->adapter->dev, + "attached %s into i2c adapter successfully\n", + client->name); return 0; } static int s5p_ddc_remove(struct i2c_client *client) { - dev_info(&client->adapter->dev, "detached s5p_ddc " - "from i2c adapter successfully\n"); + dev_info(&client->adapter->dev, + "detached %s from i2c adapter successfully\n", + client->name); return 0; } static struct i2c_device_id ddc_idtable[] = { {"s5p_ddc", 0}, + {"exynos5-hdmiddc", 0}, { }, }; +static struct of_device_id hdmiddc_match_types[] = { + { + .compatible = "samsung,exynos5-hdmiddc", + }, { + /* end node */ + } +}; + struct i2c_driver ddc_driver = { .driver = { - .name = "s5p_ddc", + .name = "exynos-hdmiddc", .owner = THIS_MODULE, + .of_match_table = hdmiddc_match_types, }, .id_table = ddc_idtable, .probe = s5p_ddc_probe, diff --git a/drivers/gpu/drm/exynos/exynos_drm_connector.c b/drivers/gpu/drm/exynos/exynos_drm_connector.c index c2b1b1441ed..18c271862ca 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_connector.c +++ b/drivers/gpu/drm/exynos/exynos_drm_connector.c @@ -40,6 +40,7 @@ struct exynos_drm_connector { struct drm_connector drm_connector; uint32_t encoder_id; struct exynos_drm_manager *manager; + uint32_t dpms; }; /* convert exynos_video_timings to drm_display_mode */ @@ -149,8 +150,12 @@ static int exynos_drm_connector_get_modes(struct drm_connector *connector) count = drm_add_edid_modes(connector, edid); kfree(edid); } else { - struct drm_display_mode *mode = drm_mode_create(connector->dev); struct exynos_drm_panel_info *panel; + struct drm_display_mode *mode = drm_mode_create(connector->dev); + if (!mode) { + DRM_ERROR("failed to create a new display mode.\n"); + return 0; + } if (display_ops->get_panel) panel = display_ops->get_panel(manager->dev); @@ -194,8 +199,7 @@ static int exynos_drm_connector_mode_valid(struct drm_connector *connector, return ret; } -static struct drm_encoder *exynos_drm_best_encoder( - struct drm_connector *connector) +struct drm_encoder *exynos_drm_best_encoder(struct drm_connector *connector) { struct drm_device *dev = connector->dev; struct exynos_drm_connector *exynos_connector = @@ -224,6 +228,43 @@ static struct drm_connector_helper_funcs exynos_connector_helper_funcs = { .best_encoder = exynos_drm_best_encoder, }; +void exynos_drm_display_power(struct drm_connector *connector, int mode) +{ + struct drm_encoder *encoder = exynos_drm_best_encoder(connector); + struct exynos_drm_connector *exynos_connector; + struct exynos_drm_manager *manager = exynos_drm_get_manager(encoder); + struct exynos_drm_display_ops *display_ops = manager->display_ops; + + exynos_connector = to_exynos_connector(connector); + + if (exynos_connector->dpms == mode) { + DRM_DEBUG_KMS("desired dpms mode is same as previous one.\n"); + return; + } + + if (display_ops && display_ops->power_on) + display_ops->power_on(manager->dev, mode); + + exynos_connector->dpms = mode; +} + +static void exynos_drm_connector_dpms(struct drm_connector *connector, + int mode) +{ + DRM_DEBUG_KMS("%s\n", __FILE__); + + /* + * in case that drm_crtc_helper_set_mode() is called, + * encoder/crtc->funcs->dpms() will be just returned + * because they already were DRM_MODE_DPMS_ON so only + * exynos_drm_display_power() will be called. + */ + drm_helper_connector_dpms(connector, mode); + + exynos_drm_display_power(connector, mode); + +} + static int exynos_drm_connector_fill_modes(struct drm_connector *connector, unsigned int max_width, unsigned int max_height) { @@ -283,7 +324,7 @@ static void exynos_drm_connector_destroy(struct drm_connector *connector) } static struct drm_connector_funcs exynos_connector_funcs = { - .dpms = drm_helper_connector_dpms, + .dpms = exynos_drm_connector_dpms, .fill_modes = exynos_drm_connector_fill_modes, .detect = exynos_drm_connector_detect, .destroy = exynos_drm_connector_destroy, @@ -332,6 +373,7 @@ struct drm_connector *exynos_drm_connector_create(struct drm_device *dev, exynos_connector->encoder_id = encoder->base.id; exynos_connector->manager = manager; + exynos_connector->dpms = DRM_MODE_DPMS_OFF; connector->encoder = encoder; err = drm_mode_connector_attach_encoder(connector, encoder); diff --git a/drivers/gpu/drm/exynos/exynos_drm_connector.h b/drivers/gpu/drm/exynos/exynos_drm_connector.h index 1c7b2b5b579..22f6cc442c3 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_connector.h +++ b/drivers/gpu/drm/exynos/exynos_drm_connector.h @@ -31,4 +31,8 @@ struct drm_connector *exynos_drm_connector_create(struct drm_device *dev, struct drm_encoder *encoder); +struct drm_encoder *exynos_drm_best_encoder(struct drm_connector *connector); + +void exynos_drm_display_power(struct drm_connector *connector, int mode); + #endif diff --git a/drivers/gpu/drm/exynos/exynos_drm_core.c b/drivers/gpu/drm/exynos/exynos_drm_core.c index 19bdf0a194e..94026ad76a7 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_core.c +++ b/drivers/gpu/drm/exynos/exynos_drm_core.c @@ -34,33 +34,15 @@ static LIST_HEAD(exynos_drm_subdrv_list); -static int exynos_drm_subdrv_probe(struct drm_device *dev, +static int exynos_drm_create_enc_conn(struct drm_device *dev, struct exynos_drm_subdrv *subdrv) { struct drm_encoder *encoder; struct drm_connector *connector; + int ret; DRM_DEBUG_DRIVER("%s\n", __FILE__); - if (subdrv->probe) { - int ret; - - /* - * this probe callback would be called by sub driver - * after setting of all resources to this sub driver, - * such as clock, irq and register map are done or by load() - * of exynos drm driver. - * - * P.S. note that this driver is considered for modularization. - */ - ret = subdrv->probe(dev, subdrv->dev); - if (ret) - return ret; - } - - if (!subdrv->manager) - return 0; - subdrv->manager->dev = subdrv->dev; /* create and initialize a encoder for this sub driver. */ @@ -78,24 +60,22 @@ static int exynos_drm_subdrv_probe(struct drm_device *dev, connector = exynos_drm_connector_create(dev, encoder); if (!connector) { DRM_ERROR("failed to create connector\n"); - encoder->funcs->destroy(encoder); - return -EFAULT; + ret = -EFAULT; + goto err_destroy_encoder; } subdrv->encoder = encoder; subdrv->connector = connector; return 0; + +err_destroy_encoder: + encoder->funcs->destroy(encoder); + return ret; } -static void exynos_drm_subdrv_remove(struct drm_device *dev, - struct exynos_drm_subdrv *subdrv) +static void exynos_drm_destroy_enc_conn(struct exynos_drm_subdrv *subdrv) { - DRM_DEBUG_DRIVER("%s\n", __FILE__); - - if (subdrv->remove) - subdrv->remove(dev); - if (subdrv->encoder) { struct drm_encoder *encoder = subdrv->encoder; encoder->funcs->destroy(encoder); @@ -109,9 +89,43 @@ static void exynos_drm_subdrv_remove(struct drm_device *dev, } } +static int exynos_drm_subdrv_probe(struct drm_device *dev, + struct exynos_drm_subdrv *subdrv) +{ + if (subdrv->probe) { + int ret; + + subdrv->drm_dev = dev; + + /* + * this probe callback would be called by sub driver + * after setting of all resources to this sub driver, + * such as clock, irq and register map are done or by load() + * of exynos drm driver. + * + * P.S. note that this driver is considered for modularization. + */ + ret = subdrv->probe(dev, subdrv->dev); + if (ret) + return ret; + } + + return 0; +} + +static void exynos_drm_subdrv_remove(struct drm_device *dev, + struct exynos_drm_subdrv *subdrv) +{ + DRM_DEBUG_DRIVER("%s\n", __FILE__); + + if (subdrv->remove) + subdrv->remove(dev, subdrv->dev); +} + int exynos_drm_device_register(struct drm_device *dev) { struct exynos_drm_subdrv *subdrv, *n; + unsigned int fine_cnt = 0; int err; DRM_DEBUG_DRIVER("%s\n", __FILE__); @@ -120,14 +134,36 @@ int exynos_drm_device_register(struct drm_device *dev) return -EINVAL; list_for_each_entry_safe(subdrv, n, &exynos_drm_subdrv_list, list) { - subdrv->drm_dev = dev; err = exynos_drm_subdrv_probe(dev, subdrv); if (err) { DRM_DEBUG("exynos drm subdrv probe failed.\n"); list_del(&subdrv->list); + continue; + } + + /* + * if manager is null then it means that this sub driver + * doesn't need encoder and connector. + */ + if (!subdrv->manager) { + fine_cnt++; + continue; + } + + err = exynos_drm_create_enc_conn(dev, subdrv); + if (err) { + DRM_DEBUG("failed to create encoder and connector.\n"); + exynos_drm_subdrv_remove(dev, subdrv); + list_del(&subdrv->list); + continue; } + + fine_cnt++; } + if (!fine_cnt) + return -EINVAL; + return 0; } EXPORT_SYMBOL_GPL(exynos_drm_device_register); @@ -143,8 +179,10 @@ int exynos_drm_device_unregister(struct drm_device *dev) return -EINVAL; } - list_for_each_entry(subdrv, &exynos_drm_subdrv_list, list) + list_for_each_entry(subdrv, &exynos_drm_subdrv_list, list) { exynos_drm_subdrv_remove(dev, subdrv); + exynos_drm_destroy_enc_conn(subdrv); + } return 0; } diff --git a/drivers/gpu/drm/exynos/exynos_drm_crtc.c b/drivers/gpu/drm/exynos/exynos_drm_crtc.c index df1e34f0f09..fce245f64c4 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_crtc.c +++ b/drivers/gpu/drm/exynos/exynos_drm_crtc.c @@ -66,7 +66,6 @@ struct exynos_drm_crtc { static void exynos_drm_crtc_dpms(struct drm_crtc *crtc, int mode) { - struct drm_device *dev = crtc->dev; struct exynos_drm_crtc *exynos_crtc = to_exynos_crtc(crtc); DRM_DEBUG_KMS("crtc[%d] mode[%d]\n", crtc->base.id, mode); @@ -76,12 +75,8 @@ static void exynos_drm_crtc_dpms(struct drm_crtc *crtc, int mode) return; } - mutex_lock(&dev->struct_mutex); - exynos_drm_fn_encoder(crtc, &mode, exynos_drm_encoder_crtc_dpms); exynos_crtc->dpms = mode; - - mutex_unlock(&dev->struct_mutex); } static void exynos_drm_crtc_prepare(struct drm_crtc *crtc) @@ -97,6 +92,7 @@ static void exynos_drm_crtc_commit(struct drm_crtc *crtc) DRM_DEBUG_KMS("%s\n", __FILE__); + exynos_drm_crtc_dpms(crtc, DRM_MODE_DPMS_ON); exynos_plane_commit(exynos_crtc->plane); exynos_plane_dpms(exynos_crtc->plane, DRM_MODE_DPMS_ON); } @@ -126,8 +122,6 @@ exynos_drm_crtc_mode_set(struct drm_crtc *crtc, struct drm_display_mode *mode, DRM_DEBUG_KMS("%s\n", __FILE__); - exynos_drm_crtc_dpms(crtc, DRM_MODE_DPMS_ON); - /* * copy the mode data adjusted by mode_fixup() into crtc->mode * so that hardware can be seet to proper mode. @@ -161,6 +155,12 @@ static int exynos_drm_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y, DRM_DEBUG_KMS("%s\n", __FILE__); + /* when framebuffer changing is requested, crtc's dpms should be on */ + if (exynos_crtc->dpms > DRM_MODE_DPMS_ON) { + DRM_ERROR("failed framebuffer changing request.\n"); + return -EPERM; + } + crtc_w = crtc->fb->width - x; crtc_h = crtc->fb->height - y; @@ -213,6 +213,12 @@ static int exynos_drm_crtc_page_flip(struct drm_crtc *crtc, DRM_DEBUG_KMS("%s\n", __FILE__); + /* when the page flip is requested, crtc's dpms should be on */ + if (exynos_crtc->dpms > DRM_MODE_DPMS_ON) { + DRM_ERROR("failed page flip request.\n"); + return -EINVAL; + } + mutex_lock(&dev->struct_mutex); if (event) { diff --git a/drivers/gpu/drm/exynos/exynos_drm_drv.h b/drivers/gpu/drm/exynos/exynos_drm_drv.h index a4ab98b52dd..a3423103649 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_drv.h +++ b/drivers/gpu/drm/exynos/exynos_drm_drv.h @@ -36,6 +36,20 @@ #define MAX_FB_BUFFER 4 #define DEFAULT_ZPOS -1 +#define _wait_for(COND, MS) ({ \ + unsigned long timeout__ = jiffies + msecs_to_jiffies(MS); \ + int ret__ = 0; \ + while (!(COND)) { \ + if (time_after(jiffies, timeout__)) { \ + ret__ = -ETIMEDOUT; \ + break; \ + } \ + } \ + ret__; \ +}) + +#define wait_for(COND, MS) _wait_for(COND, MS) + struct drm_device; struct exynos_drm_overlay; struct drm_connector; @@ -60,6 +74,8 @@ enum exynos_drm_output_type { * @commit: apply hardware specific overlay data to registers. * @enable: enable hardware specific overlay. * @disable: disable hardware specific overlay. + * @wait_for_vblank: wait for vblank interrupt to make sure that + * hardware overlay is disabled. */ struct exynos_drm_overlay_ops { void (*mode_set)(struct device *subdrv_dev, @@ -67,6 +83,7 @@ struct exynos_drm_overlay_ops { void (*commit)(struct device *subdrv_dev, int zpos); void (*enable)(struct device *subdrv_dev, int zpos); void (*disable)(struct device *subdrv_dev, int zpos); + void (*wait_for_vblank)(struct device *subdrv_dev); }; /* @@ -265,7 +282,7 @@ struct exynos_drm_subdrv { struct exynos_drm_manager *manager; int (*probe)(struct drm_device *drm_dev, struct device *dev); - void (*remove)(struct drm_device *dev); + void (*remove)(struct drm_device *drm_dev, struct device *dev); int (*open)(struct drm_device *drm_dev, struct device *dev, struct drm_file *file); void (*close)(struct drm_device *drm_dev, struct device *dev, diff --git a/drivers/gpu/drm/exynos/exynos_drm_encoder.c b/drivers/gpu/drm/exynos/exynos_drm_encoder.c index 39bd8abff3f..e51503fbaf2 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_encoder.c +++ b/drivers/gpu/drm/exynos/exynos_drm_encoder.c @@ -31,6 +31,7 @@ #include "exynos_drm_drv.h" #include "exynos_drm_encoder.h" +#include "exynos_drm_connector.h" #define to_exynos_encoder(x) container_of(x, struct exynos_drm_encoder,\ drm_encoder) @@ -44,26 +45,23 @@ * @dpms: store the encoder dpms value. */ struct exynos_drm_encoder { + struct drm_crtc *old_crtc; struct drm_encoder drm_encoder; struct exynos_drm_manager *manager; int dpms; }; -static void exynos_drm_display_power(struct drm_encoder *encoder, int mode) +static void exynos_drm_connector_power(struct drm_encoder *encoder, int mode) { struct drm_device *dev = encoder->dev; struct drm_connector *connector; - struct exynos_drm_manager *manager = exynos_drm_get_manager(encoder); list_for_each_entry(connector, &dev->mode_config.connector_list, head) { - if (connector->encoder == encoder) { - struct exynos_drm_display_ops *display_ops = - manager->display_ops; - + if (exynos_drm_best_encoder(connector) == encoder) { DRM_DEBUG_KMS("connector[%d] dpms[%d]\n", connector->base.id, mode); - if (display_ops && display_ops->power_on) - display_ops->power_on(manager->dev, mode); + + exynos_drm_display_power(connector, mode); } } } @@ -88,13 +86,13 @@ static void exynos_drm_encoder_dpms(struct drm_encoder *encoder, int mode) case DRM_MODE_DPMS_ON: if (manager_ops && manager_ops->apply) manager_ops->apply(manager->dev); - exynos_drm_display_power(encoder, mode); + exynos_drm_connector_power(encoder, mode); exynos_encoder->dpms = mode; break; case DRM_MODE_DPMS_STANDBY: case DRM_MODE_DPMS_SUSPEND: case DRM_MODE_DPMS_OFF: - exynos_drm_display_power(encoder, mode); + exynos_drm_connector_power(encoder, mode); exynos_encoder->dpms = mode; break; default: @@ -127,24 +125,74 @@ exynos_drm_encoder_mode_fixup(struct drm_encoder *encoder, return true; } +static void disable_plane_to_crtc(struct drm_device *dev, + struct drm_crtc *old_crtc, + struct drm_crtc *new_crtc) +{ + struct drm_plane *plane; + + /* + * if old_crtc isn't same as encoder->crtc then it means that + * user changed crtc id to another one so the plane to old_crtc + * should be disabled and plane->crtc should be set to new_crtc + * (encoder->crtc) + */ + list_for_each_entry(plane, &dev->mode_config.plane_list, head) { + if (plane->crtc == old_crtc) { + /* + * do not change below call order. + * + * plane->funcs->disable_plane call checks + * if encoder->crtc is same as plane->crtc and if same + * then overlay_ops->disable callback will be called + * to diasble current hw overlay so plane->crtc should + * have new_crtc because new_crtc was set to + * encoder->crtc in advance. + */ + plane->crtc = new_crtc; + plane->funcs->disable_plane(plane); + } + } +} + static void exynos_drm_encoder_mode_set(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; + struct exynos_drm_manager *manager; + struct exynos_drm_manager_ops *manager_ops; DRM_DEBUG_KMS("%s\n", __FILE__); - exynos_drm_encoder_dpms(encoder, DRM_MODE_DPMS_ON); - list_for_each_entry(connector, &dev->mode_config.connector_list, head) { - if (connector->encoder == encoder) + if (connector->encoder == encoder) { + struct exynos_drm_encoder *exynos_encoder; + + exynos_encoder = to_exynos_encoder(encoder); + + if (exynos_encoder->old_crtc != encoder->crtc && + exynos_encoder->old_crtc) { + + /* + * disable a plane to old crtc and change + * crtc of the plane to new one. + */ + disable_plane_to_crtc(dev, + exynos_encoder->old_crtc, + encoder->crtc); + } + + manager = exynos_drm_get_manager(encoder); + manager_ops = manager->ops; + if (manager_ops && manager_ops->mode_set) manager_ops->mode_set(manager->dev, adjusted_mode); + + exynos_encoder->old_crtc = encoder->crtc; + } } } @@ -166,12 +214,27 @@ static void exynos_drm_encoder_commit(struct drm_encoder *encoder) manager_ops->commit(manager->dev); } +static void exynos_drm_encoder_disable(struct drm_encoder *encoder) +{ + struct drm_plane *plane; + struct drm_device *dev = encoder->dev; + + exynos_drm_encoder_dpms(encoder, DRM_MODE_DPMS_OFF); + + /* all planes connected to this encoder should be also disabled. */ + list_for_each_entry(plane, &dev->mode_config.plane_list, head) { + if (plane->crtc == encoder->crtc) + plane->funcs->disable_plane(plane); + } +} + static struct drm_encoder_helper_funcs exynos_encoder_helper_funcs = { .dpms = exynos_drm_encoder_dpms, .mode_fixup = exynos_drm_encoder_mode_fixup, .mode_set = exynos_drm_encoder_mode_set, .prepare = exynos_drm_encoder_prepare, .commit = exynos_drm_encoder_commit, + .disable = exynos_drm_encoder_disable, }; static void exynos_drm_encoder_destroy(struct drm_encoder *encoder) @@ -338,6 +401,19 @@ void exynos_drm_encoder_crtc_dpms(struct drm_encoder *encoder, void *data) manager_ops->dpms(manager->dev, mode); /* + * set current mode to new one so that data aren't updated into + * registers by drm_helper_connector_dpms two times. + * + * in case that drm_crtc_helper_set_mode() is called, + * overlay_ops->commit() and manager_ops->commit() callbacks + * can be called two times, first at drm_crtc_helper_set_mode() + * and second at drm_helper_connector_dpms(). + * so with this setting, when drm_helper_connector_dpms() is called + * encoder->funcs->dpms() will be ignored. + */ + exynos_encoder->dpms = mode; + + /* * if this condition is ok then it means that the crtc is already * detached from encoder and last function for detaching is properly * done, so clear pipe from manager to prevent repeated call. @@ -422,4 +498,14 @@ void exynos_drm_encoder_plane_disable(struct drm_encoder *encoder, void *data) if (overlay_ops && overlay_ops->disable) overlay_ops->disable(manager->dev, zpos); + + /* + * wait for vblank interrupt + * - this makes sure that hardware overlay is disabled to avoid + * for the dma accesses to memory after gem buffer was released + * because the setting for disabling the overlay will be updated + * at vsync. + */ + if (overlay_ops->wait_for_vblank) + overlay_ops->wait_for_vblank(manager->dev); } diff --git a/drivers/gpu/drm/exynos/exynos_drm_fb.c b/drivers/gpu/drm/exynos/exynos_drm_fb.c index 53afcc5f094..4ef4cd3f993 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_fb.c +++ b/ |