| 1 | // SPDX-License-Identifier: GPL-2.0-only |
| 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(matches: onboard_dev_match, node: 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(bus: 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(hub: parent_hub, port1: 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, phandle_name: "peer-hub" , index: 0); |
| 90 | if (npc) { |
| 91 | if (!usb_hcd_is_primary_hcd(hcd)) { |
| 92 | of_node_put(node: npc); |
| 93 | goto node_put; |
| 94 | } |
| 95 | |
| 96 | pdev = of_find_device_by_node(np: npc); |
| 97 | of_node_put(node: npc); |
| 98 | |
| 99 | if (pdev) { |
| 100 | put_device(dev: &pdev->dev); |
| 101 | goto node_put; |
| 102 | } |
| 103 | } |
| 104 | |
| 105 | pdev = of_platform_device_create(np, NULL, parent: &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(sizeof(*pdle), GFP_KERNEL); |
| 113 | if (!pdle) { |
| 114 | of_platform_device_destroy(dev: &pdev->dev, NULL); |
| 115 | goto node_put; |
| 116 | } |
| 117 | |
| 118 | pdle->pdev = pdev; |
| 119 | list_add(new: &pdle->node, head: pdev_list); |
| 120 | |
| 121 | node_put: |
| 122 | of_node_put(node: 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(entry: &pdle->node); |
| 140 | of_platform_device_destroy(dev: &pdle->pdev->dev, NULL); |
| 141 | kfree(objp: pdle); |
| 142 | } |
| 143 | } |
| 144 | EXPORT_SYMBOL_GPL(onboard_dev_destroy_pdevs); |
| 145 | |