From 5e211fd0670b69a46ce2cfb419477b0566487c37 Mon Sep 17 00:00:00 2001 From: Algea Cao Date: Thu, 17 Nov 2022 22:05:38 +0800 Subject: [PATCH] drm/rockchip: dw_hdmi: Workaround for RK3528 hdmi hpd RK3528 hdmi hpd 5v io is always being pulled down. This workaround is set hpd 5v io to gpio function and receive hpd signal from sink. When hpd status is changed, gpio interrupt will trigger, then set the hdmi_snk_det reg to change the hpd status of the hdmi controller. Finally trigger the hpd interrupt of the hdmi controller. Signed-off-by: Algea Cao Change-Id: Iac5140a09d3dda172f0125a25dbc8b281c8e0fa5 --- drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c | 96 ++++++++++++++++++++- 1 file changed, 93 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c index adb429e6dfbc..16cccabb9701 100644 --- a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c +++ b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -64,10 +65,21 @@ #define RK3399_HDMI_LCDC_SEL BIT(6) #define RK3528_VO_GRF_HDMI_MASK 0x60014 +#define RK3528_HDMI_SNKDET_SEL BIT(6) +#define RK3528_HDMI_SNKDET BIT(5) #define RK3528_HDMI_CECIN_MSK BIT(2) #define RK3528_HDMI_SDAIN_MSK BIT(1) #define RK3528_HDMI_SCLIN_MSK BIT(0) +#define RK3528PMU_GRF_SOC_CON6 0x70018 +#define RK3528_HDMI_SDA5V_GRF BIT(6) +#define RK3528_HDMI_SCL5V_GRF BIT(5) +#define RK3528_HDMI_CEC5V_GRF BIT(4) +#define RK3528_HDMI_HPD5V_GRF BIT(3) + +#define RK3528_GPIO_SWPORT_DR_L 0x0000 +#define RK3528_GPIO0_A2_DR BIT(2) + #define RK3568_GRF_VO_CON1 0x0364 #define RK3568_HDMI_SDAIN_MSK BIT(15) #define RK3568_HDMI_SCLIN_MSK BIT(14) @@ -154,6 +166,7 @@ struct rockchip_hdmi { struct device *dev; struct regmap *regmap; struct regmap *vo1_regmap; + void __iomem *gpio_base; struct drm_encoder encoder; struct drm_device *drm_dev; const struct rockchip_hdmi_chip_data *chip_data; @@ -224,6 +237,7 @@ struct rockchip_hdmi { struct delayed_work work; struct workqueue_struct *workqueue; + struct gpio_desc *hpd_gpiod; }; #define to_rockchip_hdmi(x) container_of(x, struct rockchip_hdmi, x) @@ -1259,6 +1273,21 @@ static void init_hpd_work(struct rockchip_hdmi *hdmi) INIT_DELAYED_WORK(&hdmi->work, repo_hpd_event); } +static irqreturn_t rockchip_hdmi_hpd_irq_handler(int irq, void *arg) +{ + u32 val; + struct rockchip_hdmi *hdmi = arg; + + val = gpiod_get_value(hdmi->hpd_gpiod); + if (val) + val = HIWORD_UPDATE(RK3528_HDMI_SNKDET, RK3528_HDMI_SNKDET); + else + val = HIWORD_UPDATE(0, RK3528_HDMI_SNKDET); + regmap_write(hdmi->regmap, RK3528_VO_GRF_HDMI_MASK, val); + + return IRQ_HANDLED; +} + static int rockchip_hdmi_parse_dt(struct rockchip_hdmi *hdmi) { int ret, val, phy_table_size; @@ -1404,6 +1433,47 @@ static int rockchip_hdmi_parse_dt(struct rockchip_hdmi *hdmi) dev_dbg(hdmi->dev, "use default hdmi phy table\n"); } + hdmi->hpd_gpiod = devm_gpiod_get_optional(hdmi->dev, "hpd", GPIOD_IN); + + if (IS_ERR(hdmi->hpd_gpiod)) { + dev_err(hdmi->dev, "error getting HDP GPIO: %ld\n", + PTR_ERR(hdmi->hpd_gpiod)); + return PTR_ERR(hdmi->hpd_gpiod); + } + + if (hdmi->hpd_gpiod) { + struct resource *res; + struct platform_device *pdev = to_platform_device(hdmi->dev); + + /* gpio interrupt reflects hpd status */ + hdmi->hpd_irq = gpiod_to_irq(hdmi->hpd_gpiod); + if (hdmi->hpd_irq < 0) + return -EINVAL; + + ret = devm_request_threaded_irq(hdmi->dev, hdmi->hpd_irq, NULL, + rockchip_hdmi_hpd_irq_handler, + IRQF_TRIGGER_RISING | + IRQF_TRIGGER_FALLING | + IRQF_ONESHOT, + "hdmi-hpd", hdmi); + if (ret) { + dev_err(hdmi->dev, "failed to request hpd IRQ: %d\n", ret); + return ret; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (!res) { + DRM_DEV_ERROR(hdmi->dev, "failed to get gpio regs\n"); + return -EINVAL; + } + + hdmi->gpio_base = devm_ioremap(hdmi->dev, res->start, resource_size(res)); + if (IS_ERR(hdmi->gpio_base)) { + DRM_DEV_ERROR(hdmi->dev, "Unable to get gpio ioregmap\n"); + return PTR_ERR(hdmi->gpio_base); + } + } + return 0; } @@ -2992,7 +3062,7 @@ static const struct dw_hdmi_phy_ops rk3328_hdmi_phy_ops = { static enum drm_connector_status dw_hdmi_rk3528_read_hpd(struct dw_hdmi *dw_hdmi, void *data) { - return connector_status_connected; + return dw_hdmi_phy_read_hpd(dw_hdmi, data); } static void dw_hdmi_rk3528_setup_hpd(struct dw_hdmi *dw_hdmi, void *data) @@ -3000,8 +3070,22 @@ static void dw_hdmi_rk3528_setup_hpd(struct dw_hdmi *dw_hdmi, void *data) struct rockchip_hdmi *hdmi = (struct rockchip_hdmi *)data; u32 val; - val = HIWORD_UPDATE(RK3528_HDMI_SDAIN_MSK | RK3528_HDMI_SCLIN_MSK | RK3528_HDMI_CECIN_MSK, - RK3528_HDMI_SDAIN_MSK | RK3528_HDMI_SCLIN_MSK | RK3528_HDMI_CECIN_MSK); + dw_hdmi_phy_setup_hpd(dw_hdmi, data); + + if (hdmi->hpd_gpiod) { + /* gpio0_a2's input enable is controlled by gpio output data bit */ + val = HIWORD_UPDATE(RK3528_GPIO0_A2_DR, RK3528_GPIO0_A2_DR); + writel(val, hdmi->gpio_base + RK3528_GPIO_SWPORT_DR_L); + + val = HIWORD_UPDATE(RK3528_HDMI_SNKDET_SEL | RK3528_HDMI_SDAIN_MSK | + RK3528_HDMI_SCLIN_MSK, + RK3528_HDMI_SNKDET_SEL | RK3528_HDMI_SDAIN_MSK | + RK3528_HDMI_SCLIN_MSK); + } else { + val = HIWORD_UPDATE(RK3528_HDMI_SDAIN_MSK | RK3528_HDMI_SCLIN_MSK, + RK3528_HDMI_SDAIN_MSK | RK3528_HDMI_SCLIN_MSK); + } + regmap_write(hdmi->regmap, RK3528_VO_GRF_HDMI_MASK, val); } @@ -3531,6 +3615,8 @@ static void dw_hdmi_rockchip_shutdown(struct platform_device *pdev) flush_workqueue(hdmi->workqueue); dw_hdmi_qp_suspend(hdmi->dev, hdmi->hdmi_qp); } else { + if (hdmi->hpd_gpiod) + disable_irq(hdmi->hpd_irq); dw_hdmi_suspend(hdmi->hdmi); } pm_runtime_put_sync(&pdev->dev); @@ -3553,6 +3639,8 @@ static int dw_hdmi_rockchip_suspend(struct device *dev) disable_irq(hdmi->hpd_irq); dw_hdmi_qp_suspend(dev, hdmi->hdmi_qp); } else { + if (hdmi->hpd_gpiod) + disable_irq(hdmi->hpd_irq); dw_hdmi_suspend(hdmi->hdmi); } pm_runtime_put_sync(dev); @@ -3601,6 +3689,8 @@ static int dw_hdmi_rockchip_resume(struct device *dev) enable_irq(hdmi->hpd_irq); drm_helper_hpd_irq_event(hdmi->drm_dev); } else { + if (hdmi->hpd_gpiod) + enable_irq(hdmi->hpd_irq); dw_hdmi_resume(hdmi->hdmi); } pm_runtime_get_sync(dev);