From 95843d5e61fc05201ce6584e1c33e8142b2e3410 Mon Sep 17 00:00:00 2001 From: Zheng Yang Date: Tue, 11 Jul 2017 16:03:48 +0800 Subject: [PATCH] phy: rockchip-inno-hdmi-phy: support rk3328 hdmi phy RK3328 hdmi phy is an upgraded version of 3228 hdmi phy, with a completely different register layout. It improves physical and antistatic capabilities. It's max output bandwidth is same as rk3228 hdmi phy, optimized up to 3.72Gbps per TMDS link. Change-Id: I03c718c49e44cfeaa113e3ed07eec7055b4380cc Signed-off-by: Zheng Yang --- .../phy/phy-rockchip-inno-hdmi-phy.txt | 3 +- drivers/phy/phy-rockchip-inno-hdmi-phy.c | 808 +++++++++++++----- 2 files changed, 604 insertions(+), 207 deletions(-) diff --git a/Documentation/devicetree/bindings/phy/phy-rockchip-inno-hdmi-phy.txt b/Documentation/devicetree/bindings/phy/phy-rockchip-inno-hdmi-phy.txt index 26e7a82f12b7..952dda54ec01 100644 --- a/Documentation/devicetree/bindings/phy/phy-rockchip-inno-hdmi-phy.txt +++ b/Documentation/devicetree/bindings/phy/phy-rockchip-inno-hdmi-phy.txt @@ -1,7 +1,8 @@ ROCKCHIP HDMI PHY WITH INNO IP BLOCK Required properties: - - compatible : should be "rockchip,rk3228-hdmi-phy"; + - compatible : "rockchip,rk3228-hdmi-phy", + "rockchip,rk3328-hdmi-phy"; - reg : the address offset of register for hdmi phy configuration. - #phy-cells : must be 0. See ./phy-bindings.txt for details. - clocks and clock-names: diff --git a/drivers/phy/phy-rockchip-inno-hdmi-phy.c b/drivers/phy/phy-rockchip-inno-hdmi-phy.c index f035bbdeb39d..ff8413db3999 100644 --- a/drivers/phy/phy-rockchip-inno-hdmi-phy.c +++ b/drivers/phy/phy-rockchip-inno-hdmi-phy.c @@ -23,6 +23,7 @@ #include #include #include +#include #include #define INNO_HDMI_PHY_TIMEOUT_LOOP_COUNT 1000 @@ -147,10 +148,23 @@ #define TMDS_DATA_CH1_OUTPUT_SWING(x) UPDATE(x, 7, 4) #define TMDS_DATA_CH0_OUTPUT_SWING(x) UPDATE(x, 3, 0) +enum inno_hdmi_phy_type { + INNO_HDMI_PHY_RK3228, + INNO_HDMI_PHY_RK3328 +}; + +struct inno_hdmi_phy_drv_data; + struct inno_hdmi_phy { struct device *dev; struct regmap *regmap; + struct phy *phy; + struct clk *sysclk; + + /* platform data */ + struct inno_hdmi_phy_drv_data *plat_data; + /* clk provider */ struct clk_hw hw; struct clk *pclk; @@ -170,24 +184,7 @@ struct pre_pll_config { u8 pclk_div_c; u8 pclk_div_d; u8 vco_div_5_en; -}; - -static const struct pre_pll_config pre_pll_cfg_table[] = { - { 27000000, 27000000, 1, 90, 3, 2, 2, 10, 3, 3, 4, 0}, - { 27000000, 33750000, 1, 90, 1, 3, 3, 10, 3, 3, 4, 0}, - { 40000000, 40000000, 1, 80, 2, 2, 2, 12, 2, 2, 2, 0}, - { 59400000, 59400000, 1, 99, 3, 1, 1, 1, 3, 3, 4, 0}, - { 59400000, 74250000, 1, 99, 1, 2, 2, 1, 3, 3, 4, 0}, - { 74250000, 74250000, 1, 99, 1, 2, 2, 1, 2, 3, 4, 0}, - { 74250000, 92812500, 4, 495, 1, 2, 2, 1, 3, 3, 4, 0}, - {148500000, 148500000, 1, 99, 1, 1, 1, 1, 2, 2, 2, 0}, - {148500000, 185625000, 4, 495, 0, 2, 2, 1, 3, 2, 2, 0}, - {297000000, 297000000, 1, 99, 0, 1, 1, 1, 0, 2, 2, 0}, - {297000000, 371250000, 4, 495, 1, 2, 0, 1, 3, 1, 1, 0}, - {594000000, 297000000, 1, 99, 0, 1, 1, 1, 0, 2, 1, 0}, - {594000000, 371250000, 4, 495, 1, 2, 0, 1, 3, 1, 1, 1}, - {594000000, 594000000, 1, 99, 0, 2, 0, 1, 0, 1, 1, 0}, - { ~0UL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} + u32 fracdiv; }; struct post_pll_config { @@ -195,32 +192,116 @@ struct post_pll_config { u8 prediv; u16 fbdiv; u8 postdiv; + u8 version; +}; + +struct phy_config { + unsigned long tmdsclock; + u8 regs[14]; +}; + +struct inno_hdmi_phy_ops { + void (*init)(struct inno_hdmi_phy *inno); + int (*power_on)(struct inno_hdmi_phy *inno, + const struct post_pll_config *cfg, + const struct phy_config *phy_cfg); + void (*power_off)(struct inno_hdmi_phy *inno); + int (*pre_pll_update)(struct inno_hdmi_phy *inno, + const struct pre_pll_config *cfg); + unsigned long (*recalc_rate)(struct inno_hdmi_phy *inno, + unsigned long parent_rate); +}; + +struct inno_hdmi_phy_drv_data { + enum inno_hdmi_phy_type dev_type; + const struct inno_hdmi_phy_ops *ops; + const struct phy_config *phy_cfg_table; +}; + +static const struct pre_pll_config pre_pll_cfg_table[] = { + { 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 post_pll_config post_pll_cfg_table[] = { - { 33750000, 1, 40, 8}, - { 74250000, 1, 40, 8}, - {148500000, 2, 40, 4}, - {297000000, 4, 40, 2}, - {371250000, 8, 40, 1}, - { ~0UL, 0, 0, 0} + {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}, + {594000000, 8, 40, 1, 3}, + { ~0UL, 0, 0, 0, 0} }; -struct phy_config { - unsigned long tmdsclock; - u8 pre_emphasis; - u8 clk_level; - u8 data0_level; - u8 data1_level; - u8 data2_level; +static const struct phy_config rk3228_phy_cfg[] = { + { 165000000, { + 0xaa, 0x00, 0x44, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, + }, + }, { + 340000000, { + 0xaa, 0x15, 0x6a, 0xaa, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, + }, + }, { + 594000000, { + 0xaa, 0x15, 0x7a, 0xaa, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, + }, + }, { + ~0UL, { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, + }, + } }; -static const struct phy_config phy_cfg_table[] = { - {165000000, 0, 4, 4, 4, 4}, - {225000000, 0, 6, 6, 6, 6}, - {340000000, 1, 6, 10, 10, 10}, - {594000000, 1, 7, 10, 10, 10}, - { ~0UL, 0, 0, 0, 0, 0} +static const struct phy_config rk3328_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 inline struct inno_hdmi_phy *to_inno_hdmi_phy(struct clk_hw *hw) @@ -248,125 +329,75 @@ static inline void inno_update_bits(struct inno_hdmi_phy *inno, u8 reg, regmap_update_bits(inno->regmap, reg * 4, mask, val); } -static void inno_hdmi_phy_post_pll_update(struct inno_hdmi_phy *inno, - const struct post_pll_config *cfg) +static u32 inno_hdmi_phy_get_tmdsclk(struct inno_hdmi_phy *inno, int rate) { - u8 m, v; - - m = POST_PLL_PRE_DIV_MASK; - v = POST_PLL_PRE_DIV(cfg->prediv); - inno_update_bits(inno, 0xe9, m, v); - - m = POST_PLL_FB_DIV_8_MASK; - v = POST_PLL_FB_DIV_8(cfg->fbdiv >> 8); - inno_update_bits(inno, 0xeb, m, v); - inno_write(inno, 0xea, POST_PLL_FB_DIV_7_0(cfg->fbdiv)); - - if (cfg->postdiv == 1) { - /* Disable Post-PLL post divider */ - m = POST_PLL_POST_DIV_EN_MASK; - v = POST_PLL_POST_DIV_DISABLE; - inno_update_bits(inno, 0xe9, m, v); - } else { - /* Enable Post-PLL post divider */ - m = POST_PLL_POST_DIV_EN_MASK; - v = POST_PLL_POST_DIV_ENABLE; - inno_update_bits(inno, 0xe9, m, v); - - m = POST_PLL_POST_DIV_MASK; - v = POST_PLL_POST_DIV(cfg->postdiv / 2 - 1); - inno_update_bits(inno, 0xeb, m, v); + int bus_width = phy_get_bus_width(inno->phy); + u32 tmdsclk; + + switch (bus_width) { + case 4: + tmdsclk = rate / 2; + break; + case 5: + tmdsclk = rate * 5 / 8; + break; + case 6: + tmdsclk = rate * 3 / 4; + break; + case 10: + tmdsclk = rate * 5 / 4; + break; + case 12: + tmdsclk = rate * 3 / 2; + break; + case 16: + tmdsclk = rate * 2; + break; + default: + tmdsclk = rate; } + + return tmdsclk; } static int inno_hdmi_phy_power_on(struct phy *phy) { struct inno_hdmi_phy *inno = phy_get_drvdata(phy); const struct post_pll_config *cfg = post_pll_cfg_table; - const struct phy_config *phy_cfg = phy_cfg_table; - int pll_tries; - u32 m, v; + const struct phy_config *phy_cfg = inno->plat_data->phy_cfg_table; + u32 tmdsclk = inno_hdmi_phy_get_tmdsclk(inno, inno->pixclock); + u32 chipversion = 1; + + if (inno->plat_data->dev_type == INNO_HDMI_PHY_RK3328 && + rockchip_get_cpu_version()) + chipversion = 2; for (; cfg->tmdsclock != ~0UL; cfg++) - if (inno->pixclock <= cfg->tmdsclock) + if (tmdsclk <= cfg->tmdsclock && + cfg->version & chipversion) break; for (; phy_cfg->tmdsclock != ~0UL; phy_cfg++) - if (inno->pixclock <= cfg->tmdsclock) + if (tmdsclk <= phy_cfg->tmdsclock) break; if (cfg->tmdsclock == ~0UL || phy_cfg->tmdsclock == ~0UL) return -EINVAL; - /* pdata_en disable */ - inno_update_bits(inno, 0x02, PDATAEN_MASK, PDATAEN_DISABLE); - - /* Power down Post-PLL */ - inno_update_bits(inno, 0xe0, POST_PLL_POWER_MASK, POST_PLL_POWER_DOWN); - - /* Pre-emphasis level control */ - m = TMDS_DATA_CH2_PRE_EMPHASIS_MASK | TMDS_DATA_CH1_PRE_EMPHASIS_MASK | - TMDS_DATA_CH0_PRE_EMPHASIS_MASK; - v = TMDS_DATA_CH2_PRE_EMPHASIS(phy_cfg->pre_emphasis) | - TMDS_DATA_CH1_PRE_EMPHASIS(phy_cfg->pre_emphasis) | - TMDS_DATA_CH0_PRE_EMPHASIS(phy_cfg->pre_emphasis); - inno_update_bits(inno, 0xf0, m, v); - - /* Output swing control */ - v = TMDS_CLK_CH_OUTPUT_SWING(phy_cfg->clk_level) | - TMDS_DATA_CH2_OUTPUT_SWING(phy_cfg->data2_level); - inno_write(inno, 0xf1, v); - v = TMDS_DATA_CH1_OUTPUT_SWING(phy_cfg->data1_level) | - TMDS_DATA_CH0_OUTPUT_SWING(phy_cfg->data0_level); - inno_write(inno, 0xf2, v); - - /* Post-PLL update */ - inno_hdmi_phy_post_pll_update(inno, cfg); - - /* Power up Post-PLL */ - inno_update_bits(inno, 0xe0, POST_PLL_POWER_MASK, POST_PLL_POWER_UP); - - /* BandGap enable */ - inno_update_bits(inno, 0xe1, BANDGAP_MASK, BANDGAP_ENABLE); - - /* TMDS driver enable */ - inno_update_bits(inno, 0xe1, TMDS_DRIVER_MASK, TMDS_DRIVER_ENABLE); - - /* Wait for post PLL lock */ - pll_tries = 0; - while (!(inno_read(inno, 0xeb) & POST_PLL_LOCK_STATUS)) { - if (pll_tries == INNO_HDMI_PHY_TIMEOUT_LOOP_COUNT) { - dev_err(inno->dev, "Post-PLL unlock\n"); - return -ETIMEDOUT; - } - - pll_tries++; - usleep_range(100, 110); - } - - if (cfg->tmdsclock > 340000000) - msleep(100); - - /* pdata_en enable */ - inno_update_bits(inno, 0x02, PDATAEN_MASK, PDATAEN_ENABLE); - dev_dbg(inno->dev, "Inno HDMI PHY Power On\n"); - return 0; + if (inno->plat_data->ops->power_on) + return inno->plat_data->ops->power_on(inno, cfg, phy_cfg); + else + return -EINVAL; } static int inno_hdmi_phy_power_off(struct phy *phy) { struct inno_hdmi_phy *inno = phy_get_drvdata(phy); - /* TMDS driver Disable */ - inno_update_bits(inno, 0xe1, TMDS_DRIVER_MASK, TMDS_DRIVER_DISABLE); - - /* BandGap Disable */ - inno_update_bits(inno, 0xe1, BANDGAP_MASK, BANDGAP_DISABLE); - - /* Post-PLL power down */ - inno_update_bits(inno, 0xe0, POST_PLL_POWER_MASK, POST_PLL_POWER_DOWN); + if (inno->plat_data->ops->power_off) + inno->plat_data->ops->power_off(inno); dev_dbg(inno->dev, "Inno HDMI PHY Power Off\n"); @@ -384,7 +415,10 @@ static int inno_hdmi_phy_clk_is_prepared(struct clk_hw *hw) struct inno_hdmi_phy *inno = to_inno_hdmi_phy(hw); u8 status; - status = inno_read(inno, 0xe0) & PRE_PLL_POWER_MASK; + if (inno->plat_data->dev_type == INNO_HDMI_PHY_RK3228) + status = inno_read(inno, 0xe0) & PRE_PLL_POWER_MASK; + else + status = inno_read(inno, 0xa0) & 1; return status ? 0 : 1; } @@ -393,7 +427,11 @@ static int inno_hdmi_phy_clk_prepare(struct clk_hw *hw) { struct inno_hdmi_phy *inno = to_inno_hdmi_phy(hw); - inno_update_bits(inno, 0xe0, PRE_PLL_POWER_MASK, PRE_PLL_POWER_UP); + if (inno->plat_data->dev_type == INNO_HDMI_PHY_RK3228) + inno_update_bits(inno, 0xe0, PRE_PLL_POWER_MASK, + PRE_PLL_POWER_UP); + else + inno_update_bits(inno, 0xa0, 1, 0); return 0; } @@ -402,7 +440,11 @@ static void inno_hdmi_phy_clk_unprepare(struct clk_hw *hw) { struct inno_hdmi_phy *inno = to_inno_hdmi_phy(hw); - inno_update_bits(inno, 0xe0, PRE_PLL_POWER_MASK, PRE_PLL_POWER_DOWN); + if (inno->plat_data->dev_type == INNO_HDMI_PHY_RK3228) + inno_update_bits(inno, 0xe0, PRE_PLL_POWER_MASK, + PRE_PLL_POWER_DOWN); + else + inno_update_bits(inno, 0xa0, 1, 0); } static unsigned long inno_hdmi_phy_clk_recalc_rate(struct clk_hw *hw, @@ -410,7 +452,10 @@ static unsigned long inno_hdmi_phy_clk_recalc_rate(struct clk_hw *hw, { struct inno_hdmi_phy *inno = to_inno_hdmi_phy(hw); - return inno->pixclock; + if (inno->plat_data->ops->recalc_rate) + return inno->plat_data->ops->recalc_rate(inno, parent_rate); + else + return inno->pixclock; } static long inno_hdmi_phy_clk_round_rate(struct clk_hw *hw, unsigned long rate, @@ -423,8 +468,8 @@ static long inno_hdmi_phy_clk_round_rate(struct clk_hw *hw, unsigned long rate, if (cfg->pixclock == rate) break; - /* XXX: Limit pixel clock under 300MHz */ - if (cfg->pixclock > 300000000) + /* XXX: Limit pixel clock under 600MHz */ + if (cfg->pixclock > 600000000) return -EINVAL; dev_dbg(inno->dev, "%s: rate=%ld\n", __func__, cfg->pixclock); @@ -432,45 +477,15 @@ static long inno_hdmi_phy_clk_round_rate(struct clk_hw *hw, unsigned long rate, return cfg->pixclock; } -static void inno_hdmi_phy_pre_pll_update(struct inno_hdmi_phy *inno, - const struct pre_pll_config *cfg) -{ - u32 m, v; - - m = PRE_PLL_FB_DIV_8_MASK | PCLK_VCO_DIV_5_MASK | PRE_PLL_PRE_DIV_MASK; - v = PRE_PLL_FB_DIV_8(cfg->fbdiv >> 8) | - PCLK_VCO_DIV_5(cfg->vco_div_5_en) | PRE_PLL_PRE_DIV(cfg->prediv); - inno_update_bits(inno, 0xe2, m, v); - - inno_write(inno, 0xe3, PRE_PLL_FB_DIV_7_0(cfg->fbdiv)); - - m = PRE_PLL_PCLK_DIV_B_MASK | PRE_PLL_PCLK_DIV_A_MASK; - v = PRE_PLL_PCLK_DIV_B(cfg->pclk_div_b) | - PRE_PLL_PCLK_DIV_A(cfg->pclk_div_a); - inno_update_bits(inno, 0xe4, m, v); - - m = PRE_PLL_PCLK_DIV_C_MASK | PRE_PLL_PCLK_DIV_D_MASK; - v = PRE_PLL_PCLK_DIV_C(cfg->pclk_div_c) | - PRE_PLL_PCLK_DIV_D(cfg->pclk_div_d); - inno_update_bits(inno, 0xe5, m, v); - - m = PRE_PLL_TMDSCLK_DIV_C_MASK | PRE_PLL_TMDSCLK_DIV_A_MASK | - PRE_PLL_TMDSCLK_DIV_B_MASK; - v = PRE_PLL_TMDSCLK_DIV_C(cfg->tmds_div_c) | - PRE_PLL_TMDSCLK_DIV_A(cfg->tmds_div_a) | - PRE_PLL_TMDSCLK_DIV_B(cfg->tmds_div_b); - inno_update_bits(inno, 0xe6, m, v); -} - static int inno_hdmi_phy_clk_set_rate(struct clk_hw *hw, unsigned long rate, unsigned long parent_rate) { struct inno_hdmi_phy *inno = to_inno_hdmi_phy(hw); const struct pre_pll_config *cfg = pre_pll_cfg_table; - int pll_tries; + u32 tmdsclk = inno_hdmi_phy_get_tmdsclk(inno, rate); for (; cfg->pixclock != ~0UL; cfg++) - if (cfg->pixclock == rate && cfg->tmdsclock == rate) + if (cfg->pixclock == rate && cfg->tmdsclock == tmdsclk) break; if (cfg->pixclock == ~0UL) { @@ -478,25 +493,8 @@ static int inno_hdmi_phy_clk_set_rate(struct clk_hw *hw, unsigned long rate, return -EINVAL; } - /* Power down PRE-PLL */ - inno_update_bits(inno, 0xe0, PRE_PLL_POWER_MASK, PRE_PLL_POWER_DOWN); - - inno_hdmi_phy_pre_pll_update(inno, cfg); - - /* Power up PRE-PLL */ - inno_update_bits(inno, 0xe0, PRE_PLL_POWER_MASK, PRE_PLL_POWER_UP); - - /* Wait for Pre-PLL lock */ - pll_tries = 0; - while (!(inno_read(inno, 0xe8) & PRE_PLL_LOCK_STATUS)) { - if (pll_tries == INNO_HDMI_PHY_TIMEOUT_LOOP_COUNT) { - dev_err(inno->dev, "Pre-PLL unlock\n"); - return -ETIMEDOUT; - } - - pll_tries++; - usleep_range(100, 110); - } + if (inno->plat_data->ops->pre_pll_update) + inno->plat_data->ops->pre_pll_update(inno, cfg); inno->pixclock = rate; @@ -556,11 +554,14 @@ static int inno_hdmi_phy_clk_register(struct inno_hdmi_phy *inno) return 0; } -static void inno_hdmi_phy_init(struct inno_hdmi_phy *inno) +static void inno_hdmi_phy_rk3228_init(struct inno_hdmi_phy *inno) { u32 m, v; - /* Use internal control rxsense/poweron/pllpd/pdataen signal. */ + /* + * Use phy internal register control + * rxsense/poweron/pllpd/pdataen signal. + */ m = BYPASS_RXSENSE_EN_MASK | BYPASS_PWRON_EN_MASK | BYPASS_PLLPD_EN_MASK; v = BYPASS_RXSENSE_EN | BYPASS_PWRON_EN | BYPASS_PLLPD_EN; @@ -571,6 +572,376 @@ static void inno_hdmi_phy_init(struct inno_hdmi_phy *inno) inno_update_bits(inno, 0xaa, POST_PLL_CTRL_MASK, POST_PLL_CTRL_MANUAL); } +static int +inno_hdmi_phy_rk3228_power_on(struct inno_hdmi_phy *inno, + const struct post_pll_config *cfg, + const struct phy_config *phy_cfg) +{ + int pll_tries; + u32 m, v; + + /* pdata_en disable */ + inno_update_bits(inno, 0x02, PDATAEN_MASK, PDATAEN_DISABLE); + + /* Power down Post-PLL */ + inno_update_bits(inno, 0xe0, POST_PLL_POWER_MASK, POST_PLL_POWER_DOWN); + + /* Post-PLL update */ + m = POST_PLL_PRE_DIV_MASK; + v = POST_PLL_PRE_DIV(cfg->prediv); + inno_update_bits(inno, 0xe9, m, v); + + m = POST_PLL_FB_DIV_8_MASK; + v = POST_PLL_FB_DIV_8(cfg->fbdiv >> 8); + inno_update_bits(inno, 0xeb, m, v); + inno_write(inno, 0xea, POST_PLL_FB_DIV_7_0(cfg->fbdiv)); + + if (cfg->postdiv == 1) { + /* Disable Post-PLL post divider */ + m = POST_PLL_POST_DIV_EN_MASK; + v = POST_PLL_POST_DIV_DISABLE; + inno_update_bits(inno, 0xe9, m, v); + } else { + /* Enable Post-PLL post divider */ + m = POST_PLL_POST_DIV_EN_MASK; + v = POST_PLL_POST_DIV_ENABLE; + inno_update_bits(inno, 0xe9, m, v); + + m = POST_PLL_POST_DIV_MASK; + v = POST_PLL_POST_DIV(cfg->postdiv / 2 - 1); + inno_update_bits(inno, 0xeb, m, v); + } + + for (v = 0; v < 4; v++) + inno_write(inno, 0xef + v, phy_cfg->regs[v]); + + /* Power up Post-PLL */ + inno_update_bits(inno, 0xe0, POST_PLL_POWER_MASK, POST_PLL_POWER_UP); + + /* BandGap enable */ + inno_update_bits(inno, 0xe1, BANDGAP_MASK, BANDGAP_ENABLE); + + /* TMDS driver enable */ + inno_update_bits(inno, 0xe1, TMDS_DRIVER_MASK, TMDS_DRIVER_ENABLE); + + /* Wait for post PLL lock */ + pll_tries = 0; + while (!(inno_read(inno, 0xeb) & POST_PLL_LOCK_STATUS)) { + if (pll_tries == INNO_HDMI_PHY_TIMEOUT_LOOP_COUNT) { + dev_err(inno->dev, "Post-PLL unlock\n"); + return -ETIMEDOUT; + } + + pll_tries++; + usleep_range(100, 110); + } + + if (cfg->tmdsclock > 340000000) + msleep(100); + + /* pdata_en enable */ + inno_update_bits(inno, 0x02, PDATAEN_MASK, PDATAEN_ENABLE); + return 0; +} + +static void inno_hdmi_phy_rk3228_power_off(struct inno_hdmi_phy *inno) +{ + /* TMDS driver Disable */ + inno_update_bits(inno, 0xe1, TMDS_DRIVER_MASK, TMDS_DRIVER_DISABLE); + + /* BandGap Disable */ + inno_update_bits(inno, 0xe1, BANDGAP_MASK, BANDGAP_DISABLE); + + /* Post-PLL power down */ + inno_update_bits(inno, 0xe0, POST_PLL_POWER_MASK, POST_PLL_POWER_DOWN); +} + +static int +inno_hdmi_phy_rk3228_pre_pll_update(struct inno_hdmi_phy *inno, + const struct pre_pll_config *cfg) +{ + int pll_tries; + u32 m, v; + + /* Power down PRE-PLL */ + inno_update_bits(inno, 0xe0, PRE_PLL_POWER_MASK, PRE_PLL_POWER_DOWN); + + m = PRE_PLL_FB_DIV_8_MASK | PCLK_VCO_DIV_5_MASK | PRE_PLL_PRE_DIV_MASK; + v = PRE_PLL_FB_DIV_8(cfg->fbdiv >> 8) | + PCLK_VCO_DIV_5(cfg->vco_div_5_en) | PRE_PLL_PRE_DIV(cfg->prediv); + inno_update_bits(inno, 0xe2, m, v); + + inno_write(inno, 0xe3, PRE_PLL_FB_DIV_7_0(cfg->fbdiv)); + + m = PRE_PLL_PCLK_DIV_B_MASK | PRE_PLL_PCLK_DIV_A_MASK; + v = PRE_PLL_PCLK_DIV_B(cfg->pclk_div_b) | + PRE_PLL_PCLK_DIV_A(cfg->pclk_div_a); + inno_update_bits(inno, 0xe4, m, v); + + m = PRE_PLL_PCLK_DIV_C_MASK | PRE_PLL_PCLK_DIV_D_MASK; + v = PRE_PLL_PCLK_DIV_C(cfg->pclk_div_c) | + PRE_PLL_PCLK_DIV_D(cfg->pclk_div_d); + inno_update_bits(inno, 0xe5, m, v); + + m = PRE_PLL_TMDSCLK_DIV_C_MASK | PRE_PLL_TMDSCLK_DIV_A_MASK | + PRE_PLL_TMDSCLK_DIV_B_MASK; + v = PRE_PLL_TMDSCLK_DIV_C(cfg->tmds_div_c) | + PRE_PLL_TMDSCLK_DIV_A(cfg->tmds_div_a) | + PRE_PLL_TMDSCLK_DIV_B(cfg->tmds_div_b); + inno_update_bits(inno, 0xe6, m, v); + + /* Power up PRE-PLL */ + inno_update_bits(inno, 0xe0, PRE_PLL_POWER_MASK, PRE_PLL_POWER_UP); + + /* Wait for Pre-PLL lock */ + pll_tries = 0; + while (!(inno_read(inno, 0xe8) & PRE_PLL_LOCK_STATUS)) { + if (pll_tries == INNO_HDMI_PHY_TIMEOUT_LOOP_COUNT) { + dev_err(inno->dev, "Pre-PLL unlock\n"); + return -ETIMEDOUT; + } + + pll_tries++; + usleep_range(100, 110); + } + + return 0; +} + +static void inno_hdmi_phy_rk3328_init(struct inno_hdmi_phy *inno) +{ + /* + * Use phy internal register control + * rxsense/poweron/pllpd/pdataen signal. + */ + inno_write(inno, 0x01, 0x07); + inno_write(inno, 0x02, 0x91); +} + +static int +inno_hdmi_phy_rk3328_power_on(struct inno_hdmi_phy *inno, + const struct post_pll_config *cfg, + const struct phy_config *phy_cfg) +{ + u32 val; + + /* set pdata_en to 0 */ + inno_update_bits(inno, 0x02, 1, 0); + /* Power off post PLL */ + inno_update_bits(inno, 0xaa, 1, 1); + + val = cfg->fbdiv & 0xff; + inno_write(inno, 0xac, val); + if (cfg->postdiv == 1) { + inno_write(inno, 0xaa, 2); + val = (cfg->fbdiv >> 8) | cfg->prediv; + inno_write(inno, 0xab, val); + } else { + val = (cfg->postdiv / 2) - 1; + inno_write(inno, 0xad, val); + val = (cfg->fbdiv >> 8) | cfg->prediv; + inno_write(inno, 0xab, val); + inno_write(inno, 0xaa, 0x0e); + } + + for (val = 0; val < 14; val++) + inno_write(inno, 0xb5 + val, phy_cfg->regs[val]); + + /* 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_write(inno, 0xc8, 0); + inno_write(inno, 0xc9, 0); + inno_write(inno, 0xca, 0); + inno_write(inno, 0xcb, 0); + + if (phy_cfg->tmdsclock > 340000000) { + /* Set termination resistor to 100ohm */ + val = clk_get_rate(inno->sysclk) / 100000; + inno_write(inno, 0xc5, ((val >> 8) & 0xff) | 0x80); + inno_write(inno, 0xc6, val & 0xff); + inno_write(inno, 0xc7, 3 << 1); + inno_write(inno, 0xc5, ((val >> 8) & 0xff)); + } else if (phy_cfg->tmdsclock > 165000000) { + inno_write(inno, 0xc5, 0x81); + /* clk termination resistor is 50ohm + * data termination resistor is 150ohm + */ + inno_write(inno, 0xc8, 0x30); + inno_write(inno, 0xc9, 0x10); + inno_write(inno, 0xca, 0x10); + inno_write(inno, 0xcb, 0x10); + } else { + inno_write(inno, 0xc5, 0x81); + } + + /* Power up post PLL */ + inno_update_bits(inno, 0xaa, 1, 0); + /* Power up tmds driver */ + inno_update_bits(inno, 0xb0, 4, 4); + inno_write(inno, 0xb2, 0x0f); + + /* Wait for post PLL lock */ + for (val = 0; val < 5; val++) { + if (inno_read(inno, 0xaf) & 1) + break; + usleep_range(1000, 2000); + } + if (!(inno_read(inno, 0xaf) & 1)) { + dev_err(inno->dev, "HDMI PHY Post PLL unlock\n"); + return -ETIMEDOUT; + } + if (phy_cfg->tmdsclock > 340000000) + msleep(100); + /* set pdata_en to 1 */ + inno_update_bits(inno, 0x02, 1, 1); + + return 0; +} + +static void inno_hdmi_phy_rk3328_power_off(struct inno_hdmi_phy *inno) +{ + /* Power off driver */ + inno_write(inno, 0xb2, 0); + /* Power off band gap */ + inno_update_bits(inno, 0xb0, 4, 0); + /* Power off post pll */ + inno_update_bits(inno, 0xaa, 1, 1); +} + +static int +inno_hdmi_phy_rk3328_pre_pll_update(struct inno_hdmi_phy *inno, + const struct pre_pll_config *cfg) +{ + u32 val; + + /* Power off PLL */ + inno_update_bits(inno, 0xa0, 1, 1); + /* Configure pre-pll */ + inno_update_bits(inno, 0xa0, 2, (cfg->vco_div_5_en & 1) << 1); + inno_write(inno, 0xa1, cfg->prediv); + if (cfg->fracdiv) + val = ((cfg->fbdiv >> 8) & 0x0f) | 0xc0; + else + val = ((cfg->fbdiv >> 8) & 0x0f) | 0xf0; + inno_write(inno, 0xa2, val); + inno_write(inno, 0xa3, cfg->fbdiv & 0xff); + val = (cfg->pclk_div_a & 0x1f) | + ((cfg->pclk_div_b & 3) << 5); + inno_write(inno, 0xa5, val); + val = (cfg->pclk_div_d & 0x1f) | + ((cfg->pclk_div_c & 3) << 5); + inno_write(inno, 0xa6, val); + val = ((cfg->tmds_div_a & 3) << 4) | + ((cfg->tmds_div_b & 3) << 2) | + (cfg->tmds_div_c & 3); + inno_write(inno, 0xa4, val); + + if (cfg->fracdiv) { + val = cfg->fracdiv & 0xff; + inno_write(inno, 0xd3, val); + val = (cfg->fracdiv >> 8) & 0xff; + inno_write(inno, 0xd2, val); + val = (cfg->fracdiv >> 16) & 0xff; + inno_write(inno, 0xd1, val); + } else { + inno_write(inno, 0xd3, 0); + inno_write(inno, 0xd2, 0); + inno_write(inno, 0xd1, 0); + } + + /* Power up PLL */ + inno_update_bits(inno, 0xa0, 1, 0); + + /* Wait for PLL lock */ + for (val = 0; val < 5; val++) { + if (inno_read(inno, 0xa9) & 1) + break; + usleep_range(1000, 2000); + } + if (val == 5) { + dev_err(inno->dev, "Pre-PLL unlock\n"); + return -ETIMEDOUT; + } + + return 0; +} + +static unsigned long +inno_hdmi_3328_phy_pll_recalc_rate(struct inno_hdmi_phy *inno, + unsigned long parent_rate) +{ + unsigned long rate, vco, frac; + u8 nd, no_a, no_b, no_c, no_d; + u16 nf; + + nd = inno_read(inno, 0xa1) & 0x3f; + nf = ((inno_read(inno, 0xa2) & 0x0f) << 8) | inno_read(inno, 0xa3); + vco = parent_rate * nf; + if ((inno_read(inno, 0xa2) & 0x30) == 0) { + frac = inno_read(inno, 0xd3) | + (inno_read(inno, 0xd2) << 8) | + (inno_read(inno, 0xd1) << 16); + vco += DIV_ROUND_CLOSEST(parent_rate * frac, (1 << 24)); + } + if (inno_read(inno, 0xa0) & 2) { + rate = vco / (nd * 5); + } else { + no_a = inno_read(inno, 0xa5) & 0x1f; + no_b = ((inno_read(inno, 0xa5) >> 5) & 7) + 2; + no_c = (1 << ((inno_read(inno, 0xa6) >> 5) & 7)); + no_d = inno_read(inno, 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 const struct inno_hdmi_phy_ops rk3228_hdmi_phy_ops = { + .init = inno_hdmi_phy_rk3228_init, + .power_on = inno_hdmi_phy_rk3228_power_on, + .power_off = inno_hdmi_phy_rk3228_power_off, + .pre_pll_update = inno_hdmi_phy_rk3228_pre_pll_update, +}; + +static const struct inno_hdmi_phy_ops rk3328_hdmi_phy_ops = { + .init = inno_hdmi_phy_rk3328_init, + .power_on = inno_hdmi_phy_rk3328_power_on, + .power_off = inno_hdmi_phy_rk3328_power_off, + .pre_pll_update = inno_hdmi_phy_rk3328_pre_pll_update, + .recalc_rate = inno_hdmi_3328_phy_pll_recalc_rate, +}; + +static const struct inno_hdmi_phy_drv_data rk3228_hdmi_phy_drv_data = { + .dev_type = INNO_HDMI_PHY_RK3228, + .ops = &rk3228_hdmi_phy_ops, + .phy_cfg_table = rk3228_phy_cfg, +}; + +static const struct inno_hdmi_phy_drv_data rk3328_hdmi_phy_drv_data = { + .dev_type = INNO_HDMI_PHY_RK3328, + .ops = &rk3328_hdmi_phy_ops, + .phy_cfg_table = rk3328_phy_cfg, +}; + +static const struct of_device_id inno_hdmi_phy_of_match[] = { + { .compatible = "rockchip,rk3228-hdmi-phy", + .data = &rk3228_hdmi_phy_drv_data + }, + { .compatible = "rockchip,rk3328-hdmi-phy", + .data = &rk3328_hdmi_phy_drv_data + }, + {} +}; +MODULE_DEVICE_TABLE(of, inno_hdmi_phy_of_match); + static const struct regmap_config inno_hdmi_phy_regmap_config = { .reg_bits = 32, .val_bits = 32, @@ -582,7 +953,7 @@ static int inno_hdmi_phy_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct inno_hdmi_phy *inno; - struct phy *phy; + const struct of_device_id *match; struct phy_provider *phy_provider; struct resource *res; void __iomem *regs; @@ -594,40 +965,71 @@ static int inno_hdmi_phy_probe(struct platform_device *pdev) inno->dev = dev; + match = of_match_node(inno_hdmi_phy_of_match, pdev->dev.of_node); + inno->plat_data = (struct inno_hdmi_phy_drv_data *)match->data; + if (!inno->plat_data || !inno->plat_data->ops) + return -EINVAL; + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); regs = devm_ioremap_resource(dev, res); if (IS_ERR(regs)) return PTR_ERR(regs); - inno->regmap = devm_regmap_init_mmio_clk(dev, "sysclk", regs, - &inno_hdmi_phy_regmap_config); + inno->sysclk = devm_clk_get(inno->dev, "sysclk"); + if (IS_ERR(inno->sysclk)) { + ret = PTR_ERR(inno->sysclk); + dev_err(inno->dev, "Unable to get inno phy sysclk: %d\n", ret); + return ret; + } + ret = clk_prepare_enable(inno->sysclk); + if (ret) { + dev_err(inno->dev, "Cannot enable inno phy sysclk: %d\n", ret); + return ret; + } + + inno->regmap = devm_regmap_init_mmio(dev, regs, + &inno_hdmi_phy_regmap_config); if (IS_ERR(inno->regmap)) { ret = PTR_ERR(inno->regmap); dev_err(dev, "failed to init regmap: %d\n", ret); - return ret; + goto err_regsmap; } - phy = devm_phy_create(dev, NULL, &inno_hdmi_phy_ops); - if (IS_ERR(phy)) { + inno->phy = devm_phy_create(dev, NULL, &inno_hdmi_phy_ops); + if (IS_ERR(inno->phy)) { dev_err(dev, "failed to create HDMI PHY\n"); - return PTR_ERR(phy); + ret = PTR_ERR(inno->phy); + goto err_phy; } - phy_set_drvdata(phy, inno); + phy_set_drvdata(inno->phy, inno); + phy_set_bus_width(inno->phy, 8); phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); if (IS_ERR(phy_provider)) { dev_err(dev, "failed to register PHY provider\n"); - return PTR_ERR(phy_provider); + ret = PTR_ERR(phy_provider); + goto err_provider; } - inno_hdmi_phy_init(inno); + if (inno->plat_data->ops->init) + inno->plat_data->ops->init(inno); ret = inno_hdmi_phy_clk_register(inno); if (ret) - return ret; + goto err_register; return 0; + +err_register: + devm_of_phy_provider_unregister(dev, phy_provider); +err_provider: + devm_phy_destroy(dev, inno->phy); +err_phy: + regmap_exit(inno->regmap); +err_regsmap: + clk_disable_unprepare(inno->sysclk); + return ret; } static int inno_hdmi_phy_remove(struct platform_device *pdev) @@ -637,12 +1039,6 @@ static int inno_hdmi_phy_remove(struct platform_device *pdev) return 0; } -static const struct of_device_id inno_hdmi_phy_of_match[] = { - { .compatible = "rockchip,rk3228-hdmi-phy" }, - { /* Sentinel */ } -}; -MODULE_DEVICE_TABLE(of, inno_hdmi_phy_of_match); - static struct platform_driver inno_hdmi_phy_driver = { .probe = inno_hdmi_phy_probe, .remove = inno_hdmi_phy_remove, -- 2.34.1