Commit | Line | Data |
---|---|---|
8bc06364 MK |
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> | |
2d937c64 | 18 | #include <linux/usb/onboard_hub.h> |
8bc06364 MK |
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(onboard_hub_match, 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(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(parent_hub, 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, "peer-hub", 0); | |
89 | if (npc) { | |
90 | if (!usb_hcd_is_primary_hcd(hcd)) { | |
91 | of_node_put(npc); | |
92 | goto node_put; | |
93 | } | |
94 | ||
95 | pdev = of_find_device_by_node(npc); | |
96 | of_node_put(npc); | |
97 | ||
98 | if (pdev) { | |
99 | put_device(&pdev->dev); | |
100 | goto node_put; | |
101 | } | |
102 | } | |
103 | ||
104 | pdev = of_platform_device_create(np, NULL, &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(sizeof(*pdle), GFP_KERNEL); | |
112 | if (!pdle) { | |
113 | of_platform_device_destroy(&pdev->dev, NULL); | |
114 | goto node_put; | |
115 | } | |
116 | ||
117 | pdle->pdev = pdev; | |
118 | list_add(&pdle->node, pdev_list); | |
119 | ||
120 | node_put: | |
121 | of_node_put(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(&pdle->node); | |
139 | of_platform_device_destroy(&pdle->pdev->dev, NULL); | |
140 | kfree(pdle); | |
141 | } | |
142 | } | |
143 | EXPORT_SYMBOL_GPL(onboard_hub_destroy_pdevs); |