1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | |
3 | #include <linux/clk.h> |
4 | #include <linux/err.h> |
5 | #include <linux/io.h> |
6 | #include <linux/module.h> |
7 | #include <linux/of.h> |
8 | #include <linux/phy/phy.h> |
9 | #include <linux/platform_device.h> |
10 | #include <linux/delay.h> |
11 | #include <linux/regmap.h> |
12 | #include <linux/mfd/syscon.h> |
13 | #include <linux/bitfield.h> |
14 | |
15 | /* USB QSCRATCH Hardware registers */ |
16 | #define QSCRATCH_GENERAL_CFG (0x08) |
17 | #define HSUSB_PHY_CTRL_REG (0x10) |
18 | |
19 | /* PHY_CTRL_REG */ |
20 | #define HSUSB_CTRL_DMSEHV_CLAMP BIT(24) |
21 | #define HSUSB_CTRL_USB2_SUSPEND BIT(23) |
22 | #define HSUSB_CTRL_UTMI_CLK_EN BIT(21) |
23 | #define HSUSB_CTRL_UTMI_OTG_VBUS_VALID BIT(20) |
24 | #define HSUSB_CTRL_USE_CLKCORE BIT(18) |
25 | #define HSUSB_CTRL_DPSEHV_CLAMP BIT(17) |
26 | #define HSUSB_CTRL_COMMONONN BIT(11) |
27 | #define HSUSB_CTRL_ID_HV_CLAMP BIT(9) |
28 | #define HSUSB_CTRL_OTGSESSVLD_CLAMP BIT(8) |
29 | #define HSUSB_CTRL_CLAMP_EN BIT(7) |
30 | #define HSUSB_CTRL_RETENABLEN BIT(1) |
31 | #define HSUSB_CTRL_POR BIT(0) |
32 | |
33 | /* QSCRATCH_GENERAL_CFG */ |
34 | #define HSUSB_GCFG_XHCI_REV BIT(2) |
35 | |
36 | /* USB QSCRATCH Hardware registers */ |
37 | #define SSUSB_PHY_CTRL_REG (0x00) |
38 | #define SSUSB_PHY_PARAM_CTRL_1 (0x04) |
39 | #define SSUSB_PHY_PARAM_CTRL_2 (0x08) |
40 | #define CR_PROTOCOL_DATA_IN_REG (0x0c) |
41 | #define CR_PROTOCOL_DATA_OUT_REG (0x10) |
42 | #define CR_PROTOCOL_CAP_ADDR_REG (0x14) |
43 | #define CR_PROTOCOL_CAP_DATA_REG (0x18) |
44 | #define CR_PROTOCOL_READ_REG (0x1c) |
45 | #define CR_PROTOCOL_WRITE_REG (0x20) |
46 | |
47 | /* PHY_CTRL_REG */ |
48 | #define SSUSB_CTRL_REF_USE_PAD BIT(28) |
49 | #define SSUSB_CTRL_TEST_POWERDOWN BIT(27) |
50 | #define SSUSB_CTRL_LANE0_PWR_PRESENT BIT(24) |
51 | #define SSUSB_CTRL_SS_PHY_EN BIT(8) |
52 | #define SSUSB_CTRL_SS_PHY_RESET BIT(7) |
53 | |
54 | /* SSPHY control registers - Does this need 0x30? */ |
55 | #define SSPHY_CTRL_RX_OVRD_IN_HI(lane) (0x1006 + 0x100 * (lane)) |
56 | #define SSPHY_CTRL_TX_OVRD_DRV_LO(lane) (0x1002 + 0x100 * (lane)) |
57 | |
58 | /* SSPHY SoC version specific values */ |
59 | #define SSPHY_RX_EQ_VALUE 4 /* Override value for rx_eq */ |
60 | /* Override value for transmit preemphasis */ |
61 | #define SSPHY_TX_DEEMPH_3_5DB 23 |
62 | /* Override value for mpll */ |
63 | #define SSPHY_MPLL_VALUE 0 |
64 | |
65 | /* QSCRATCH PHY_PARAM_CTRL1 fields */ |
66 | #define PHY_PARAM_CTRL1_TX_FULL_SWING_MASK GENMASK(26, 19) |
67 | #define PHY_PARAM_CTRL1_TX_DEEMPH_6DB_MASK GENMASK(19, 13) |
68 | #define PHY_PARAM_CTRL1_TX_DEEMPH_3_5DB_MASK GENMASK(13, 7) |
69 | #define PHY_PARAM_CTRL1_LOS_BIAS_MASK GENMASK(7, 2) |
70 | |
71 | #define PHY_PARAM_CTRL1_MASK \ |
72 | (PHY_PARAM_CTRL1_TX_FULL_SWING_MASK | \ |
73 | PHY_PARAM_CTRL1_TX_DEEMPH_6DB_MASK | \ |
74 | PHY_PARAM_CTRL1_TX_DEEMPH_3_5DB_MASK | \ |
75 | PHY_PARAM_CTRL1_LOS_BIAS_MASK) |
76 | |
77 | #define PHY_PARAM_CTRL1_TX_FULL_SWING(x) \ |
78 | FIELD_PREP(PHY_PARAM_CTRL1_TX_FULL_SWING_MASK, (x)) |
79 | #define PHY_PARAM_CTRL1_TX_DEEMPH_6DB(x) \ |
80 | FIELD_PREP(PHY_PARAM_CTRL1_TX_DEEMPH_6DB_MASK, (x)) |
81 | #define PHY_PARAM_CTRL1_TX_DEEMPH_3_5DB(x) \ |
82 | FIELD_PREP(PHY_PARAM_CTRL1_TX_DEEMPH_3_5DB_MASK, x) |
83 | #define PHY_PARAM_CTRL1_LOS_BIAS(x) \ |
84 | FIELD_PREP(PHY_PARAM_CTRL1_LOS_BIAS_MASK, (x)) |
85 | |
86 | /* RX OVRD IN HI bits */ |
87 | #define RX_OVRD_IN_HI_RX_RESET_OVRD BIT(13) |
88 | #define RX_OVRD_IN_HI_RX_RX_RESET BIT(12) |
89 | #define RX_OVRD_IN_HI_RX_EQ_OVRD BIT(11) |
90 | #define RX_OVRD_IN_HI_RX_EQ_MASK GENMASK(10, 7) |
91 | #define RX_OVRD_IN_HI_RX_EQ(x) FIELD_PREP(RX_OVRD_IN_HI_RX_EQ_MASK, (x)) |
92 | #define RX_OVRD_IN_HI_RX_EQ_EN_OVRD BIT(7) |
93 | #define RX_OVRD_IN_HI_RX_EQ_EN BIT(6) |
94 | #define RX_OVRD_IN_HI_RX_LOS_FILTER_OVRD BIT(5) |
95 | #define RX_OVRD_IN_HI_RX_LOS_FILTER_MASK GENMASK(4, 2) |
96 | #define RX_OVRD_IN_HI_RX_RATE_OVRD BIT(2) |
97 | #define RX_OVRD_IN_HI_RX_RATE_MASK GENMASK(2, 0) |
98 | |
99 | /* TX OVRD DRV LO register bits */ |
100 | #define TX_OVRD_DRV_LO_AMPLITUDE_MASK GENMASK(6, 0) |
101 | #define TX_OVRD_DRV_LO_PREEMPH_MASK GENMASK(13, 6) |
102 | #define TX_OVRD_DRV_LO_PREEMPH(x) ((x) << 7) |
103 | #define TX_OVRD_DRV_LO_EN BIT(14) |
104 | |
105 | /* MPLL bits */ |
106 | #define SSPHY_MPLL_MASK GENMASK(8, 5) |
107 | #define SSPHY_MPLL(x) ((x) << 5) |
108 | |
109 | /* SS CAP register bits */ |
110 | #define SS_CR_CAP_ADDR_REG BIT(0) |
111 | #define SS_CR_CAP_DATA_REG BIT(0) |
112 | #define SS_CR_READ_REG BIT(0) |
113 | #define SS_CR_WRITE_REG BIT(0) |
114 | |
115 | #define LATCH_SLEEP 40 |
116 | #define LATCH_TIMEOUT 100 |
117 | |
118 | struct usb_phy { |
119 | void __iomem *base; |
120 | struct device *dev; |
121 | struct clk *xo_clk; |
122 | struct clk *ref_clk; |
123 | u32 rx_eq; |
124 | u32 tx_deamp_3_5db; |
125 | u32 mpll; |
126 | }; |
127 | |
128 | struct phy_drvdata { |
129 | struct phy_ops ops; |
130 | u32 clk_rate; |
131 | }; |
132 | |
133 | /** |
134 | * usb_phy_write_readback() - Write register and read back masked value to |
135 | * confirm it is written |
136 | * |
137 | * @phy_dwc3: QCOM DWC3 phy context |
138 | * @offset: register offset. |
139 | * @mask: register bitmask specifying what should be updated |
140 | * @val: value to write. |
141 | */ |
142 | static inline void usb_phy_write_readback(struct usb_phy *phy_dwc3, |
143 | u32 offset, |
144 | const u32 mask, u32 val) |
145 | { |
146 | u32 write_val, tmp = readl(addr: phy_dwc3->base + offset); |
147 | |
148 | tmp &= ~mask; /* retain other bits */ |
149 | write_val = tmp | val; |
150 | |
151 | writel(val: write_val, addr: phy_dwc3->base + offset); |
152 | |
153 | /* Read back to see if val was written */ |
154 | tmp = readl(addr: phy_dwc3->base + offset); |
155 | tmp &= mask; /* clear other bits */ |
156 | |
157 | if (tmp != val) |
158 | dev_err(phy_dwc3->dev, "write: %x to QSCRATCH: %x FAILED\n" , val, offset); |
159 | } |
160 | |
161 | static int wait_for_latch(void __iomem *addr) |
162 | { |
163 | u32 val; |
164 | |
165 | return readl_poll_timeout(addr, val, !val, LATCH_SLEEP, LATCH_TIMEOUT); |
166 | } |
167 | |
168 | /** |
169 | * usb_ss_write_phycreg() - Write SSPHY register |
170 | * |
171 | * @phy_dwc3: QCOM DWC3 phy context |
172 | * @addr: SSPHY address to write. |
173 | * @val: value to write. |
174 | */ |
175 | static int usb_ss_write_phycreg(struct usb_phy *phy_dwc3, |
176 | u32 addr, u32 val) |
177 | { |
178 | int ret; |
179 | |
180 | writel(val: addr, addr: phy_dwc3->base + CR_PROTOCOL_DATA_IN_REG); |
181 | writel(SS_CR_CAP_ADDR_REG, |
182 | addr: phy_dwc3->base + CR_PROTOCOL_CAP_ADDR_REG); |
183 | |
184 | ret = wait_for_latch(addr: phy_dwc3->base + CR_PROTOCOL_CAP_ADDR_REG); |
185 | if (ret) |
186 | goto err_wait; |
187 | |
188 | writel(val, addr: phy_dwc3->base + CR_PROTOCOL_DATA_IN_REG); |
189 | writel(SS_CR_CAP_DATA_REG, |
190 | addr: phy_dwc3->base + CR_PROTOCOL_CAP_DATA_REG); |
191 | |
192 | ret = wait_for_latch(addr: phy_dwc3->base + CR_PROTOCOL_CAP_DATA_REG); |
193 | if (ret) |
194 | goto err_wait; |
195 | |
196 | writel(SS_CR_WRITE_REG, addr: phy_dwc3->base + CR_PROTOCOL_WRITE_REG); |
197 | |
198 | ret = wait_for_latch(addr: phy_dwc3->base + CR_PROTOCOL_WRITE_REG); |
199 | |
200 | err_wait: |
201 | if (ret) |
202 | dev_err(phy_dwc3->dev, "timeout waiting for latch\n" ); |
203 | return ret; |
204 | } |
205 | |
206 | /** |
207 | * usb_ss_read_phycreg() - Read SSPHY register. |
208 | * |
209 | * @phy_dwc3: QCOM DWC3 phy context |
210 | * @addr: SSPHY address to read. |
211 | * @val: pointer in which read is store. |
212 | */ |
213 | static int usb_ss_read_phycreg(struct usb_phy *phy_dwc3, |
214 | u32 addr, u32 *val) |
215 | { |
216 | int ret; |
217 | |
218 | writel(val: addr, addr: phy_dwc3->base + CR_PROTOCOL_DATA_IN_REG); |
219 | writel(SS_CR_CAP_ADDR_REG, |
220 | addr: phy_dwc3->base + CR_PROTOCOL_CAP_ADDR_REG); |
221 | |
222 | ret = wait_for_latch(addr: phy_dwc3->base + CR_PROTOCOL_CAP_ADDR_REG); |
223 | if (ret) |
224 | goto err_wait; |
225 | |
226 | /* |
227 | * Due to hardware bug, first read of SSPHY register might be |
228 | * incorrect. Hence as workaround, SW should perform SSPHY register |
229 | * read twice, but use only second read and ignore first read. |
230 | */ |
231 | writel(SS_CR_READ_REG, addr: phy_dwc3->base + CR_PROTOCOL_READ_REG); |
232 | |
233 | ret = wait_for_latch(addr: phy_dwc3->base + CR_PROTOCOL_READ_REG); |
234 | if (ret) |
235 | goto err_wait; |
236 | |
237 | /* throwaway read */ |
238 | readl(addr: phy_dwc3->base + CR_PROTOCOL_DATA_OUT_REG); |
239 | |
240 | writel(SS_CR_READ_REG, addr: phy_dwc3->base + CR_PROTOCOL_READ_REG); |
241 | |
242 | ret = wait_for_latch(addr: phy_dwc3->base + CR_PROTOCOL_READ_REG); |
243 | if (ret) |
244 | goto err_wait; |
245 | |
246 | *val = readl(addr: phy_dwc3->base + CR_PROTOCOL_DATA_OUT_REG); |
247 | |
248 | err_wait: |
249 | return ret; |
250 | } |
251 | |
252 | static int qcom_ipq806x_usb_hs_phy_init(struct phy *phy) |
253 | { |
254 | struct usb_phy *phy_dwc3 = phy_get_drvdata(phy); |
255 | int ret; |
256 | u32 val; |
257 | |
258 | ret = clk_prepare_enable(clk: phy_dwc3->xo_clk); |
259 | if (ret) |
260 | return ret; |
261 | |
262 | ret = clk_prepare_enable(clk: phy_dwc3->ref_clk); |
263 | if (ret) { |
264 | clk_disable_unprepare(clk: phy_dwc3->xo_clk); |
265 | return ret; |
266 | } |
267 | |
268 | /* |
269 | * HSPHY Initialization: Enable UTMI clock, select 19.2MHz fsel |
270 | * enable clamping, and disable RETENTION (power-on default is ENABLED) |
271 | */ |
272 | val = HSUSB_CTRL_DPSEHV_CLAMP | HSUSB_CTRL_DMSEHV_CLAMP | |
273 | HSUSB_CTRL_RETENABLEN | HSUSB_CTRL_COMMONONN | |
274 | HSUSB_CTRL_OTGSESSVLD_CLAMP | HSUSB_CTRL_ID_HV_CLAMP | |
275 | HSUSB_CTRL_UTMI_OTG_VBUS_VALID | HSUSB_CTRL_UTMI_CLK_EN | |
276 | HSUSB_CTRL_CLAMP_EN | 0x70; |
277 | |
278 | /* use core clock if external reference is not present */ |
279 | if (!phy_dwc3->xo_clk) |
280 | val |= HSUSB_CTRL_USE_CLKCORE; |
281 | |
282 | writel(val, addr: phy_dwc3->base + HSUSB_PHY_CTRL_REG); |
283 | usleep_range(min: 2000, max: 2200); |
284 | |
285 | /* Disable (bypass) VBUS and ID filters */ |
286 | writel(HSUSB_GCFG_XHCI_REV, addr: phy_dwc3->base + QSCRATCH_GENERAL_CFG); |
287 | |
288 | return 0; |
289 | } |
290 | |
291 | static int qcom_ipq806x_usb_hs_phy_exit(struct phy *phy) |
292 | { |
293 | struct usb_phy *phy_dwc3 = phy_get_drvdata(phy); |
294 | |
295 | clk_disable_unprepare(clk: phy_dwc3->ref_clk); |
296 | clk_disable_unprepare(clk: phy_dwc3->xo_clk); |
297 | |
298 | return 0; |
299 | } |
300 | |
301 | static int qcom_ipq806x_usb_ss_phy_init(struct phy *phy) |
302 | { |
303 | struct usb_phy *phy_dwc3 = phy_get_drvdata(phy); |
304 | int ret; |
305 | u32 data; |
306 | |
307 | ret = clk_prepare_enable(clk: phy_dwc3->xo_clk); |
308 | if (ret) |
309 | return ret; |
310 | |
311 | ret = clk_prepare_enable(clk: phy_dwc3->ref_clk); |
312 | if (ret) { |
313 | clk_disable_unprepare(clk: phy_dwc3->xo_clk); |
314 | return ret; |
315 | } |
316 | |
317 | /* reset phy */ |
318 | data = readl(addr: phy_dwc3->base + SSUSB_PHY_CTRL_REG); |
319 | writel(val: data | SSUSB_CTRL_SS_PHY_RESET, |
320 | addr: phy_dwc3->base + SSUSB_PHY_CTRL_REG); |
321 | usleep_range(min: 2000, max: 2200); |
322 | writel(val: data, addr: phy_dwc3->base + SSUSB_PHY_CTRL_REG); |
323 | |
324 | /* clear REF_PAD if we don't have XO clk */ |
325 | if (!phy_dwc3->xo_clk) |
326 | data &= ~SSUSB_CTRL_REF_USE_PAD; |
327 | else |
328 | data |= SSUSB_CTRL_REF_USE_PAD; |
329 | |
330 | writel(val: data, addr: phy_dwc3->base + SSUSB_PHY_CTRL_REG); |
331 | |
332 | /* wait for ref clk to become stable, this can take up to 30ms */ |
333 | msleep(msecs: 30); |
334 | |
335 | data |= SSUSB_CTRL_SS_PHY_EN | SSUSB_CTRL_LANE0_PWR_PRESENT; |
336 | writel(val: data, addr: phy_dwc3->base + SSUSB_PHY_CTRL_REG); |
337 | |
338 | /* |
339 | * WORKAROUND: There is SSPHY suspend bug due to which USB enumerates |
340 | * in HS mode instead of SS mode. Workaround it by asserting |
341 | * LANE0.TX_ALT_BLOCK.EN_ALT_BUS to enable TX to use alt bus mode |
342 | */ |
343 | ret = usb_ss_read_phycreg(phy_dwc3, addr: 0x102D, val: &data); |
344 | if (ret) |
345 | goto err_phy_trans; |
346 | |
347 | data |= (1 << 7); |
348 | ret = usb_ss_write_phycreg(phy_dwc3, addr: 0x102D, val: data); |
349 | if (ret) |
350 | goto err_phy_trans; |
351 | |
352 | ret = usb_ss_read_phycreg(phy_dwc3, addr: 0x1010, val: &data); |
353 | if (ret) |
354 | goto err_phy_trans; |
355 | |
356 | data &= ~0xff0; |
357 | data |= 0x20; |
358 | ret = usb_ss_write_phycreg(phy_dwc3, addr: 0x1010, val: data); |
359 | if (ret) |
360 | goto err_phy_trans; |
361 | |
362 | /* |
363 | * Fix RX Equalization setting as follows |
364 | * LANE0.RX_OVRD_IN_HI. RX_EQ_EN set to 0 |
365 | * LANE0.RX_OVRD_IN_HI.RX_EQ_EN_OVRD set to 1 |
366 | * LANE0.RX_OVRD_IN_HI.RX_EQ set based on SoC version |
367 | * LANE0.RX_OVRD_IN_HI.RX_EQ_OVRD set to 1 |
368 | */ |
369 | ret = usb_ss_read_phycreg(phy_dwc3, SSPHY_CTRL_RX_OVRD_IN_HI(0), val: &data); |
370 | if (ret) |
371 | goto err_phy_trans; |
372 | |
373 | data &= ~RX_OVRD_IN_HI_RX_EQ_EN; |
374 | data |= RX_OVRD_IN_HI_RX_EQ_EN_OVRD; |
375 | data &= ~RX_OVRD_IN_HI_RX_EQ_MASK; |
376 | data |= RX_OVRD_IN_HI_RX_EQ(phy_dwc3->rx_eq); |
377 | data |= RX_OVRD_IN_HI_RX_EQ_OVRD; |
378 | ret = usb_ss_write_phycreg(phy_dwc3, |
379 | SSPHY_CTRL_RX_OVRD_IN_HI(0), val: data); |
380 | if (ret) |
381 | goto err_phy_trans; |
382 | |
383 | /* |
384 | * Set EQ and TX launch amplitudes as follows |
385 | * LANE0.TX_OVRD_DRV_LO.PREEMPH set based on SoC version |
386 | * LANE0.TX_OVRD_DRV_LO.AMPLITUDE set to 110 |
387 | * LANE0.TX_OVRD_DRV_LO.EN set to 1. |
388 | */ |
389 | ret = usb_ss_read_phycreg(phy_dwc3, |
390 | SSPHY_CTRL_TX_OVRD_DRV_LO(0), val: &data); |
391 | if (ret) |
392 | goto err_phy_trans; |
393 | |
394 | data &= ~TX_OVRD_DRV_LO_PREEMPH_MASK; |
395 | data |= TX_OVRD_DRV_LO_PREEMPH(phy_dwc3->tx_deamp_3_5db); |
396 | data &= ~TX_OVRD_DRV_LO_AMPLITUDE_MASK; |
397 | data |= 0x6E; |
398 | data |= TX_OVRD_DRV_LO_EN; |
399 | ret = usb_ss_write_phycreg(phy_dwc3, |
400 | SSPHY_CTRL_TX_OVRD_DRV_LO(0), val: data); |
401 | if (ret) |
402 | goto err_phy_trans; |
403 | |
404 | data = 0; |
405 | data &= ~SSPHY_MPLL_MASK; |
406 | data |= SSPHY_MPLL(phy_dwc3->mpll); |
407 | usb_ss_write_phycreg(phy_dwc3, addr: 0x30, val: data); |
408 | |
409 | /* |
410 | * Set the QSCRATCH PHY_PARAM_CTRL1 parameters as follows |
411 | * TX_FULL_SWING [26:20] amplitude to 110 |
412 | * TX_DEEMPH_6DB [19:14] to 32 |
413 | * TX_DEEMPH_3_5DB [13:8] set based on SoC version |
414 | * LOS_BIAS [7:3] to 9 |
415 | */ |
416 | data = readl(addr: phy_dwc3->base + SSUSB_PHY_PARAM_CTRL_1); |
417 | |
418 | data &= ~PHY_PARAM_CTRL1_MASK; |
419 | |
420 | data |= PHY_PARAM_CTRL1_TX_FULL_SWING(0x6e) | |
421 | PHY_PARAM_CTRL1_TX_DEEMPH_6DB(0x20) | |
422 | PHY_PARAM_CTRL1_TX_DEEMPH_3_5DB(phy_dwc3->tx_deamp_3_5db) | |
423 | PHY_PARAM_CTRL1_LOS_BIAS(0x9); |
424 | |
425 | usb_phy_write_readback(phy_dwc3, SSUSB_PHY_PARAM_CTRL_1, |
426 | PHY_PARAM_CTRL1_MASK, val: data); |
427 | |
428 | err_phy_trans: |
429 | return ret; |
430 | } |
431 | |
432 | static int qcom_ipq806x_usb_ss_phy_exit(struct phy *phy) |
433 | { |
434 | struct usb_phy *phy_dwc3 = phy_get_drvdata(phy); |
435 | |
436 | /* Sequence to put SSPHY in low power state: |
437 | * 1. Clear REF_PHY_EN in PHY_CTRL_REG |
438 | * 2. Clear REF_USE_PAD in PHY_CTRL_REG |
439 | * 3. Set TEST_POWERED_DOWN in PHY_CTRL_REG to enable PHY retention |
440 | */ |
441 | usb_phy_write_readback(phy_dwc3, SSUSB_PHY_CTRL_REG, |
442 | SSUSB_CTRL_SS_PHY_EN, val: 0x0); |
443 | usb_phy_write_readback(phy_dwc3, SSUSB_PHY_CTRL_REG, |
444 | SSUSB_CTRL_REF_USE_PAD, val: 0x0); |
445 | usb_phy_write_readback(phy_dwc3, SSUSB_PHY_CTRL_REG, |
446 | SSUSB_CTRL_TEST_POWERDOWN, val: 0x0); |
447 | |
448 | clk_disable_unprepare(clk: phy_dwc3->ref_clk); |
449 | clk_disable_unprepare(clk: phy_dwc3->xo_clk); |
450 | |
451 | return 0; |
452 | } |
453 | |
454 | static const struct phy_drvdata qcom_ipq806x_usb_hs_drvdata = { |
455 | .ops = { |
456 | .init = qcom_ipq806x_usb_hs_phy_init, |
457 | .exit = qcom_ipq806x_usb_hs_phy_exit, |
458 | .owner = THIS_MODULE, |
459 | }, |
460 | .clk_rate = 60000000, |
461 | }; |
462 | |
463 | static const struct phy_drvdata qcom_ipq806x_usb_ss_drvdata = { |
464 | .ops = { |
465 | .init = qcom_ipq806x_usb_ss_phy_init, |
466 | .exit = qcom_ipq806x_usb_ss_phy_exit, |
467 | .owner = THIS_MODULE, |
468 | }, |
469 | .clk_rate = 125000000, |
470 | }; |
471 | |
472 | static const struct of_device_id qcom_ipq806x_usb_phy_table[] = { |
473 | { .compatible = "qcom,ipq806x-usb-phy-hs" , |
474 | .data = &qcom_ipq806x_usb_hs_drvdata }, |
475 | { .compatible = "qcom,ipq806x-usb-phy-ss" , |
476 | .data = &qcom_ipq806x_usb_ss_drvdata }, |
477 | { /* Sentinel */ } |
478 | }; |
479 | MODULE_DEVICE_TABLE(of, qcom_ipq806x_usb_phy_table); |
480 | |
481 | static int qcom_ipq806x_usb_phy_probe(struct platform_device *pdev) |
482 | { |
483 | struct resource *res; |
484 | resource_size_t size; |
485 | struct phy *generic_phy; |
486 | struct usb_phy *phy_dwc3; |
487 | const struct phy_drvdata *data; |
488 | struct phy_provider *phy_provider; |
489 | |
490 | phy_dwc3 = devm_kzalloc(dev: &pdev->dev, size: sizeof(*phy_dwc3), GFP_KERNEL); |
491 | if (!phy_dwc3) |
492 | return -ENOMEM; |
493 | |
494 | data = of_device_get_match_data(dev: &pdev->dev); |
495 | |
496 | phy_dwc3->dev = &pdev->dev; |
497 | |
498 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
499 | if (!res) |
500 | return -EINVAL; |
501 | size = resource_size(res); |
502 | phy_dwc3->base = devm_ioremap(dev: phy_dwc3->dev, offset: res->start, size); |
503 | |
504 | if (!phy_dwc3->base) { |
505 | dev_err(phy_dwc3->dev, "failed to map reg\n" ); |
506 | return -ENOMEM; |
507 | } |
508 | |
509 | phy_dwc3->ref_clk = devm_clk_get(dev: phy_dwc3->dev, id: "ref" ); |
510 | if (IS_ERR(ptr: phy_dwc3->ref_clk)) { |
511 | dev_dbg(phy_dwc3->dev, "cannot get reference clock\n" ); |
512 | return PTR_ERR(ptr: phy_dwc3->ref_clk); |
513 | } |
514 | |
515 | clk_set_rate(clk: phy_dwc3->ref_clk, rate: data->clk_rate); |
516 | |
517 | phy_dwc3->xo_clk = devm_clk_get(dev: phy_dwc3->dev, id: "xo" ); |
518 | if (IS_ERR(ptr: phy_dwc3->xo_clk)) { |
519 | dev_dbg(phy_dwc3->dev, "cannot get TCXO clock\n" ); |
520 | phy_dwc3->xo_clk = NULL; |
521 | } |
522 | |
523 | /* Parse device node to probe HSIO settings */ |
524 | if (device_property_read_u32(dev: &pdev->dev, propname: "qcom,rx-eq" , |
525 | val: &phy_dwc3->rx_eq)) |
526 | phy_dwc3->rx_eq = SSPHY_RX_EQ_VALUE; |
527 | |
528 | if (device_property_read_u32(dev: &pdev->dev, propname: "qcom,tx-deamp_3_5db" , |
529 | val: &phy_dwc3->tx_deamp_3_5db)) |
530 | phy_dwc3->tx_deamp_3_5db = SSPHY_TX_DEEMPH_3_5DB; |
531 | |
532 | if (device_property_read_u32(dev: &pdev->dev, propname: "qcom,mpll" , val: &phy_dwc3->mpll)) |
533 | phy_dwc3->mpll = SSPHY_MPLL_VALUE; |
534 | |
535 | generic_phy = devm_phy_create(dev: phy_dwc3->dev, node: pdev->dev.of_node, ops: &data->ops); |
536 | |
537 | if (IS_ERR(ptr: generic_phy)) |
538 | return PTR_ERR(ptr: generic_phy); |
539 | |
540 | phy_set_drvdata(phy: generic_phy, data: phy_dwc3); |
541 | platform_set_drvdata(pdev, data: phy_dwc3); |
542 | |
543 | phy_provider = devm_of_phy_provider_register(phy_dwc3->dev, |
544 | of_phy_simple_xlate); |
545 | |
546 | if (IS_ERR(ptr: phy_provider)) |
547 | return PTR_ERR(ptr: phy_provider); |
548 | |
549 | return 0; |
550 | } |
551 | |
552 | static struct platform_driver qcom_ipq806x_usb_phy_driver = { |
553 | .probe = qcom_ipq806x_usb_phy_probe, |
554 | .driver = { |
555 | .name = "qcom-ipq806x-usb-phy" , |
556 | .of_match_table = qcom_ipq806x_usb_phy_table, |
557 | }, |
558 | }; |
559 | |
560 | module_platform_driver(qcom_ipq806x_usb_phy_driver); |
561 | |
562 | MODULE_ALIAS("platform:phy-qcom-ipq806x-usb" ); |
563 | MODULE_LICENSE("GPL v2" ); |
564 | MODULE_AUTHOR("Andy Gross <agross@codeaurora.org>" ); |
565 | MODULE_AUTHOR("Ivan T. Ivanov <iivanov@mm-sol.com>" ); |
566 | MODULE_DESCRIPTION("DesignWare USB3 QCOM PHY driver" ); |
567 | |