34f7e4885b2e8cb056ac9b1155e346388ca6f2f4
[linux-2.6-microblaze.git] / drivers / usb / misc / onboard_usb_hub.c
1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3  * Driver for onboard USB hubs
4  *
5  * Copyright (c) 2022, Google LLC
6  */
7
8 #include <linux/device.h>
9 #include <linux/export.h>
10 #include <linux/init.h>
11 #include <linux/kernel.h>
12 #include <linux/list.h>
13 #include <linux/module.h>
14 #include <linux/mutex.h>
15 #include <linux/of.h>
16 #include <linux/of_platform.h>
17 #include <linux/platform_device.h>
18 #include <linux/regulator/consumer.h>
19 #include <linux/suspend.h>
20 #include <linux/sysfs.h>
21 #include <linux/usb.h>
22 #include <linux/usb/hcd.h>
23 #include <linux/usb/of.h>
24 #include <linux/usb/onboard_hub.h>
25
26 static struct usb_device_driver onboard_hub_usbdev_driver;
27
28 /************************** Platform driver **************************/
29
30 struct usbdev_node {
31         struct usb_device *udev;
32         struct list_head list;
33 };
34
35 struct onboard_hub {
36         struct regulator *vdd;
37         struct device *dev;
38         bool always_powered_in_suspend;
39         bool is_powered_on;
40         bool going_away;
41         struct list_head udev_list;
42         struct mutex lock;
43 };
44
45 static int onboard_hub_power_on(struct onboard_hub *hub)
46 {
47         int err;
48
49         err = regulator_enable(hub->vdd);
50         if (err) {
51                 dev_err(hub->dev, "failed to enable regulator: %d\n", err);
52                 return err;
53         }
54
55         hub->is_powered_on = true;
56
57         return 0;
58 }
59
60 static int onboard_hub_power_off(struct onboard_hub *hub)
61 {
62         int err;
63
64         err = regulator_disable(hub->vdd);
65         if (err) {
66                 dev_err(hub->dev, "failed to disable regulator: %d\n", err);
67                 return err;
68         }
69
70         hub->is_powered_on = false;
71
72         return 0;
73 }
74
75 static int __maybe_unused onboard_hub_suspend(struct device *dev)
76 {
77         struct onboard_hub *hub = dev_get_drvdata(dev);
78         struct usbdev_node *node;
79         bool power_off = true;
80
81         if (hub->always_powered_in_suspend)
82                 return 0;
83
84         mutex_lock(&hub->lock);
85
86         list_for_each_entry(node, &hub->udev_list, list) {
87                 if (!device_may_wakeup(node->udev->bus->controller))
88                         continue;
89
90                 if (usb_wakeup_enabled_descendants(node->udev)) {
91                         power_off = false;
92                         break;
93                 }
94         }
95
96         mutex_unlock(&hub->lock);
97
98         if (!power_off)
99                 return 0;
100
101         return onboard_hub_power_off(hub);
102 }
103
104 static int __maybe_unused onboard_hub_resume(struct device *dev)
105 {
106         struct onboard_hub *hub = dev_get_drvdata(dev);
107
108         if (hub->is_powered_on)
109                 return 0;
110
111         return onboard_hub_power_on(hub);
112 }
113
114 static inline void get_udev_link_name(const struct usb_device *udev, char *buf, size_t size)
115 {
116         snprintf(buf, size, "usb_dev.%s", dev_name(&udev->dev));
117 }
118
119 static int onboard_hub_add_usbdev(struct onboard_hub *hub, struct usb_device *udev)
120 {
121         struct usbdev_node *node;
122         char link_name[64];
123         int err;
124
125         mutex_lock(&hub->lock);
126
127         if (hub->going_away) {
128                 err = -EINVAL;
129                 goto error;
130         }
131
132         node = kzalloc(sizeof(*node), GFP_KERNEL);
133         if (!node) {
134                 err = -ENOMEM;
135                 goto error;
136         }
137
138         node->udev = udev;
139
140         list_add(&node->list, &hub->udev_list);
141
142         mutex_unlock(&hub->lock);
143
144         get_udev_link_name(udev, link_name, sizeof(link_name));
145         WARN_ON(sysfs_create_link(&hub->dev->kobj, &udev->dev.kobj, link_name));
146
147         return 0;
148
149 error:
150         mutex_unlock(&hub->lock);
151
152         return err;
153 }
154
155 static void onboard_hub_remove_usbdev(struct onboard_hub *hub, const struct usb_device *udev)
156 {
157         struct usbdev_node *node;
158         char link_name[64];
159
160         get_udev_link_name(udev, link_name, sizeof(link_name));
161         sysfs_remove_link(&hub->dev->kobj, link_name);
162
163         mutex_lock(&hub->lock);
164
165         list_for_each_entry(node, &hub->udev_list, list) {
166                 if (node->udev == udev) {
167                         list_del(&node->list);
168                         kfree(node);
169                         break;
170                 }
171         }
172
173         mutex_unlock(&hub->lock);
174 }
175
176 static ssize_t always_powered_in_suspend_show(struct device *dev, struct device_attribute *attr,
177                            char *buf)
178 {
179         const struct onboard_hub *hub = dev_get_drvdata(dev);
180
181         return sysfs_emit(buf, "%d\n", hub->always_powered_in_suspend);
182 }
183
184 static ssize_t always_powered_in_suspend_store(struct device *dev, struct device_attribute *attr,
185                             const char *buf, size_t count)
186 {
187         struct onboard_hub *hub = dev_get_drvdata(dev);
188         bool val;
189         int ret;
190
191         ret = kstrtobool(buf, &val);
192         if (ret < 0)
193                 return ret;
194
195         hub->always_powered_in_suspend = val;
196
197         return count;
198 }
199 static DEVICE_ATTR_RW(always_powered_in_suspend);
200
201 static struct attribute *onboard_hub_attrs[] = {
202         &dev_attr_always_powered_in_suspend.attr,
203         NULL,
204 };
205 ATTRIBUTE_GROUPS(onboard_hub);
206
207 static int onboard_hub_probe(struct platform_device *pdev)
208 {
209         struct device *dev = &pdev->dev;
210         struct onboard_hub *hub;
211         int err;
212
213         hub = devm_kzalloc(dev, sizeof(*hub), GFP_KERNEL);
214         if (!hub)
215                 return -ENOMEM;
216
217         hub->vdd = devm_regulator_get(dev, "vdd");
218         if (IS_ERR(hub->vdd))
219                 return PTR_ERR(hub->vdd);
220
221         hub->dev = dev;
222         mutex_init(&hub->lock);
223         INIT_LIST_HEAD(&hub->udev_list);
224
225         dev_set_drvdata(dev, hub);
226
227         err = onboard_hub_power_on(hub);
228         if (err)
229                 return err;
230
231         /*
232          * The USB driver might have been detached from the USB devices by
233          * onboard_hub_remove(), make sure to re-attach it if needed.
234          */
235         err = driver_attach(&onboard_hub_usbdev_driver.drvwrap.driver);
236         if (err) {
237                 onboard_hub_power_off(hub);
238                 return err;
239         }
240
241         return 0;
242 }
243
244 static int onboard_hub_remove(struct platform_device *pdev)
245 {
246         struct onboard_hub *hub = dev_get_drvdata(&pdev->dev);
247         struct usbdev_node *node;
248         struct usb_device *udev;
249
250         hub->going_away = true;
251
252         mutex_lock(&hub->lock);
253
254         /* unbind the USB devices to avoid dangling references to this device */
255         while (!list_empty(&hub->udev_list)) {
256                 node = list_first_entry(&hub->udev_list, struct usbdev_node, list);
257                 udev = node->udev;
258
259                 /*
260                  * Unbinding the driver will call onboard_hub_remove_usbdev(),
261                  * which acquires hub->lock.  We must release the lock first.
262                  */
263                 get_device(&udev->dev);
264                 mutex_unlock(&hub->lock);
265                 device_release_driver(&udev->dev);
266                 put_device(&udev->dev);
267                 mutex_lock(&hub->lock);
268         }
269
270         mutex_unlock(&hub->lock);
271
272         return onboard_hub_power_off(hub);
273 }
274
275 static const struct of_device_id onboard_hub_match[] = {
276         { .compatible = "usbbda,411" },
277         { .compatible = "usbbda,5411" },
278         { .compatible = "usbbda,414" },
279         { .compatible = "usbbda,5414" },
280         {}
281 };
282 MODULE_DEVICE_TABLE(of, onboard_hub_match);
283
284 static bool of_is_onboard_usb_hub(const struct device_node *np)
285 {
286         return !!of_match_node(onboard_hub_match, np);
287 }
288
289 static const struct dev_pm_ops __maybe_unused onboard_hub_pm_ops = {
290         SET_LATE_SYSTEM_SLEEP_PM_OPS(onboard_hub_suspend, onboard_hub_resume)
291 };
292
293 static struct platform_driver onboard_hub_driver = {
294         .probe = onboard_hub_probe,
295         .remove = onboard_hub_remove,
296
297         .driver = {
298                 .name = "onboard-usb-hub",
299                 .of_match_table = onboard_hub_match,
300                 .pm = pm_ptr(&onboard_hub_pm_ops),
301                 .dev_groups = onboard_hub_groups,
302         },
303 };
304
305 /************************** USB driver **************************/
306
307 #define VENDOR_ID_REALTEK       0x0bda
308
309 /*
310  * Returns the onboard_hub platform device that is associated with the USB
311  * device passed as parameter.
312  */
313 static struct onboard_hub *_find_onboard_hub(struct device *dev)
314 {
315         struct platform_device *pdev;
316         struct device_node *np;
317         struct onboard_hub *hub;
318
319         pdev = of_find_device_by_node(dev->of_node);
320         if (!pdev) {
321                 np = of_parse_phandle(dev->of_node, "companion-hub", 0);
322                 if (!np) {
323                         dev_err(dev, "failed to find device node for companion hub\n");
324                         return ERR_PTR(-EINVAL);
325                 }
326
327                 pdev = of_find_device_by_node(np);
328                 of_node_put(np);
329
330                 if (!pdev)
331                         return ERR_PTR(-ENODEV);
332         }
333
334         hub = dev_get_drvdata(&pdev->dev);
335         put_device(&pdev->dev);
336
337         /*
338          * The presence of drvdata ('hub') indicates that the platform driver
339          * finished probing. This handles the case where (conceivably) we could
340          * be running at the exact same time as the platform driver's probe. If
341          * we detect the race we request probe deferral and we'll come back and
342          * try again.
343          */
344         if (!hub)
345                 return ERR_PTR(-EPROBE_DEFER);
346
347         return hub;
348 }
349
350 static int onboard_hub_usbdev_probe(struct usb_device *udev)
351 {
352         struct device *dev = &udev->dev;
353         struct onboard_hub *hub;
354         int err;
355
356         /* ignore supported hubs without device tree node */
357         if (!dev->of_node)
358                 return -ENODEV;
359
360         hub = _find_onboard_hub(dev);
361         if (IS_ERR(hub))
362                 return PTR_ERR(hub);
363
364         dev_set_drvdata(dev, hub);
365
366         err = onboard_hub_add_usbdev(hub, udev);
367         if (err)
368                 return err;
369
370         return 0;
371 }
372
373 static void onboard_hub_usbdev_disconnect(struct usb_device *udev)
374 {
375         struct onboard_hub *hub = dev_get_drvdata(&udev->dev);
376
377         onboard_hub_remove_usbdev(hub, udev);
378 }
379
380 static const struct usb_device_id onboard_hub_id_table[] = {
381         { USB_DEVICE(VENDOR_ID_REALTEK, 0x0411) }, /* RTS5411 USB 3.1 */
382         { USB_DEVICE(VENDOR_ID_REALTEK, 0x5411) }, /* RTS5411 USB 2.1 */
383         { USB_DEVICE(VENDOR_ID_REALTEK, 0x0414) }, /* RTS5414 USB 3.2 */
384         { USB_DEVICE(VENDOR_ID_REALTEK, 0x5414) }, /* RTS5414 USB 2.1 */
385         {}
386 };
387 MODULE_DEVICE_TABLE(usb, onboard_hub_id_table);
388
389 static struct usb_device_driver onboard_hub_usbdev_driver = {
390         .name = "onboard-usb-hub",
391         .probe = onboard_hub_usbdev_probe,
392         .disconnect = onboard_hub_usbdev_disconnect,
393         .generic_subclass = 1,
394         .supports_autosuspend = 1,
395         .id_table = onboard_hub_id_table,
396 };
397
398 /*** Helpers for creating/destroying platform devices for onboard hubs ***/
399
400 struct pdev_list_entry {
401         struct platform_device *pdev;
402         struct list_head node;
403 };
404
405 /**
406  * onboard_hub_create_pdevs -- create platform devices for onboard USB hubs
407  * @parent_hub  : parent hub to scan for connected onboard hubs
408  * @pdev_list   : list of onboard hub platform devices owned by the parent hub
409  *
410  * Creates a platform device for each supported onboard hub that is connected to
411  * the given parent hub. To keep track of the platform devices they are added to
412  * a list that is owned by the parent hub.
413  */
414 void onboard_hub_create_pdevs(struct usb_device *parent_hub, struct list_head *pdev_list)
415 {
416         int i;
417         struct device_node *np, *npc;
418         struct platform_device *pdev = NULL;
419         struct pdev_list_entry *pdle;
420
421         INIT_LIST_HEAD(pdev_list);
422
423         for (i = 1; i <= parent_hub->maxchild; i++) {
424                 np = usb_of_get_device_node(parent_hub, i);
425                 if (!np)
426                         continue;
427
428                 if (!of_is_onboard_usb_hub(np))
429                         goto node_put;
430
431                 npc = of_parse_phandle(np, "companion-hub", 0);
432                 if (npc) {
433                         pdev = of_find_device_by_node(npc);
434                         of_node_put(npc);
435                 }
436
437                 if (pdev) {
438                         /* the companion hub already has a platform device, nothing to do here */
439                         put_device(&pdev->dev);
440                         goto node_put;
441                 }
442
443                 pdev = of_platform_device_create(np, NULL, &parent_hub->dev);
444                 if (!pdev) {
445                         dev_err(&parent_hub->dev,
446                                 "failed to create platform device for onboard hub '%pOF'\n", np);
447                         goto node_put;
448                 }
449
450                 pdle = devm_kzalloc(&pdev->dev, sizeof(*pdle), GFP_KERNEL);
451                 if (!pdle) {
452                         of_platform_device_destroy(&pdev->dev, NULL);
453                         goto node_put;
454                 }
455
456                 pdle->pdev = pdev;
457                 list_add(&pdle->node, pdev_list);
458
459 node_put:
460                 of_node_put(np);
461         }
462 }
463 EXPORT_SYMBOL_GPL(onboard_hub_create_pdevs);
464
465 /**
466  * onboard_hub_destroy_pdevs -- free resources of onboard hub platform devices
467  * @pdev_list   : list of onboard hub platform devices
468  *
469  * Destroys the platform devices in the given list and frees the memory associated
470  * with the list entry.
471  */
472 void onboard_hub_destroy_pdevs(struct list_head *pdev_list)
473 {
474         struct pdev_list_entry *pdle, *tmp;
475
476         list_for_each_entry_safe(pdle, tmp, pdev_list, node) {
477                 list_del(&pdle->node);
478                 of_platform_device_destroy(&pdle->pdev->dev, NULL);
479         }
480 }
481 EXPORT_SYMBOL_GPL(onboard_hub_destroy_pdevs);
482
483 /************************** Driver (de)registration **************************/
484
485 static int __init onboard_hub_init(void)
486 {
487         int ret;
488
489         ret = platform_driver_register(&onboard_hub_driver);
490         if (ret)
491                 return ret;
492
493         ret = usb_register_device_driver(&onboard_hub_usbdev_driver, THIS_MODULE);
494         if (ret)
495                 platform_driver_unregister(&onboard_hub_driver);
496
497         return ret;
498 }
499 module_init(onboard_hub_init);
500
501 static void __exit onboard_hub_exit(void)
502 {
503         usb_deregister_device_driver(&onboard_hub_usbdev_driver);
504         platform_driver_unregister(&onboard_hub_driver);
505 }
506 module_exit(onboard_hub_exit);
507
508 MODULE_AUTHOR("Matthias Kaehlcke <mka@chromium.org>");
509 MODULE_DESCRIPTION("Driver for discrete onboard USB hubs");
510 MODULE_LICENSE("GPL v2");