Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

phy: rockchip-usb: expose the phy-internal PLLs

The USB phys on Rockchip SoCs contain their own internal PLLs to create
the 480MHz needed. Additionally this PLL output is also fed back into the
core clock-controller as possible source for clocks like the GPU or others.

Until now this was modelled incorrectly with a "virtual" factor clock in
the clock controller. The one big caveat is that if we turn off the usb phy
via the siddq signal, all analog components get turned off, including the
PLLs. It is therefore possible that a source clock gets disabled without
the clock driver ever knowing, possibly making the system hang.

Therefore register the phy-plls as real clocks that the clock driver can
then reference again normally, making the clock hirarchy finally reflect
the actual hardware.

The phy-ops get converted to simply turning that new clock on and off
which in turn controls the siddq signal of the phy.

Through this the driver gains handling for platform-specific data, to
handle the phy->clock name association.

Signed-off-by: Heiko Stuebner <heiko@sntech.de>
Reviewed-by: Douglas Anderson <dianders@chromium.org>
Signed-off-by: Kishon Vijay Abraham I <kishon@ti.com>

authored by

Heiko Stuebner and committed by
Kishon Vijay Abraham I
b74fe7c7 c2bfc3b8

+163 -28
+1
Documentation/devicetree/bindings/phy/rockchip-usb-phy.txt
··· 24 24 Optional Properties: 25 25 - clocks : phandle + clock specifier for the phy clocks 26 26 - clock-names: string, clock name, must be "phyclk" 27 + - #clock-cells: for users of the phy-pll, should be 0 27 28 28 29 Example: 29 30
+162 -28
drivers/phy/phy-rockchip-usb.c
··· 15 15 */ 16 16 17 17 #include <linux/clk.h> 18 + #include <linux/clk-provider.h> 18 19 #include <linux/io.h> 19 20 #include <linux/kernel.h> 20 21 #include <linux/module.h> 21 22 #include <linux/mutex.h> 22 23 #include <linux/of.h> 23 24 #include <linux/of_address.h> 25 + #include <linux/of_platform.h> 24 26 #include <linux/phy/phy.h> 25 27 #include <linux/platform_device.h> 26 28 #include <linux/regulator/consumer.h> ··· 38 36 #define SIDDQ_ON BIT(13) 39 37 #define SIDDQ_OFF (0 << 13) 40 38 39 + struct rockchip_usb_phys { 40 + int reg; 41 + const char *pll_name; 42 + }; 43 + 44 + struct rockchip_usb_phy_pdata { 45 + struct rockchip_usb_phys *phys; 46 + }; 47 + 41 48 struct rockchip_usb_phy_base { 42 49 struct device *dev; 43 50 struct regmap *reg_base; 51 + const struct rockchip_usb_phy_pdata *pdata; 44 52 }; 45 53 46 54 struct rockchip_usb_phy { 47 55 struct rockchip_usb_phy_base *base; 56 + struct device_node *np; 48 57 unsigned int reg_offset; 49 58 struct clk *clk; 59 + struct clk *clk480m; 60 + struct clk_hw clk480m_hw; 50 61 struct phy *phy; 51 62 }; 52 63 ··· 70 55 SIDDQ_WRITE_ENA | (siddq ? SIDDQ_ON : SIDDQ_OFF)); 71 56 } 72 57 58 + static unsigned long rockchip_usb_phy480m_recalc_rate(struct clk_hw *hw, 59 + unsigned long parent_rate) 60 + { 61 + return 480000000; 62 + } 63 + 64 + static void rockchip_usb_phy480m_disable(struct clk_hw *hw) 65 + { 66 + struct rockchip_usb_phy *phy = container_of(hw, 67 + struct rockchip_usb_phy, 68 + clk480m_hw); 69 + 70 + /* Power down usb phy analog blocks by set siddq 1 */ 71 + rockchip_usb_phy_power(phy, 1); 72 + } 73 + 74 + static int rockchip_usb_phy480m_enable(struct clk_hw *hw) 75 + { 76 + struct rockchip_usb_phy *phy = container_of(hw, 77 + struct rockchip_usb_phy, 78 + clk480m_hw); 79 + 80 + /* Power up usb phy analog blocks by set siddq 0 */ 81 + return rockchip_usb_phy_power(phy, 0); 82 + } 83 + 84 + static int rockchip_usb_phy480m_is_enabled(struct clk_hw *hw) 85 + { 86 + struct rockchip_usb_phy *phy = container_of(hw, 87 + struct rockchip_usb_phy, 88 + clk480m_hw); 89 + int ret; 90 + u32 val; 91 + 92 + ret = regmap_read(phy->base->reg_base, phy->reg_offset, &val); 93 + if (ret < 0) 94 + return ret; 95 + 96 + return (val & SIDDQ_ON) ? 0 : 1; 97 + } 98 + 99 + static const struct clk_ops rockchip_usb_phy480m_ops = { 100 + .enable = rockchip_usb_phy480m_enable, 101 + .disable = rockchip_usb_phy480m_disable, 102 + .is_enabled = rockchip_usb_phy480m_is_enabled, 103 + .recalc_rate = rockchip_usb_phy480m_recalc_rate, 104 + }; 105 + 73 106 static int rockchip_usb_phy_power_off(struct phy *_phy) 74 107 { 75 108 struct rockchip_usb_phy *phy = phy_get_drvdata(_phy); 76 - int ret = 0; 77 109 78 - /* Power down usb phy analog blocks by set siddq 1 */ 79 - ret = rockchip_usb_phy_power(phy, 1); 80 - if (ret) 81 - return ret; 82 - 83 - clk_disable_unprepare(phy->clk); 110 + clk_disable_unprepare(phy->clk480m); 84 111 85 112 return 0; 86 113 } ··· 130 73 static int rockchip_usb_phy_power_on(struct phy *_phy) 131 74 { 132 75 struct rockchip_usb_phy *phy = phy_get_drvdata(_phy); 133 - int ret = 0; 134 76 135 - ret = clk_prepare_enable(phy->clk); 136 - if (ret) 137 - return ret; 138 - 139 - /* Power up usb phy analog blocks by set siddq 0 */ 140 - ret = rockchip_usb_phy_power(phy, 0); 141 - if (ret) { 142 - clk_disable_unprepare(phy->clk); 143 - return ret; 144 - } 145 - 146 - return 0; 77 + return clk_prepare_enable(phy->clk480m); 147 78 } 148 79 149 80 static const struct phy_ops ops = { ··· 144 99 { 145 100 struct rockchip_usb_phy *rk_phy = data; 146 101 102 + of_clk_del_provider(rk_phy->np); 103 + clk_unregister(rk_phy->clk480m); 104 + 147 105 if (rk_phy->clk) 148 106 clk_put(rk_phy->clk); 149 107 } ··· 156 108 { 157 109 struct rockchip_usb_phy *rk_phy; 158 110 unsigned int reg_offset; 159 - int err; 111 + const char *clk_name; 112 + struct clk_init_data init; 113 + int err, i; 160 114 161 115 rk_phy = devm_kzalloc(base->dev, sizeof(*rk_phy), GFP_KERNEL); 162 116 if (!rk_phy) 163 117 return -ENOMEM; 164 118 165 119 rk_phy->base = base; 120 + rk_phy->np = child; 166 121 167 122 if (of_property_read_u32(child, "reg", &reg_offset)) { 168 123 dev_err(base->dev, "missing reg property in node %s\n", ··· 175 124 176 125 rk_phy->reg_offset = reg_offset; 177 126 178 - err = devm_add_action(base->dev, rockchip_usb_phy_action, rk_phy); 179 - if (err) 180 - return err; 181 - 182 127 rk_phy->clk = of_clk_get_by_name(child, "phyclk"); 183 128 if (IS_ERR(rk_phy->clk)) 184 129 rk_phy->clk = NULL; 130 + 131 + i = 0; 132 + init.name = NULL; 133 + while (base->pdata->phys[i].reg) { 134 + if (base->pdata->phys[i].reg == reg_offset) { 135 + init.name = base->pdata->phys[i].pll_name; 136 + break; 137 + } 138 + i++; 139 + } 140 + 141 + if (!init.name) { 142 + dev_err(base->dev, "phy data not found\n"); 143 + return -EINVAL; 144 + } 145 + 146 + if (rk_phy->clk) { 147 + clk_name = __clk_get_name(rk_phy->clk); 148 + init.flags = 0; 149 + init.parent_names = &clk_name; 150 + init.num_parents = 1; 151 + } else { 152 + init.flags = CLK_IS_ROOT; 153 + init.parent_names = NULL; 154 + init.num_parents = 0; 155 + } 156 + 157 + init.ops = &rockchip_usb_phy480m_ops; 158 + rk_phy->clk480m_hw.init = &init; 159 + 160 + rk_phy->clk480m = clk_register(base->dev, &rk_phy->clk480m_hw); 161 + if (IS_ERR(rk_phy->clk480m)) { 162 + err = PTR_ERR(rk_phy->clk480m); 163 + goto err_clk; 164 + } 165 + 166 + err = of_clk_add_provider(child, of_clk_src_simple_get, 167 + rk_phy->clk480m); 168 + if (err < 0) 169 + goto err_clk_prov; 170 + 171 + err = devm_add_action(base->dev, rockchip_usb_phy_action, rk_phy); 172 + if (err) 173 + goto err_devm_action; 185 174 186 175 rk_phy->phy = devm_phy_create(base->dev, child, &ops); 187 176 if (IS_ERR(rk_phy->phy)) { ··· 232 141 233 142 /* only power up usb phy when it use, so disable it when init*/ 234 143 return rockchip_usb_phy_power(rk_phy, 1); 144 + 145 + err_devm_action: 146 + of_clk_del_provider(child); 147 + err_clk_prov: 148 + clk_unregister(rk_phy->clk480m); 149 + err_clk: 150 + if (rk_phy->clk) 151 + clk_put(rk_phy->clk); 152 + return err; 235 153 } 154 + 155 + static const struct rockchip_usb_phy_pdata rk3066a_pdata = { 156 + .phys = (struct rockchip_usb_phys[]){ 157 + { .reg = 0x17c, .pll_name = "sclk_otgphy0_480m" }, 158 + { .reg = 0x188, .pll_name = "sclk_otgphy1_480m" }, 159 + { /* sentinel */ } 160 + }, 161 + }; 162 + 163 + static const struct rockchip_usb_phy_pdata rk3188_pdata = { 164 + .phys = (struct rockchip_usb_phys[]){ 165 + { .reg = 0x10c, .pll_name = "sclk_otgphy0_480m" }, 166 + { .reg = 0x11c, .pll_name = "sclk_otgphy1_480m" }, 167 + { /* sentinel */ } 168 + }, 169 + }; 170 + 171 + static const struct rockchip_usb_phy_pdata rk3288_pdata = { 172 + .phys = (struct rockchip_usb_phys[]){ 173 + { .reg = 0x320, .pll_name = "sclk_otgphy0_480m" }, 174 + { .reg = 0x334, .pll_name = "sclk_otgphy1_480m" }, 175 + { .reg = 0x348, .pll_name = "sclk_otgphy2_480m" }, 176 + { /* sentinel */ } 177 + }, 178 + }; 236 179 237 180 static int rockchip_usb_phy_probe(struct platform_device *pdev) 238 181 { 239 182 struct device *dev = &pdev->dev; 240 183 struct rockchip_usb_phy_base *phy_base; 241 184 struct phy_provider *phy_provider; 185 + const struct of_device_id *match; 242 186 struct device_node *child; 243 187 int err; 244 188 245 189 phy_base = devm_kzalloc(dev, sizeof(*phy_base), GFP_KERNEL); 246 190 if (!phy_base) 247 191 return -ENOMEM; 192 + 193 + match = of_match_device(dev->driver->of_match_table, dev); 194 + if (!match || !match->data) { 195 + dev_err(dev, "missing phy data\n"); 196 + return -EINVAL; 197 + } 198 + 199 + phy_base->pdata = match->data; 248 200 249 201 phy_base->dev = dev; 250 202 phy_base->reg_base = syscon_regmap_lookup_by_phandle(dev->of_node, ··· 310 176 } 311 177 312 178 static const struct of_device_id rockchip_usb_phy_dt_ids[] = { 313 - { .compatible = "rockchip,rk3066a-usb-phy" }, 314 - { .compatible = "rockchip,rk3188-usb-phy" }, 315 - { .compatible = "rockchip,rk3288-usb-phy" }, 179 + { .compatible = "rockchip,rk3066a-usb-phy", .data = &rk3066a_pdata }, 180 + { .compatible = "rockchip,rk3188-usb-phy", .data = &rk3188_pdata }, 181 + { .compatible = "rockchip,rk3288-usb-phy", .data = &rk3288_pdata }, 316 182 {} 317 183 }; 318 184