1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * Setup platform devices needed by the Freescale multi-port host |
4 | * and/or dual-role USB controller modules based on the description |
5 | * in flat device tree. |
6 | */ |
7 | |
8 | #include <linux/kernel.h> |
9 | #include <linux/platform_device.h> |
10 | #include <linux/fsl_devices.h> |
11 | #include <linux/err.h> |
12 | #include <linux/io.h> |
13 | #include <linux/of.h> |
14 | #include <linux/of_device.h> |
15 | #include <linux/clk.h> |
16 | #include <linux/module.h> |
17 | #include <linux/dma-mapping.h> |
18 | |
19 | struct fsl_usb2_dev_data { |
20 | char *dr_mode; /* controller mode */ |
21 | char *drivers[3]; /* drivers to instantiate for this mode */ |
22 | enum fsl_usb2_operating_modes op_mode; /* operating mode */ |
23 | }; |
24 | |
25 | static struct fsl_usb2_dev_data dr_mode_data[] = { |
26 | { |
27 | .dr_mode = "host" , |
28 | .drivers = { "fsl-ehci" , NULL, NULL, }, |
29 | .op_mode = FSL_USB2_DR_HOST, |
30 | }, |
31 | { |
32 | .dr_mode = "otg" , |
33 | .drivers = { "fsl-usb2-otg" , "fsl-ehci" , "fsl-usb2-udc" , }, |
34 | .op_mode = FSL_USB2_DR_OTG, |
35 | }, |
36 | { |
37 | .dr_mode = "peripheral" , |
38 | .drivers = { "fsl-usb2-udc" , NULL, NULL, }, |
39 | .op_mode = FSL_USB2_DR_DEVICE, |
40 | }, |
41 | }; |
42 | |
43 | static struct fsl_usb2_dev_data *get_dr_mode_data(struct device_node *np) |
44 | { |
45 | const unsigned char *prop; |
46 | int i; |
47 | |
48 | prop = of_get_property(node: np, name: "dr_mode" , NULL); |
49 | if (prop) { |
50 | for (i = 0; i < ARRAY_SIZE(dr_mode_data); i++) { |
51 | if (!strcmp(prop, dr_mode_data[i].dr_mode)) |
52 | return &dr_mode_data[i]; |
53 | } |
54 | } |
55 | pr_warn("%pOF: Invalid 'dr_mode' property, fallback to host mode\n" , |
56 | np); |
57 | return &dr_mode_data[0]; /* mode not specified, use host */ |
58 | } |
59 | |
60 | static enum fsl_usb2_phy_modes determine_usb_phy(const char *phy_type) |
61 | { |
62 | if (!phy_type) |
63 | return FSL_USB2_PHY_NONE; |
64 | if (!strcasecmp(s1: phy_type, s2: "ulpi" )) |
65 | return FSL_USB2_PHY_ULPI; |
66 | if (!strcasecmp(s1: phy_type, s2: "utmi" )) |
67 | return FSL_USB2_PHY_UTMI; |
68 | if (!strcasecmp(s1: phy_type, s2: "utmi_wide" )) |
69 | return FSL_USB2_PHY_UTMI_WIDE; |
70 | if (!strcasecmp(s1: phy_type, s2: "utmi_dual" )) |
71 | return FSL_USB2_PHY_UTMI_DUAL; |
72 | if (!strcasecmp(s1: phy_type, s2: "serial" )) |
73 | return FSL_USB2_PHY_SERIAL; |
74 | |
75 | return FSL_USB2_PHY_NONE; |
76 | } |
77 | |
78 | static struct platform_device *fsl_usb2_device_register( |
79 | struct platform_device *ofdev, |
80 | struct fsl_usb2_platform_data *pdata, |
81 | const char *name, int id) |
82 | { |
83 | struct platform_device *pdev; |
84 | const struct resource *res = ofdev->resource; |
85 | unsigned int num = ofdev->num_resources; |
86 | int retval; |
87 | |
88 | pdev = platform_device_alloc(name, id); |
89 | if (!pdev) { |
90 | retval = -ENOMEM; |
91 | goto error; |
92 | } |
93 | |
94 | pdev->dev.parent = &ofdev->dev; |
95 | |
96 | pdev->dev.coherent_dma_mask = ofdev->dev.coherent_dma_mask; |
97 | |
98 | if (!pdev->dev.dma_mask) { |
99 | pdev->dev.dma_mask = &ofdev->dev.coherent_dma_mask; |
100 | } else { |
101 | retval = dma_set_mask(dev: &pdev->dev, DMA_BIT_MASK(32)); |
102 | if (retval) |
103 | goto error; |
104 | } |
105 | |
106 | retval = platform_device_add_data(pdev, data: pdata, size: sizeof(*pdata)); |
107 | if (retval) |
108 | goto error; |
109 | |
110 | if (num) { |
111 | retval = platform_device_add_resources(pdev, res, num); |
112 | if (retval) |
113 | goto error; |
114 | } |
115 | |
116 | device_set_of_node_from_dev(dev: &pdev->dev, dev2: &ofdev->dev); |
117 | |
118 | retval = platform_device_add(pdev); |
119 | if (retval) |
120 | goto error; |
121 | |
122 | return pdev; |
123 | |
124 | error: |
125 | platform_device_put(pdev); |
126 | return ERR_PTR(error: retval); |
127 | } |
128 | |
129 | static const struct of_device_id fsl_usb2_mph_dr_of_match[]; |
130 | |
131 | static enum fsl_usb2_controller_ver usb_get_ver_info(struct device_node *np) |
132 | { |
133 | enum fsl_usb2_controller_ver ver = FSL_USB_VER_NONE; |
134 | |
135 | /* |
136 | * returns 1 for usb controller version 1.6 |
137 | * returns 2 for usb controller version 2.2 |
138 | * returns 3 for usb controller version 2.4 |
139 | * returns 4 for usb controller version 2.5 |
140 | * returns 0 otherwise |
141 | */ |
142 | if (of_device_is_compatible(device: np, "fsl-usb2-dr" )) { |
143 | if (of_device_is_compatible(device: np, "fsl-usb2-dr-v1.6" )) |
144 | ver = FSL_USB_VER_1_6; |
145 | else if (of_device_is_compatible(device: np, "fsl-usb2-dr-v2.2" )) |
146 | ver = FSL_USB_VER_2_2; |
147 | else if (of_device_is_compatible(device: np, "fsl-usb2-dr-v2.4" )) |
148 | ver = FSL_USB_VER_2_4; |
149 | else if (of_device_is_compatible(device: np, "fsl-usb2-dr-v2.5" )) |
150 | ver = FSL_USB_VER_2_5; |
151 | else /* for previous controller versions */ |
152 | ver = FSL_USB_VER_OLD; |
153 | |
154 | if (ver > FSL_USB_VER_NONE) |
155 | return ver; |
156 | } |
157 | |
158 | if (of_device_is_compatible(device: np, "fsl,mpc5121-usb2-dr" )) |
159 | return FSL_USB_VER_OLD; |
160 | |
161 | if (of_device_is_compatible(device: np, "fsl-usb2-mph" )) { |
162 | if (of_device_is_compatible(device: np, "fsl-usb2-mph-v1.6" )) |
163 | ver = FSL_USB_VER_1_6; |
164 | else if (of_device_is_compatible(device: np, "fsl-usb2-mph-v2.2" )) |
165 | ver = FSL_USB_VER_2_2; |
166 | else if (of_device_is_compatible(device: np, "fsl-usb2-mph-v2.4" )) |
167 | ver = FSL_USB_VER_2_4; |
168 | else if (of_device_is_compatible(device: np, "fsl-usb2-mph-v2.5" )) |
169 | ver = FSL_USB_VER_2_5; |
170 | else /* for previous controller versions */ |
171 | ver = FSL_USB_VER_OLD; |
172 | } |
173 | |
174 | return ver; |
175 | } |
176 | |
177 | static int fsl_usb2_mph_dr_of_probe(struct platform_device *ofdev) |
178 | { |
179 | struct device_node *np = ofdev->dev.of_node; |
180 | struct platform_device *usb_dev; |
181 | struct fsl_usb2_platform_data data, *pdata; |
182 | struct fsl_usb2_dev_data *dev_data; |
183 | const struct of_device_id *match; |
184 | const unsigned char *prop; |
185 | static unsigned int idx; |
186 | int i; |
187 | |
188 | if (!of_device_is_available(device: np)) |
189 | return -ENODEV; |
190 | |
191 | match = of_match_device(matches: fsl_usb2_mph_dr_of_match, dev: &ofdev->dev); |
192 | if (!match) |
193 | return -ENODEV; |
194 | |
195 | pdata = &data; |
196 | if (match->data) |
197 | memcpy(pdata, match->data, sizeof(data)); |
198 | else |
199 | memset(pdata, 0, sizeof(data)); |
200 | |
201 | dev_data = get_dr_mode_data(np); |
202 | |
203 | if (of_device_is_compatible(device: np, "fsl-usb2-mph" )) { |
204 | if (of_property_present(np, propname: "port0" )) |
205 | pdata->port_enables |= FSL_USB2_PORT0_ENABLED; |
206 | |
207 | if (of_property_present(np, propname: "port1" )) |
208 | pdata->port_enables |= FSL_USB2_PORT1_ENABLED; |
209 | |
210 | pdata->operating_mode = FSL_USB2_MPH_HOST; |
211 | } else { |
212 | pdata->invert_drvvbus = of_property_read_bool(np, propname: "fsl,invert-drvvbus" ); |
213 | pdata->invert_pwr_fault = of_property_read_bool(np, propname: "fsl,invert-pwr-fault" ); |
214 | |
215 | /* setup mode selected in the device tree */ |
216 | pdata->operating_mode = dev_data->op_mode; |
217 | } |
218 | |
219 | prop = of_get_property(node: np, name: "phy_type" , NULL); |
220 | pdata->phy_mode = determine_usb_phy(phy_type: prop); |
221 | pdata->controller_ver = usb_get_ver_info(np); |
222 | |
223 | /* Activate Erratum by reading property in device tree */ |
224 | pdata->has_fsl_erratum_a007792 = |
225 | of_property_read_bool(np, propname: "fsl,usb-erratum-a007792" ); |
226 | pdata->has_fsl_erratum_a005275 = |
227 | of_property_read_bool(np, propname: "fsl,usb-erratum-a005275" ); |
228 | pdata->has_fsl_erratum_a005697 = |
229 | of_property_read_bool(np, propname: "fsl,usb_erratum-a005697" ); |
230 | pdata->has_fsl_erratum_a006918 = |
231 | of_property_read_bool(np, propname: "fsl,usb_erratum-a006918" ); |
232 | pdata->has_fsl_erratum_14 = |
233 | of_property_read_bool(np, propname: "fsl,usb_erratum-14" ); |
234 | |
235 | /* |
236 | * Determine whether phy_clk_valid needs to be checked |
237 | * by reading property in device tree |
238 | */ |
239 | pdata->check_phy_clk_valid = |
240 | of_property_read_bool(np, propname: "phy-clk-valid" ); |
241 | |
242 | if (pdata->have_sysif_regs) { |
243 | if (pdata->controller_ver == FSL_USB_VER_NONE) { |
244 | dev_warn(&ofdev->dev, "Could not get controller version\n" ); |
245 | return -ENODEV; |
246 | } |
247 | } |
248 | |
249 | for (i = 0; i < ARRAY_SIZE(dev_data->drivers); i++) { |
250 | if (!dev_data->drivers[i]) |
251 | continue; |
252 | usb_dev = fsl_usb2_device_register(ofdev, pdata, |
253 | name: dev_data->drivers[i], id: idx); |
254 | if (IS_ERR(ptr: usb_dev)) { |
255 | dev_err(&ofdev->dev, "Can't register usb device\n" ); |
256 | return PTR_ERR(ptr: usb_dev); |
257 | } |
258 | } |
259 | idx++; |
260 | return 0; |
261 | } |
262 | |
263 | static int __unregister_subdev(struct device *dev, void *d) |
264 | { |
265 | platform_device_unregister(to_platform_device(dev)); |
266 | return 0; |
267 | } |
268 | |
269 | static void fsl_usb2_mph_dr_of_remove(struct platform_device *ofdev) |
270 | { |
271 | device_for_each_child(dev: &ofdev->dev, NULL, fn: __unregister_subdev); |
272 | } |
273 | |
274 | #ifdef CONFIG_PPC_MPC512x |
275 | |
276 | #define USBGENCTRL 0x200 /* NOTE: big endian */ |
277 | #define GC_WU_INT_CLR (1 << 5) /* Wakeup int clear */ |
278 | #define GC_ULPI_SEL (1 << 4) /* ULPI i/f select (usb0 only)*/ |
279 | #define GC_PPP (1 << 3) /* Inv. Port Power Polarity */ |
280 | #define GC_PFP (1 << 2) /* Inv. Power Fault Polarity */ |
281 | #define GC_WU_ULPI_EN (1 << 1) /* Wakeup on ULPI event */ |
282 | #define GC_WU_IE (1 << 1) /* Wakeup interrupt enable */ |
283 | |
284 | #define ISIPHYCTRL 0x204 /* NOTE: big endian */ |
285 | #define PHYCTRL_PHYE (1 << 4) /* On-chip UTMI PHY enable */ |
286 | #define PHYCTRL_BSENH (1 << 3) /* Bit Stuff Enable High */ |
287 | #define PHYCTRL_BSEN (1 << 2) /* Bit Stuff Enable */ |
288 | #define PHYCTRL_LSFE (1 << 1) /* Line State Filter Enable */ |
289 | #define PHYCTRL_PXE (1 << 0) /* PHY oscillator enable */ |
290 | |
291 | int fsl_usb2_mpc5121_init(struct platform_device *pdev) |
292 | { |
293 | struct fsl_usb2_platform_data *pdata = dev_get_platdata(&pdev->dev); |
294 | struct clk *clk; |
295 | int err; |
296 | |
297 | clk = devm_clk_get(pdev->dev.parent, "ipg" ); |
298 | if (IS_ERR(clk)) { |
299 | dev_err(&pdev->dev, "failed to get clk\n" ); |
300 | return PTR_ERR(clk); |
301 | } |
302 | err = clk_prepare_enable(clk); |
303 | if (err) { |
304 | dev_err(&pdev->dev, "failed to enable clk\n" ); |
305 | return err; |
306 | } |
307 | pdata->clk = clk; |
308 | |
309 | if (pdata->phy_mode == FSL_USB2_PHY_UTMI_WIDE) { |
310 | u32 reg = 0; |
311 | |
312 | if (pdata->invert_drvvbus) |
313 | reg |= GC_PPP; |
314 | |
315 | if (pdata->invert_pwr_fault) |
316 | reg |= GC_PFP; |
317 | |
318 | out_be32(pdata->regs + ISIPHYCTRL, PHYCTRL_PHYE | PHYCTRL_PXE); |
319 | out_be32(pdata->regs + USBGENCTRL, reg); |
320 | } |
321 | return 0; |
322 | } |
323 | |
324 | static void fsl_usb2_mpc5121_exit(struct platform_device *pdev) |
325 | { |
326 | struct fsl_usb2_platform_data *pdata = dev_get_platdata(&pdev->dev); |
327 | |
328 | pdata->regs = NULL; |
329 | |
330 | if (pdata->clk) |
331 | clk_disable_unprepare(pdata->clk); |
332 | } |
333 | |
334 | static struct fsl_usb2_platform_data fsl_usb2_mpc5121_pd = { |
335 | .big_endian_desc = 1, |
336 | .big_endian_mmio = 1, |
337 | .es = 1, |
338 | .have_sysif_regs = 0, |
339 | .le_setup_buf = 1, |
340 | .init = fsl_usb2_mpc5121_init, |
341 | .exit = fsl_usb2_mpc5121_exit, |
342 | }; |
343 | #endif /* CONFIG_PPC_MPC512x */ |
344 | |
345 | static struct fsl_usb2_platform_data fsl_usb2_mpc8xxx_pd = { |
346 | .have_sysif_regs = 1, |
347 | }; |
348 | |
349 | static const struct of_device_id fsl_usb2_mph_dr_of_match[] = { |
350 | { .compatible = "fsl-usb2-mph" , .data = &fsl_usb2_mpc8xxx_pd, }, |
351 | { .compatible = "fsl-usb2-dr" , .data = &fsl_usb2_mpc8xxx_pd, }, |
352 | #ifdef CONFIG_PPC_MPC512x |
353 | { .compatible = "fsl,mpc5121-usb2-dr" , .data = &fsl_usb2_mpc5121_pd, }, |
354 | #endif |
355 | {}, |
356 | }; |
357 | MODULE_DEVICE_TABLE(of, fsl_usb2_mph_dr_of_match); |
358 | |
359 | static struct platform_driver fsl_usb2_mph_dr_driver = { |
360 | .driver = { |
361 | .name = "fsl-usb2-mph-dr" , |
362 | .of_match_table = fsl_usb2_mph_dr_of_match, |
363 | }, |
364 | .probe = fsl_usb2_mph_dr_of_probe, |
365 | .remove_new = fsl_usb2_mph_dr_of_remove, |
366 | }; |
367 | |
368 | module_platform_driver(fsl_usb2_mph_dr_driver); |
369 | |
370 | MODULE_DESCRIPTION("FSL MPH DR OF devices driver" ); |
371 | MODULE_AUTHOR("Anatolij Gustschin <agust@denx.de>" ); |
372 | MODULE_LICENSE("GPL" ); |
373 | |