File Index
Files and directories
Sign in
FILE

phy.c

File preview with syntax highlighting.

linux-6.19.14/drivers/usb/core/phy.c C
  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
 17struct usb_phy_roothub {
 18	struct phy		*phy;
 19	struct list_head	list;
 20};
 21
 22/* Allocate the roothub_entry by specific name of phy */
 23static 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, dev->of_node, name);
 30	if (IS_ERR(phy))
 31		return PTR_ERR(phy);
 32
 33	roothub_entry = devm_kzalloc(dev, sizeof(*roothub_entry), GFP_KERNEL);
 34	if (!roothub_entry)
 35		return -ENOMEM;
 36
 37	INIT_LIST_HEAD(&roothub_entry->list);
 38
 39	roothub_entry->phy = phy;
 40
 41	list_add_tail(&roothub_entry->list, list);
 42
 43	return 0;
 44}
 45
 46static 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, dev->of_node, index);
 53	if (IS_ERR(phy)) {
 54		if (PTR_ERR(phy) == -ENODEV)
 55			return 0;
 56		else
 57			return PTR_ERR(phy);
 58	}
 59
 60	roothub_entry = devm_kzalloc(dev, sizeof(*roothub_entry), GFP_KERNEL);
 61	if (!roothub_entry)
 62		return -ENOMEM;
 63
 64	INIT_LIST_HEAD(&roothub_entry->list);
 65
 66	roothub_entry->phy = phy;
 67
 68	list_add_tail(&roothub_entry->list, list);
 69
 70	return 0;
 71}
 72
 73struct 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(dev->of_node, "phys",
 82					      "#phy-cells");
 83	if (num_phys <= 0)
 84		return NULL;
 85
 86	phy_roothub = devm_kzalloc(dev, sizeof(*phy_roothub), GFP_KERNEL);
 87	if (!phy_roothub)
 88		return ERR_PTR(-ENOMEM);
 89
 90	INIT_LIST_HEAD(&phy_roothub->list);
 91
 92	if (!usb_phy_roothub_add_phy_by_name(dev, "usb2-phy", &phy_roothub->list))
 93		return phy_roothub;
 94
 95	for (i = 0; i < num_phys; i++) {
 96		err = usb_phy_roothub_add_phy(dev, i, &phy_roothub->list);
 97		if (err)
 98			return ERR_PTR(err);
 99	}
