From d4d339079f5626d92953889203fbb28f1cd3fd43 Mon Sep 17 00:00:00 2001 From: xuhuicong Date: Sat, 11 Mar 2017 12:41:56 +0800 Subject: [PATCH] drm/rockchip: hdmi: support RK3328 Change-Id: I7d93f0d494f6824b0b6e2f82c2c1a57342ea551e Signed-off-by: Hans Yang Signed-off-by: Zheng Yang --- .../bindings/display/bridge/dw_hdmi.txt | 1 + .../display/rockchip/dw_hdmi-rockchip.txt | 1 + drivers/gpu/drm/bridge/dw-hdmi.c | 35 +- drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c | 621 +++++++++++++++++- include/drm/bridge/dw_hdmi.h | 6 +- 5 files changed, 651 insertions(+), 13 deletions(-) diff --git a/Documentation/devicetree/bindings/display/bridge/dw_hdmi.txt b/Documentation/devicetree/bindings/display/bridge/dw_hdmi.txt index f50d4d5dd254..531d6463b627 100644 --- a/Documentation/devicetree/bindings/display/bridge/dw_hdmi.txt +++ b/Documentation/devicetree/bindings/display/bridge/dw_hdmi.txt @@ -6,6 +6,7 @@ Required properties: * "fsl,imx6q-hdmi" * "fsl,imx6dl-hdmi" * "rockchip,rk3288-dw-hdmi" + * "rockchip,rk3328-dw-hdmi" * "rockchip,rk3399-dw-hdmi" - reg: Physical base address and length of the controller's registers. - interrupts: The HDMI interrupt number diff --git a/Documentation/devicetree/bindings/display/rockchip/dw_hdmi-rockchip.txt b/Documentation/devicetree/bindings/display/rockchip/dw_hdmi-rockchip.txt index 351c94be2dfa..a023c3304e44 100644 --- a/Documentation/devicetree/bindings/display/rockchip/dw_hdmi-rockchip.txt +++ b/Documentation/devicetree/bindings/display/rockchip/dw_hdmi-rockchip.txt @@ -3,6 +3,7 @@ Rockchip specific extensions to the Synopsys Designware HDMI Required properties: - compatible: "rockchip,rk3288-dw-hdmi", + "rockchip,rk3328-dw-hdmi", "rockchip,rk3368-dw-hdmi", "rockchip,rk3399-dw-hdmi"; - reg: Physical base address and length of the controller's registers. diff --git a/drivers/gpu/drm/bridge/dw-hdmi.c b/drivers/gpu/drm/bridge/dw-hdmi.c index c4af0fc3a17a..912210b54497 100644 --- a/drivers/gpu/drm/bridge/dw-hdmi.c +++ b/drivers/gpu/drm/bridge/dw-hdmi.c @@ -1418,12 +1418,13 @@ static void dw_hdmi_phy_disable(struct dw_hdmi *hdmi, void *data) dw_hdmi_phy_power_off(hdmi); } -static enum drm_connector_status dw_hdmi_phy_read_hpd(struct dw_hdmi *hdmi, +enum drm_connector_status dw_hdmi_phy_read_hpd(struct dw_hdmi *hdmi, void *data) { return hdmi_readb(hdmi, HDMI_PHY_STAT0) & HDMI_PHY_HPD ? connector_status_connected : connector_status_disconnected; } +EXPORT_SYMBOL_GPL(dw_hdmi_phy_read_hpd); static const struct dw_hdmi_phy_ops dw_hdmi_synopsys_phy_ops = { .init = dw_hdmi_phy_init, @@ -2366,7 +2367,12 @@ static int dw_hdmi_detect_phy(struct dw_hdmi *hdmi) phy_type = hdmi_readb(hdmi, HDMI_CONFIG2_ID); - if (phy_type == DW_HDMI_PHY_VENDOR_PHY) { + /* + * RK3328 phy_type is DW_HDMI_PHY_DWC_HDMI20_TX_PHY, + * but it has a vedor phy. + */ + if (phy_type == DW_HDMI_PHY_VENDOR_PHY || + hdmi->dev_type == RK3328_HDMI) { /* Vendor PHYs require support from the glue layer. */ if (!hdmi->plat_data->phy_ops || !hdmi->plat_data->phy_name) { dev_err(hdmi->dev, @@ -2528,12 +2534,20 @@ static const struct file_operations dw_hdmi_ctrl_fops = { static int dw_hdmi_phy_show(struct seq_file *s, void *v) { struct dw_hdmi *hdmi = s->private; - u32 i; + u32 i, total, val; - seq_puts(s, "\n>>>hdmi_phy reg "); - for (i = 0; i < 0x28; i++) - seq_printf(s, "regs %02x val %04x\n", - i, hdmi_phy_i2c_read(hdmi, i)); + seq_puts(s, "\n>>>hdmi_phy reg\n"); + if (hdmi->dev_type != RK3328_HDMI) + total = 0x28; + else + total = 0x100; + for (i = 0; i < total; i++) { + if (hdmi->dev_type != RK3328_HDMI) + val = hdmi_phy_i2c_read(hdmi, i); + else + val = hdmi->phy.ops->read(hdmi, hdmi->phy.data, i); + seq_printf(s, "regs %02x val %04x\n", i, val); + } return 0; } @@ -2555,13 +2569,16 @@ dw_hdmi_phy_write(struct file *file, const char __user *buf, return -EFAULT; if (sscanf(kbuf, "%x%x", ®, &val) == -1) return -EFAULT; - if ((reg < 0) || (reg > 0x28)) { + if ((reg < 0) || (reg > 0x100)) { dev_err(hdmi->dev, "it is not a hdmi phy register\n"); return count; } dev_info(hdmi->dev, "/*******hdmi phy register config******/"); dev_info(hdmi->dev, "\n reg=%x val=%x\n", reg, val); - dw_hdmi_phy_i2c_write(hdmi, val, reg); + if (hdmi->dev_type != RK3328_HDMI) + dw_hdmi_phy_i2c_write(hdmi, val, reg); + else + hdmi->phy.ops->write(hdmi, hdmi->phy.data, val, reg); return count; } diff --git a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c index 66a220005cbb..8c67659da743 100644 --- a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c +++ b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c @@ -8,9 +8,11 @@ */ #include +#include #include #include #include +#include #include #include @@ -29,19 +31,567 @@ #define RK3399_GRF_SOC_CON20 0x6250 #define RK3399_HDMI_LCDC_SEL BIT(6) +#define RK3328_GRF_SOC_CON2 0x0408 +#define RK3328_DDC_MASK_EN ((3 << 10) | (3 << (10 + 16))) +#define RK3328_GRF_SOC_CON3 0x040c +#define RK3328_IO_CTRL_BY_HDMI (0xf0000000 | BIT(13) | BIT(12)) +#define RK3328_GRF_SOC_CON4 0x0410 +#define RK3328_IO_3V_DOMAIN (7 << (9 + 16)) +#define RK3328_IO_5V_DOMAIN ((7 << 9) | (3 << (9 + 16))) +#define RK3328_HPD_3V (BIT(8 + 16) | BIT(13 + 16)) + #define HIWORD_UPDATE(val, mask) (val | (mask) << 16) struct rockchip_hdmi { struct device *dev; struct regmap *regmap; + void __iomem *hdmiphy; struct drm_encoder encoder; enum dw_hdmi_devtype dev_type; struct clk *vpll_clk; struct clk *grf_clk; + struct clk *hdmiphy_pclk; + struct clk *hdmiphy_clk; + struct clk *hclk_vio; + struct clk_hw hdmiphy_clkhw; }; #define to_rockchip_hdmi(x) container_of(x, struct rockchip_hdmi, x) +/* + * Inno HDMI phy pre pll output 4 clock: tmdsclock_tx/tmdsclock_ctrl/ + * preclock_ctrl/pclk_vop. + * tmdsclock_tx is used for transmit hdmi signal, it's is equal to + * tmdsclock. + * Ftmdsclock_tx = Fvco / (4 * tmds_div_a * tmds_div_b) + * tmdsclock_ctrl is output to dw-hdmi controller, it's is equal to + * tmdsclock. + * Ftmdsclock_ctrl = Fvco / (4 * tmds_div_a * tmds_div_c) + * preclock_ctrl is output to dw-hdmi controller, it's equal to mode + * pixel clock. + * Fpreclock_ctrl = (pclk_div_a == 1) ? + * Fvco / (pclk_div_b * pclk_div_c) : + * Fvco / (pclk_div_a * pclk_div_c) : + * pclk_vop is clock source of dclk_lcdc, it's equal to mode pixel clock. + * Fpclk_vop = (vco_div_5 == 1) ? Fvco / 5 : (pclk_div_a == 1) ? + * Fvco / (2 * pclk_div_b * pclk_div_d) : + * Fvco / (2 * pclk_div_a * pclk_div_d) + * Fvco = Fref * (nf + frac / (1 << 24)) / nd + */ +struct inno_pre_pll_config { + unsigned long pixclock; + u32 tmdsclock; + u8 nd; + u16 nf; + u8 tmds_div_a; + u8 tmds_div_b; + u8 tmds_div_c; + u8 pclk_div_a; + u8 pclk_div_b; + u8 pclk_div_c; + u8 pclk_div_d; + u8 vco_div_5; + u32 frac; +}; + +struct inno_post_pll_config { + unsigned long tmdsclock; + u8 nd; + u16 nf; + u8 no; + u8 version; +}; + +struct inno_phy_config { + unsigned long tmdsclock; + u8 regs[14]; +}; + +static const struct inno_pre_pll_config inno_pre_pll_cfg[] = { + {27000000, 27000000, 1, 90, 3, 2, + 2, 10, 3, 3, 4, 0, 0}, + {27000000, 33750000, 1, 90, 1, 3, + 3, 10, 3, 3, 4, 0, 0}, + {40000000, 40000000, 1, 80, 2, 2, + 2, 12, 2, 2, 2, 0, 0}, + {59341000, 59341000, 1, 98, 3, 1, + 2, 1, 3, 3, 4, 0, 0xE6AE6B}, + {59400000, 59400000, 1, 99, 3, 1, + 1, 1, 3, 3, 4, 0, 0}, + {59341000, 74176250, 1, 98, 0, 3, + 3, 1, 3, 3, 4, 0, 0xE6AE6B}, + {59400000, 74250000, 1, 99, 1, 2, + 2, 1, 3, 3, 4, 0, 0}, + {74176000, 74176000, 1, 98, 1, 2, + 2, 1, 2, 3, 4, 0, 0xE6AE6B}, + {74250000, 74250000, 1, 99, 1, 2, + 2, 1, 2, 3, 4, 0, 0}, + {74176000, 92720000, 4, 494, 1, 2, + 2, 1, 3, 3, 4, 0, 0x816817}, + {74250000, 92812500, 4, 495, 1, 2, + 2, 1, 3, 3, 4, 0, 0}, + {148352000, 148352000, 1, 98, 1, 1, + 1, 1, 2, 2, 2, 0, 0xE6AE6B}, + {148500000, 148500000, 1, 99, 1, 1, + 1, 1, 2, 2, 2, 0, 0}, + {148352000, 185440000, 4, 494, 0, 2, + 2, 1, 3, 2, 2, 0, 0x816817}, + {148500000, 185625000, 4, 495, 0, 2, + 2, 1, 3, 2, 2, 0, 0}, + {296703000, 296703000, 1, 98, 0, 1, + 1, 1, 0, 2, 2, 0, 0xE6AE6B}, + {297000000, 297000000, 1, 99, 0, 1, + 1, 1, 0, 2, 2, 0, 0}, + {296703000, 370878750, 4, 494, 1, 2, + 0, 1, 3, 1, 1, 0, 0x816817}, + {297000000, 371250000, 4, 495, 1, 2, + 0, 1, 3, 1, 1, 0, 0}, + {593407000, 296703500, 1, 98, 0, 1, + 1, 1, 0, 2, 1, 0, 0xE6AE6B}, + {594000000, 297000000, 1, 99, 0, 1, + 1, 1, 0, 2, 1, 0, 0}, + {593407000, 370879375, 4, 494, 1, 2, + 0, 1, 3, 1, 1, 1, 0x816817}, + {594000000, 371250000, 4, 495, 1, 2, + 0, 1, 3, 1, 1, 1, 0}, + {593407000, 593407000, 1, 98, 0, 2, + 0, 1, 0, 1, 1, 0, 0xE6AE6B}, + {594000000, 594000000, 1, 99, 0, 2, + 0, 1, 0, 1, 1, 0, 0}, + {~0UL, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0}, +}; + +static const struct inno_post_pll_config inno_post_pll_cfg[] = { + {33750000, 1, 40, 8, 1}, + {33750000, 1, 80, 8, 2}, + {74250000, 1, 40, 8, 1}, + {74250000, 18, 80, 8, 2}, + {148500000, 2, 40, 4, 3}, + {297000000, 4, 40, 2, 3}, + {371250000, 8, 40, 1, 3}, + {~0UL, 0, 0, 0, 0}, +}; + +static const struct inno_phy_config inno_phy_cfg[] = { + { 165000000, { + 0x07, 0x08, 0x08, 0x08, 0x00, 0x00, 0x08, 0x08, 0x08, + 0x00, 0xac, 0xcc, 0xcc, 0xcc, + }, + }, { + 340000000, { + 0x0b, 0x0d, 0x0d, 0x0d, 0x07, 0x15, 0x08, 0x08, 0x08, + 0x3f, 0xac, 0xcc, 0xcd, 0xdd, + }, + }, { + 594000000, { + 0x10, 0x1a, 0x1a, 0x1a, 0x07, 0x15, 0x08, 0x08, 0x08, + 0x00, 0xac, 0xcc, 0xcc, 0xcc, + }, + }, { + ~0UL, { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, + }, + } +}; + +static void inno_phy_writel(struct rockchip_hdmi *hdmi, int offset, u8 val) +{ + writel(val, hdmi->hdmiphy + (offset << 2)); +} + +static u8 inno_phy_readl(struct rockchip_hdmi *hdmi, int offset) +{ + return readl(hdmi->hdmiphy + (offset << 2)); +} + +static void +inno_phy_modeb(struct rockchip_hdmi *hdmi, u8 data, u8 mask, int offset) +{ + u8 val = inno_phy_readl(hdmi, offset) & ~mask; + + val |= data & mask; + inno_phy_writel(hdmi, offset, val); +} + +static int inno_dw_hdmi_phy_read(struct dw_hdmi *hdmi, void *data, int offset) +{ + struct rockchip_hdmi *rockchip_hdmi = (struct rockchip_hdmi *)data; + + return inno_phy_readl(rockchip_hdmi, offset); +} + +static void +inno_dw_hdmi_phy_write(struct dw_hdmi *hdmi, void *data, int val, int offset) +{ + struct rockchip_hdmi *rockchip_hdmi = (struct rockchip_hdmi *)data; + + inno_phy_writel(rockchip_hdmi, offset, val); +} + +static int +inno_dw_hdmi_phy_init(struct dw_hdmi *dw_hdmi, void *data, + struct drm_display_mode *mode) +{ + struct rockchip_hdmi *hdmi = (struct rockchip_hdmi *)data; + const struct inno_post_pll_config *inno_pll_config = inno_post_pll_cfg; + const struct inno_phy_config *inno_phy_config = inno_phy_cfg; + u32 val, i, chipversion = 1; + + if (rockchip_get_cpu_version()) + chipversion = 2; + + for (; inno_pll_config->tmdsclock != ~0UL; inno_pll_config++) + if (inno_pll_config->tmdsclock >= (mode->crtc_clock * 1000) && + inno_pll_config->version & chipversion) + break; + for (; inno_phy_config->tmdsclock != ~0UL; inno_phy_config++) + if (inno_phy_config->tmdsclock >= (mode->crtc_clock * 1000)) + break; + if (inno_pll_config->tmdsclock == ~0UL && + inno_phy_config->tmdsclock == ~0UL) + return -EINVAL; + + /* set pdata_en to 0 */ + inno_phy_modeb(hdmi, 0, 1, 0x01); + /* Power off post PLL */ + inno_phy_modeb(hdmi, 1, 1, 0xaa); + + val = inno_pll_config->nf & 0xff; + inno_phy_writel(hdmi, 0xac, val); + if (inno_pll_config->no == 1) { + inno_phy_writel(hdmi, 0xaa, 2); + val = (inno_pll_config->nf >> 8) | + inno_pll_config->nd; + inno_phy_writel(hdmi, 0xab, val); + } else { + val = (inno_pll_config->no / 2) - 1; + inno_phy_writel(hdmi, 0xad, val); + val = (inno_pll_config->nf >> 8) | + inno_pll_config->nd; + inno_phy_writel(hdmi, 0xab, val); + inno_phy_writel(hdmi, 0xaa, 0x0e); + } + /* Power up post PLL */ + inno_phy_modeb(hdmi, 0, 1, 0xaa); + /* Wait for post PLL lock */ + for (i = 0; i < 5; ++i) { + if (inno_phy_readl(hdmi, 0xaf) & 1) + break; + usleep_range(1000, 2000); + } + + for (i = 0; i < 14; i++) + inno_phy_writel(hdmi, 0xb5 + i, inno_phy_config->regs[i]); + + /* bit[7:6] of reg c8/c9/ca/c8 is ESD detect threshold: + * 00 - 340mV + * 01 - 280mV + * 10 - 260mV + * 11 - 240mV + * default is 240mV, now we set it to 340mV + */ + inno_phy_writel(hdmi, 0xc8, 0); + inno_phy_writel(hdmi, 0xc9, 0); + inno_phy_writel(hdmi, 0xca, 0); + inno_phy_writel(hdmi, 0xcb, 0); + + if (inno_phy_config->tmdsclock > 340000000) { + /* Set termination resistor to 100ohm */ + val = clk_get_rate(hdmi->hdmiphy_pclk) / 100000; + inno_phy_writel(hdmi, 0xc5, ((val >> 8) & 0xff) | 0x80); + inno_phy_writel(hdmi, 0xc6, val & 0xff); + inno_phy_writel(hdmi, 0xc7, 3 << 1); + inno_phy_writel(hdmi, 0xc5, ((val >> 8) & 0xff)); + } else if (inno_phy_config->tmdsclock > 165000000) { + inno_phy_writel(hdmi, 0xc5, 0x81); + /* clk termination resistor is 50ohm + * data termination resistor is 150ohm + */ + inno_phy_writel(hdmi, 0xc8, 0x30); + inno_phy_writel(hdmi, 0xc9, 0x10); + inno_phy_writel(hdmi, 0xca, 0x10); + inno_phy_writel(hdmi, 0xcb, 0x10); + } else { + inno_phy_writel(hdmi, 0xc5, 0x81); + } + + if (inno_phy_config->tmdsclock > 340000000) + msleep(100); + + inno_phy_modeb(hdmi, 4, 4, 0xb0); + inno_phy_writel(hdmi, 0xb2, 0x0f); + /* set pdata_en to 1 */ + inno_phy_modeb(hdmi, 1, 1, 0x01); + return 0; +} + +static void inno_dw_hdmi_phy_disable(struct dw_hdmi *dw_hdmi, void *data) +{ + struct rockchip_hdmi *hdmi = (struct rockchip_hdmi *)data; + + /* Power off driver */ + inno_phy_writel(hdmi, 0xb2, 0); + /* Power off band gap */ + inno_phy_modeb(hdmi, 0, 4, 0xb0); + /* Power off post pll */ + inno_phy_modeb(hdmi, 1, 1, 0xaa); +} + +static enum drm_connector_status +inno_dw_hdmi_phy_read_hpd(struct dw_hdmi *dw_hdmi, void *data) +{ + struct rockchip_hdmi *hdmi = (struct rockchip_hdmi *)data; + enum drm_connector_status status; + + status = dw_hdmi_phy_read_hpd(dw_hdmi, data); + if (status == connector_status_connected) + regmap_write(hdmi->regmap, + RK3328_GRF_SOC_CON4, + RK3328_IO_5V_DOMAIN); + else + regmap_write(hdmi->regmap, + RK3328_GRF_SOC_CON4, + RK3328_IO_3V_DOMAIN); + return status; +} + +static int inno_dw_hdmi_phy_pll_prepare(struct clk_hw *hw) +{ + struct rockchip_hdmi *hdmi = + container_of(hw, struct rockchip_hdmi, hdmiphy_clkhw); + + if (inno_phy_readl(hdmi, 0xa0) & 1) + inno_phy_modeb(hdmi, 0, 1, 0xa0); + return 0; +} + +static void inno_dw_hdmi_phy_pll_unprepare(struct clk_hw *hw) +{ + struct rockchip_hdmi *hdmi = + container_of(hw, struct rockchip_hdmi, hdmiphy_clkhw); + + if ((inno_phy_readl(hdmi, 0xa0) & 1) == 0) + inno_phy_modeb(hdmi, 1, 1, 0xa0); +} + +static int inno_dw_hdmi_phy_is_prepared(struct clk_hw *hw) +{ + struct rockchip_hdmi *hdmi = + container_of(hw, struct rockchip_hdmi, hdmiphy_clkhw); + + return (inno_phy_readl(hdmi, 0xa0) & 1) ? 0 : 1; +} + +static unsigned long +inno_dw_hdmi_phy_pll_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct rockchip_hdmi *hdmi = + container_of(hw, struct rockchip_hdmi, hdmiphy_clkhw); + unsigned long rate, vco, frac; + u8 nd, no_a, no_b, no_c, no_d; + u16 nf; + + nd = inno_phy_readl(hdmi, 0xa1) & 0x3f; + nf = ((inno_phy_readl(hdmi, 0xa2) & 0x0f) << 8) | + inno_phy_readl(hdmi, 0xa3); + vco = parent_rate * nf; + if ((inno_phy_readl(hdmi, 0xa2) & 0x30) == 0) { + frac = inno_phy_readl(hdmi, 0xd3) | + (inno_phy_readl(hdmi, 0xd2) << 8) | + (inno_phy_readl(hdmi, 0xd1) << 16); + vco += DIV_ROUND_CLOSEST(parent_rate * frac, (1 << 24)); + } + if (inno_phy_readl(hdmi, 0xa0) & 2) { + rate = vco / (nd * 5); + } else { + no_a = inno_phy_readl(hdmi, 0xa5) & 0x1f; + no_b = ((inno_phy_readl(hdmi, 0xa5) >> 5) & 7) + 2; + no_c = (1 << ((inno_phy_readl(hdmi, 0xa6) >> 5) & 7)); + no_d = inno_phy_readl(hdmi, 0xa6) & 0x1f; + if (no_a == 1) + rate = vco / (nd * no_b * no_d * 2); + else + rate = vco / (nd * no_a * no_d * 2); + } + + return rate; +} + +static long +inno_dw_hdmi_phy_pll_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + const struct inno_pre_pll_config *inno_pll_config = inno_pre_pll_cfg; + + for (; inno_pll_config->pixclock != ~0UL; inno_pll_config++) + if (inno_pll_config->pixclock == rate) + break; + /* XXX: Limit pixel clock under 300MHz */ + if (inno_pll_config->pixclock < 300000000) + return inno_pll_config->pixclock; + else + return ~0UL; +} + +static int +inno_dw_hdmi_phy_pll_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct rockchip_hdmi *hdmi = + container_of(hw, struct rockchip_hdmi, hdmiphy_clkhw); + const struct inno_pre_pll_config *inno_pll_config = inno_pre_pll_cfg; + u32 val, i; + + for (; inno_pll_config->pixclock != ~0UL; inno_pll_config++) + if (inno_pll_config->pixclock == rate && + inno_pll_config->tmdsclock == rate) + break; + if (inno_pll_config->pixclock == ~0UL) { + dev_err(hdmi->dev, "unsupported rate %lu\n", rate); + return -EINVAL; + } + /* Power off PLL */ + inno_phy_modeb(hdmi, 1, 1, 0xa0); + /* Configure pre-pll */ + inno_phy_modeb(hdmi, (inno_pll_config->vco_div_5 & 1) << 1, 2, 0xa0); + inno_phy_writel(hdmi, 0xa1, inno_pll_config->nd); + if (inno_pll_config->frac) + val = ((inno_pll_config->nf >> 8) & 0x0f) | 0xc0; + else + val = ((inno_pll_config->nf >> 8) & 0x0f) | 0xf0; + inno_phy_writel(hdmi, 0xa2, val); + val = inno_pll_config->nf & 0xff; + inno_phy_writel(hdmi, 0xa3, val); + val = (inno_pll_config->pclk_div_a & 0x1f) | + ((inno_pll_config->pclk_div_b & 3) << 5); + inno_phy_writel(hdmi, 0xa5, val); + val = (inno_pll_config->pclk_div_d & 0x1f) | + ((inno_pll_config->pclk_div_c & 3) << 5); + inno_phy_writel(hdmi, 0xa6, val); + val = ((inno_pll_config->tmds_div_a & 3) << 4) | + ((inno_pll_config->tmds_div_b & 3) << 2) | + (inno_pll_config->tmds_div_c & 3); + inno_phy_writel(hdmi, 0xa4, val); + + if (inno_pll_config->frac) { + val = inno_pll_config->frac & 0xff; + inno_phy_writel(hdmi, 0xd3, val); + val = (inno_pll_config->frac >> 8) & 0xff; + inno_phy_writel(hdmi, 0xd2, val); + val = (inno_pll_config->frac >> 16) & 0xff; + inno_phy_writel(hdmi, 0xd1, val); + } else { + inno_phy_writel(hdmi, 0xd3, 0); + inno_phy_writel(hdmi, 0xd2, 0); + inno_phy_writel(hdmi, 0xd1, 0); + } + + /* Power up PLL */ + inno_phy_modeb(hdmi, 0, 1, 0xa0); + + /* Wait for PLL lock */ + for (i = 0; i < 5; ++i) { + if (inno_phy_readl(hdmi, 0xa9) & 1) + break; + usleep_range(1000, 2000); + } + return 0; +} + +static const struct clk_ops inno_dw_hdmi_phy_pll_clk_norate_ops = { + .prepare = inno_dw_hdmi_phy_pll_prepare, + .unprepare = inno_dw_hdmi_phy_pll_unprepare, + .is_prepared = inno_dw_hdmi_phy_is_prepared, + .recalc_rate = inno_dw_hdmi_phy_pll_recalc_rate, + .round_rate = inno_dw_hdmi_phy_pll_round_rate, + .set_rate = inno_dw_hdmi_phy_pll_set_rate, +}; + +static void inno_dw_hdmi_phy_clk_unregister(void *data) +{ + struct rockchip_hdmi *hdmi = data; + + of_clk_del_provider(hdmi->dev->of_node); + clk_unregister(hdmi->hdmiphy_clk); +} + +static int inno_dw_hdmi_phy_clk_register(struct rockchip_hdmi *hdmi) +{ + struct device_node *node = hdmi->dev->of_node; + struct clk_init_data init; + const char *clk_name = "xin24m"; + int ret; + + init.flags = 0; + init.name = "hdmi_phy"; + init.ops = &inno_dw_hdmi_phy_pll_clk_norate_ops; + init.parent_names = &clk_name; + init.num_parents = 1; + + /* optional override of the clockname */ + of_property_read_string(node, "clock-output-names", &init.name); + + hdmi->hdmiphy_clkhw.init = &init; + + /* register the clock */ + hdmi->hdmiphy_clk = clk_register(hdmi->dev, &hdmi->hdmiphy_clkhw); + if (IS_ERR(hdmi->hdmiphy_clk)) { + ret = PTR_ERR(hdmi->hdmiphy_clk); + goto err_ret; + } + ret = of_clk_add_provider(node, of_clk_src_simple_get, + hdmi->hdmiphy_clk); + if (ret < 0) + goto err_clk_provider; + + ret = devm_add_action(hdmi->dev, inno_dw_hdmi_phy_clk_unregister, + hdmi); + if (ret < 0) + goto err_unreg_action; + return 0; +err_unreg_action: + of_clk_del_provider(node); +err_clk_provider: + clk_unregister(hdmi->hdmiphy_clk); +err_ret: + return ret; +} + +static int rk3328_hdmi_init(struct rockchip_hdmi *hdmi) +{ + int ret; + + ret = clk_prepare_enable(hdmi->grf_clk); + if (ret < 0) { + dev_err(hdmi->dev, "failed to enable grfclk %d\n", ret); + return -EPROBE_DEFER; + } + /* Map HPD pin to 3V io */ + regmap_write(hdmi->regmap, + RK3328_GRF_SOC_CON4, + RK3328_IO_3V_DOMAIN | + RK3328_HPD_3V); + /* Map ddc pin to 5V io */ + regmap_write(hdmi->regmap, + RK3328_GRF_SOC_CON3, + RK3328_IO_CTRL_BY_HDMI); + regmap_write(hdmi->regmap, + RK3328_GRF_SOC_CON2, + RK3328_DDC_MASK_EN | + BIT(18)); + clk_disable_unprepare(hdmi->grf_clk); + /* + * Use phy internal register control + * rxsense/poweron/pllpd/pdataen signal. + */ + inno_phy_writel(hdmi, 0x01, 0x07); + inno_phy_writel(hdmi, 0x02, 0x91); + return 0; +} + /* * There are some rates that would be ranged for better clock jitter at * Chrome OS tree, like 25.175Mhz would range to 25.170732Mhz. But due @@ -229,12 +779,46 @@ static int rockchip_hdmi_parse_dt(struct rockchip_hdmi *hdmi) return PTR_ERR(hdmi->grf_clk); } + hdmi->hclk_vio = devm_clk_get(hdmi->dev, "hclk_vio"); + if (PTR_ERR(hdmi->hclk_vio) == -ENOENT) { + hdmi->hclk_vio = NULL; + } else if (PTR_ERR(hdmi->hclk_vio) == -EPROBE_DEFER) { + return -EPROBE_DEFER; + } else if (IS_ERR(hdmi->hclk_vio)) { + dev_err(hdmi->dev, "failed to get hclk_vio clock\n"); + return PTR_ERR(hdmi->hclk_vio); + } + + hdmi->hdmiphy_pclk = devm_clk_get(hdmi->dev, "pclk_hdmiphy"); + if (PTR_ERR(hdmi->hdmiphy_pclk) == -ENOENT) { + hdmi->hdmiphy_pclk = NULL; + } else if (PTR_ERR(hdmi->hdmiphy_pclk) == -EPROBE_DEFER) { + return -EPROBE_DEFER; + } else if (IS_ERR(hdmi->hdmiphy_pclk)) { + dev_err(hdmi->dev, "failed to get pclk_hdmiphy clock\n"); + return PTR_ERR(hdmi->hdmiphy_pclk); + } + ret = clk_prepare_enable(hdmi->vpll_clk); if (ret) { dev_err(hdmi->dev, "Failed to enable HDMI vpll: %d\n", ret); return ret; } + ret = clk_prepare_enable(hdmi->hclk_vio); + if (ret) { + dev_err(hdmi->dev, "Failed to eanble HDMI hclk_vio: %d\n", + ret); + return ret; + } + + ret = clk_prepare_enable(hdmi->hdmiphy_pclk); + if (ret) { + dev_err(hdmi->dev, "Failed to eanble HDMI pclk_hdmiphy: %d\n", + ret); + return ret; + } + if (of_get_property(np, "rockchip,phy-table", &val)) { phy_config = kmalloc(val, GFP_KERNEL); if (!phy_config) { @@ -395,6 +979,14 @@ static const struct drm_encoder_helper_funcs dw_hdmi_rockchip_encoder_helper_fun .atomic_check = dw_hdmi_rockchip_encoder_atomic_check, }; +static const struct dw_hdmi_phy_ops inno_dw_hdmi_phy_ops = { + .init = inno_dw_hdmi_phy_init, + .disable = inno_dw_hdmi_phy_disable, + .read_hpd = inno_dw_hdmi_phy_read_hpd, + .read = inno_dw_hdmi_phy_read, + .write = inno_dw_hdmi_phy_write, +}; + static const struct dw_hdmi_plat_data rk3288_hdmi_drv_data = { .mode_valid = dw_hdmi_rockchip_mode_valid, .mpll_cfg = rockchip_mpll_cfg, @@ -404,6 +996,13 @@ static const struct dw_hdmi_plat_data rk3288_hdmi_drv_data = { .tmds_n_table = rockchip_werid_tmds_n_table, }; +static const struct dw_hdmi_plat_data rk3328_hdmi_drv_data = { + .mode_valid = dw_hdmi_rockchip_mode_valid, + .phy_ops = &inno_dw_hdmi_phy_ops, + .phy_name = "inno_dw_hdmi_phy2", + .dev_type = RK3328_HDMI, +}; + static const struct dw_hdmi_plat_data rk3368_hdmi_drv_data = { .mode_valid = dw_hdmi_rockchip_mode_valid, .mpll_cfg = rockchip_mpll_cfg, @@ -424,6 +1023,10 @@ static const struct of_device_id dw_hdmi_rockchip_dt_ids[] = { { .compatible = "rockchip,rk3288-dw-hdmi", .data = &rk3288_hdmi_drv_data }, + { + .compatible = "rockchip,rk3328-dw-hdmi", + .data = &rk3328_hdmi_drv_data + }, { .compatible = "rockchip,rk3368-dw-hdmi", .data = &rk3368_hdmi_drv_data @@ -439,12 +1042,12 @@ static int dw_hdmi_rockchip_bind(struct device *dev, struct device *master, void *data) { struct platform_device *pdev = to_platform_device(dev); - const struct dw_hdmi_plat_data *plat_data; + struct dw_hdmi_plat_data *plat_data; const struct of_device_id *match; struct drm_device *drm = data; struct drm_encoder *encoder; struct rockchip_hdmi *hdmi; - struct resource *iores; + struct resource *iores, *phyres; int irq; int ret; @@ -456,7 +1059,7 @@ static int dw_hdmi_rockchip_bind(struct device *dev, struct device *master, return -ENOMEM; match = of_match_node(dw_hdmi_rockchip_dt_ids, pdev->dev.of_node); - plat_data = match->data; + plat_data = (struct dw_hdmi_plat_data *)match->data; hdmi->dev = &pdev->dev; hdmi->dev_type = plat_data->dev_type; encoder = &hdmi->encoder; @@ -485,6 +1088,18 @@ static int dw_hdmi_rockchip_bind(struct device *dev, struct device *master, return ret; } + if (hdmi->dev_type == RK3328_HDMI) { + phyres = platform_get_resource(pdev, IORESOURCE_MEM, 1); + hdmi->hdmiphy = devm_ioremap_resource(dev, phyres); + if (IS_ERR(hdmi->hdmiphy)) + return PTR_ERR(hdmi->hdmiphy); + plat_data->phy_data = hdmi; + ret = rk3328_hdmi_init(hdmi); + if (ret < 0) + return ret; + inno_dw_hdmi_phy_clk_register(hdmi); + } + drm_encoder_helper_add(encoder, &dw_hdmi_rockchip_encoder_helper_funcs); drm_encoder_init(drm, encoder, &dw_hdmi_rockchip_encoder_funcs, DRM_MODE_ENCODER_TMDS, NULL); diff --git a/include/drm/bridge/dw_hdmi.h b/include/drm/bridge/dw_hdmi.h index f5447b6a4d34..e1dc2a93bb89 100644 --- a/include/drm/bridge/dw_hdmi.h +++ b/include/drm/bridge/dw_hdmi.h @@ -25,6 +25,7 @@ enum dw_hdmi_devtype { IMX6Q_HDMI, IMX6DL_HDMI, RK3288_HDMI, + RK3328_HDMI, RK3368_HDMI, RK3399_HDMI, }; @@ -71,6 +72,8 @@ struct dw_hdmi_phy_ops { struct drm_display_mode *mode); void (*disable)(struct dw_hdmi *hdmi, void *data); enum drm_connector_status (*read_hpd)(struct dw_hdmi *hdmi, void *data); + int (*read)(struct dw_hdmi *hdmi, void *data, int offset); + void (*write)(struct dw_hdmi *hdmi, void *data, int val, int offset); }; struct dw_hdmi_plat_data { @@ -99,7 +102,8 @@ int dw_hdmi_bind(struct device *dev, struct device *master, const struct dw_hdmi_plat_data *plat_data); void dw_hdmi_suspend(struct device *dev); void dw_hdmi_resume(struct device *dev); - +enum drm_connector_status dw_hdmi_phy_read_hpd(struct dw_hdmi *hdmi, + void *data); void dw_hdmi_set_sample_rate(struct dw_hdmi *hdmi, unsigned int rate); void dw_hdmi_audio_enable(struct dw_hdmi *hdmi); void dw_hdmi_audio_disable(struct dw_hdmi *hdmi); -- 2.34.1