From efae8c5e28b8bf2c544da76882eabcba1be4aa4c Mon Sep 17 00:00:00 2001 From: Algea Cao Date: Thu, 29 Mar 2018 10:36:04 +0800 Subject: [PATCH] drm/rockchip: inno-hdmi-phy: Get inno hdmi phy config from dts Setting inno phy config table in dts. According to tmds clock range, phy config data can be chosen. We can also filter some video modes which tmds clock out of range we set. Change-Id: I666c825921877fe2cdf45c2ccd1415815a4b7715 Signed-off-by: Algea Cao --- .../phy/phy-rockchip-inno-hdmi-phy.txt | 8 ++ .../phy/rockchip/phy-rockchip-inno-hdmi-phy.c | 100 ++++++++++++++++-- 2 files changed, 102 insertions(+), 6 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 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);