1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * phy-brcm-usb.c - Broadcom USB Phy Driver |
4 | * |
5 | * Copyright (C) 2015-2017 Broadcom |
6 | */ |
7 | |
8 | #include <linux/clk.h> |
9 | #include <linux/delay.h> |
10 | #include <linux/err.h> |
11 | #include <linux/io.h> |
12 | #include <linux/module.h> |
13 | #include <linux/of.h> |
14 | #include <linux/phy/phy.h> |
15 | #include <linux/platform_device.h> |
16 | #include <linux/interrupt.h> |
17 | #include <linux/soc/brcmstb/brcmstb.h> |
18 | #include <dt-bindings/phy/phy.h> |
19 | #include <linux/mfd/syscon.h> |
20 | #include <linux/suspend.h> |
21 | |
22 | #include "phy-brcm-usb-init.h" |
23 | |
24 | static DEFINE_MUTEX(sysfs_lock); |
25 | |
26 | enum brcm_usb_phy_id { |
27 | BRCM_USB_PHY_2_0 = 0, |
28 | BRCM_USB_PHY_3_0, |
29 | BRCM_USB_PHY_ID_MAX |
30 | }; |
31 | |
32 | struct value_to_name_map { |
33 | int value; |
34 | const char *name; |
35 | }; |
36 | |
37 | struct match_chip_info { |
38 | void (*init_func)(struct brcm_usb_init_params *params); |
39 | u8 required_regs[BRCM_REGS_MAX + 1]; |
40 | u8 optional_reg; |
41 | }; |
42 | |
43 | static const struct value_to_name_map brcm_dr_mode_to_name[] = { |
44 | { USB_CTLR_MODE_HOST, "host" }, |
45 | { USB_CTLR_MODE_DEVICE, "peripheral" }, |
46 | { USB_CTLR_MODE_DRD, "drd" }, |
47 | { USB_CTLR_MODE_TYPEC_PD, "typec-pd" } |
48 | }; |
49 | |
50 | static const struct value_to_name_map brcm_dual_mode_to_name[] = { |
51 | { 0, "host" }, |
52 | { 1, "device" }, |
53 | { 2, "auto" }, |
54 | }; |
55 | |
56 | struct brcm_usb_phy { |
57 | struct phy *phy; |
58 | unsigned int id; |
59 | bool inited; |
60 | }; |
61 | |
62 | struct brcm_usb_phy_data { |
63 | struct brcm_usb_init_params ini; |
64 | bool has_eohci; |
65 | bool has_xhci; |
66 | struct clk *usb_20_clk; |
67 | struct clk *usb_30_clk; |
68 | struct clk *suspend_clk; |
69 | struct mutex mutex; /* serialize phy init */ |
70 | int init_count; |
71 | int wake_irq; |
72 | struct brcm_usb_phy phys[BRCM_USB_PHY_ID_MAX]; |
73 | struct notifier_block pm_notifier; |
74 | bool pm_active; |
75 | }; |
76 | |
77 | static s8 *node_reg_names[BRCM_REGS_MAX] = { |
78 | "crtl" , "xhci_ec" , "xhci_gbl" , "usb_phy" , "usb_mdio" , "bdc_ec" |
79 | }; |
80 | |
81 | static int brcm_pm_notifier(struct notifier_block *notifier, |
82 | unsigned long pm_event, |
83 | void *unused) |
84 | { |
85 | struct brcm_usb_phy_data *priv = |
86 | container_of(notifier, struct brcm_usb_phy_data, pm_notifier); |
87 | |
88 | switch (pm_event) { |
89 | case PM_HIBERNATION_PREPARE: |
90 | case PM_SUSPEND_PREPARE: |
91 | priv->pm_active = true; |
92 | break; |
93 | case PM_POST_RESTORE: |
94 | case PM_POST_HIBERNATION: |
95 | case PM_POST_SUSPEND: |
96 | priv->pm_active = false; |
97 | break; |
98 | } |
99 | return NOTIFY_DONE; |
100 | } |
101 | |
102 | static irqreturn_t brcm_usb_phy_wake_isr(int irq, void *dev_id) |
103 | { |
104 | struct device *dev = dev_id; |
105 | |
106 | pm_wakeup_event(dev, msec: 0); |
107 | |
108 | return IRQ_HANDLED; |
109 | } |
110 | |
111 | static int brcm_usb_phy_init(struct phy *gphy) |
112 | { |
113 | struct brcm_usb_phy *phy = phy_get_drvdata(phy: gphy); |
114 | struct brcm_usb_phy_data *priv = |
115 | container_of(phy, struct brcm_usb_phy_data, phys[phy->id]); |
116 | |
117 | if (priv->pm_active) |
118 | return 0; |
119 | |
120 | /* |
121 | * Use a lock to make sure a second caller waits until |
122 | * the base phy is inited before using it. |
123 | */ |
124 | mutex_lock(&priv->mutex); |
125 | if (priv->init_count++ == 0) { |
126 | clk_prepare_enable(clk: priv->usb_20_clk); |
127 | clk_prepare_enable(clk: priv->usb_30_clk); |
128 | clk_prepare_enable(clk: priv->suspend_clk); |
129 | brcm_usb_init_common(ini: &priv->ini); |
130 | } |
131 | mutex_unlock(lock: &priv->mutex); |
132 | if (phy->id == BRCM_USB_PHY_2_0) |
133 | brcm_usb_init_eohci(ini: &priv->ini); |
134 | else if (phy->id == BRCM_USB_PHY_3_0) |
135 | brcm_usb_init_xhci(ini: &priv->ini); |
136 | phy->inited = true; |
137 | dev_dbg(&gphy->dev, "INIT, id: %d, total: %d\n" , phy->id, |
138 | priv->init_count); |
139 | |
140 | return 0; |
141 | } |
142 | |
143 | static int brcm_usb_phy_exit(struct phy *gphy) |
144 | { |
145 | struct brcm_usb_phy *phy = phy_get_drvdata(phy: gphy); |
146 | struct brcm_usb_phy_data *priv = |
147 | container_of(phy, struct brcm_usb_phy_data, phys[phy->id]); |
148 | |
149 | if (priv->pm_active) |
150 | return 0; |
151 | |
152 | dev_dbg(&gphy->dev, "EXIT\n" ); |
153 | if (phy->id == BRCM_USB_PHY_2_0) |
154 | brcm_usb_uninit_eohci(ini: &priv->ini); |
155 | if (phy->id == BRCM_USB_PHY_3_0) |
156 | brcm_usb_uninit_xhci(ini: &priv->ini); |
157 | |
158 | /* If both xhci and eohci are gone, reset everything else */ |
159 | mutex_lock(&priv->mutex); |
160 | if (--priv->init_count == 0) { |
161 | brcm_usb_uninit_common(ini: &priv->ini); |
162 | clk_disable_unprepare(clk: priv->usb_20_clk); |
163 | clk_disable_unprepare(clk: priv->usb_30_clk); |
164 | clk_disable_unprepare(clk: priv->suspend_clk); |
165 | } |
166 | mutex_unlock(lock: &priv->mutex); |
167 | phy->inited = false; |
168 | return 0; |
169 | } |
170 | |
171 | static const struct phy_ops brcm_usb_phy_ops = { |
172 | .init = brcm_usb_phy_init, |
173 | .exit = brcm_usb_phy_exit, |
174 | .owner = THIS_MODULE, |
175 | }; |
176 | |
177 | static struct phy *brcm_usb_phy_xlate(struct device *dev, |
178 | const struct of_phandle_args *args) |
179 | { |
180 | struct brcm_usb_phy_data *data = dev_get_drvdata(dev); |
181 | |
182 | /* |
183 | * values 0 and 1 are for backward compatibility with |
184 | * device tree nodes from older bootloaders. |
185 | */ |
186 | switch (args->args[0]) { |
187 | case 0: |
188 | case PHY_TYPE_USB2: |
189 | if (data->phys[BRCM_USB_PHY_2_0].phy) |
190 | return data->phys[BRCM_USB_PHY_2_0].phy; |
191 | dev_warn(dev, "Error, 2.0 Phy not found\n" ); |
192 | break; |
193 | case 1: |
194 | case PHY_TYPE_USB3: |
195 | if (data->phys[BRCM_USB_PHY_3_0].phy) |
196 | return data->phys[BRCM_USB_PHY_3_0].phy; |
197 | dev_warn(dev, "Error, 3.0 Phy not found\n" ); |
198 | break; |
199 | } |
200 | return ERR_PTR(error: -ENODEV); |
201 | } |
202 | |
203 | static int name_to_value(const struct value_to_name_map *table, int count, |
204 | const char *name, int *value) |
205 | { |
206 | int x; |
207 | |
208 | *value = 0; |
209 | for (x = 0; x < count; x++) { |
210 | if (sysfs_streq(s1: name, s2: table[x].name)) { |
211 | *value = x; |
212 | return 0; |
213 | } |
214 | } |
215 | return -EINVAL; |
216 | } |
217 | |
218 | static const char *value_to_name(const struct value_to_name_map *table, int count, |
219 | int value) |
220 | { |
221 | if (value >= count) |
222 | return "unknown" ; |
223 | return table[value].name; |
224 | } |
225 | |
226 | static ssize_t dr_mode_show(struct device *dev, |
227 | struct device_attribute *attr, |
228 | char *buf) |
229 | { |
230 | struct brcm_usb_phy_data *priv = dev_get_drvdata(dev); |
231 | |
232 | return sprintf(buf, fmt: "%s\n" , |
233 | value_to_name(table: &brcm_dr_mode_to_name[0], |
234 | ARRAY_SIZE(brcm_dr_mode_to_name), |
235 | value: priv->ini.supported_port_modes)); |
236 | } |
237 | static DEVICE_ATTR_RO(dr_mode); |
238 | |
239 | static ssize_t dual_select_store(struct device *dev, |
240 | struct device_attribute *attr, |
241 | const char *buf, size_t len) |
242 | { |
243 | struct brcm_usb_phy_data *priv = dev_get_drvdata(dev); |
244 | int value; |
245 | int res; |
246 | |
247 | mutex_lock(&sysfs_lock); |
248 | res = name_to_value(table: &brcm_dual_mode_to_name[0], |
249 | ARRAY_SIZE(brcm_dual_mode_to_name), name: buf, value: &value); |
250 | if (!res) { |
251 | priv->ini.port_mode = value; |
252 | brcm_usb_set_dual_select(ini: &priv->ini); |
253 | res = len; |
254 | } |
255 | mutex_unlock(lock: &sysfs_lock); |
256 | return res; |
257 | } |
258 | |
259 | static ssize_t dual_select_show(struct device *dev, |
260 | struct device_attribute *attr, |
261 | char *buf) |
262 | { |
263 | struct brcm_usb_phy_data *priv = dev_get_drvdata(dev); |
264 | int value; |
265 | |
266 | mutex_lock(&sysfs_lock); |
267 | value = brcm_usb_get_dual_select(ini: &priv->ini); |
268 | mutex_unlock(lock: &sysfs_lock); |
269 | return sprintf(buf, fmt: "%s\n" , |
270 | value_to_name(table: &brcm_dual_mode_to_name[0], |
271 | ARRAY_SIZE(brcm_dual_mode_to_name), |
272 | value)); |
273 | } |
274 | static DEVICE_ATTR_RW(dual_select); |
275 | |
276 | static struct attribute *brcm_usb_phy_attrs[] = { |
277 | &dev_attr_dr_mode.attr, |
278 | &dev_attr_dual_select.attr, |
279 | NULL |
280 | }; |
281 | |
282 | static const struct attribute_group brcm_usb_phy_group = { |
283 | .attrs = brcm_usb_phy_attrs, |
284 | }; |
285 | |
286 | static const struct match_chip_info chip_info_4908 = { |
287 | .init_func = &brcm_usb_dvr_init_4908, |
288 | .required_regs = { |
289 | BRCM_REGS_CTRL, |
290 | BRCM_REGS_XHCI_EC, |
291 | -1, |
292 | }, |
293 | }; |
294 | |
295 | static const struct match_chip_info chip_info_7216 = { |
296 | .init_func = &brcm_usb_dvr_init_7216, |
297 | .required_regs = { |
298 | BRCM_REGS_CTRL, |
299 | BRCM_REGS_XHCI_EC, |
300 | BRCM_REGS_XHCI_GBL, |
301 | -1, |
302 | }, |
303 | }; |
304 | |
305 | static const struct match_chip_info chip_info_7211b0 = { |
306 | .init_func = &brcm_usb_dvr_init_7211b0, |
307 | .required_regs = { |
308 | BRCM_REGS_CTRL, |
309 | BRCM_REGS_XHCI_EC, |
310 | BRCM_REGS_XHCI_GBL, |
311 | BRCM_REGS_USB_PHY, |
312 | BRCM_REGS_USB_MDIO, |
313 | -1, |
314 | }, |
315 | .optional_reg = BRCM_REGS_BDC_EC, |
316 | }; |
317 | |
318 | static const struct match_chip_info chip_info_7445 = { |
319 | .init_func = &brcm_usb_dvr_init_7445, |
320 | .required_regs = { |
321 | BRCM_REGS_CTRL, |
322 | BRCM_REGS_XHCI_EC, |
323 | -1, |
324 | }, |
325 | }; |
326 | |
327 | static const struct of_device_id brcm_usb_dt_ids[] = { |
328 | { |
329 | .compatible = "brcm,bcm4908-usb-phy" , |
330 | .data = &chip_info_4908, |
331 | }, |
332 | { |
333 | .compatible = "brcm,bcm7216-usb-phy" , |
334 | .data = &chip_info_7216, |
335 | }, |
336 | { |
337 | .compatible = "brcm,bcm7211-usb-phy" , |
338 | .data = &chip_info_7211b0, |
339 | }, |
340 | { |
341 | .compatible = "brcm,brcmstb-usb-phy" , |
342 | .data = &chip_info_7445, |
343 | }, |
344 | { /* sentinel */ } |
345 | }; |
346 | |
347 | static int brcm_usb_get_regs(struct platform_device *pdev, |
348 | enum brcmusb_reg_sel regs, |
349 | struct brcm_usb_init_params *ini, |
350 | bool optional) |
351 | { |
352 | struct resource *res; |
353 | |
354 | /* Older DT nodes have ctrl and optional xhci_ec by index only */ |
355 | res = platform_get_resource_byname(pdev, IORESOURCE_MEM, |
356 | node_reg_names[regs]); |
357 | if (res == NULL) { |
358 | if (regs == BRCM_REGS_CTRL) { |
359 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
360 | } else if (regs == BRCM_REGS_XHCI_EC) { |
361 | res = platform_get_resource(pdev, IORESOURCE_MEM, 1); |
362 | /* XHCI_EC registers are optional */ |
363 | if (res == NULL) |
364 | return 0; |
365 | } |
366 | if (res == NULL) { |
367 | if (optional) { |
368 | dev_dbg(&pdev->dev, |
369 | "Optional reg %s not found\n" , |
370 | node_reg_names[regs]); |
371 | return 0; |
372 | } |
373 | dev_err(&pdev->dev, "can't get %s base addr\n" , |
374 | node_reg_names[regs]); |
375 | return 1; |
376 | } |
377 | } |
378 | ini->regs[regs] = devm_ioremap_resource(dev: &pdev->dev, res); |
379 | if (IS_ERR(ptr: ini->regs[regs])) { |
380 | dev_err(&pdev->dev, "can't map %s register space\n" , |
381 | node_reg_names[regs]); |
382 | return 1; |
383 | } |
384 | return 0; |
385 | } |
386 | |
387 | static int brcm_usb_phy_dvr_init(struct platform_device *pdev, |
388 | struct brcm_usb_phy_data *priv, |
389 | struct device_node *dn) |
390 | { |
391 | struct device *dev = &pdev->dev; |
392 | struct phy *gphy = NULL; |
393 | int err; |
394 | |
395 | priv->usb_20_clk = of_clk_get_by_name(np: dn, name: "sw_usb" ); |
396 | if (IS_ERR(ptr: priv->usb_20_clk)) { |
397 | if (PTR_ERR(ptr: priv->usb_20_clk) == -EPROBE_DEFER) |
398 | return -EPROBE_DEFER; |
399 | dev_info(dev, "Clock not found in Device Tree\n" ); |
400 | priv->usb_20_clk = NULL; |
401 | } |
402 | err = clk_prepare_enable(clk: priv->usb_20_clk); |
403 | if (err) |
404 | return err; |
405 | |
406 | if (priv->has_eohci) { |
407 | gphy = devm_phy_create(dev, NULL, ops: &brcm_usb_phy_ops); |
408 | if (IS_ERR(ptr: gphy)) { |
409 | dev_err(dev, "failed to create EHCI/OHCI PHY\n" ); |
410 | return PTR_ERR(ptr: gphy); |
411 | } |
412 | priv->phys[BRCM_USB_PHY_2_0].phy = gphy; |
413 | priv->phys[BRCM_USB_PHY_2_0].id = BRCM_USB_PHY_2_0; |
414 | phy_set_drvdata(phy: gphy, data: &priv->phys[BRCM_USB_PHY_2_0]); |
415 | } |
416 | |
417 | if (priv->has_xhci) { |
418 | gphy = devm_phy_create(dev, NULL, ops: &brcm_usb_phy_ops); |
419 | if (IS_ERR(ptr: gphy)) { |
420 | dev_err(dev, "failed to create XHCI PHY\n" ); |
421 | return PTR_ERR(ptr: gphy); |
422 | } |
423 | priv->phys[BRCM_USB_PHY_3_0].phy = gphy; |
424 | priv->phys[BRCM_USB_PHY_3_0].id = BRCM_USB_PHY_3_0; |
425 | phy_set_drvdata(phy: gphy, data: &priv->phys[BRCM_USB_PHY_3_0]); |
426 | |
427 | priv->usb_30_clk = of_clk_get_by_name(np: dn, name: "sw_usb3" ); |
428 | if (IS_ERR(ptr: priv->usb_30_clk)) { |
429 | if (PTR_ERR(ptr: priv->usb_30_clk) == -EPROBE_DEFER) |
430 | return -EPROBE_DEFER; |
431 | dev_info(dev, |
432 | "USB3.0 clock not found in Device Tree\n" ); |
433 | priv->usb_30_clk = NULL; |
434 | } |
435 | err = clk_prepare_enable(clk: priv->usb_30_clk); |
436 | if (err) |
437 | return err; |
438 | } |
439 | |
440 | priv->suspend_clk = clk_get(dev, id: "usb0_freerun" ); |
441 | if (IS_ERR(ptr: priv->suspend_clk)) { |
442 | if (PTR_ERR(ptr: priv->suspend_clk) == -EPROBE_DEFER) |
443 | return -EPROBE_DEFER; |
444 | dev_err(dev, "Suspend Clock not found in Device Tree\n" ); |
445 | priv->suspend_clk = NULL; |
446 | } |
447 | |
448 | priv->wake_irq = platform_get_irq_byname_optional(dev: pdev, name: "wake" ); |
449 | if (priv->wake_irq < 0) |
450 | priv->wake_irq = platform_get_irq_byname_optional(dev: pdev, name: "wakeup" ); |
451 | if (priv->wake_irq >= 0) { |
452 | err = devm_request_irq(dev, irq: priv->wake_irq, |
453 | handler: brcm_usb_phy_wake_isr, irqflags: 0, |
454 | devname: dev_name(dev), dev_id: dev); |
455 | if (err < 0) |
456 | return err; |
457 | device_set_wakeup_capable(dev, capable: 1); |
458 | } else { |
459 | dev_info(dev, |
460 | "Wake interrupt missing, system wake not supported\n" ); |
461 | } |
462 | |
463 | return 0; |
464 | } |
465 | |
466 | static int brcm_usb_phy_probe(struct platform_device *pdev) |
467 | { |
468 | struct device *dev = &pdev->dev; |
469 | struct brcm_usb_phy_data *priv; |
470 | struct phy_provider *phy_provider; |
471 | struct device_node *dn = pdev->dev.of_node; |
472 | int err; |
473 | const char *mode; |
474 | const struct match_chip_info *info; |
475 | struct regmap *rmap; |
476 | int x; |
477 | |
478 | priv = devm_kzalloc(dev, size: sizeof(*priv), GFP_KERNEL); |
479 | if (!priv) |
480 | return -ENOMEM; |
481 | platform_set_drvdata(pdev, data: priv); |
482 | |
483 | priv->ini.family_id = brcmstb_get_family_id(); |
484 | priv->ini.product_id = brcmstb_get_product_id(); |
485 | |
486 | info = of_device_get_match_data(dev: &pdev->dev); |
487 | if (!info) |
488 | return -ENOENT; |
489 | |
490 | info->init_func(&priv->ini); |
491 | |
492 | dev_dbg(dev, "Best mapping table is for %s\n" , |
493 | priv->ini.family_name); |
494 | |
495 | of_property_read_u32(np: dn, propname: "brcm,ipp" , out_value: &priv->ini.ipp); |
496 | of_property_read_u32(np: dn, propname: "brcm,ioc" , out_value: &priv->ini.ioc); |
497 | |
498 | priv->ini.supported_port_modes = USB_CTLR_MODE_HOST; |
499 | err = of_property_read_string(np: dn, propname: "dr_mode" , out_string: &mode); |
500 | if (err == 0) { |
501 | name_to_value(table: &brcm_dr_mode_to_name[0], |
502 | ARRAY_SIZE(brcm_dr_mode_to_name), |
503 | name: mode, value: &priv->ini.supported_port_modes); |
504 | } |
505 | /* Default port_mode to supported port_modes */ |
506 | priv->ini.port_mode = priv->ini.supported_port_modes; |
507 | |
508 | if (of_property_read_bool(np: dn, propname: "brcm,has-xhci" )) |
509 | priv->has_xhci = true; |
510 | if (of_property_read_bool(np: dn, propname: "brcm,has-eohci" )) |
511 | priv->has_eohci = true; |
512 | |
513 | for (x = 0; x < BRCM_REGS_MAX; x++) { |
514 | if (info->required_regs[x] >= BRCM_REGS_MAX) |
515 | break; |
516 | |
517 | err = brcm_usb_get_regs(pdev, regs: info->required_regs[x], |
518 | ini: &priv->ini, optional: false); |
519 | if (err) |
520 | return -EINVAL; |
521 | } |
522 | if (info->optional_reg) { |
523 | err = brcm_usb_get_regs(pdev, regs: info->optional_reg, |
524 | ini: &priv->ini, optional: true); |
525 | if (err) |
526 | return -EINVAL; |
527 | } |
528 | |
529 | err = brcm_usb_phy_dvr_init(pdev, priv, dn); |
530 | if (err) |
531 | return err; |
532 | |
533 | priv->pm_notifier.notifier_call = brcm_pm_notifier; |
534 | register_pm_notifier(nb: &priv->pm_notifier); |
535 | |
536 | mutex_init(&priv->mutex); |
537 | |
538 | /* make sure invert settings are correct */ |
539 | brcm_usb_init_ipp(ini: &priv->ini); |
540 | |
541 | /* |
542 | * Create sysfs entries for mode. |
543 | * Remove "dual_select" attribute if not in dual mode |
544 | */ |
545 | if (priv->ini.supported_port_modes != USB_CTLR_MODE_DRD) |
546 | brcm_usb_phy_attrs[1] = NULL; |
547 | err = sysfs_create_group(kobj: &dev->kobj, grp: &brcm_usb_phy_group); |
548 | if (err) |
549 | dev_warn(dev, "Error creating sysfs attributes\n" ); |
550 | |
551 | /* Get piarbctl syscon if it exists */ |
552 | rmap = syscon_regmap_lookup_by_phandle(np: dev->of_node, |
553 | property: "syscon-piarbctl" ); |
554 | if (IS_ERR(ptr: rmap)) |
555 | rmap = syscon_regmap_lookup_by_phandle(np: dev->of_node, |
556 | property: "brcm,syscon-piarbctl" ); |
557 | if (!IS_ERR(ptr: rmap)) |
558 | priv->ini.syscon_piarbctl = rmap; |
559 | |
560 | /* start with everything off */ |
561 | if (priv->has_xhci) |
562 | brcm_usb_uninit_xhci(ini: &priv->ini); |
563 | if (priv->has_eohci) |
564 | brcm_usb_uninit_eohci(ini: &priv->ini); |
565 | brcm_usb_uninit_common(ini: &priv->ini); |
566 | clk_disable_unprepare(clk: priv->usb_20_clk); |
567 | clk_disable_unprepare(clk: priv->usb_30_clk); |
568 | |
569 | phy_provider = devm_of_phy_provider_register(dev, brcm_usb_phy_xlate); |
570 | |
571 | return PTR_ERR_OR_ZERO(ptr: phy_provider); |
572 | } |
573 | |
574 | static void brcm_usb_phy_remove(struct platform_device *pdev) |
575 | { |
576 | struct brcm_usb_phy_data *priv = dev_get_drvdata(dev: &pdev->dev); |
577 | |
578 | sysfs_remove_group(kobj: &pdev->dev.kobj, grp: &brcm_usb_phy_group); |
579 | unregister_pm_notifier(nb: &priv->pm_notifier); |
580 | } |
581 | |
582 | #ifdef CONFIG_PM_SLEEP |
583 | static int brcm_usb_phy_suspend(struct device *dev) |
584 | { |
585 | struct brcm_usb_phy_data *priv = dev_get_drvdata(dev); |
586 | |
587 | if (priv->init_count) { |
588 | dev_dbg(dev, "SUSPEND\n" ); |
589 | priv->ini.wake_enabled = device_may_wakeup(dev); |
590 | if (priv->phys[BRCM_USB_PHY_3_0].inited) |
591 | brcm_usb_uninit_xhci(ini: &priv->ini); |
592 | if (priv->phys[BRCM_USB_PHY_2_0].inited) |
593 | brcm_usb_uninit_eohci(ini: &priv->ini); |
594 | brcm_usb_uninit_common(ini: &priv->ini); |
595 | |
596 | /* |
597 | * Handle the clocks unless needed for wake. This has |
598 | * to work for both older XHCI->3.0-clks, EOHCI->2.0-clks |
599 | * and newer XHCI->2.0-clks/3.0-clks. |
600 | */ |
601 | |
602 | if (!priv->ini.wake_enabled) { |
603 | if (priv->phys[BRCM_USB_PHY_3_0].inited) |
604 | clk_disable_unprepare(clk: priv->usb_30_clk); |
605 | if (priv->phys[BRCM_USB_PHY_2_0].inited || |
606 | !priv->has_eohci) |
607 | clk_disable_unprepare(clk: priv->usb_20_clk); |
608 | } |
609 | if (priv->wake_irq >= 0) |
610 | enable_irq_wake(irq: priv->wake_irq); |
611 | } |
612 | return 0; |
613 | } |
614 | |
615 | static int brcm_usb_phy_resume(struct device *dev) |
616 | { |
617 | struct brcm_usb_phy_data *priv = dev_get_drvdata(dev); |
618 | |
619 | if (!priv->ini.wake_enabled) { |
620 | clk_prepare_enable(clk: priv->usb_20_clk); |
621 | clk_prepare_enable(clk: priv->usb_30_clk); |
622 | } |
623 | brcm_usb_init_ipp(ini: &priv->ini); |
624 | |
625 | /* |
626 | * Initialize anything that was previously initialized. |
627 | * Uninitialize anything that wasn't previously initialized. |
628 | */ |
629 | if (priv->init_count) { |
630 | dev_dbg(dev, "RESUME\n" ); |
631 | if (priv->wake_irq >= 0) |
632 | disable_irq_wake(irq: priv->wake_irq); |
633 | brcm_usb_init_common(ini: &priv->ini); |
634 | if (priv->phys[BRCM_USB_PHY_2_0].inited) { |
635 | brcm_usb_init_eohci(ini: &priv->ini); |
636 | } else if (priv->has_eohci) { |
637 | brcm_usb_uninit_eohci(ini: &priv->ini); |
638 | clk_disable_unprepare(clk: priv->usb_20_clk); |
639 | } |
640 | if (priv->phys[BRCM_USB_PHY_3_0].inited) { |
641 | brcm_usb_init_xhci(ini: &priv->ini); |
642 | } else if (priv->has_xhci) { |
643 | brcm_usb_uninit_xhci(ini: &priv->ini); |
644 | clk_disable_unprepare(clk: priv->usb_30_clk); |
645 | if (!priv->has_eohci) |
646 | clk_disable_unprepare(clk: priv->usb_20_clk); |
647 | } |
648 | } else { |
649 | if (priv->has_xhci) |
650 | brcm_usb_uninit_xhci(ini: &priv->ini); |
651 | if (priv->has_eohci) |
652 | brcm_usb_uninit_eohci(ini: &priv->ini); |
653 | brcm_usb_uninit_common(ini: &priv->ini); |
654 | clk_disable_unprepare(clk: priv->usb_20_clk); |
655 | clk_disable_unprepare(clk: priv->usb_30_clk); |
656 | } |
657 | priv->ini.wake_enabled = false; |
658 | return 0; |
659 | } |
660 | #endif /* CONFIG_PM_SLEEP */ |
661 | |
662 | static const struct dev_pm_ops brcm_usb_phy_pm_ops = { |
663 | SET_LATE_SYSTEM_SLEEP_PM_OPS(brcm_usb_phy_suspend, brcm_usb_phy_resume) |
664 | }; |
665 | |
666 | MODULE_DEVICE_TABLE(of, brcm_usb_dt_ids); |
667 | |
668 | static struct platform_driver brcm_usb_driver = { |
669 | .probe = brcm_usb_phy_probe, |
670 | .remove_new = brcm_usb_phy_remove, |
671 | .driver = { |
672 | .name = "brcmstb-usb-phy" , |
673 | .pm = &brcm_usb_phy_pm_ops, |
674 | .of_match_table = brcm_usb_dt_ids, |
675 | }, |
676 | }; |
677 | |
678 | module_platform_driver(brcm_usb_driver); |
679 | |
680 | MODULE_ALIAS("platform:brcmstb-usb-phy" ); |
681 | MODULE_AUTHOR("Al Cooper <acooper@broadcom.com>" ); |
682 | MODULE_DESCRIPTION("BRCM USB PHY driver" ); |
683 | MODULE_LICENSE("GPL v2" ); |
684 | |