100
101	return phy_roothub;
102}
103EXPORT_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 */
114struct usb_phy_roothub *usb_phy_roothub_alloc_usb3_phy(struct device *dev)
115{
116	struct usb_phy_roothub *phy_roothub;
117	int num_phys, usb2_phy_index;
118
119	if (!IS_ENABLED(CONFIG_GENERIC_PHY))
120		return NULL;
121
122	num_phys = of_count_phandle_with_args(dev->of_node, "phys",
123					      "#phy-cells");
124	if (num_phys <= 0)
125		return NULL;
126
127	/*
128	 * If 'usb2-phy' is not present, usb_phy_roothub_alloc() added
129	 * all PHYs to the primary HCD's phy_roothub already, so skip
130	 * adding 'usb3-phy' here to avoid double use of that.
131	 */
132	usb2_phy_index = of_property_match_string(dev->of_node, "phy-names",
133						  "usb2-phy");
134	if (usb2_phy_index < 0)
135		return NULL;
136
137	phy_roothub = devm_kzalloc(dev, sizeof(*phy_roothub), GFP_KERNEL);
138	if (!phy_roothub)
139		return ERR_PTR(-ENOMEM);
140
141	INIT_LIST_HEAD(&phy_roothub->list);
142
143	if (!usb_phy_roothub_add_phy_by_name(dev, "usb3-phy", &phy_roothub->list))
144		return phy_roothub;
145
146	return NULL;
147}
148EXPORT_SYMBOL_GPL(usb_phy_roothub_alloc_usb3_phy);
149
150int usb_phy_roothub_init(struct usb_phy_roothub *phy_roothub)
151{
152	struct usb_phy_roothub *roothub_entry;
153	struct list_head *head;
154	int err;
155
156	if (!phy_roothub)
157		return 0;
158
159	head = &phy_roothub->list;
160
161	list_for_each_entry(roothub_entry, head, list) {
162		err = phy_init(roothub_entry->phy);
163		if (err)
164			goto err_exit_phys;
165	}
166
167	return 0;
168
169err_exit_phys:
170	list_for_each_entry_continue_reverse(roothub_entry, head, list)
171		phy_exit(roothub_entry->phy);
172
173	return err;
174}
175EXPORT_SYMBOL_GPL(usb_phy_roothub_init);
176
177int usb_phy_roothub_exit(struct usb_phy_roothub *phy_roothub)
178{
179	struct usb_phy_roothub *roothub_entry;
180	struct list_head *head;
181	int err, ret = 0;
182
183	if (!phy_roothub)
184		return 0;
185
186	head = &phy_roothub->list;
187
188	list_for_each_entry(roothub_entry, head, list) {
189		err = phy_exit(roothub_entry->phy);
190		if (err)
191			ret = err;
192	}
193
194	return ret;
195}
196EXPORT_SYMBOL_GPL(usb_phy_roothub_exit);
197
198int usb_phy_roothub_set_mode(struct usb_phy_roothub *phy_roothub,
199			     enum phy_mode mode)
200{
201	struct usb_phy_roothub *roothub_entry;
202	struct list_head *head;
203	int err;
204
205	if (!phy_roothub)
206		return 0;
207
208	head = &phy_roothub->list;
209
210	list_for_each_entry(roothub_entry, head, list) {
211		err = phy_set_mode(roothub_entry->phy, mode);
212		if (err)
213			return err;
214	}
215
216	return 0;
217}
218EXPORT_SYMBOL_GPL(usb_phy_roothub_set_mode);
219
220int usb_phy_roothub_calibrate(struct usb_phy_roothub *phy_roothub)
221{
222	struct usb_phy_roothub *roothub_entry;
223	struct list_head *head;
224	int err;
225
226	if (!phy_roothub)
227		return 0;
228
229	head = &phy_roothub->list;
230
231	list_for_each_entry(roothub_entry, head, list) {
232		err = phy_calibrate(roothub_entry->phy);
233		if (err)
234			return err;
235	}
236
237	return 0;
238}
239EXPORT_SYMBOL_GPL(usb_phy_roothub_calibrate);
240
241/**
242 * usb_phy_roothub_notify_connect() - connect notification
243 * @phy_roothub: the phy of roothub, if the host use a generic phy.
244 * @port: the port index for connect
245 *
246 * If the phy needs to get connection status, the callback can be used.
247 * Returns: %0 if successful, a negative error code otherwise
248 */
249int usb_phy_roothub_notify_connect(struct usb_phy_roothub *phy_roothub, int port)
250{
251	struct usb_phy_roothub *roothub_entry;
252	struct list_head *head;
253	int err;
254
255	if (!phy_roothub)
256		return 0;
257
258	head = &phy_roothub->list;
259
260	list_for_each_entry(roothub_entry, head, list) {
261		err = phy_notify_connect(roothub_entry->phy, port);
262		if (err)
263			return err;
264	}
265
266	return 0;
267}
268EXPORT_SYMBOL_GPL(usb_phy_roothub_notify_connect);
269
270/**
271 * usb_phy_roothub_notify_disconnect() - disconnect notification
272 * @phy_roothub: the phy of roothub, if the host use a generic phy.
273 * @port: the port index for disconnect
274 *
275 * If the phy needs to get connection status, the callback can be used.
276 * Returns: %0 if successful, a negative error code otherwise
277 */
278int usb_phy_roothub_notify_disconnect(struct usb_phy_roothub *phy_roothub, int port)
279{
280	struct usb_phy_roothub *roothub_entry;
281	struct list_head *head;
282	int err;
283
284	if (!phy_roothub)
285		return 0;
286
287	head = &phy_roothub->list;
288
289	list_for_each_entry(roothub_entry, head, list) {
290		err = phy_notify_disconnect(roothub_entry->phy, port);
291		if (err)
292			return err;
293	}
294
295	return 0;
296}
297EXPORT_SYMBOL_GPL(usb_phy_roothub_notify_disconnect);
298
299int usb_phy_roothub_power_on(struct usb_phy_roothub *phy_roothub)
300{
301	struct usb_phy_roothub *roothub_entry;
302	struct list_head *head;
303	int err;
304
305	if (!phy_roothub)
306		return 0;
307
308	head = &phy_roothub->list;
309
310	list_for_each_entry(roothub_entry, head, list) {
311		err = phy_power_on(roothub_entry->phy);
312		if (err)
313			goto err_out;
314	}
315
316	return 0;
317
318err_out:
319	list_for_each_entry_continue_reverse(roothub_entry, head, list)
320		phy_power_off(roothub_entry->phy);
321
322	return err;
323}
324EXPORT_SYMBOL_GPL(usb_phy_roothub_power_on);
325
326void usb_phy_roothub_power_off(struct usb_phy_roothub *phy_roothub)
327{
328	struct usb_phy_roothub *roothub_entry;
329
330	if (!phy_roothub)
331		return;
332
333	list_for_each_entry_reverse(roothub_entry, &phy_roothub->list, list)
334		phy_power_off(roothub_entry->phy);
335}
336EXPORT_SYMBOL_GPL(usb_phy_roothub_power_off);
337
338int usb_phy_roothub_suspend(struct device *controller_dev,
339			    struct usb_phy_roothub *phy_roothub)
340{
341	usb_phy_roothub_power_off(phy_roothub);
342
343	/* keep the PHYs initialized so the device can wake up the system */
344	if (device_may_wakeup(controller_dev))
345		return 0;
346
347	return usb_phy_roothub_exit(phy_roothub);
348}
349EXPORT_SYMBOL_GPL(usb_phy_roothub_suspend);
350
351int usb_phy_roothub_resume(struct device *controller_dev,
352			   struct usb_phy_roothub *phy_roothub)
353{
354	int err;
355
356	/* if the device can't wake up the system _exit was called */
357	if (!device_may_wakeup(controller_dev)) {
358		err = usb_phy_roothub_init(phy_roothub);
359		if (err)
360			return err;
361	}
362
363	err = usb_phy_roothub_power_on(phy_roothub);
364
365	/* undo _init if _power_on failed */
366	if (err && !device_may_wakeup(controller_dev))
367		usb_phy_roothub_exit(phy_roothub);
368
369	return err;
370}
371EXPORT_SYMBOL_GPL(usb_phy_roothub_resume);
~/ Read-only file preview.