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 952dda54ec01..14590f97809e 100644 --- a/Documentation/devicetree/bindings/phy/phy-rockchip-inno-hdmi-phy.txt +++ b/Documentation/devicetree/bindings/phy/phy-rockchip-inno-hdmi-phy.txt @@ -12,6 +12,7 @@ Required properties: to PLL - #clock-cells: should be 0. - clock-output-names : shall be the corresponding names of the outputs. + - rockchip,phy-table: the parameter table of hdmi phy configuration. Example: @@ -23,6 +24,13 @@ Example: clock-names = "sysclk", "refclk"; #clock-cells = <0>; clock-output-names = "hdmiphy_phy"; + rockchip,phy-table = + <165000000 0x07 0x0a 0x0a 0x0a 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>; status = "disabled"; }; diff --git a/drivers/phy/rockchip/phy-rockchip-inno-hdmi-phy.c b/drivers/phy/rockchip/phy-rockchip-inno-hdmi-phy.c index 7a991780af88..d17c7a143d9c 100644 --- a/drivers/phy/rockchip/phy-rockchip-inno-hdmi-phy.c +++ b/drivers/phy/rockchip/phy-rockchip-inno-hdmi-phy.c @@ -156,6 +156,11 @@ enum inno_hdmi_phy_type { INNO_HDMI_PHY_RK3328 }; +struct phy_config { + unsigned long tmdsclock; + u8 regs[14]; +}; + struct inno_hdmi_phy_drv_data; struct inno_hdmi_phy { @@ -166,6 +171,7 @@ struct inno_hdmi_phy { struct phy *phy; struct clk *sysclk; + struct phy_config *phy_cfg; /* platform data */ struct inno_hdmi_phy_drv_data *plat_data; @@ -204,11 +210,6 @@ struct post_pll_config { 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, @@ -423,6 +424,9 @@ static int inno_hdmi_phy_power_on(struct phy *phy) u32 tmdsclock = inno_hdmi_phy_get_tmdsclk(inno, inno->pixclock); u32 chipversion = 1; + if (inno->phy_cfg) + phy_cfg = inno->phy_cfg; + if (!tmdsclock) { dev_err(inno->dev, "TMDS clock is zero!\n"); return -EINVAL; @@ -526,7 +530,10 @@ static unsigned long inno_hdmi_phy_clk_recalc_rate(struct clk_hw *hw, static long inno_hdmi_phy_clk_round_rate(struct clk_hw *hw, unsigned long rate, unsigned long *parent_rate) { + int i; const struct pre_pll_config *cfg = pre_pll_cfg_table; + struct inno_hdmi_phy *inno = to_inno_hdmi_phy(hw); + u32 tmdsclock = inno_hdmi_phy_get_tmdsclk(inno, rate); for (; cfg->pixclock != ~0UL; cfg++) if (cfg->pixclock == rate) @@ -536,6 +543,23 @@ static long inno_hdmi_phy_clk_round_rate(struct clk_hw *hw, unsigned long rate, if (cfg->pixclock > 600000000) return -EINVAL; + /* + * If there is no dts phy cfg table, use default phy cfg table. + * The tmds clock maximum is 594MHz. So there is no need to check + * whether tmds clock is out of range. + */ + if (!inno->phy_cfg) + return cfg->pixclock; + + /* Check if tmds clock is out of dts phy config's range. */ + for (i = 0; inno->phy_cfg[i].tmdsclock != ~0UL; i++) { + if (inno->phy_cfg[i].tmdsclock >= tmdsclock) + break; + } + + if (inno->phy_cfg[i].tmdsclock == ~0UL) + return -EINVAL; + return cfg->pixclock; } @@ -1109,15 +1133,45 @@ static const struct regmap_config inno_hdmi_phy_regmap_config = { .max_register = 0x400, }; +static +int inno_hdmi_update_phy_table(struct inno_hdmi_phy *inno, u32 *config, + struct phy_config *phy_cfg, + int phy_table_size) +{ + int i, j; + + for (i = 0; i < phy_table_size; i++) { + phy_cfg[i].tmdsclock = + (unsigned long)config[i * 15]; + + for (j = 0; j < 14; j++) + phy_cfg[i].regs[j] = (u8)config[i * 15 + 1 + j]; + } + + /* + * The last set of phy cfg is used to indicate whether + * there is no more phy cfg data. + */ + phy_cfg[i].tmdsclock = ~0UL; + for (j = 0; j < 14; j++) + phy_cfg[i].regs[j] = 0; + + return 0; +} + +#define PHY_TAB_LEN 60 + static int inno_hdmi_phy_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; struct inno_hdmi_phy *inno; const struct of_device_id *match; struct phy_provider *phy_provider; struct resource *res; void __iomem *regs; - int ret; + u32 *phy_config; + int ret, val, phy_table_size; inno = devm_kzalloc(dev, sizeof(*inno), GFP_KERNEL); if (!inno) @@ -1162,6 +1216,40 @@ static int inno_hdmi_phy_probe(struct platform_device *pdev) goto err_regsmap; } + if (of_get_property(np, "rockchip,phy-table", &val)) { + if (val % PHY_TAB_LEN || !val) { + dev_err(dev, "Invalid phy cfg table format!\n"); + return -EINVAL; + } + + phy_config = kmalloc(val, GFP_KERNEL); + if (!phy_config) { + dev_err(dev, "kmalloc phy table failed\n"); + return -ENOMEM; + } + + phy_table_size = val / PHY_TAB_LEN; + /* Effective phy cfg data and the end of phy cfg table */ + inno->phy_cfg = devm_kzalloc(dev, val + PHY_TAB_LEN, + GFP_KERNEL); + if (!inno->phy_cfg) { + kfree(phy_config); + return -ENOMEM; + } + of_property_read_u32_array(np, "rockchip,phy-table", + phy_config, val / sizeof(u32)); + ret = inno_hdmi_update_phy_table(inno, phy_config, + inno->phy_cfg, + phy_table_size); + if (ret) { + kfree(phy_config); + return ret; + } + kfree(phy_config); + } else { + dev_dbg(dev, "use default hdmi phy table\n"); + } + phy_set_drvdata(inno->phy, inno); phy_set_bus_width(inno->phy, 8);