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