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);