|
@@ -23,14 +23,14 @@
|
|
struct hdmi_connector {
|
|
struct hdmi_connector {
|
|
struct drm_connector base;
|
|
struct drm_connector base;
|
|
struct hdmi *hdmi;
|
|
struct hdmi *hdmi;
|
|
|
|
+ struct work_struct hpd_work;
|
|
};
|
|
};
|
|
#define to_hdmi_connector(x) container_of(x, struct hdmi_connector, base)
|
|
#define to_hdmi_connector(x) container_of(x, struct hdmi_connector, base)
|
|
|
|
|
|
static int gpio_config(struct hdmi *hdmi, bool on)
|
|
static int gpio_config(struct hdmi *hdmi, bool on)
|
|
{
|
|
{
|
|
struct drm_device *dev = hdmi->dev;
|
|
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;
|
|
int ret;
|
|
|
|
|
|
if (on) {
|
|
if (on) {
|
|
@@ -40,26 +40,43 @@ static int gpio_config(struct hdmi *hdmi, bool on)
|
|
"HDMI_DDC_CLK", config->ddc_clk_gpio, ret);
|
|
"HDMI_DDC_CLK", config->ddc_clk_gpio, ret);
|
|
goto error1;
|
|
goto error1;
|
|
}
|
|
}
|
|
|
|
+ gpio_set_value_cansleep(config->ddc_clk_gpio, 1);
|
|
|
|
+
|
|
ret = gpio_request(config->ddc_data_gpio, "HDMI_DDC_DATA");
|
|
ret = gpio_request(config->ddc_data_gpio, "HDMI_DDC_DATA");
|
|
if (ret) {
|
|
if (ret) {
|
|
dev_err(dev->dev, "'%s'(%d) gpio_request failed: %d\n",
|
|
dev_err(dev->dev, "'%s'(%d) gpio_request failed: %d\n",
|
|
"HDMI_DDC_DATA", config->ddc_data_gpio, ret);
|
|
"HDMI_DDC_DATA", config->ddc_data_gpio, ret);
|
|
goto error2;
|
|
goto error2;
|
|
}
|
|
}
|
|
|
|
+ gpio_set_value_cansleep(config->ddc_data_gpio, 1);
|
|
|
|
+
|
|
ret = gpio_request(config->hpd_gpio, "HDMI_HPD");
|
|
ret = gpio_request(config->hpd_gpio, "HDMI_HPD");
|
|
if (ret) {
|
|
if (ret) {
|
|
dev_err(dev->dev, "'%s'(%d) gpio_request failed: %d\n",
|
|
dev_err(dev->dev, "'%s'(%d) gpio_request failed: %d\n",
|
|
"HDMI_HPD", config->hpd_gpio, ret);
|
|
"HDMI_HPD", config->hpd_gpio, ret);
|
|
goto error3;
|
|
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) {
|
|
if (ret) {
|
|
dev_err(dev->dev, "'%s'(%d) gpio_request failed: %d\n",
|
|
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;
|
|
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");
|
|
DBG("gpio on");
|
|
} else {
|
|
} else {
|
|
@@ -67,15 +84,23 @@ static int gpio_config(struct hdmi *hdmi, bool on)
|
|
gpio_free(config->ddc_data_gpio);
|
|
gpio_free(config->ddc_data_gpio);
|
|
gpio_free(config->hpd_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");
|
|
DBG("gpio off");
|
|
}
|
|
}
|
|
|
|
|
|
return 0;
|
|
return 0;
|
|
|
|
|
|
|
|
+error5:
|
|
|
|
+ if (config->mux_en_gpio != -1)
|
|
|
|
+ gpio_free(config->mux_en_gpio);
|
|
error4:
|
|
error4:
|
|
gpio_free(config->hpd_gpio);
|
|
gpio_free(config->hpd_gpio);
|
|
error3:
|
|
error3:
|
|
@@ -89,10 +114,11 @@ error1:
|
|
static int hpd_enable(struct hdmi_connector *hdmi_connector)
|
|
static int hpd_enable(struct hdmi_connector *hdmi_connector)
|
|
{
|
|
{
|
|
struct hdmi *hdmi = hdmi_connector->hdmi;
|
|
struct hdmi *hdmi = hdmi_connector->hdmi;
|
|
|
|
+ const struct hdmi_platform_config *config = hdmi->config;
|
|
struct drm_device *dev = hdmi_connector->base.dev;
|
|
struct drm_device *dev = hdmi_connector->base.dev;
|
|
struct hdmi_phy *phy = hdmi->phy;
|
|
struct hdmi_phy *phy = hdmi->phy;
|
|
uint32_t hpd_ctrl;
|
|
uint32_t hpd_ctrl;
|
|
- int ret;
|
|
|
|
|
|
+ int i, ret;
|
|
|
|
|
|
ret = gpio_config(hdmi, true);
|
|
ret = gpio_config(hdmi, true);
|
|
if (ret) {
|
|
if (ret) {
|
|
@@ -100,31 +126,22 @@ static int hpd_enable(struct hdmi_connector *hdmi_connector)
|
|
goto fail;
|
|
goto fail;
|
|
}
|
|
}
|
|
|
|
|
|
- ret = clk_prepare_enable(hdmi->clk);
|
|
|
|
- if (ret) {
|
|
|
|
- dev_err(dev->dev, "failed to enable 'clk': %d\n", 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;
|
|
|
|
|
|
+ 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;
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
|
|
- 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);
|
|
hdmi_set_mode(hdmi, false);
|
|
@@ -157,26 +174,26 @@ fail:
|
|
static int hdp_disable(struct hdmi_connector *hdmi_connector)
|
|
static int hdp_disable(struct hdmi_connector *hdmi_connector)
|
|
{
|
|
{
|
|
struct hdmi *hdmi = hdmi_connector->hdmi;
|
|
struct hdmi *hdmi = hdmi_connector->hdmi;
|
|
|
|
+ const struct hdmi_platform_config *config = hdmi->config;
|
|
struct drm_device *dev = hdmi_connector->base.dev;
|
|
struct drm_device *dev = hdmi_connector->base.dev;
|
|
- int ret = 0;
|
|
|
|
|
|
+ int i, ret = 0;
|
|
|
|
|
|
/* Disable HPD interrupt */
|
|
/* Disable HPD interrupt */
|
|
hdmi_write(hdmi, REG_HDMI_HPD_INT_CTRL, 0);
|
|
hdmi_write(hdmi, REG_HDMI_HPD_INT_CTRL, 0);
|
|
|
|
|
|
hdmi_set_mode(hdmi, false);
|
|
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);
|
|
ret = gpio_config(hdmi, false);
|
|
if (ret) {
|
|
if (ret) {
|
|
@@ -190,9 +207,19 @@ fail:
|
|
return ret;
|
|
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)
|
|
void hdmi_connector_irq(struct drm_connector *connector)
|
|
{
|
|
{
|
|
struct hdmi_connector *hdmi_connector = to_hdmi_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;
|
|
struct hdmi *hdmi = hdmi_connector->hdmi;
|
|
uint32_t hpd_int_status, hpd_int_ctrl;
|
|
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,
|
|
hdmi_write(hdmi, REG_HDMI_HPD_INT_CTRL,
|
|
hpd_int_ctrl | HDMI_HPD_INT_CTRL_INT_ACK);
|
|
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: */
|
|
/* detect disconnect if we are connected or visa versa: */
|
|
hpd_int_ctrl = HDMI_HPD_INT_CTRL_INT_EN;
|
|
hpd_int_ctrl = HDMI_HPD_INT_CTRL_INT_EN;
|
|
if (!detected)
|
|
if (!detected)
|
|
hpd_int_ctrl |= HDMI_HPD_INT_CTRL_INT_CONNECT;
|
|
hpd_int_ctrl |= HDMI_HPD_INT_CTRL_INT_CONNECT;
|
|
hdmi_write(hdmi, REG_HDMI_HPD_INT_CTRL, hpd_int_ctrl);
|
|
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_connector *hdmi_connector = to_hdmi_connector(connector);
|
|
struct hdmi *hdmi = hdmi_connector->hdmi;
|
|
struct hdmi *hdmi = hdmi_connector->hdmi;
|
|
|
|
+ const struct hdmi_platform_config *config = hdmi->config;
|
|
uint32_t hpd_int_status;
|
|
uint32_t hpd_int_status;
|
|
int retry = 20;
|
|
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:
|
|
* let that trick us into thinking the monitor is gone:
|
|
*/
|
|
*/
|
|
while (retry-- && !(hpd_int_status & HDMI_HPD_INT_STATUS_CABLE_DETECTED)) {
|
|
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);
|
|
mdelay(10);
|
|
hpd_int_status = hdmi_read(hdmi, REG_HDMI_HPD_INT_STATUS);
|
|
hpd_int_status = hdmi_read(hdmi, REG_HDMI_HPD_INT_STATUS);
|
|
DBG("status=%08x", 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 drm_display_mode *mode)
|
|
{
|
|
{
|
|
struct hdmi_connector *hdmi_connector = to_hdmi_connector(connector);
|
|
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_drm_private *priv = connector->dev->dev_private;
|
|
struct msm_kms *kms = priv->kms;
|
|
struct msm_kms *kms = priv->kms;
|
|
long actual, requested;
|
|
long actual, requested;
|
|
@@ -294,6 +332,13 @@ static int hdmi_connector_mode_valid(struct drm_connector *connector,
|
|
actual = kms->funcs->round_pixclk(kms,
|
|
actual = kms->funcs->round_pixclk(kms,
|
|
requested, hdmi_connector->hdmi->encoder);
|
|
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);
|
|
DBG("requested=%ld, actual=%ld", requested, actual);
|
|
|
|
|
|
if (actual != requested)
|
|
if (actual != requested)
|
|
@@ -336,6 +381,7 @@ struct drm_connector *hdmi_connector_init(struct hdmi *hdmi)
|
|
}
|
|
}
|
|
|
|
|
|
hdmi_connector->hdmi = hdmi_reference(hdmi);
|
|
hdmi_connector->hdmi = hdmi_reference(hdmi);
|
|
|
|
+ INIT_WORK(&hdmi_connector->hpd_work, hotplug_work);
|
|
|
|
|
|
connector = &hdmi_connector->base;
|
|
connector = &hdmi_connector->base;
|
|
|
|
|