diff --git a/drivers/gpu/drm/msm/Makefile b/drivers/gpu/drm/msm/Makefile index 17847af4e7ab..59b76edb5fdd 100644 --- a/drivers/gpu/drm/msm/Makefile +++ b/drivers/gpu/drm/msm/Makefile @@ -12,6 +12,7 @@ msm-y := \ hdmi/hdmi_i2c.o \ hdmi/hdmi_phy_8960.o \ hdmi/hdmi_phy_8x60.o \ + hdmi/hdmi_phy_8x74.o \ mdp/mdp_format.o \ mdp/mdp_kms.o \ mdp/mdp4/mdp4_crtc.o \ diff --git a/drivers/gpu/drm/msm/hdmi/hdmi.c b/drivers/gpu/drm/msm/hdmi/hdmi.c index 32f26f855050..6f1588aa9071 100644 --- a/drivers/gpu/drm/msm/hdmi/hdmi.c +++ b/drivers/gpu/drm/msm/hdmi/hdmi.c @@ -41,7 +41,7 @@ void hdmi_set_mode(struct hdmi *hdmi, bool power_on) power_on ? "Enable" : "Disable", ctrl); } -static irqreturn_t hdmi_irq(int irq, void *dev_id) +irqreturn_t hdmi_irq(int irq, void *dev_id) { struct hdmi *hdmi = dev_id; @@ -71,13 +71,13 @@ void hdmi_destroy(struct kref *kref) } /* initialize connector */ -int hdmi_init(struct drm_device *dev, struct drm_encoder *encoder) +struct hdmi *hdmi_init(struct drm_device *dev, struct drm_encoder *encoder) { struct hdmi *hdmi = NULL; struct msm_drm_private *priv = dev->dev_private; struct platform_device *pdev = hdmi_pdev; struct hdmi_platform_config *config; - int ret; + int i, ret; if (!pdev) { dev_err(dev->dev, "no hdmi device\n"); @@ -99,6 +99,7 @@ int hdmi_init(struct drm_device *dev, struct drm_encoder *encoder) hdmi->dev = dev; hdmi->pdev = pdev; + hdmi->config = config; hdmi->encoder = encoder; /* not sure about which phy maps to which msm.. probably I miss some */ @@ -114,44 +115,70 @@ int hdmi_init(struct drm_device *dev, struct drm_encoder *encoder) goto fail; } - hdmi->mmio = msm_ioremap(pdev, "hdmi_msm_hdmi_addr", "HDMI"); + hdmi->mmio = msm_ioremap(pdev, config->mmio_name, "HDMI"); if (IS_ERR(hdmi->mmio)) { ret = PTR_ERR(hdmi->mmio); goto fail; } - hdmi->mvs = devm_regulator_get(&pdev->dev, "8901_hdmi_mvs"); - if (IS_ERR(hdmi->mvs)) - hdmi->mvs = devm_regulator_get(&pdev->dev, "8921_hdmi_mvs"); - if (IS_ERR(hdmi->mvs)) { - ret = PTR_ERR(hdmi->mvs); - dev_err(dev->dev, "failed to get mvs regulator: %d\n", ret); - goto fail; + BUG_ON(config->hpd_reg_cnt > ARRAY_SIZE(hdmi->hpd_regs)); + for (i = 0; i < config->hpd_reg_cnt; i++) { + struct regulator *reg; + + reg = devm_regulator_get(&pdev->dev, config->hpd_reg_names[i]); + if (IS_ERR(reg)) { + ret = PTR_ERR(reg); + dev_err(dev->dev, "failed to get hpd regulator: %s (%d)\n", + config->hpd_reg_names[i], ret); + goto fail; + } + + hdmi->hpd_regs[i] = reg; } - hdmi->mpp0 = devm_regulator_get(&pdev->dev, "8901_mpp0"); - if (IS_ERR(hdmi->mpp0)) - hdmi->mpp0 = NULL; + BUG_ON(config->pwr_reg_cnt > ARRAY_SIZE(hdmi->pwr_regs)); + for (i = 0; i < config->pwr_reg_cnt; i++) { + struct regulator *reg; - hdmi->clk = devm_clk_get(&pdev->dev, "core_clk"); - if (IS_ERR(hdmi->clk)) { - ret = PTR_ERR(hdmi->clk); - dev_err(dev->dev, "failed to get 'clk': %d\n", ret); - goto fail; + reg = devm_regulator_get(&pdev->dev, config->pwr_reg_names[i]); + if (IS_ERR(reg)) { + ret = PTR_ERR(reg); + dev_err(dev->dev, "failed to get pwr regulator: %s (%d)\n", + config->pwr_reg_names[i], ret); + goto fail; + } + + hdmi->pwr_regs[i] = reg; } - hdmi->m_pclk = devm_clk_get(&pdev->dev, "master_iface_clk"); - if (IS_ERR(hdmi->m_pclk)) { - ret = PTR_ERR(hdmi->m_pclk); - dev_err(dev->dev, "failed to get 'm_pclk': %d\n", ret); - goto fail; + BUG_ON(config->hpd_clk_cnt > ARRAY_SIZE(hdmi->hpd_clks)); + for (i = 0; i < config->hpd_clk_cnt; i++) { + struct clk *clk; + + clk = devm_clk_get(&pdev->dev, config->hpd_clk_names[i]); + if (IS_ERR(clk)) { + ret = PTR_ERR(clk); + dev_err(dev->dev, "failed to get hpd clk: %s (%d)\n", + config->hpd_clk_names[i], ret); + goto fail; + } + + hdmi->hpd_clks[i] = clk; } - hdmi->s_pclk = devm_clk_get(&pdev->dev, "slave_iface_clk"); - if (IS_ERR(hdmi->s_pclk)) { - ret = PTR_ERR(hdmi->s_pclk); - dev_err(dev->dev, "failed to get 's_pclk': %d\n", ret); - goto fail; + BUG_ON(config->pwr_clk_cnt > ARRAY_SIZE(hdmi->pwr_clks)); + for (i = 0; i < config->pwr_clk_cnt; i++) { + struct clk *clk; + + clk = devm_clk_get(&pdev->dev, config->pwr_clk_names[i]); + if (IS_ERR(clk)) { + ret = PTR_ERR(clk); + dev_err(dev->dev, "failed to get pwr clk: %s (%d)\n", + config->pwr_clk_names[i], ret); + goto fail; + } + + hdmi->pwr_clks[i] = clk; } hdmi->i2c = hdmi_i2c_init(hdmi); @@ -178,20 +205,22 @@ int hdmi_init(struct drm_device *dev, struct drm_encoder *encoder) goto fail; } - hdmi->irq = platform_get_irq(pdev, 0); - if (hdmi->irq < 0) { - ret = hdmi->irq; - dev_err(dev->dev, "failed to get irq: %d\n", ret); - goto fail; - } + if (!config->shared_irq) { + hdmi->irq = platform_get_irq(pdev, 0); + if (hdmi->irq < 0) { + ret = hdmi->irq; + dev_err(dev->dev, "failed to get irq: %d\n", ret); + goto fail; + } - ret = devm_request_threaded_irq(&pdev->dev, hdmi->irq, - NULL, hdmi_irq, IRQF_TRIGGER_HIGH | IRQF_ONESHOT, - "hdmi_isr", hdmi); - if (ret < 0) { - dev_err(dev->dev, "failed to request IRQ%u: %d\n", - hdmi->irq, ret); - goto fail; + ret = devm_request_threaded_irq(&pdev->dev, hdmi->irq, + NULL, hdmi_irq, IRQF_TRIGGER_HIGH | IRQF_ONESHOT, + "hdmi_isr", hdmi); + if (ret < 0) { + dev_err(dev->dev, "failed to request IRQ%u: %d\n", + hdmi->irq, ret); + goto fail; + } } encoder->bridge = hdmi->bridge; @@ -199,7 +228,7 @@ int hdmi_init(struct drm_device *dev, struct drm_encoder *encoder) priv->bridges[priv->num_bridges++] = hdmi->bridge; priv->connectors[priv->num_connectors++] = hdmi->connector; - return 0; + return hdmi; fail: if (hdmi) { @@ -211,37 +240,100 @@ fail: hdmi_destroy(&hdmi->refcount); } - return ret; + return ERR_PTR(ret); } /* * The hdmi device: */ +#include + static int hdmi_dev_probe(struct platform_device *pdev) { static struct hdmi_platform_config config = {}; #ifdef CONFIG_OF - /* TODO */ + struct device_node *of_node = pdev->dev.of_node; + + int get_gpio(const char *name) + { + int gpio = of_get_named_gpio(of_node, name, 0); + if (gpio < 0) { + dev_err(&pdev->dev, "failed to get gpio: %s (%d)\n", + name, gpio); + gpio = -1; + } + return gpio; + } + + /* TODO actually use DT.. */ + static const char *hpd_reg_names[] = {"hpd-gdsc", "hpd-5v"}; + static const char *pwr_reg_names[] = {"core-vdda", "core-vcc"}; + static const char *hpd_clk_names[] = {"iface_clk", "core_clk", "mdp_core_clk"}; + static const char *pwr_clk_names[] = {"extp_clk", "alt_iface_clk"}; + + config.phy_init = hdmi_phy_8x74_init; + config.mmio_name = "core_physical"; + config.hpd_reg_names = hpd_reg_names; + config.hpd_reg_cnt = ARRAY_SIZE(hpd_reg_names); + config.pwr_reg_names = pwr_reg_names; + config.pwr_reg_cnt = ARRAY_SIZE(pwr_reg_names); + config.hpd_clk_names = hpd_clk_names; + config.hpd_clk_cnt = ARRAY_SIZE(hpd_clk_names); + config.pwr_clk_names = pwr_clk_names; + config.pwr_clk_cnt = ARRAY_SIZE(pwr_clk_names); + config.ddc_clk_gpio = get_gpio("qcom,hdmi-tx-ddc-clk"); + config.ddc_data_gpio = get_gpio("qcom,hdmi-tx-ddc-data"); + config.hpd_gpio = get_gpio("qcom,hdmi-tx-hpd"); + config.mux_en_gpio = get_gpio("qcom,hdmi-tx-mux-en"); + config.mux_sel_gpio = get_gpio("qcom,hdmi-tx-mux-sel"); + config.shared_irq = true; + #else + static const char *hpd_clk_names[] = { + "core_clk", "master_iface_clk", "slave_iface_clk", + }; if (cpu_is_apq8064()) { + static const char *hpd_reg_names[] = {"8921_hdmi_mvs"}; config.phy_init = hdmi_phy_8960_init; + config.mmio_name = "hdmi_msm_hdmi_addr"; + config.hpd_reg_names = hpd_reg_names; + config.hpd_reg_cnt = ARRAY_SIZE(hpd_reg_names); + config.hpd_clk_names = hpd_clk_names; + config.hpd_clk_cnt = ARRAY_SIZE(hpd_clk_names); config.ddc_clk_gpio = 70; config.ddc_data_gpio = 71; config.hpd_gpio = 72; - config.pmic_gpio = 13 + NR_GPIO_IRQS; + config.mux_en_gpio = -1; + config.mux_sel_gpio = 13 + NR_GPIO_IRQS; } else if (cpu_is_msm8960() || cpu_is_msm8960ab()) { + static const char *hpd_reg_names[] = {"8921_hdmi_mvs"}; config.phy_init = hdmi_phy_8960_init; + config.mmio_name = "hdmi_msm_hdmi_addr"; + config.hpd_reg_names = hpd_reg_names; + config.hpd_reg_cnt = ARRAY_SIZE(hpd_reg_names); + config.hpd_clk_names = hpd_clk_names; + config.hpd_clk_cnt = ARRAY_SIZE(hpd_clk_names); config.ddc_clk_gpio = 100; config.ddc_data_gpio = 101; config.hpd_gpio = 102; - config.pmic_gpio = -1; + config.mux_en_gpio = -1; + config.mux_sel_gpio = -1; } else if (cpu_is_msm8x60()) { + static const char *hpd_reg_names[] = { + "8901_hdmi_mvs", "8901_mpp0" + }; config.phy_init = hdmi_phy_8x60_init; + config.mmio_name = "hdmi_msm_hdmi_addr"; + config.hpd_reg_names = hpd_reg_names; + config.hpd_reg_cnt = ARRAY_SIZE(hpd_reg_names); + config.hpd_clk_names = hpd_clk_names; + config.hpd_clk_cnt = ARRAY_SIZE(hpd_clk_names); config.ddc_clk_gpio = 170; config.ddc_data_gpio = 171; config.hpd_gpio = 172; - config.pmic_gpio = -1; + config.mux_en_gpio = -1; + config.mux_sel_gpio = -1; } #endif pdev->dev.platform_data = &config; @@ -255,10 +347,19 @@ static int hdmi_dev_remove(struct platform_device *pdev) return 0; } +static const struct of_device_id dt_match[] = { + { .compatible = "qcom,hdmi-tx" }, + {} +}; +MODULE_DEVICE_TABLE(of, dt_match); + static struct platform_driver hdmi_driver = { .probe = hdmi_dev_probe, .remove = hdmi_dev_remove, - .driver.name = "hdmi_msm", + .driver = { + .name = "hdmi_msm", + .of_match_table = dt_match, + }, }; void __init hdmi_register(void) diff --git a/drivers/gpu/drm/msm/hdmi/hdmi.h b/drivers/gpu/drm/msm/hdmi/hdmi.h index 2c2ec566394c..41b29add70b1 100644 --- a/drivers/gpu/drm/msm/hdmi/hdmi.h +++ b/drivers/gpu/drm/msm/hdmi/hdmi.h @@ -28,6 +28,7 @@ struct hdmi_phy; +struct hdmi_platform_config; struct hdmi { struct kref refcount; @@ -35,14 +36,14 @@ struct hdmi { struct drm_device *dev; struct platform_device *pdev; + const struct hdmi_platform_config *config; + void __iomem *mmio; - struct regulator *mvs; /* HDMI_5V */ - struct regulator *mpp0; /* External 5V */ - - struct clk *clk; - struct clk *m_pclk; - struct clk *s_pclk; + struct regulator *hpd_regs[2]; + struct regulator *pwr_regs[2]; + struct clk *hpd_clks[3]; + struct clk *pwr_clks[2]; struct hdmi_phy *phy; struct i2c_adapter *i2c; @@ -60,7 +61,29 @@ struct hdmi { /* platform config data (ie. from DT, or pdata) */ struct hdmi_platform_config { struct hdmi_phy *(*phy_init)(struct hdmi *hdmi); - int ddc_clk_gpio, ddc_data_gpio, hpd_gpio, pmic_gpio; + const char *mmio_name; + + /* regulators that need to be on for hpd: */ + const char **hpd_reg_names; + int hpd_reg_cnt; + + /* regulators that need to be on for screen pwr: */ + const char **pwr_reg_names; + int pwr_reg_cnt; + + /* clks that need to be on for hpd: */ + const char **hpd_clk_names; + int hpd_clk_cnt; + + /* clks that need to be on for screen pwr (ie pixel clk): */ + const char **pwr_clk_names; + int pwr_clk_cnt; + + /* gpio's: */ + int ddc_clk_gpio, ddc_data_gpio, hpd_gpio, mux_en_gpio, mux_sel_gpio; + + /* older devices had their own irq, mdp5+ it is shared w/ mdp: */ + bool shared_irq; }; void hdmi_set_mode(struct hdmi *hdmi, bool power_on); @@ -106,6 +129,7 @@ struct hdmi_phy { struct hdmi_phy *hdmi_phy_8960_init(struct hdmi *hdmi); struct hdmi_phy *hdmi_phy_8x60_init(struct hdmi *hdmi); +struct hdmi_phy *hdmi_phy_8x74_init(struct hdmi *hdmi); /* * hdmi bridge: diff --git a/drivers/gpu/drm/msm/hdmi/hdmi_bridge.c b/drivers/gpu/drm/msm/hdmi/hdmi_bridge.c index 5a8ee3473cf5..7d10e55403c6 100644 --- a/drivers/gpu/drm/msm/hdmi/hdmi_bridge.c +++ b/drivers/gpu/drm/msm/hdmi/hdmi_bridge.c @@ -21,6 +21,7 @@ struct hdmi_bridge { struct drm_bridge base; struct hdmi *hdmi; + bool power_on; unsigned long int pixclock; }; @@ -34,6 +35,65 @@ static void hdmi_bridge_destroy(struct drm_bridge *bridge) kfree(hdmi_bridge); } +static void power_on(struct drm_bridge *bridge) +{ + struct drm_device *dev = bridge->dev; + struct hdmi_bridge *hdmi_bridge = to_hdmi_bridge(bridge); + struct hdmi *hdmi = hdmi_bridge->hdmi; + const struct hdmi_platform_config *config = hdmi->config; + int i, ret; + + for (i = 0; i < config->pwr_reg_cnt; i++) { + ret = regulator_enable(hdmi->pwr_regs[i]); + if (ret) { + dev_err(dev->dev, "failed to enable pwr regulator: %s (%d)\n", + config->pwr_reg_names[i], ret); + } + } + + if (config->pwr_clk_cnt > 0) { + DBG("pixclock: %lu", hdmi_bridge->pixclock); + ret = clk_set_rate(hdmi->pwr_clks[0], hdmi_bridge->pixclock); + if (ret) { + dev_err(dev->dev, "failed to set pixel clk: %s (%d)\n", + config->pwr_clk_names[0], ret); + } + } + + for (i = 0; i < config->pwr_clk_cnt; i++) { + ret = clk_prepare_enable(hdmi->pwr_clks[i]); + if (ret) { + dev_err(dev->dev, "failed to enable pwr clk: %s (%d)\n", + config->pwr_clk_names[i], ret); + } + } +} + +static void power_off(struct drm_bridge *bridge) +{ + struct drm_device *dev = bridge->dev; + struct hdmi_bridge *hdmi_bridge = to_hdmi_bridge(bridge); + struct hdmi *hdmi = hdmi_bridge->hdmi; + const struct hdmi_platform_config *config = hdmi->config; + int i, ret; + + /* TODO do we need to wait for final vblank somewhere before + * cutting the clocks? + */ + mdelay(16 + 4); + + for (i = 0; i < config->pwr_clk_cnt; i++) + clk_disable_unprepare(hdmi->pwr_clks[i]); + + for (i = 0; i < config->pwr_reg_cnt; i++) { + ret = regulator_disable(hdmi->pwr_regs[i]); + if (ret) { + dev_err(dev->dev, "failed to disable pwr regulator: %s (%d)\n", + config->pwr_reg_names[i], ret); + } + } +} + static void hdmi_bridge_pre_enable(struct drm_bridge *bridge) { struct hdmi_bridge *hdmi_bridge = to_hdmi_bridge(bridge); @@ -41,6 +101,12 @@ static void hdmi_bridge_pre_enable(struct drm_bridge *bridge) struct hdmi_phy *phy = hdmi->phy; DBG("power up"); + + if (!hdmi_bridge->power_on) { + power_on(bridge); + hdmi_bridge->power_on = true; + } + phy->funcs->powerup(phy, hdmi_bridge->pixclock); hdmi_set_mode(hdmi, true); } @@ -62,6 +128,11 @@ static void hdmi_bridge_post_disable(struct drm_bridge *bridge) DBG("power down"); hdmi_set_mode(hdmi, false); phy->funcs->powerdown(phy); + + if (hdmi_bridge->power_on) { + power_off(bridge); + hdmi_bridge->power_on = false; + } } static void hdmi_bridge_mode_set(struct drm_bridge *bridge, diff --git a/drivers/gpu/drm/msm/hdmi/hdmi_connector.c b/drivers/gpu/drm/msm/hdmi/hdmi_connector.c index 197b34899b5a..7dedfdd12075 100644 --- a/drivers/gpu/drm/msm/hdmi/hdmi_connector.c +++ b/drivers/gpu/drm/msm/hdmi/hdmi_connector.c @@ -23,14 +23,14 @@ struct hdmi_connector { struct drm_connector base; struct hdmi *hdmi; + struct work_struct hpd_work; }; #define to_hdmi_connector(x) container_of(x, struct hdmi_connector, base) static int gpio_config(struct hdmi *hdmi, bool on) { struct drm_device *dev = hdmi->dev; - struct hdmi_platform_config *config = - hdmi->pdev->dev.platform_data; + const struct hdmi_platform_config *config = hdmi->config; int ret; if (on) { @@ -40,26 +40,43 @@ static int gpio_config(struct hdmi *hdmi, bool on) "HDMI_DDC_CLK", config->ddc_clk_gpio, ret); goto error1; } + gpio_set_value_cansleep(config->ddc_clk_gpio, 1); + ret = gpio_request(config->ddc_data_gpio, "HDMI_DDC_DATA"); if (ret) { dev_err(dev->dev, "'%s'(%d) gpio_request failed: %d\n", "HDMI_DDC_DATA", config->ddc_data_gpio, ret); goto error2; } + gpio_set_value_cansleep(config->ddc_data_gpio, 1); + ret = gpio_request(config->hpd_gpio, "HDMI_HPD"); if (ret) { dev_err(dev->dev, "'%s'(%d) gpio_request failed: %d\n", "HDMI_HPD", config->hpd_gpio, ret); goto error3; } - if (config->pmic_gpio != -1) { - ret = gpio_request(config->pmic_gpio, "PMIC_HDMI_MUX_SEL"); + gpio_direction_input(config->hpd_gpio); + gpio_set_value_cansleep(config->hpd_gpio, 1); + + if (config->mux_en_gpio != -1) { + ret = gpio_request(config->mux_en_gpio, "HDMI_MUX_EN"); if (ret) { dev_err(dev->dev, "'%s'(%d) gpio_request failed: %d\n", - "PMIC_HDMI_MUX_SEL", config->pmic_gpio, ret); + "HDMI_MUX_SEL", config->mux_en_gpio, ret); goto error4; } - gpio_set_value_cansleep(config->pmic_gpio, 0); + gpio_set_value_cansleep(config->mux_en_gpio, 1); + } + + if (config->mux_sel_gpio != -1) { + ret = gpio_request(config->mux_sel_gpio, "HDMI_MUX_SEL"); + if (ret) { + dev_err(dev->dev, "'%s'(%d) gpio_request failed: %d\n", + "HDMI_MUX_SEL", config->mux_sel_gpio, ret); + goto error5; + } + gpio_set_value_cansleep(config->mux_sel_gpio, 0); } DBG("gpio on"); } else { @@ -67,15 +84,23 @@ static int gpio_config(struct hdmi *hdmi, bool on) gpio_free(config->ddc_data_gpio); gpio_free(config->hpd_gpio); - if (config->pmic_gpio != -1) { - gpio_set_value_cansleep(config->pmic_gpio, 1); - gpio_free(config->pmic_gpio); + if (config->mux_en_gpio != -1) { + gpio_set_value_cansleep(config->mux_en_gpio, 0); + gpio_free(config->mux_en_gpio); + } + + if (config->mux_sel_gpio != -1) { + gpio_set_value_cansleep(config->mux_sel_gpio, 1); + gpio_free(config->mux_sel_gpio); } DBG("gpio off"); } return 0; +error5: + if (config->mux_en_gpio != -1) + gpio_free(config->mux_en_gpio); error4: gpio_free(config->hpd_gpio); error3: @@ -89,10 +114,11 @@ error1: static int hpd_enable(struct hdmi_connector *hdmi_connector) { struct hdmi *hdmi = hdmi_connector->hdmi; + const struct hdmi_platform_config *config = hdmi->config; struct drm_device *dev = hdmi_connector->base.dev; struct hdmi_phy *phy = hdmi->phy; uint32_t hpd_ctrl; - int ret; + int i, ret; ret = gpio_config(hdmi, true); if (ret) { @@ -100,31 +126,22 @@ static int hpd_enable(struct hdmi_connector *hdmi_connector) goto fail; } - ret = clk_prepare_enable(hdmi->clk); - if (ret) { - dev_err(dev->dev, "failed to enable 'clk': %d\n", ret); - goto fail; + for (i = 0; i < config->hpd_clk_cnt; i++) { + ret = clk_prepare_enable(hdmi->hpd_clks[i]); + if (ret) { + dev_err(dev->dev, "failed to enable hpd clk: %s (%d)\n", + config->hpd_clk_names[i], ret); + goto fail; + } } - ret = clk_prepare_enable(hdmi->m_pclk); - if (ret) { - dev_err(dev->dev, "failed to enable 'm_pclk': %d\n", ret); - goto fail; - } - - ret = clk_prepare_enable(hdmi->s_pclk); - if (ret) { - dev_err(dev->dev, "failed to enable 's_pclk': %d\n", ret); - goto fail; - } - - if (hdmi->mpp0) - ret = regulator_enable(hdmi->mpp0); - if (!ret) - ret = regulator_enable(hdmi->mvs); - if (ret) { - dev_err(dev->dev, "failed to enable regulators: %d\n", ret); - goto fail; + for (i = 0; i < config->hpd_reg_cnt; i++) { + ret = regulator_enable(hdmi->hpd_regs[i]); + if (ret) { + dev_err(dev->dev, "failed to enable hpd regulator: %s (%d)\n", + config->hpd_reg_names[i], ret); + goto fail; + } } hdmi_set_mode(hdmi, false); @@ -157,26 +174,26 @@ fail: static int hdp_disable(struct hdmi_connector *hdmi_connector) { struct hdmi *hdmi = hdmi_connector->hdmi; + const struct hdmi_platform_config *config = hdmi->config; struct drm_device *dev = hdmi_connector->base.dev; - int ret = 0; + int i, ret = 0; /* Disable HPD interrupt */ hdmi_write(hdmi, REG_HDMI_HPD_INT_CTRL, 0); hdmi_set_mode(hdmi, false); - if (hdmi->mpp0) - ret = regulator_disable(hdmi->mpp0); - if (!ret) - ret = regulator_disable(hdmi->mvs); - if (ret) { - dev_err(dev->dev, "failed to enable regulators: %d\n", ret); - goto fail; + for (i = 0; i < config->hpd_reg_cnt; i++) { + ret = regulator_disable(hdmi->hpd_regs[i]); + if (ret) { + dev_err(dev->dev, "failed to disable hpd regulator: %s (%d)\n", + config->hpd_reg_names[i], ret); + goto fail; + } } - clk_disable_unprepare(hdmi->clk); - clk_disable_unprepare(hdmi->m_pclk); - clk_disable_unprepare(hdmi->s_pclk); + for (i = 0; i < config->hpd_clk_cnt; i++) + clk_disable_unprepare(hdmi->hpd_clks[i]); ret = gpio_config(hdmi, false); if (ret) { @@ -190,9 +207,19 @@ fail: return ret; } +static void +hotplug_work(struct work_struct *work) +{ + struct hdmi_connector *hdmi_connector = + container_of(work, struct hdmi_connector, hpd_work); + struct drm_connector *connector = &hdmi_connector->base; + drm_helper_hpd_irq_event(connector->dev); +} + void hdmi_connector_irq(struct drm_connector *connector) { struct hdmi_connector *hdmi_connector = to_hdmi_connector(connector); + struct msm_drm_private *priv = connector->dev->dev_private; struct hdmi *hdmi = hdmi_connector->hdmi; uint32_t hpd_int_status, hpd_int_ctrl; @@ -210,13 +237,13 @@ void hdmi_connector_irq(struct drm_connector *connector) hdmi_write(hdmi, REG_HDMI_HPD_INT_CTRL, hpd_int_ctrl | HDMI_HPD_INT_CTRL_INT_ACK); - drm_helper_hpd_irq_event(connector->dev); - /* detect disconnect if we are connected or visa versa: */ hpd_int_ctrl = HDMI_HPD_INT_CTRL_INT_EN; if (!detected) hpd_int_ctrl |= HDMI_HPD_INT_CTRL_INT_CONNECT; hdmi_write(hdmi, REG_HDMI_HPD_INT_CTRL, hpd_int_ctrl); + + queue_work(priv->wq, &hdmi_connector->hpd_work); } } @@ -225,6 +252,7 @@ static enum drm_connector_status hdmi_connector_detect( { struct hdmi_connector *hdmi_connector = to_hdmi_connector(connector); struct hdmi *hdmi = hdmi_connector->hdmi; + const struct hdmi_platform_config *config = hdmi->config; uint32_t hpd_int_status; int retry = 20; @@ -234,6 +262,14 @@ static enum drm_connector_status hdmi_connector_detect( * let that trick us into thinking the monitor is gone: */ while (retry-- && !(hpd_int_status & HDMI_HPD_INT_STATUS_CABLE_DETECTED)) { + /* hdmi debounce logic seems to get stuck sometimes, + * read directly the gpio to get a second opinion: + */ + if (gpio_get_value(config->hpd_gpio)) { + DBG("gpio tells us we are connected!"); + hpd_int_status |= HDMI_HPD_INT_STATUS_CABLE_DETECTED; + break; + } mdelay(10); hpd_int_status = hdmi_read(hdmi, REG_HDMI_HPD_INT_STATUS); DBG("status=%08x", hpd_int_status); @@ -286,6 +322,8 @@ static int hdmi_connector_mode_valid(struct drm_connector *connector, struct drm_display_mode *mode) { struct hdmi_connector *hdmi_connector = to_hdmi_connector(connector); + struct hdmi *hdmi = hdmi_connector->hdmi; + const struct hdmi_platform_config *config = hdmi->config; struct msm_drm_private *priv = connector->dev->dev_private; struct msm_kms *kms = priv->kms; long actual, requested; @@ -294,6 +332,13 @@ static int hdmi_connector_mode_valid(struct drm_connector *connector, actual = kms->funcs->round_pixclk(kms, requested, hdmi_connector->hdmi->encoder); + /* for mdp5/apq8074, we manage our own pixel clk (as opposed to + * mdp4/dtv stuff where pixel clk is assigned to mdp/encoder + * instead): + */ + if (config->pwr_clk_cnt > 0) + actual = clk_round_rate(hdmi->pwr_clks[0], actual); + DBG("requested=%ld, actual=%ld", requested, actual); if (actual != requested) @@ -336,6 +381,7 @@ struct drm_connector *hdmi_connector_init(struct hdmi *hdmi) } hdmi_connector->hdmi = hdmi_reference(hdmi); + INIT_WORK(&hdmi_connector->hpd_work, hotplug_work); connector = &hdmi_connector->base; diff --git a/drivers/gpu/drm/msm/hdmi/hdmi_phy_8x74.c b/drivers/gpu/drm/msm/hdmi/hdmi_phy_8x74.c new file mode 100644 index 000000000000..59fa6cdacb2a --- /dev/null +++ b/drivers/gpu/drm/msm/hdmi/hdmi_phy_8x74.c @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2013 Red Hat + * Author: Rob Clark + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#include "hdmi.h" + +struct hdmi_phy_8x74 { + struct hdmi_phy base; + struct hdmi *hdmi; + void __iomem *mmio; +}; +#define to_hdmi_phy_8x74(x) container_of(x, struct hdmi_phy_8x74, base) + + +static void phy_write(struct hdmi_phy_8x74 *phy, u32 reg, u32 data) +{ + msm_writel(data, phy->mmio + reg); +} + +//static u32 phy_read(struct hdmi_phy_8x74 *phy, u32 reg) +//{ +// return msm_readl(phy->mmio + reg); +//} + +static void hdmi_phy_8x74_destroy(struct hdmi_phy *phy) +{ + struct hdmi_phy_8x74 *phy_8x74 = to_hdmi_phy_8x74(phy); + kfree(phy_8x74); +} + +static void hdmi_phy_8x74_reset(struct hdmi_phy *phy) +{ + struct hdmi_phy_8x74 *phy_8x74 = to_hdmi_phy_8x74(phy); + struct hdmi *hdmi = phy_8x74->hdmi; + unsigned int val; + + /* NOTE that HDMI_PHY_CTL is in core mmio, not phy mmio: */ + + val = hdmi_read(hdmi, REG_HDMI_PHY_CTRL); + + if (val & HDMI_PHY_CTRL_SW_RESET_LOW) { + /* pull low */ + hdmi_write(hdmi, REG_HDMI_PHY_CTRL, + val & ~HDMI_PHY_CTRL_SW_RESET); + } else { + /* pull high */ + hdmi_write(hdmi, REG_HDMI_PHY_CTRL, + val | HDMI_PHY_CTRL_SW_RESET); + } + + if (val & HDMI_PHY_CTRL_SW_RESET_PLL_LOW) { + /* pull low */ + hdmi_write(hdmi, REG_HDMI_PHY_CTRL, + val & ~HDMI_PHY_CTRL_SW_RESET_PLL); + } else { + /* pull high */ + hdmi_write(hdmi, REG_HDMI_PHY_CTRL, + val | HDMI_PHY_CTRL_SW_RESET_PLL); + } + + msleep(100); + + if (val & HDMI_PHY_CTRL_SW_RESET_LOW) { + /* pull high */ + hdmi_write(hdmi, REG_HDMI_PHY_CTRL, + val | HDMI_PHY_CTRL_SW_RESET); + } else { + /* pull low */ + hdmi_write(hdmi, REG_HDMI_PHY_CTRL, + val & ~HDMI_PHY_CTRL_SW_RESET); + } + + if (val & HDMI_PHY_CTRL_SW_RESET_PLL_LOW) { + /* pull high */ + hdmi_write(hdmi, REG_HDMI_PHY_CTRL, + val | HDMI_PHY_CTRL_SW_RESET_PLL); + } else { + /* pull low */ + hdmi_write(hdmi, REG_HDMI_PHY_CTRL, + val & ~HDMI_PHY_CTRL_SW_RESET_PLL); + } +} + +static void hdmi_phy_8x74_powerup(struct hdmi_phy *phy, + unsigned long int pixclock) +{ + struct hdmi_phy_8x74 *phy_8x74 = to_hdmi_phy_8x74(phy); + + phy_write(phy_8x74, REG_HDMI_8x74_ANA_CFG0, 0x1b); + phy_write(phy_8x74, REG_HDMI_8x74_ANA_CFG1, 0xf2); + phy_write(phy_8x74, REG_HDMI_8x74_BIST_CFG0, 0x0); + phy_write(phy_8x74, REG_HDMI_8x74_BIST_PATN0, 0x0); + phy_write(phy_8x74, REG_HDMI_8x74_BIST_PATN1, 0x0); + phy_write(phy_8x74, REG_HDMI_8x74_BIST_PATN2, 0x0); + phy_write(phy_8x74, REG_HDMI_8x74_BIST_PATN3, 0x0); + phy_write(phy_8x74, REG_HDMI_8x74_PD_CTRL1, 0x20); +} + +static void hdmi_phy_8x74_powerdown(struct hdmi_phy *phy) +{ + struct hdmi_phy_8x74 *phy_8x74 = to_hdmi_phy_8x74(phy); + phy_write(phy_8x74, REG_HDMI_8x74_PD_CTRL0, 0x7f); +} + +static const struct hdmi_phy_funcs hdmi_phy_8x74_funcs = { + .destroy = hdmi_phy_8x74_destroy, + .reset = hdmi_phy_8x74_reset, + .powerup = hdmi_phy_8x74_powerup, + .powerdown = hdmi_phy_8x74_powerdown, +}; + +struct hdmi_phy *hdmi_phy_8x74_init(struct hdmi *hdmi) +{ + struct hdmi_phy_8x74 *phy_8x74; + struct hdmi_phy *phy = NULL; + int ret; + + phy_8x74 = kzalloc(sizeof(*phy_8x74), GFP_KERNEL); + if (!phy_8x74) { + ret = -ENOMEM; + goto fail; + } + + phy = &phy_8x74->base; + + phy->funcs = &hdmi_phy_8x74_funcs; + + phy_8x74->hdmi = hdmi; + + /* for 8x74, the phy mmio is mapped separately: */ + phy_8x74->mmio = msm_ioremap(hdmi->pdev, + "phy_physical", "HDMI_8x74"); + if (IS_ERR(phy_8x74->mmio)) { + ret = PTR_ERR(phy_8x74->mmio); + goto fail; + } + + return phy; + +fail: + if (phy) + hdmi_phy_8x74_destroy(phy); + return ERR_PTR(ret); +} diff --git a/drivers/gpu/drm/msm/mdp/mdp4/mdp4_kms.c b/drivers/gpu/drm/msm/mdp/mdp4/mdp4_kms.c index 4d1cc2ea700e..272e707c9487 100644 --- a/drivers/gpu/drm/msm/mdp/mdp4/mdp4_kms.c +++ b/drivers/gpu/drm/msm/mdp/mdp4/mdp4_kms.c @@ -39,7 +39,7 @@ static int mdp4_hw_init(struct msm_kms *kms) major = FIELD(version, MDP4_VERSION_MAJOR); minor = FIELD(version, MDP4_VERSION_MINOR); - DBG("found MDP version v%d.%d", major, minor); + DBG("found MDP4 version v%d.%d", major, minor); if (major != 4) { dev_err(dev->dev, "unexpected MDP version: v%d.%d\n", @@ -195,6 +195,7 @@ static int modeset_init(struct mdp4_kms *mdp4_kms) struct drm_plane *plane; struct drm_crtc *crtc; struct drm_encoder *encoder; + struct hdmi *hdmi; int ret; /* @@ -244,9 +245,10 @@ static int modeset_init(struct mdp4_kms *mdp4_kms) encoder->possible_crtcs = 0x1; /* DTV can be hooked to DMA_E */ priv->encoders[priv->num_encoders++] = encoder; - ret = hdmi_init(dev, encoder); - if (ret) { - dev_err(dev->dev, "failed to initialize HDMI\n"); + hdmi = hdmi_init(dev, encoder); + if (IS_ERR(hdmi)) { + ret = PTR_ERR(hdmi); + dev_err(dev->dev, "failed to initialize HDMI: %d\n", ret); goto fail; } diff --git a/drivers/gpu/drm/msm/msm_drv.h b/drivers/gpu/drm/msm/msm_drv.h index aa78bede1446..3d63269c5b29 100644 --- a/drivers/gpu/drm/msm/msm_drv.h +++ b/drivers/gpu/drm/msm/msm_drv.h @@ -190,7 +190,9 @@ struct drm_framebuffer *msm_framebuffer_create(struct drm_device *dev, struct drm_fb_helper *msm_fbdev_init(struct drm_device *dev); -int hdmi_init(struct drm_device *dev, struct drm_encoder *encoder); +struct hdmi; +struct hdmi *hdmi_init(struct drm_device *dev, struct drm_encoder *encoder); +irqreturn_t hdmi_irq(int irq, void *dev_id); void __init hdmi_register(void); void __exit hdmi_unregister(void);