phy: rockchip-inno-hdmi-phy: support rk3328 hdmi phy
authorZheng Yang <zhengyang@rock-chips.com>
Tue, 11 Jul 2017 08:03:48 +0000 (16:03 +0800)
committerHuang, Tao <huangtao@rock-chips.com>
Wed, 12 Jul 2017 11:05:15 +0000 (19:05 +0800)
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 <zhengyang@rock-chips.com>
Documentation/devicetree/bindings/phy/phy-rockchip-inno-hdmi-phy.txt
drivers/phy/phy-rockchip-inno-hdmi-phy.c

index 26e7a82f12b796ef5ad69b2d5126ce2cdfd37ae0..952dda54ec0156c1ffdfe396c047149cfa972317 100644 (file)
@@ -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:
index f035bbdeb39d59fa0f682e5d8093380a29d6da55..ff8413db3999b8085a109a97817743f6fdd42e37 100644 (file)
@@ -23,6 +23,7 @@
 #include <linux/of.h>
 #include <linux/platform_device.h>
 #include <linux/regmap.h>
+#include <linux/rockchip/cpu.h>
 #include <linux/phy/phy.h>
 
 #define INNO_HDMI_PHY_TIMEOUT_LOOP_COUNT       1000
 #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,