| 1 | |
| 2 | |
| 3 | * API for creating and destroying USB onboard platform devices
|
| 4 | *
|
| 5 | * Copyright (c) 2022, Google LLC
|
| 6 |
|
| 7 |
|
| 8 | #include <linux/device.h>
|
| 9 | #include <linux/export.h>
|
| 10 | #include <linux/kernel.h>
|
| 11 | #include <linux/list.h>
|
| 12 | #include <linux/of.h>
|
| 13 | #include <linux/of_platform.h>
|
| 14 | #include <linux/platform_device.h>
|
| 15 | #include <linux/usb.h>
|
| 16 | #include <linux/usb/hcd.h>
|
| 17 | #include <linux/usb/of.h>
|
| 18 | #include <linux/usb/onboard_dev.h>
|
| 19 |
|
| 20 | #include "onboard_usb_dev.h"
|
| 21 |
|
| 22 | struct pdev_list_entry {
|
| 23 | struct platform_device *pdev;
|
| 24 | struct list_head node;
|
| 25 | };
|
| 26 |
|
| 27 | static bool of_is_onboard_usb_dev(struct device_node *np)
|
| 28 | {
|
| 29 | return !!of_match_node(onboard_dev_match, np);
|
| 30 | }
|
| 31 |
|
| 32 | |
| 33 | * onboard_dev_create_pdevs -- create platform devices for onboard USB devices
|
| 34 | * @parent_hub : parent hub to scan for connected onboard devices
|
| 35 | * @pdev_list : list of onboard platform devices owned by the parent hub
|
| 36 | *
|
| 37 | * Creates a platform device for each supported onboard device that is connected
|
| 38 | * to the given parent hub. The platform device is in charge of initializing the
|
| 39 | * device (enable regulators, take the device out of reset, ...). For onboard
|
| 40 | * hubs, it can optionally control whether the device remains powered during
|
| 41 | * system suspend or not.
|
| 42 | *
|
| 43 | * To keep track of the platform devices they are added to a list that is owned
|
| 44 | * by the parent hub.
|
| 45 | *
|
| 46 | * Some background about the logic in this function, which can be a bit hard
|
| 47 | * to follow:
|
| 48 | *
|
| 49 | * Root hubs don't have dedicated device tree nodes, but use the node of their
|
| 50 | * HCD. The primary and secondary HCD are usually represented by a single DT
|
| 51 | * node. That means the root hubs of the primary and secondary HCD share the
|
| 52 | * same device tree node (the HCD node). As a result this function can be called
|
| 53 | * twice with the same DT node for root hubs. We only want to create a single
|
| 54 | * platform device for each physical onboard device, hence for root hubs the
|
| 55 | * loop is only executed for the root hub of the primary HCD. Since the function
|
| 56 | * scans through all child nodes it still creates pdevs for onboard devices
|
| 57 | * connected to the root hub of the secondary HCD if needed.
|
| 58 | *
|
| 59 | * Further there must be only one platform device for onboard hubs with a peer
|
| 60 | * hub (the hub is a single physical device). To achieve this two measures are
|
| 61 | * taken: pdevs for onboard hubs with a peer are only created when the function
|
| 62 | * is called on behalf of the parent hub that is connected to the primary HCD
|
| 63 | * (directly or through other hubs). For onboard hubs connected to root hubs
|
| 64 | * the function processes the nodes of both peers. A platform device is only
|
| 65 | * created if the peer hub doesn't have one already.
|
| 66 |
|
| 67 | void onboard_dev_create_pdevs(struct usb_device *parent_hub, struct list_head *pdev_list)
|
| 68 | {
|
| 69 | int i;
|
| 70 | struct usb_hcd *hcd = bus_to_hcd(parent_hub->bus);
|
| 71 | struct device_node *np, *npc;
|
| 72 | struct platform_device *pdev;
|
| 73 | struct pdev_list_entry *pdle;
|
| 74 |
|
| 75 | if (!parent_hub->dev.of_node)
|
| 76 | return;
|
| 77 |
|
| 78 | if (!parent_hub->parent && !usb_hcd_is_primary_hcd(hcd))
|
| 79 | return;
|
| 80 |
|
| 81 | for (i = 1; i <= parent_hub->maxchild; i++) {
|
| 82 | np = usb_of_get_device_node(parent_hub, i);
|
| 83 | if (!np)
|
| 84 | continue;
|
| 85 |
|
| 86 | if (!of_is_onboard_usb_dev(np))
|
| 87 | goto node_put;
|
| 88 |
|
| 89 | npc = of_parse_phandle(np, "peer-hub", 0);
|
| 90 | if (npc) {
|
| 91 | if (!usb_hcd_is_primary_hcd(hcd)) {
|
| 92 | of_node_put(npc);
|
| 93 | goto node_put;
|
| 94 | }
|
| 95 |
|
| 96 | pdev = of_find_device_by_node(npc);
|
| 97 | of_node_put(npc);
|
| 98 |
|
| 99 | if (pdev) {
|
| 100 | put_device(&pdev->dev);
|
| 101 | goto node_put;
|
| 102 | }
|
| 103 | }
|
| 104 |
|
| 105 | pdev = of_platform_device_create(np, NULL, &parent_hub->dev);
|
| 106 | if (!pdev) {
|
| 107 | dev_err(&parent_hub->dev,
|
| 108 | "failed to create platform device for onboard dev '%pOF'\n", np);
|
| 109 | goto node_put;
|
| 110 | }
|
| 111 |
|
| 112 | pdle = kzalloc_obj(*pdle);
|
| 113 | if (!pdle) {
|
| 114 | of_platform_device_destroy(&pdev->dev, NULL);
|
| 115 | goto node_put;
|
| 116 | }
|
| 117 |
|
| 118 | pdle->pdev = pdev;
|
| 119 | list_add(&pdle->node, pdev_list);
|
| 120 |
|
| 121 | node_put:
|
| 122 | of_node_put(np);
|
| 123 | }
|
| 124 | }
|
| 125 | EXPORT_SYMBOL_GPL(onboard_dev_create_pdevs);
|
| 126 |
|
| 127 | |
| 128 | * onboard_dev_destroy_pdevs -- free resources of onboard platform devices
|
| 129 | * @pdev_list : list of onboard platform devices
|
| 130 | *
|
| 131 | * Destroys the platform devices in the given list and frees the memory associated
|
| 132 | * with the list entry.
|
| 133 |
|
| 134 | void onboard_dev_destroy_pdevs(struct list_head *pdev_list)
|
| 135 | {
|
| 136 | struct pdev_list_entry *pdle, *tmp;
|
| 137 |
|
| 138 | list_for_each_entry_safe(pdle, tmp, pdev_list, node) {
|
| 139 | list_del(&pdle->node);
|
| 140 | of_platform_device_destroy(&pdle->pdev->dev, NULL);
|
| 141 | kfree(pdle);
|
| 142 | }
|
| 143 | }
|
| 144 | EXPORT_SYMBOL_GPL(onboard_dev_destroy_pdevs);
|