1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * A wrapper for multiple PHYs which passes all phy_* function calls to |
4 | * multiple (actual) PHY devices. This is comes handy when initializing |
5 | * all PHYs on a HCD and to keep them all in the same state. |
6 | * |
7 | * Copyright (C) 2018 Martin Blumenstingl <martin.blumenstingl@googlemail.com> |
8 | */ |
9 | |
10 | #include <linux/device.h> |
11 | #include <linux/list.h> |
12 | #include <linux/phy/phy.h> |
13 | #include <linux/of.h> |
14 | |
15 | #include "phy.h" |
16 | |
17 | struct usb_phy_roothub { |
18 | struct phy *phy; |
19 | struct list_head list; |
20 | }; |
21 | |
22 | /* Allocate the roothub_entry by specific name of phy */ |
23 | static int usb_phy_roothub_add_phy_by_name(struct device *dev, const char *name, |
24 | struct list_head *list) |
25 | { |
26 | struct usb_phy_roothub *roothub_entry; |
27 | struct phy *phy; |
28 | |
29 | phy = devm_of_phy_get(dev, np: dev->of_node, con_id: name); |
30 | if (IS_ERR(ptr: phy)) |
31 | return PTR_ERR(ptr: phy); |
32 | |
33 | roothub_entry = devm_kzalloc(dev, size: sizeof(*roothub_entry), GFP_KERNEL); |
34 | if (!roothub_entry) |
35 | return -ENOMEM; |
36 | |
37 | INIT_LIST_HEAD(list: &roothub_entry->list); |
38 | |
39 | roothub_entry->phy = phy; |
40 | |
41 | list_add_tail(new: &roothub_entry->list, head: list); |
42 | |
43 | return 0; |
44 | } |
45 | |
46 | static int usb_phy_roothub_add_phy(struct device *dev, int index, |
47 | struct list_head *list) |
48 | { |
49 | struct usb_phy_roothub *roothub_entry; |
50 | struct phy *phy; |
51 | |
52 | phy = devm_of_phy_get_by_index(dev, np: dev->of_node, index); |
53 | if (IS_ERR(ptr: phy)) { |
54 | if (PTR_ERR(ptr: phy) == -ENODEV) |
55 | return 0; |
56 | else |
57 | return PTR_ERR(ptr: phy); |
58 | } |
59 | |
60 | roothub_entry = devm_kzalloc(dev, size: sizeof(*roothub_entry), GFP_KERNEL); |
61 | if (!roothub_entry) |
62 | return -ENOMEM; |
63 | |
64 | INIT_LIST_HEAD(list: &roothub_entry->list); |
65 | |
66 | roothub_entry->phy = phy; |
67 | |
68 | list_add_tail(new: &roothub_entry->list, head: list); |
69 | |
70 | return 0; |
71 | } |
72 | |
73 | struct usb_phy_roothub *usb_phy_roothub_alloc(struct device *dev) |
74 | { |
75 | struct usb_phy_roothub *phy_roothub; |
76 | int i, num_phys, err; |
77 | |
78 | if (!IS_ENABLED(CONFIG_GENERIC_PHY)) |
79 | return NULL; |
80 | |
81 | num_phys = of_count_phandle_with_args(np: dev->of_node, list_name: "phys" , |
82 | cells_name: "#phy-cells" ); |
83 | if (num_phys <= 0) |
84 | return NULL; |
85 | |
86 | phy_roothub = devm_kzalloc(dev, size: sizeof(*phy_roothub), GFP_KERNEL); |
87 | if (!phy_roothub) |
88 | return ERR_PTR(error: -ENOMEM); |
89 | |
90 | INIT_LIST_HEAD(list: &phy_roothub->list); |
91 | |
92 | if (!usb_phy_roothub_add_phy_by_name(dev, name: "usb2-phy" , list: &phy_roothub->list)) |
93 | return phy_roothub; |
94 | |
95 | for (i = 0; i < num_phys; i++) { |
96 | err = usb_phy_roothub_add_phy(dev, index: i, list: &phy_roothub->list); |
97 | if (err) |
98 | return ERR_PTR(error: err); |
99 | } |
100 | |
101 | return phy_roothub; |
102 | } |
103 | EXPORT_SYMBOL_GPL(usb_phy_roothub_alloc); |
104 | |
105 | /** |
106 | * usb_phy_roothub_alloc_usb3_phy - alloc the roothub |
107 | * @dev: the device of the host controller |
108 | * |
109 | * Allocate the usb phy roothub if the host use a generic usb3-phy. |
110 | * |
111 | * Return: On success, a pointer to the usb_phy_roothub. Otherwise, |
112 | * %NULL if no use usb3 phy or %-ENOMEM if out of memory. |
113 | */ |
114 | struct usb_phy_roothub *usb_phy_roothub_alloc_usb3_phy(struct device *dev) |
115 | { |
116 | struct usb_phy_roothub *phy_roothub; |
117 | int num_phys; |
118 | |
119 | if (!IS_ENABLED(CONFIG_GENERIC_PHY)) |
120 | return NULL; |
121 | |
122 | num_phys = of_count_phandle_with_args(np: dev->of_node, list_name: "phys" , |
123 | cells_name: "#phy-cells" ); |
124 | if (num_phys <= 0) |
125 | return NULL; |
126 | |
127 | phy_roothub = devm_kzalloc(dev, size: sizeof(*phy_roothub), GFP_KERNEL); |
128 | if (!phy_roothub) |
129 | return ERR_PTR(error: -ENOMEM); |
130 | |
131 | INIT_LIST_HEAD(list: &phy_roothub->list); |
132 | |
133 | if (!usb_phy_roothub_add_phy_by_name(dev, name: "usb3-phy" , list: &phy_roothub->list)) |
134 | return phy_roothub; |
135 | |
136 | return NULL; |
137 | } |
138 | EXPORT_SYMBOL_GPL(usb_phy_roothub_alloc_usb3_phy); |
139 | |
140 | int usb_phy_roothub_init(struct usb_phy_roothub *phy_roothub) |
141 | { |
142 | struct usb_phy_roothub *roothub_entry; |
143 | struct list_head *head; |
144 | int err; |
145 | |
146 | if (!phy_roothub) |
147 | return 0; |
148 | |
149 | head = &phy_roothub->list; |
150 | |
151 | list_for_each_entry(roothub_entry, head, list) { |
152 | err = phy_init(phy: roothub_entry->phy); |
153 | if (err) |
154 | goto err_exit_phys; |
155 | } |
156 | |
157 | return 0; |
158 | |
159 | err_exit_phys: |
160 | list_for_each_entry_continue_reverse(roothub_entry, head, list) |
161 | phy_exit(phy: roothub_entry->phy); |
162 | |
163 | return err; |
164 | } |
165 | EXPORT_SYMBOL_GPL(usb_phy_roothub_init); |
166 | |
167 | int usb_phy_roothub_exit(struct usb_phy_roothub *phy_roothub) |
168 | { |
169 | struct usb_phy_roothub *roothub_entry; |
170 | struct list_head *head; |
171 | int err, ret = 0; |
172 | |
173 | if (!phy_roothub) |
174 | return 0; |
175 | |
176 | head = &phy_roothub->list; |
177 | |
178 | list_for_each_entry(roothub_entry, head, list) { |
179 | err = phy_exit(phy: roothub_entry->phy); |
180 | if (err) |
181 | ret = err; |
182 | } |
183 | |
184 | return ret; |
185 | } |
186 | EXPORT_SYMBOL_GPL(usb_phy_roothub_exit); |
187 | |
188 | int usb_phy_roothub_set_mode(struct usb_phy_roothub *phy_roothub, |
189 | enum phy_mode mode) |
190 | { |
191 | struct usb_phy_roothub *roothub_entry; |
192 | struct list_head *head; |
193 | int err; |
194 | |
195 | if (!phy_roothub) |
196 | return 0; |
197 | |
198 | head = &phy_roothub->list; |
199 | |
200 | list_for_each_entry(roothub_entry, head, list) { |
201 | err = phy_set_mode(roothub_entry->phy, mode); |
202 | if (err) |
203 | goto err_out; |
204 | } |
205 | |
206 | return 0; |
207 | |
208 | err_out: |
209 | list_for_each_entry_continue_reverse(roothub_entry, head, list) |
210 | phy_power_off(phy: roothub_entry->phy); |
211 | |
212 | return err; |
213 | } |
214 | EXPORT_SYMBOL_GPL(usb_phy_roothub_set_mode); |
215 | |
216 | int usb_phy_roothub_calibrate(struct usb_phy_roothub *phy_roothub) |
217 | { |
218 | struct usb_phy_roothub *roothub_entry; |
219 | struct list_head *head; |
220 | int err; |
221 | |
222 | if (!phy_roothub) |
223 | return 0; |
224 | |
225 | head = &phy_roothub->list; |
226 | |
227 | list_for_each_entry(roothub_entry, head, list) { |
228 | err = phy_calibrate(phy: roothub_entry->phy); |
229 | if (err) |
230 | return err; |
231 | } |
232 | |
233 | return 0; |
234 | } |
235 | EXPORT_SYMBOL_GPL(usb_phy_roothub_calibrate); |
236 | |
237 | /** |
238 | * usb_phy_roothub_notify_connect() - connect notification |
239 | * @phy_roothub: the phy of roothub, if the host use a generic phy. |
240 | * @port: the port index for connect |
241 | * |
242 | * If the phy needs to get connection status, the callback can be used. |
243 | * Returns: %0 if successful, a negative error code otherwise |
244 | */ |
245 | int usb_phy_roothub_notify_connect(struct usb_phy_roothub *phy_roothub, int port) |
246 | { |
247 | struct usb_phy_roothub *roothub_entry; |
248 | struct list_head *head; |
249 | int err; |
250 | |
251 | if (!phy_roothub) |
252 | return 0; |
253 | |
254 | head = &phy_roothub->list; |
255 | |
256 | list_for_each_entry(roothub_entry, head, list) { |
257 | err = phy_notify_connect(phy: roothub_entry->phy, port); |
258 | if (err) |
259 | return err; |
260 | } |
261 | |
262 | return 0; |
263 | } |
264 | EXPORT_SYMBOL_GPL(usb_phy_roothub_notify_connect); |
265 | |
266 | /** |
267 | * usb_phy_roothub_notify_disconnect() - disconnect notification |
268 | * @phy_roothub: the phy of roothub, if the host use a generic phy. |
269 | * @port: the port index for disconnect |
270 | * |
271 | * If the phy needs to get connection status, the callback can be used. |
272 | * Returns: %0 if successful, a negative error code otherwise |
273 | */ |
274 | int usb_phy_roothub_notify_disconnect(struct usb_phy_roothub *phy_roothub, int port) |
275 | { |
276 | struct usb_phy_roothub *roothub_entry; |
277 | struct list_head *head; |
278 | int err; |
279 | |
280 | if (!phy_roothub) |
281 | return 0; |
282 | |
283 | head = &phy_roothub->list; |
284 | |
285 | list_for_each_entry(roothub_entry, head, list) { |
286 | err = phy_notify_disconnect(phy: roothub_entry->phy, port); |
287 | if (err) |
288 | return err; |
289 | } |
290 | |
291 | return 0; |
292 | } |
293 | EXPORT_SYMBOL_GPL(usb_phy_roothub_notify_disconnect); |
294 | |
295 | int usb_phy_roothub_power_on(struct usb_phy_roothub *phy_roothub) |
296 | { |
297 | struct usb_phy_roothub *roothub_entry; |
298 | struct list_head *head; |
299 | int err; |
300 | |
301 | if (!phy_roothub) |
302 | return 0; |
303 | |
304 | head = &phy_roothub->list; |
305 | |
306 | list_for_each_entry(roothub_entry, head, list) { |
307 | err = phy_power_on(phy: roothub_entry->phy); |
308 | if (err) |
309 | goto err_out; |
310 | } |
311 | |
312 | return 0; |
313 | |
314 | err_out: |
315 | list_for_each_entry_continue_reverse(roothub_entry, head, list) |
316 | phy_power_off(phy: roothub_entry->phy); |
317 | |
318 | return err; |
319 | } |
320 | EXPORT_SYMBOL_GPL(usb_phy_roothub_power_on); |
321 | |
322 | void usb_phy_roothub_power_off(struct usb_phy_roothub *phy_roothub) |
323 | { |
324 | struct usb_phy_roothub *roothub_entry; |
325 | |
326 | if (!phy_roothub) |
327 | return; |
328 | |
329 | list_for_each_entry_reverse(roothub_entry, &phy_roothub->list, list) |
330 | phy_power_off(phy: roothub_entry->phy); |
331 | } |
332 | EXPORT_SYMBOL_GPL(usb_phy_roothub_power_off); |
333 | |
334 | int usb_phy_roothub_suspend(struct device *controller_dev, |
335 | struct usb_phy_roothub *phy_roothub) |
336 | { |
337 | usb_phy_roothub_power_off(phy_roothub); |
338 | |
339 | /* keep the PHYs initialized so the device can wake up the system */ |
340 | if (device_may_wakeup(dev: controller_dev)) |
341 | return 0; |
342 | |
343 | return usb_phy_roothub_exit(phy_roothub); |
344 | } |
345 | EXPORT_SYMBOL_GPL(usb_phy_roothub_suspend); |
346 | |
347 | int usb_phy_roothub_resume(struct device *controller_dev, |
348 | struct usb_phy_roothub *phy_roothub) |
349 | { |
350 | int err; |
351 | |
352 | /* if the device can't wake up the system _exit was called */ |
353 | if (!device_may_wakeup(dev: controller_dev)) { |
354 | err = usb_phy_roothub_init(phy_roothub); |
355 | if (err) |
356 | return err; |
357 | } |
358 | |
359 | err = usb_phy_roothub_power_on(phy_roothub); |
360 | |
361 | /* undo _init if _power_on failed */ |
362 | if (err && !device_may_wakeup(dev: controller_dev)) |
363 | usb_phy_roothub_exit(phy_roothub); |
364 | |
365 | return err; |
366 | } |
367 | EXPORT_SYMBOL_GPL(usb_phy_roothub_resume); |
368 | |