xref: /openbmc/linux/drivers/usb/core/phy.c (revision 2e554390)
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 static struct usb_phy_roothub *usb_phy_roothub_alloc(struct device *dev)
23 {
24 	struct usb_phy_roothub *roothub_entry;
25 
26 	roothub_entry = devm_kzalloc(dev, sizeof(*roothub_entry), GFP_KERNEL);
27 	if (!roothub_entry)
28 		return ERR_PTR(-ENOMEM);
29 
30 	INIT_LIST_HEAD(&roothub_entry->list);
31 
32 	return roothub_entry;
33 }
34 
35 static int usb_phy_roothub_add_phy(struct device *dev, int index,
36 				   struct list_head *list)
37 {
38 	struct usb_phy_roothub *roothub_entry;
39 	struct phy *phy = devm_of_phy_get_by_index(dev, dev->of_node, index);
40 
41 	if (IS_ERR_OR_NULL(phy)) {
42 		if (!phy || PTR_ERR(phy) == -ENODEV)
43 			return 0;
44 		else
45 			return PTR_ERR(phy);
46 	}
47 
48 	roothub_entry = usb_phy_roothub_alloc(dev);
49 	if (IS_ERR(roothub_entry))
50 		return PTR_ERR(roothub_entry);
51 
52 	roothub_entry->phy = phy;
53 
54 	list_add_tail(&roothub_entry->list, list);
55 
56 	return 0;
57 }
58 
59 struct usb_phy_roothub *usb_phy_roothub_init(struct device *dev)
60 {
61 	struct usb_phy_roothub *phy_roothub;
62 	struct usb_phy_roothub *roothub_entry;
63 	struct list_head *head;
64 	int i, num_phys, err;
65 
66 	num_phys = of_count_phandle_with_args(dev->of_node, "phys",
67 					      "#phy-cells");
68 	if (num_phys <= 0)
69 		return NULL;
70 
71 	phy_roothub = usb_phy_roothub_alloc(dev);
72 	if (IS_ERR(phy_roothub))
73 		return phy_roothub;
74 
75 	for (i = 0; i < num_phys; i++) {
76 		err = usb_phy_roothub_add_phy(dev, i, &phy_roothub->list);
77 		if (err)
78 			goto err_out;
79 	}
80 
81 	head = &phy_roothub->list;
82 
83 	list_for_each_entry(roothub_entry, head, list) {
84 		err = phy_init(roothub_entry->phy);
85 		if (err)
86 			goto err_exit_phys;
87 	}
88 
89 	return phy_roothub;
90 
91 err_exit_phys:
92 	list_for_each_entry_continue_reverse(roothub_entry, head, list)
93 		phy_exit(roothub_entry->phy);
94 
95 err_out:
96 	return ERR_PTR(err);
97 }
98 EXPORT_SYMBOL_GPL(usb_phy_roothub_init);
99 
100 int usb_phy_roothub_exit(struct usb_phy_roothub *phy_roothub)
101 {
102 	struct usb_phy_roothub *roothub_entry;
103 	struct list_head *head;
104 	int err, ret = 0;
105 
106 	if (!phy_roothub)
107 		return 0;
108 
109 	head = &phy_roothub->list;
110 
111 	list_for_each_entry(roothub_entry, head, list) {
112 		err = phy_exit(roothub_entry->phy);
113 		if (err)
114 			ret = ret;
115 	}
116 
117 	return ret;
118 }
119 EXPORT_SYMBOL_GPL(usb_phy_roothub_exit);
120 
121 int usb_phy_roothub_power_on(struct usb_phy_roothub *phy_roothub)
122 {
123 	struct usb_phy_roothub *roothub_entry;
124 	struct list_head *head;
125 	int err;
126 
127 	if (!phy_roothub)
128 		return 0;
129 
130 	head = &phy_roothub->list;
131 
132 	list_for_each_entry(roothub_entry, head, list) {
133 		err = phy_power_on(roothub_entry->phy);
134 		if (err)
135 			goto err_out;
136 	}
137 
138 	return 0;
139 
140 err_out:
141 	list_for_each_entry_continue_reverse(roothub_entry, head, list)
142 		phy_power_off(roothub_entry->phy);
143 
144 	return err;
145 }
146 EXPORT_SYMBOL_GPL(usb_phy_roothub_power_on);
147 
148 void usb_phy_roothub_power_off(struct usb_phy_roothub *phy_roothub)
149 {
150 	struct usb_phy_roothub *roothub_entry;
151 
152 	if (!phy_roothub)
153 		return;
154 
155 	list_for_each_entry_reverse(roothub_entry, &phy_roothub->list, list)
156 		phy_power_off(roothub_entry->phy);
157 }
158 EXPORT_SYMBOL_GPL(usb_phy_roothub_power_off);
159