diff options
Diffstat (limited to 'drivers/gpu/drm/exynos/exynos_hdmi.c')
-rw-r--r-- | drivers/gpu/drm/exynos/exynos_hdmi.c | 208 |
1 files changed, 204 insertions, 4 deletions
diff --git a/drivers/gpu/drm/exynos/exynos_hdmi.c b/drivers/gpu/drm/exynos/exynos_hdmi.c index 0c44cb7..48d7f98 100644 --- a/drivers/gpu/drm/exynos/exynos_hdmi.c +++ b/drivers/gpu/drm/exynos/exynos_hdmi.c @@ -37,6 +37,7 @@ #include "exynos_drm_drv.h" #include "exynos_drm_hdmi.h" +#include "exynos_drm_iommu.h" #include "exynos_hdmi.h" @@ -61,6 +62,7 @@ struct hdmi_context { bool powered; bool is_v13; bool dvi_mode; + bool iommu_on; struct mutex hdmi_mutex; struct resource *regs_res; @@ -927,8 +929,6 @@ static const struct hdmi_conf hdmi_confs[] = { { 720, 480, 60, false, hdmiphy_conf27_027, &hdmi_conf_480p60 }, { 1280, 720, 50, false, hdmiphy_conf74_25, &hdmi_conf_720p50 }, { 1280, 720, 60, false, hdmiphy_conf74_25, &hdmi_conf_720p60 }, - { 1920, 1080, 50, true, hdmiphy_conf74_25, &hdmi_conf_1080i50 }, - { 1920, 1080, 60, true, hdmiphy_conf74_25, &hdmi_conf_1080i60 }, { 1920, 1080, 30, false, hdmiphy_conf74_176, &hdmi_conf_1080p30 }, }; @@ -1838,6 +1838,104 @@ static void hdmi_timing_apply(struct hdmi_context *hdata) hdmi_v14_timing_apply(hdata); } +static int hdmi_phy_ctrl(struct i2c_client *client, u8 reg, u8 bit, + u8 *read_buffer, bool enable) +{ + int ret; + u8 operation[2]; + + operation[0] = reg; + operation[1] = enable ? (read_buffer[reg] & (~(1 << bit))) : + (read_buffer[reg] | (1 << bit)); + read_buffer[reg] = operation[1]; + + ret = i2c_master_send(client, operation, 2); + if (ret != 2) { + DRM_ERROR("failed to turn %s HDMIPHY via I2C\n", + enable ? "enable" : "disable"); + return -EIO; + } + + return 0; +} + +static int hdmi_phy_power_ctrl(struct hdmi_context *hdata, bool enable) +{ + struct hdmi_resources *res = &hdata->res; + u8 operation[2]; + u8 read_buffer[32]; + int ret = 0, i; + + DRM_DEBUG_KMS("%s:enable[%d]\n", __func__, enable); + + clk_enable(res->hdmiphy); + + /* read full register */ + operation[0] = 0x1; + i2c_master_send(hdata->hdmiphy_port, operation, 1); + + memset(read_buffer, 0x0, sizeof(read_buffer)); + ret = i2c_master_recv(hdata->hdmiphy_port, read_buffer, 32); + if (ret < 0) { + DRM_ERROR("failed to read hdmiphy config\n"); + goto err_clear; + } + + for (i = 0; i < ret; i++) + DRM_DEBUG_KMS("hdmiphy[0x%02x] write[0x%02x] - " + "recv [0x%02x]\n", i, operation[i], read_buffer[i]); + + /* ocspad control */ + operation[0] = 0x0b; + if (enable) + operation[1] = 0xd8; + else + operation[1] = 0x18; + read_buffer[0x0b] = operation[1]; + + ret = i2c_master_send(hdata->hdmiphy_port, operation, 2); + if (ret != 2) { + DRM_ERROR("failed to %s osc pad\n", + enable ? "enable" : "disable"); + goto err_clear; + } + + hdmi_phy_ctrl(hdata->hdmiphy_port, 0x1d, 0x7, read_buffer, enable); + hdmi_phy_ctrl(hdata->hdmiphy_port, 0x1d, 0x0, read_buffer, enable); + hdmi_phy_ctrl(hdata->hdmiphy_port, 0x1d, 0x1, read_buffer, enable); + hdmi_phy_ctrl(hdata->hdmiphy_port, 0x1d, 0x2, read_buffer, enable); + hdmi_phy_ctrl(hdata->hdmiphy_port, 0x1d, 0x4, read_buffer, enable); + hdmi_phy_ctrl(hdata->hdmiphy_port, 0x1d, 0x5, read_buffer, enable); + hdmi_phy_ctrl(hdata->hdmiphy_port, 0x1d, 0x6, read_buffer, enable); + + if (!enable) + hdmi_phy_ctrl(hdata->hdmiphy_port, 0x4, 0x3, read_buffer, 0); + + /* read full register */ + operation[0] = 0x1; + i2c_master_send(hdata->hdmiphy_port, operation, 1); + + memset(read_buffer, 0x0, sizeof(read_buffer)); + ret = i2c_master_recv(hdata->hdmiphy_port, read_buffer, 32); + if (ret < 0) { + DRM_ERROR("failed to read hdmiphy config\n"); + goto err_clear; + } + + for (i = 0; i < ret; i++) + DRM_DEBUG_KMS("hdmiphy[0x%02x] write[0x%02x] - " + "recv [0x%02x]\n", i, operation[i], read_buffer[i]); + + clk_disable(res->hdmiphy); + + return 0; + +err_clear: + clk_disable(res->hdmiphy); + + return ret; +} + static void hdmiphy_conf_reset(struct hdmi_context *hdata) { u8 buffer[2]; @@ -2003,15 +2101,46 @@ static void hdmi_get_max_resol(void *ctx, unsigned int *width, static void hdmi_commit(void *ctx) { struct hdmi_context *hdata = ctx; + struct exynos_drm_private *drm_priv; + struct exynos_drm_hdmi_context *drm_hdmi_ctx; + struct drm_device *drm_dev; DRM_DEBUG_KMS("[%d] %s\n", __LINE__, __func__); hdmi_conf_apply(hdata); + + /* + * parent_ctx is created at hdmi_probe() and + * parent_ctx->drm_dev is set at hdmi_subdrv_probe() + */ + drm_hdmi_ctx = hdata->parent_ctx; + drm_dev = drm_hdmi_ctx->drm_dev; + if (drm_dev) + drm_priv = drm_dev->dev_private; + else + return; + + /* + * if iommu support for exynos drm was enabled, this function is + * called first time(!hdata->iommu_on) then enable iommu unit. + */ + if (drm_priv->vmm && !hdata->iommu_on) { + int ret; + + ret = exynos_drm_iommu_activate(drm_priv->vmm, hdata->dev); + if (ret < 0) { + DRM_ERROR("failed to activate iommu.\n"); + return; + } + + hdata->iommu_on = true; + } } static void hdmi_poweron(struct hdmi_context *hdata) { struct hdmi_resources *res = &hdata->res; + int ret; DRM_DEBUG_KMS("[%d] %s\n", __LINE__, __func__); @@ -2029,6 +2158,32 @@ static void hdmi_poweron(struct hdmi_context *hdata) pm_runtime_get_sync(hdata->dev); + ret = hdmi_phy_power_ctrl(hdata, true); + if (ret) { + DRM_ERROR("failed to control phy power\n"); + return; + } + + if (hdata->iommu_on) { + struct exynos_drm_private *drm_priv; + struct exynos_drm_hdmi_context *drm_hdmi_ctx; + struct drm_device *drm_dev; + + drm_hdmi_ctx = hdata->parent_ctx; + drm_dev = drm_hdmi_ctx->drm_dev; + + if (drm_dev) + drm_priv = drm_dev->dev_private; + else + return; + + ret = exynos_drm_iommu_activate(drm_priv->vmm, hdata->dev); + if (ret < 0) { + DRM_ERROR("failed to activate iommu.\n"); + return; + } + } + regulator_bulk_enable(res->regul_count, res->regul_bulk); clk_enable(res->hdmiphy); clk_enable(res->hdmi); @@ -2038,6 +2193,7 @@ static void hdmi_poweron(struct hdmi_context *hdata) static void hdmi_poweroff(struct hdmi_context *hdata) { struct hdmi_resources *res = &hdata->res; + int ret; DRM_DEBUG_KMS("[%d] %s\n", __LINE__, __func__); @@ -2057,16 +2213,48 @@ static void hdmi_poweroff(struct hdmi_context *hdata) clk_disable(res->hdmiphy); regulator_bulk_disable(res->regul_count, res->regul_bulk); - pm_runtime_put_sync(hdata->dev); - mutex_lock(&hdata->hdmi_mutex); if (hdata->cfg_hpd) hdata->cfg_hpd(false); + ret = hdmi_phy_power_ctrl(hdata, false); + if (ret) { + DRM_ERROR("failed to control phy power\n"); + return; + } + + if (hdata->iommu_on) { + struct exynos_drm_private *drm_priv; + struct exynos_drm_hdmi_context *drm_hdmi_ctx; + struct drm_device *drm_dev; + + drm_hdmi_ctx = hdata->parent_ctx; + drm_dev = drm_hdmi_ctx->drm_dev; + if (drm_dev) + drm_priv = drm_dev->dev_private; + else { + if (hdata->cfg_hpd) + hdata->cfg_hpd(true); + + regulator_bulk_enable(res->regul_count, + res->regul_bulk); + + clk_enable(res->hdmiphy); + clk_enable(res->hdmi); + clk_enable(res->sclk_hdmi); + + mutex_unlock(&hdata->hdmi_mutex); + return; + } + + exynos_drm_iommu_deactivate(drm_priv->vmm, hdata->dev); + } + hdata->powered = false; out: mutex_unlock(&hdata->hdmi_mutex); + pm_runtime_put_sync(hdata->dev); } static void hdmi_dpms(void *ctx, int mode) @@ -2350,6 +2538,17 @@ static int __devinit hdmi_probe(struct platform_device *pdev) hdata->hdmiphy_port = hdmi_hdmiphy; + /* + * HDMI PHY power off + * HDMI PHY is on as default configuration + * So, HDMI PHY must be turned off if it's not used + */ + ret = hdmi_phy_power_ctrl(hdata, false); + if (ret) { + DRM_ERROR("failed to control phy power\n"); + goto err_hdmiphy; + } + hdata->external_irq = platform_get_irq_byname(pdev, "external_irq"); if (hdata->external_irq < 0) { DRM_ERROR("failed to get platform irq\n"); @@ -2435,6 +2634,7 @@ static int __devexit hdmi_remove(struct platform_device *pdev) i2c_del_driver(&ddc_driver); kfree(hdata); + kfree(ctx); return 0; } |