1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Lantiq XWAY SoC RCU module based USB 1.1/2.0 PHY driver |
4 | * |
5 | * Copyright (C) 2016 Martin Blumenstingl <martin.blumenstingl@googlemail.com> |
6 | * Copyright (C) 2017 Hauke Mehrtens <hauke@hauke-m.de> |
7 | */ |
8 | |
9 | #include <linux/clk.h> |
10 | #include <linux/delay.h> |
11 | #include <linux/mfd/syscon.h> |
12 | #include <linux/module.h> |
13 | #include <linux/of.h> |
14 | #include <linux/of_address.h> |
15 | #include <linux/phy/phy.h> |
16 | #include <linux/platform_device.h> |
17 | #include <linux/property.h> |
18 | #include <linux/regmap.h> |
19 | #include <linux/reset.h> |
20 | |
21 | /* Transmitter HS Pre-Emphasis Enable */ |
22 | #define RCU_CFG1_TX_PEE BIT(0) |
23 | /* Disconnect Threshold */ |
24 | #define RCU_CFG1_DIS_THR_MASK 0x00038000 |
25 | #define RCU_CFG1_DIS_THR_SHIFT 15 |
26 | |
27 | struct ltq_rcu_usb2_bits { |
28 | u8 hostmode; |
29 | u8 slave_endianness; |
30 | u8 host_endianness; |
31 | bool have_ana_cfg; |
32 | }; |
33 | |
34 | struct ltq_rcu_usb2_priv { |
35 | struct regmap *regmap; |
36 | unsigned int phy_reg_offset; |
37 | unsigned int ana_cfg1_reg_offset; |
38 | const struct ltq_rcu_usb2_bits *reg_bits; |
39 | struct device *dev; |
40 | struct phy *phy; |
41 | struct clk *phy_gate_clk; |
42 | struct reset_control *ctrl_reset; |
43 | struct reset_control *phy_reset; |
44 | }; |
45 | |
46 | static const struct ltq_rcu_usb2_bits xway_rcu_usb2_reg_bits = { |
47 | .hostmode = 11, |
48 | .slave_endianness = 9, |
49 | .host_endianness = 10, |
50 | .have_ana_cfg = false, |
51 | }; |
52 | |
53 | static const struct ltq_rcu_usb2_bits xrx100_rcu_usb2_reg_bits = { |
54 | .hostmode = 11, |
55 | .slave_endianness = 17, |
56 | .host_endianness = 10, |
57 | .have_ana_cfg = false, |
58 | }; |
59 | |
60 | static const struct ltq_rcu_usb2_bits xrx200_rcu_usb2_reg_bits = { |
61 | .hostmode = 11, |
62 | .slave_endianness = 9, |
63 | .host_endianness = 10, |
64 | .have_ana_cfg = true, |
65 | }; |
66 | |
67 | static const struct of_device_id ltq_rcu_usb2_phy_of_match[] = { |
68 | { |
69 | .compatible = "lantiq,ase-usb2-phy" , |
70 | .data = &xway_rcu_usb2_reg_bits, |
71 | }, |
72 | { |
73 | .compatible = "lantiq,danube-usb2-phy" , |
74 | .data = &xway_rcu_usb2_reg_bits, |
75 | }, |
76 | { |
77 | .compatible = "lantiq,xrx100-usb2-phy" , |
78 | .data = &xrx100_rcu_usb2_reg_bits, |
79 | }, |
80 | { |
81 | .compatible = "lantiq,xrx200-usb2-phy" , |
82 | .data = &xrx200_rcu_usb2_reg_bits, |
83 | }, |
84 | { |
85 | .compatible = "lantiq,xrx300-usb2-phy" , |
86 | .data = &xrx200_rcu_usb2_reg_bits, |
87 | }, |
88 | { }, |
89 | }; |
90 | MODULE_DEVICE_TABLE(of, ltq_rcu_usb2_phy_of_match); |
91 | |
92 | static int ltq_rcu_usb2_phy_init(struct phy *phy) |
93 | { |
94 | struct ltq_rcu_usb2_priv *priv = phy_get_drvdata(phy); |
95 | |
96 | if (priv->reg_bits->have_ana_cfg) { |
97 | regmap_update_bits(map: priv->regmap, reg: priv->ana_cfg1_reg_offset, |
98 | RCU_CFG1_TX_PEE, RCU_CFG1_TX_PEE); |
99 | regmap_update_bits(map: priv->regmap, reg: priv->ana_cfg1_reg_offset, |
100 | RCU_CFG1_DIS_THR_MASK, val: 7 << RCU_CFG1_DIS_THR_SHIFT); |
101 | } |
102 | |
103 | /* Configure core to host mode */ |
104 | regmap_update_bits(map: priv->regmap, reg: priv->phy_reg_offset, |
105 | BIT(priv->reg_bits->hostmode), val: 0); |
106 | |
107 | /* Select DMA endianness (Host-endian: big-endian) */ |
108 | regmap_update_bits(map: priv->regmap, reg: priv->phy_reg_offset, |
109 | BIT(priv->reg_bits->slave_endianness), val: 0); |
110 | regmap_update_bits(map: priv->regmap, reg: priv->phy_reg_offset, |
111 | BIT(priv->reg_bits->host_endianness), |
112 | BIT(priv->reg_bits->host_endianness)); |
113 | |
114 | return 0; |
115 | } |
116 | |
117 | static int ltq_rcu_usb2_phy_power_on(struct phy *phy) |
118 | { |
119 | struct ltq_rcu_usb2_priv *priv = phy_get_drvdata(phy); |
120 | struct device *dev = priv->dev; |
121 | int ret; |
122 | |
123 | reset_control_deassert(rstc: priv->phy_reset); |
124 | |
125 | ret = clk_prepare_enable(clk: priv->phy_gate_clk); |
126 | if (ret) { |
127 | dev_err(dev, "failed to enable PHY gate\n" ); |
128 | return ret; |
129 | } |
130 | |
131 | /* |
132 | * at least the xrx200 usb2 phy requires some extra time to be |
133 | * operational after enabling the clock |
134 | */ |
135 | usleep_range(min: 100, max: 200); |
136 | |
137 | return ret; |
138 | } |
139 | |
140 | static int ltq_rcu_usb2_phy_power_off(struct phy *phy) |
141 | { |
142 | struct ltq_rcu_usb2_priv *priv = phy_get_drvdata(phy); |
143 | |
144 | reset_control_assert(rstc: priv->phy_reset); |
145 | |
146 | clk_disable_unprepare(clk: priv->phy_gate_clk); |
147 | |
148 | return 0; |
149 | } |
150 | |
151 | static const struct phy_ops ltq_rcu_usb2_phy_ops = { |
152 | .init = ltq_rcu_usb2_phy_init, |
153 | .power_on = ltq_rcu_usb2_phy_power_on, |
154 | .power_off = ltq_rcu_usb2_phy_power_off, |
155 | .owner = THIS_MODULE, |
156 | }; |
157 | |
158 | static int ltq_rcu_usb2_of_parse(struct ltq_rcu_usb2_priv *priv, |
159 | struct platform_device *pdev) |
160 | { |
161 | struct device *dev = priv->dev; |
162 | const __be32 *offset; |
163 | |
164 | priv->reg_bits = of_device_get_match_data(dev); |
165 | |
166 | priv->regmap = syscon_node_to_regmap(np: dev->of_node->parent); |
167 | if (IS_ERR(ptr: priv->regmap)) { |
168 | dev_err(dev, "Failed to lookup RCU regmap\n" ); |
169 | return PTR_ERR(ptr: priv->regmap); |
170 | } |
171 | |
172 | offset = of_get_address(dev: dev->of_node, index: 0, NULL, NULL); |
173 | if (!offset) { |
174 | dev_err(dev, "Failed to get RCU PHY reg offset\n" ); |
175 | return -ENOENT; |
176 | } |
177 | priv->phy_reg_offset = __be32_to_cpu(*offset); |
178 | |
179 | if (priv->reg_bits->have_ana_cfg) { |
180 | offset = of_get_address(dev: dev->of_node, index: 1, NULL, NULL); |
181 | if (!offset) { |
182 | dev_err(dev, "Failed to get RCU ANA CFG1 reg offset\n" ); |
183 | return -ENOENT; |
184 | } |
185 | priv->ana_cfg1_reg_offset = __be32_to_cpu(*offset); |
186 | } |
187 | |
188 | priv->phy_gate_clk = devm_clk_get(dev, id: "phy" ); |
189 | if (IS_ERR(ptr: priv->phy_gate_clk)) { |
190 | dev_err(dev, "Unable to get USB phy gate clk\n" ); |
191 | return PTR_ERR(ptr: priv->phy_gate_clk); |
192 | } |
193 | |
194 | priv->ctrl_reset = devm_reset_control_get_shared(dev, id: "ctrl" ); |
195 | if (IS_ERR(ptr: priv->ctrl_reset)) { |
196 | if (PTR_ERR(ptr: priv->ctrl_reset) != -EPROBE_DEFER) |
197 | dev_err(dev, "failed to get 'ctrl' reset\n" ); |
198 | return PTR_ERR(ptr: priv->ctrl_reset); |
199 | } |
200 | |
201 | priv->phy_reset = devm_reset_control_get_optional(dev, id: "phy" ); |
202 | |
203 | return PTR_ERR_OR_ZERO(ptr: priv->phy_reset); |
204 | } |
205 | |
206 | static int ltq_rcu_usb2_phy_probe(struct platform_device *pdev) |
207 | { |
208 | struct device *dev = &pdev->dev; |
209 | struct ltq_rcu_usb2_priv *priv; |
210 | struct phy_provider *provider; |
211 | int ret; |
212 | |
213 | priv = devm_kzalloc(dev, size: sizeof(*priv), GFP_KERNEL); |
214 | if (!priv) |
215 | return -ENOMEM; |
216 | |
217 | priv->dev = dev; |
218 | |
219 | ret = ltq_rcu_usb2_of_parse(priv, pdev); |
220 | if (ret) |
221 | return ret; |
222 | |
223 | /* Reset USB core through reset controller */ |
224 | reset_control_deassert(rstc: priv->ctrl_reset); |
225 | |
226 | reset_control_assert(rstc: priv->phy_reset); |
227 | |
228 | priv->phy = devm_phy_create(dev, node: dev->of_node, ops: <q_rcu_usb2_phy_ops); |
229 | if (IS_ERR(ptr: priv->phy)) { |
230 | dev_err(dev, "failed to create PHY\n" ); |
231 | return PTR_ERR(ptr: priv->phy); |
232 | } |
233 | |
234 | phy_set_drvdata(phy: priv->phy, data: priv); |
235 | |
236 | provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); |
237 | if (IS_ERR(ptr: provider)) |
238 | return PTR_ERR(ptr: provider); |
239 | |
240 | dev_set_drvdata(dev: priv->dev, data: priv); |
241 | return 0; |
242 | } |
243 | |
244 | static struct platform_driver ltq_rcu_usb2_phy_driver = { |
245 | .probe = ltq_rcu_usb2_phy_probe, |
246 | .driver = { |
247 | .name = "lantiq-rcu-usb2-phy" , |
248 | .of_match_table = ltq_rcu_usb2_phy_of_match, |
249 | } |
250 | }; |
251 | module_platform_driver(ltq_rcu_usb2_phy_driver); |
252 | |
253 | MODULE_AUTHOR("Martin Blumenstingl <martin.blumenstingl@googlemail.com>" ); |
254 | MODULE_DESCRIPTION("Lantiq XWAY USB2 PHY driver" ); |
255 | MODULE_LICENSE("GPL v2" ); |
256 | |