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(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(sizeof(*pdle), GFP_KERNEL); 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); 145