1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * MediaTek USB3.1 gen2 xsphy Driver |
4 | * |
5 | * Copyright (c) 2018 MediaTek Inc. |
6 | * Author: Chunfeng Yun <chunfeng.yun@mediatek.com> |
7 | * |
8 | */ |
9 | |
10 | #include <dt-bindings/phy/phy.h> |
11 | #include <linux/clk.h> |
12 | #include <linux/delay.h> |
13 | #include <linux/iopoll.h> |
14 | #include <linux/module.h> |
15 | #include <linux/of_address.h> |
16 | #include <linux/phy/phy.h> |
17 | #include <linux/platform_device.h> |
18 | |
19 | #include "phy-mtk-io.h" |
20 | |
21 | /* u2 phy banks */ |
22 | #define SSUSB_SIFSLV_MISC 0x000 |
23 | #define SSUSB_SIFSLV_U2FREQ 0x100 |
24 | #define SSUSB_SIFSLV_U2PHY_COM 0x300 |
25 | |
26 | /* u3 phy shared banks */ |
27 | #define SSPXTP_SIFSLV_DIG_GLB 0x000 |
28 | #define SSPXTP_SIFSLV_PHYA_GLB 0x100 |
29 | |
30 | /* u3 phy banks */ |
31 | #define SSPXTP_SIFSLV_DIG_LN_TOP 0x000 |
32 | #define SSPXTP_SIFSLV_DIG_LN_TX0 0x100 |
33 | #define SSPXTP_SIFSLV_DIG_LN_RX0 0x200 |
34 | #define SSPXTP_SIFSLV_DIG_LN_DAIF 0x300 |
35 | #define SSPXTP_SIFSLV_PHYA_LN 0x400 |
36 | |
37 | #define XSP_U2FREQ_FMCR0 ((SSUSB_SIFSLV_U2FREQ) + 0x00) |
38 | #define P2F_RG_FREQDET_EN BIT(24) |
39 | #define P2F_RG_CYCLECNT GENMASK(23, 0) |
40 | |
41 | #define XSP_U2FREQ_MMONR0 ((SSUSB_SIFSLV_U2FREQ) + 0x0c) |
42 | |
43 | #define XSP_U2FREQ_FMMONR1 ((SSUSB_SIFSLV_U2FREQ) + 0x10) |
44 | #define P2F_RG_FRCK_EN BIT(8) |
45 | #define P2F_USB_FM_VALID BIT(0) |
46 | |
47 | #define XSP_USBPHYACR0 ((SSUSB_SIFSLV_U2PHY_COM) + 0x00) |
48 | #define P2A0_RG_INTR_EN BIT(5) |
49 | |
50 | #define XSP_USBPHYACR1 ((SSUSB_SIFSLV_U2PHY_COM) + 0x04) |
51 | #define P2A1_RG_INTR_CAL GENMASK(23, 19) |
52 | #define P2A1_RG_VRT_SEL GENMASK(14, 12) |
53 | #define P2A1_RG_TERM_SEL GENMASK(10, 8) |
54 | |
55 | #define XSP_USBPHYACR5 ((SSUSB_SIFSLV_U2PHY_COM) + 0x014) |
56 | #define P2A5_RG_HSTX_SRCAL_EN BIT(15) |
57 | #define P2A5_RG_HSTX_SRCTRL GENMASK(14, 12) |
58 | |
59 | #define XSP_USBPHYACR6 ((SSUSB_SIFSLV_U2PHY_COM) + 0x018) |
60 | #define P2A6_RG_BC11_SW_EN BIT(23) |
61 | #define P2A6_RG_OTG_VBUSCMP_EN BIT(20) |
62 | |
63 | #define XSP_U2PHYDTM1 ((SSUSB_SIFSLV_U2PHY_COM) + 0x06C) |
64 | #define P2D_FORCE_IDDIG BIT(9) |
65 | #define P2D_RG_VBUSVALID BIT(5) |
66 | #define P2D_RG_SESSEND BIT(4) |
67 | #define P2D_RG_AVALID BIT(2) |
68 | #define P2D_RG_IDDIG BIT(1) |
69 | |
70 | #define SSPXTP_PHYA_GLB_00 ((SSPXTP_SIFSLV_PHYA_GLB) + 0x00) |
71 | #define RG_XTP_GLB_BIAS_INTR_CTRL GENMASK(21, 16) |
72 | |
73 | #define SSPXTP_PHYA_LN_04 ((SSPXTP_SIFSLV_PHYA_LN) + 0x04) |
74 | #define RG_XTP_LN0_TX_IMPSEL GENMASK(4, 0) |
75 | |
76 | #define SSPXTP_PHYA_LN_14 ((SSPXTP_SIFSLV_PHYA_LN) + 0x014) |
77 | #define RG_XTP_LN0_RX_IMPSEL GENMASK(4, 0) |
78 | |
79 | #define XSP_REF_CLK 26 /* MHZ */ |
80 | #define XSP_SLEW_RATE_COEF 17 |
81 | #define XSP_SR_COEF_DIVISOR 1000 |
82 | #define XSP_FM_DET_CYCLE_CNT 1024 |
83 | |
84 | struct xsphy_instance { |
85 | struct phy *phy; |
86 | void __iomem *port_base; |
87 | struct clk *ref_clk; /* reference clock of anolog phy */ |
88 | u32 index; |
89 | u32 type; |
90 | /* only for HQA test */ |
91 | int efuse_intr; |
92 | int efuse_tx_imp; |
93 | int efuse_rx_imp; |
94 | /* u2 eye diagram */ |
95 | int eye_src; |
96 | int eye_vrt; |
97 | int eye_term; |
98 | }; |
99 | |
100 | struct mtk_xsphy { |
101 | struct device *dev; |
102 | void __iomem *glb_base; /* only shared u3 sif */ |
103 | struct xsphy_instance **phys; |
104 | int nphys; |
105 | int src_ref_clk; /* MHZ, reference clock for slew rate calibrate */ |
106 | int src_coef; /* coefficient for slew rate calibrate */ |
107 | }; |
108 | |
109 | static void u2_phy_slew_rate_calibrate(struct mtk_xsphy *xsphy, |
110 | struct xsphy_instance *inst) |
111 | { |
112 | void __iomem *pbase = inst->port_base; |
113 | int calib_val; |
114 | int fm_out; |
115 | u32 tmp; |
116 | |
117 | /* use force value */ |
118 | if (inst->eye_src) |
119 | return; |
120 | |
121 | /* enable USB ring oscillator */ |
122 | mtk_phy_set_bits(reg: pbase + XSP_USBPHYACR5, P2A5_RG_HSTX_SRCAL_EN); |
123 | udelay(1); /* wait clock stable */ |
124 | |
125 | /* enable free run clock */ |
126 | mtk_phy_set_bits(reg: pbase + XSP_U2FREQ_FMMONR1, P2F_RG_FRCK_EN); |
127 | |
128 | /* set cycle count as 1024 */ |
129 | mtk_phy_update_field(pbase + XSP_U2FREQ_FMCR0, P2F_RG_CYCLECNT, |
130 | XSP_FM_DET_CYCLE_CNT); |
131 | |
132 | /* enable frequency meter */ |
133 | mtk_phy_set_bits(reg: pbase + XSP_U2FREQ_FMCR0, P2F_RG_FREQDET_EN); |
134 | |
135 | /* ignore return value */ |
136 | readl_poll_timeout(pbase + XSP_U2FREQ_FMMONR1, tmp, |
137 | (tmp & P2F_USB_FM_VALID), 10, 200); |
138 | |
139 | fm_out = readl(addr: pbase + XSP_U2FREQ_MMONR0); |
140 | |
141 | /* disable frequency meter */ |
142 | mtk_phy_clear_bits(reg: pbase + XSP_U2FREQ_FMCR0, P2F_RG_FREQDET_EN); |
143 | |
144 | /* disable free run clock */ |
145 | mtk_phy_clear_bits(reg: pbase + XSP_U2FREQ_FMMONR1, P2F_RG_FRCK_EN); |
146 | |
147 | if (fm_out) { |
148 | /* (1024 / FM_OUT) x reference clock frequency x coefficient */ |
149 | tmp = xsphy->src_ref_clk * xsphy->src_coef; |
150 | tmp = (tmp * XSP_FM_DET_CYCLE_CNT) / fm_out; |
151 | calib_val = DIV_ROUND_CLOSEST(tmp, XSP_SR_COEF_DIVISOR); |
152 | } else { |
153 | /* if FM detection fail, set default value */ |
154 | calib_val = 3; |
155 | } |
156 | dev_dbg(xsphy->dev, "phy.%d, fm_out:%d, calib:%d (clk:%d, coef:%d)\n" , |
157 | inst->index, fm_out, calib_val, |
158 | xsphy->src_ref_clk, xsphy->src_coef); |
159 | |
160 | /* set HS slew rate */ |
161 | mtk_phy_update_field(pbase + XSP_USBPHYACR5, P2A5_RG_HSTX_SRCTRL, calib_val); |
162 | |
163 | /* disable USB ring oscillator */ |
164 | mtk_phy_clear_bits(reg: pbase + XSP_USBPHYACR5, P2A5_RG_HSTX_SRCAL_EN); |
165 | } |
166 | |
167 | static void u2_phy_instance_init(struct mtk_xsphy *xsphy, |
168 | struct xsphy_instance *inst) |
169 | { |
170 | void __iomem *pbase = inst->port_base; |
171 | |
172 | /* DP/DM BC1.1 path Disable */ |
173 | mtk_phy_clear_bits(reg: pbase + XSP_USBPHYACR6, P2A6_RG_BC11_SW_EN); |
174 | |
175 | mtk_phy_set_bits(reg: pbase + XSP_USBPHYACR0, P2A0_RG_INTR_EN); |
176 | } |
177 | |
178 | static void u2_phy_instance_power_on(struct mtk_xsphy *xsphy, |
179 | struct xsphy_instance *inst) |
180 | { |
181 | void __iomem *pbase = inst->port_base; |
182 | u32 index = inst->index; |
183 | |
184 | mtk_phy_set_bits(reg: pbase + XSP_USBPHYACR6, P2A6_RG_OTG_VBUSCMP_EN); |
185 | |
186 | mtk_phy_update_bits(reg: pbase + XSP_U2PHYDTM1, |
187 | P2D_RG_VBUSVALID | P2D_RG_AVALID | P2D_RG_SESSEND, |
188 | P2D_RG_VBUSVALID | P2D_RG_AVALID); |
189 | |
190 | dev_dbg(xsphy->dev, "%s(%d)\n" , __func__, index); |
191 | } |
192 | |
193 | static void u2_phy_instance_power_off(struct mtk_xsphy *xsphy, |
194 | struct xsphy_instance *inst) |
195 | { |
196 | void __iomem *pbase = inst->port_base; |
197 | u32 index = inst->index; |
198 | |
199 | mtk_phy_clear_bits(reg: pbase + XSP_USBPHYACR6, P2A6_RG_OTG_VBUSCMP_EN); |
200 | |
201 | mtk_phy_update_bits(reg: pbase + XSP_U2PHYDTM1, |
202 | P2D_RG_VBUSVALID | P2D_RG_AVALID | P2D_RG_SESSEND, |
203 | P2D_RG_SESSEND); |
204 | |
205 | dev_dbg(xsphy->dev, "%s(%d)\n" , __func__, index); |
206 | } |
207 | |
208 | static void u2_phy_instance_set_mode(struct mtk_xsphy *xsphy, |
209 | struct xsphy_instance *inst, |
210 | enum phy_mode mode) |
211 | { |
212 | u32 tmp; |
213 | |
214 | tmp = readl(addr: inst->port_base + XSP_U2PHYDTM1); |
215 | switch (mode) { |
216 | case PHY_MODE_USB_DEVICE: |
217 | tmp |= P2D_FORCE_IDDIG | P2D_RG_IDDIG; |
218 | break; |
219 | case PHY_MODE_USB_HOST: |
220 | tmp |= P2D_FORCE_IDDIG; |
221 | tmp &= ~P2D_RG_IDDIG; |
222 | break; |
223 | case PHY_MODE_USB_OTG: |
224 | tmp &= ~(P2D_FORCE_IDDIG | P2D_RG_IDDIG); |
225 | break; |
226 | default: |
227 | return; |
228 | } |
229 | writel(val: tmp, addr: inst->port_base + XSP_U2PHYDTM1); |
230 | } |
231 | |
232 | static void phy_parse_property(struct mtk_xsphy *xsphy, |
233 | struct xsphy_instance *inst) |
234 | { |
235 | struct device *dev = &inst->phy->dev; |
236 | |
237 | switch (inst->type) { |
238 | case PHY_TYPE_USB2: |
239 | device_property_read_u32(dev, propname: "mediatek,efuse-intr" , |
240 | val: &inst->efuse_intr); |
241 | device_property_read_u32(dev, propname: "mediatek,eye-src" , |
242 | val: &inst->eye_src); |
243 | device_property_read_u32(dev, propname: "mediatek,eye-vrt" , |
244 | val: &inst->eye_vrt); |
245 | device_property_read_u32(dev, propname: "mediatek,eye-term" , |
246 | val: &inst->eye_term); |
247 | dev_dbg(dev, "intr:%d, src:%d, vrt:%d, term:%d\n" , |
248 | inst->efuse_intr, inst->eye_src, |
249 | inst->eye_vrt, inst->eye_term); |
250 | break; |
251 | case PHY_TYPE_USB3: |
252 | device_property_read_u32(dev, propname: "mediatek,efuse-intr" , |
253 | val: &inst->efuse_intr); |
254 | device_property_read_u32(dev, propname: "mediatek,efuse-tx-imp" , |
255 | val: &inst->efuse_tx_imp); |
256 | device_property_read_u32(dev, propname: "mediatek,efuse-rx-imp" , |
257 | val: &inst->efuse_rx_imp); |
258 | dev_dbg(dev, "intr:%d, tx-imp:%d, rx-imp:%d\n" , |
259 | inst->efuse_intr, inst->efuse_tx_imp, |
260 | inst->efuse_rx_imp); |
261 | break; |
262 | default: |
263 | dev_err(xsphy->dev, "incompatible phy type\n" ); |
264 | return; |
265 | } |
266 | } |
267 | |
268 | static void u2_phy_props_set(struct mtk_xsphy *xsphy, |
269 | struct xsphy_instance *inst) |
270 | { |
271 | void __iomem *pbase = inst->port_base; |
272 | |
273 | if (inst->efuse_intr) |
274 | mtk_phy_update_field(pbase + XSP_USBPHYACR1, P2A1_RG_INTR_CAL, |
275 | inst->efuse_intr); |
276 | |
277 | if (inst->eye_src) |
278 | mtk_phy_update_field(pbase + XSP_USBPHYACR5, P2A5_RG_HSTX_SRCTRL, |
279 | inst->eye_src); |
280 | |
281 | if (inst->eye_vrt) |
282 | mtk_phy_update_field(pbase + XSP_USBPHYACR1, P2A1_RG_VRT_SEL, |
283 | inst->eye_vrt); |
284 | |
285 | if (inst->eye_term) |
286 | mtk_phy_update_field(pbase + XSP_USBPHYACR1, P2A1_RG_TERM_SEL, |
287 | inst->eye_term); |
288 | } |
289 | |
290 | static void u3_phy_props_set(struct mtk_xsphy *xsphy, |
291 | struct xsphy_instance *inst) |
292 | { |
293 | void __iomem *pbase = inst->port_base; |
294 | |
295 | if (inst->efuse_intr) |
296 | mtk_phy_update_field(xsphy->glb_base + SSPXTP_PHYA_GLB_00, |
297 | RG_XTP_GLB_BIAS_INTR_CTRL, inst->efuse_intr); |
298 | |
299 | if (inst->efuse_tx_imp) |
300 | mtk_phy_update_field(pbase + SSPXTP_PHYA_LN_04, |
301 | RG_XTP_LN0_TX_IMPSEL, inst->efuse_tx_imp); |
302 | |
303 | if (inst->efuse_rx_imp) |
304 | mtk_phy_update_field(pbase + SSPXTP_PHYA_LN_14, |
305 | RG_XTP_LN0_RX_IMPSEL, inst->efuse_rx_imp); |
306 | } |
307 | |
308 | static int mtk_phy_init(struct phy *phy) |
309 | { |
310 | struct xsphy_instance *inst = phy_get_drvdata(phy); |
311 | struct mtk_xsphy *xsphy = dev_get_drvdata(dev: phy->dev.parent); |
312 | int ret; |
313 | |
314 | ret = clk_prepare_enable(clk: inst->ref_clk); |
315 | if (ret) { |
316 | dev_err(xsphy->dev, "failed to enable ref_clk\n" ); |
317 | return ret; |
318 | } |
319 | |
320 | switch (inst->type) { |
321 | case PHY_TYPE_USB2: |
322 | u2_phy_instance_init(xsphy, inst); |
323 | u2_phy_props_set(xsphy, inst); |
324 | break; |
325 | case PHY_TYPE_USB3: |
326 | u3_phy_props_set(xsphy, inst); |
327 | break; |
328 | default: |
329 | dev_err(xsphy->dev, "incompatible phy type\n" ); |
330 | clk_disable_unprepare(clk: inst->ref_clk); |
331 | return -EINVAL; |
332 | } |
333 | |
334 | return 0; |
335 | } |
336 | |
337 | static int mtk_phy_power_on(struct phy *phy) |
338 | { |
339 | struct xsphy_instance *inst = phy_get_drvdata(phy); |
340 | struct mtk_xsphy *xsphy = dev_get_drvdata(dev: phy->dev.parent); |
341 | |
342 | if (inst->type == PHY_TYPE_USB2) { |
343 | u2_phy_instance_power_on(xsphy, inst); |
344 | u2_phy_slew_rate_calibrate(xsphy, inst); |
345 | } |
346 | |
347 | return 0; |
348 | } |
349 | |
350 | static int mtk_phy_power_off(struct phy *phy) |
351 | { |
352 | struct xsphy_instance *inst = phy_get_drvdata(phy); |
353 | struct mtk_xsphy *xsphy = dev_get_drvdata(dev: phy->dev.parent); |
354 | |
355 | if (inst->type == PHY_TYPE_USB2) |
356 | u2_phy_instance_power_off(xsphy, inst); |
357 | |
358 | return 0; |
359 | } |
360 | |
361 | static int mtk_phy_exit(struct phy *phy) |
362 | { |
363 | struct xsphy_instance *inst = phy_get_drvdata(phy); |
364 | |
365 | clk_disable_unprepare(clk: inst->ref_clk); |
366 | return 0; |
367 | } |
368 | |
369 | static int mtk_phy_set_mode(struct phy *phy, enum phy_mode mode, int submode) |
370 | { |
371 | struct xsphy_instance *inst = phy_get_drvdata(phy); |
372 | struct mtk_xsphy *xsphy = dev_get_drvdata(dev: phy->dev.parent); |
373 | |
374 | if (inst->type == PHY_TYPE_USB2) |
375 | u2_phy_instance_set_mode(xsphy, inst, mode); |
376 | |
377 | return 0; |
378 | } |
379 | |
380 | static struct phy *mtk_phy_xlate(struct device *dev, |
381 | const struct of_phandle_args *args) |
382 | { |
383 | struct mtk_xsphy *xsphy = dev_get_drvdata(dev); |
384 | struct xsphy_instance *inst = NULL; |
385 | struct device_node *phy_np = args->np; |
386 | int index; |
387 | |
388 | if (args->args_count != 1) { |
389 | dev_err(dev, "invalid number of cells in 'phy' property\n" ); |
390 | return ERR_PTR(error: -EINVAL); |
391 | } |
392 | |
393 | for (index = 0; index < xsphy->nphys; index++) |
394 | if (phy_np == xsphy->phys[index]->phy->dev.of_node) { |
395 | inst = xsphy->phys[index]; |
396 | break; |
397 | } |
398 | |
399 | if (!inst) { |
400 | dev_err(dev, "failed to find appropriate phy\n" ); |
401 | return ERR_PTR(error: -EINVAL); |
402 | } |
403 | |
404 | inst->type = args->args[0]; |
405 | if (!(inst->type == PHY_TYPE_USB2 || |
406 | inst->type == PHY_TYPE_USB3)) { |
407 | dev_err(dev, "unsupported phy type: %d\n" , inst->type); |
408 | return ERR_PTR(error: -EINVAL); |
409 | } |
410 | |
411 | phy_parse_property(xsphy, inst); |
412 | |
413 | return inst->phy; |
414 | } |
415 | |
416 | static const struct phy_ops mtk_xsphy_ops = { |
417 | .init = mtk_phy_init, |
418 | .exit = mtk_phy_exit, |
419 | .power_on = mtk_phy_power_on, |
420 | .power_off = mtk_phy_power_off, |
421 | .set_mode = mtk_phy_set_mode, |
422 | .owner = THIS_MODULE, |
423 | }; |
424 | |
425 | static const struct of_device_id mtk_xsphy_id_table[] = { |
426 | { .compatible = "mediatek,xsphy" , }, |
427 | { }, |
428 | }; |
429 | MODULE_DEVICE_TABLE(of, mtk_xsphy_id_table); |
430 | |
431 | static int mtk_xsphy_probe(struct platform_device *pdev) |
432 | { |
433 | struct device *dev = &pdev->dev; |
434 | struct device_node *np = dev->of_node; |
435 | struct device_node *child_np; |
436 | struct phy_provider *provider; |
437 | struct resource *glb_res; |
438 | struct mtk_xsphy *xsphy; |
439 | struct resource res; |
440 | int port, retval; |
441 | |
442 | xsphy = devm_kzalloc(dev, size: sizeof(*xsphy), GFP_KERNEL); |
443 | if (!xsphy) |
444 | return -ENOMEM; |
445 | |
446 | xsphy->nphys = of_get_child_count(np); |
447 | xsphy->phys = devm_kcalloc(dev, n: xsphy->nphys, |
448 | size: sizeof(*xsphy->phys), GFP_KERNEL); |
449 | if (!xsphy->phys) |
450 | return -ENOMEM; |
451 | |
452 | xsphy->dev = dev; |
453 | platform_set_drvdata(pdev, data: xsphy); |
454 | |
455 | glb_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
456 | /* optional, may not exist if no u3 phys */ |
457 | if (glb_res) { |
458 | /* get banks shared by multiple u3 phys */ |
459 | xsphy->glb_base = devm_ioremap_resource(dev, res: glb_res); |
460 | if (IS_ERR(ptr: xsphy->glb_base)) { |
461 | dev_err(dev, "failed to remap glb regs\n" ); |
462 | return PTR_ERR(ptr: xsphy->glb_base); |
463 | } |
464 | } |
465 | |
466 | xsphy->src_ref_clk = XSP_REF_CLK; |
467 | xsphy->src_coef = XSP_SLEW_RATE_COEF; |
468 | /* update parameters of slew rate calibrate if exist */ |
469 | device_property_read_u32(dev, propname: "mediatek,src-ref-clk-mhz" , |
470 | val: &xsphy->src_ref_clk); |
471 | device_property_read_u32(dev, propname: "mediatek,src-coef" , val: &xsphy->src_coef); |
472 | |
473 | port = 0; |
474 | for_each_child_of_node(np, child_np) { |
475 | struct xsphy_instance *inst; |
476 | struct phy *phy; |
477 | |
478 | inst = devm_kzalloc(dev, size: sizeof(*inst), GFP_KERNEL); |
479 | if (!inst) { |
480 | retval = -ENOMEM; |
481 | goto put_child; |
482 | } |
483 | |
484 | xsphy->phys[port] = inst; |
485 | |
486 | phy = devm_phy_create(dev, node: child_np, ops: &mtk_xsphy_ops); |
487 | if (IS_ERR(ptr: phy)) { |
488 | dev_err(dev, "failed to create phy\n" ); |
489 | retval = PTR_ERR(ptr: phy); |
490 | goto put_child; |
491 | } |
492 | |
493 | retval = of_address_to_resource(dev: child_np, index: 0, r: &res); |
494 | if (retval) { |
495 | dev_err(dev, "failed to get address resource(id-%d)\n" , |
496 | port); |
497 | goto put_child; |
498 | } |
499 | |
500 | inst->port_base = devm_ioremap_resource(dev: &phy->dev, res: &res); |
501 | if (IS_ERR(ptr: inst->port_base)) { |
502 | dev_err(dev, "failed to remap phy regs\n" ); |
503 | retval = PTR_ERR(ptr: inst->port_base); |
504 | goto put_child; |
505 | } |
506 | |
507 | inst->phy = phy; |
508 | inst->index = port; |
509 | phy_set_drvdata(phy, data: inst); |
510 | port++; |
511 | |
512 | inst->ref_clk = devm_clk_get(dev: &phy->dev, id: "ref" ); |
513 | if (IS_ERR(ptr: inst->ref_clk)) { |
514 | dev_err(dev, "failed to get ref_clk(id-%d)\n" , port); |
515 | retval = PTR_ERR(ptr: inst->ref_clk); |
516 | goto put_child; |
517 | } |
518 | } |
519 | |
520 | provider = devm_of_phy_provider_register(dev, mtk_phy_xlate); |
521 | return PTR_ERR_OR_ZERO(ptr: provider); |
522 | |
523 | put_child: |
524 | of_node_put(node: child_np); |
525 | return retval; |
526 | } |
527 | |
528 | static struct platform_driver mtk_xsphy_driver = { |
529 | .probe = mtk_xsphy_probe, |
530 | .driver = { |
531 | .name = "mtk-xsphy" , |
532 | .of_match_table = mtk_xsphy_id_table, |
533 | }, |
534 | }; |
535 | |
536 | module_platform_driver(mtk_xsphy_driver); |
537 | |
538 | MODULE_AUTHOR("Chunfeng Yun <chunfeng.yun@mediatek.com>" ); |
539 | MODULE_DESCRIPTION("MediaTek USB XS-PHY driver" ); |
540 | MODULE_LICENSE("GPL v2" ); |
541 | |