Commit | Line | Data |
---|---|---|
fde0aa6c HK |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* | |
3 | * USB Role Switch Support | |
4 | * | |
5 | * Copyright (C) 2018 Intel Corporation | |
6 | * Author: Heikki Krogerus <heikki.krogerus@linux.intel.com> | |
7 | * Hans de Goede <hdegoede@redhat.com> | |
8 | */ | |
9 | ||
10 | #include <linux/usb/role.h> | |
ec69e953 | 11 | #include <linux/property.h> |
fde0aa6c HK |
12 | #include <linux/device.h> |
13 | #include <linux/module.h> | |
14 | #include <linux/mutex.h> | |
15 | #include <linux/slab.h> | |
16 | ||
17 | static struct class *role_class; | |
18 | ||
19 | struct usb_role_switch { | |
20 | struct device dev; | |
21 | struct mutex lock; /* device lock*/ | |
22 | enum usb_role role; | |
23 | ||
24 | /* From descriptor */ | |
25 | struct device *usb2_port; | |
26 | struct device *usb3_port; | |
27 | struct device *udc; | |
28 | usb_role_switch_set_t set; | |
29 | usb_role_switch_get_t get; | |
30 | bool allow_userspace_control; | |
31 | }; | |
32 | ||
33 | #define to_role_switch(d) container_of(d, struct usb_role_switch, dev) | |
34 | ||
35 | /** | |
36 | * usb_role_switch_set_role - Set USB role for a switch | |
37 | * @sw: USB role switch | |
38 | * @role: USB role to be switched to | |
39 | * | |
40 | * Set USB role @role for @sw. | |
41 | */ | |
42 | int usb_role_switch_set_role(struct usb_role_switch *sw, enum usb_role role) | |
43 | { | |
44 | int ret; | |
45 | ||
46 | if (IS_ERR_OR_NULL(sw)) | |
47 | return 0; | |
48 | ||
49 | mutex_lock(&sw->lock); | |
50 | ||
51 | ret = sw->set(sw->dev.parent, role); | |
52 | if (!ret) | |
53 | sw->role = role; | |
54 | ||
55 | mutex_unlock(&sw->lock); | |
56 | ||
57 | return ret; | |
58 | } | |
59 | EXPORT_SYMBOL_GPL(usb_role_switch_set_role); | |
60 | ||
61 | /** | |
62 | * usb_role_switch_get_role - Get the USB role for a switch | |
63 | * @sw: USB role switch | |
64 | * | |
65 | * Depending on the role-switch-driver this function returns either a cached | |
66 | * value of the last set role, or reads back the actual value from the hardware. | |
67 | */ | |
68 | enum usb_role usb_role_switch_get_role(struct usb_role_switch *sw) | |
69 | { | |
70 | enum usb_role role; | |
71 | ||
72 | if (IS_ERR_OR_NULL(sw)) | |
73 | return USB_ROLE_NONE; | |
74 | ||
75 | mutex_lock(&sw->lock); | |
76 | ||
77 | if (sw->get) | |
78 | role = sw->get(sw->dev.parent); | |
79 | else | |
80 | role = sw->role; | |
81 | ||
82 | mutex_unlock(&sw->lock); | |
83 | ||
84 | return role; | |
85 | } | |
86 | EXPORT_SYMBOL_GPL(usb_role_switch_get_role); | |
87 | ||
ec69e953 HK |
88 | static int switch_fwnode_match(struct device *dev, const void *fwnode) |
89 | { | |
90 | return dev_fwnode(dev) == fwnode; | |
91 | } | |
92 | ||
93 | static int switch_name_match(struct device *dev, const void *name) | |
fde0aa6c HK |
94 | { |
95 | return !strcmp((const char *)name, dev_name(dev)); | |
96 | } | |
97 | ||
98 | static void *usb_role_switch_match(struct device_connection *con, int ep, | |
99 | void *data) | |
100 | { | |
101 | struct device *dev; | |
102 | ||
ec69e953 | 103 | if (con->fwnode) { |
fde77779 | 104 | if (con->id && !fwnode_property_present(con->fwnode, con->id)) |
ec69e953 HK |
105 | return NULL; |
106 | ||
107 | dev = class_find_device(role_class, NULL, con->fwnode, | |
108 | switch_fwnode_match); | |
109 | } else { | |
110 | dev = class_find_device(role_class, NULL, con->endpoint[ep], | |
111 | switch_name_match); | |
112 | } | |
fde0aa6c HK |
113 | |
114 | return dev ? to_role_switch(dev) : ERR_PTR(-EPROBE_DEFER); | |
115 | } | |
116 | ||
117 | /** | |
118 | * usb_role_switch_get - Find USB role switch linked with the caller | |
119 | * @dev: The caller device | |
120 | * | |
121 | * Finds and returns role switch linked with @dev. The reference count for the | |
122 | * found switch is incremented. | |
123 | */ | |
124 | struct usb_role_switch *usb_role_switch_get(struct device *dev) | |
125 | { | |
5c54fcac HK |
126 | struct usb_role_switch *sw; |
127 | ||
128 | sw = device_connection_find_match(dev, "usb-role-switch", NULL, | |
129 | usb_role_switch_match); | |
130 | ||
131 | if (!IS_ERR_OR_NULL(sw)) | |
132 | WARN_ON(!try_module_get(sw->dev.parent->driver->owner)); | |
133 | ||
134 | return sw; | |
fde0aa6c HK |
135 | } |
136 | EXPORT_SYMBOL_GPL(usb_role_switch_get); | |
137 | ||
138 | /** | |
139 | * usb_role_switch_put - Release handle to a switch | |
140 | * @sw: USB Role Switch | |
141 | * | |
142 | * Decrement reference count for @sw. | |
143 | */ | |
144 | void usb_role_switch_put(struct usb_role_switch *sw) | |
145 | { | |
5c54fcac | 146 | if (!IS_ERR_OR_NULL(sw)) { |
fde0aa6c | 147 | put_device(&sw->dev); |
5c54fcac HK |
148 | module_put(sw->dev.parent->driver->owner); |
149 | } | |
fde0aa6c HK |
150 | } |
151 | EXPORT_SYMBOL_GPL(usb_role_switch_put); | |
152 | ||
153 | static umode_t | |
154 | usb_role_switch_is_visible(struct kobject *kobj, struct attribute *attr, int n) | |
155 | { | |
156 | struct device *dev = container_of(kobj, typeof(*dev), kobj); | |
157 | struct usb_role_switch *sw = to_role_switch(dev); | |
158 | ||
159 | if (sw->allow_userspace_control) | |
160 | return attr->mode; | |
161 | ||
162 | return 0; | |
163 | } | |
164 | ||
165 | static const char * const usb_roles[] = { | |
166 | [USB_ROLE_NONE] = "none", | |
167 | [USB_ROLE_HOST] = "host", | |
168 | [USB_ROLE_DEVICE] = "device", | |
169 | }; | |
170 | ||
171 | static ssize_t | |
172 | role_show(struct device *dev, struct device_attribute *attr, char *buf) | |
173 | { | |
174 | struct usb_role_switch *sw = to_role_switch(dev); | |
175 | enum usb_role role = usb_role_switch_get_role(sw); | |
176 | ||
177 | return sprintf(buf, "%s\n", usb_roles[role]); | |
178 | } | |
179 | ||
180 | static ssize_t role_store(struct device *dev, struct device_attribute *attr, | |
181 | const char *buf, size_t size) | |
182 | { | |
183 | struct usb_role_switch *sw = to_role_switch(dev); | |
184 | int ret; | |
185 | ||
186 | ret = sysfs_match_string(usb_roles, buf); | |
187 | if (ret < 0) { | |
188 | bool res; | |
189 | ||
190 | /* Extra check if the user wants to disable the switch */ | |
191 | ret = kstrtobool(buf, &res); | |
192 | if (ret || res) | |
193 | return -EINVAL; | |
194 | } | |
195 | ||
196 | ret = usb_role_switch_set_role(sw, ret); | |
197 | if (ret) | |
198 | return ret; | |
199 | ||
200 | return size; | |
201 | } | |
202 | static DEVICE_ATTR_RW(role); | |
203 | ||
204 | static struct attribute *usb_role_switch_attrs[] = { | |
205 | &dev_attr_role.attr, | |
206 | NULL, | |
207 | }; | |
208 | ||
209 | static const struct attribute_group usb_role_switch_group = { | |
210 | .is_visible = usb_role_switch_is_visible, | |
211 | .attrs = usb_role_switch_attrs, | |
212 | }; | |
213 | ||
214 | static const struct attribute_group *usb_role_switch_groups[] = { | |
215 | &usb_role_switch_group, | |
216 | NULL, | |
217 | }; | |
218 | ||
219 | static int | |
220 | usb_role_switch_uevent(struct device *dev, struct kobj_uevent_env *env) | |
221 | { | |
222 | int ret; | |
223 | ||
224 | ret = add_uevent_var(env, "USB_ROLE_SWITCH=%s", dev_name(dev)); | |
225 | if (ret) | |
226 | dev_err(dev, "failed to add uevent USB_ROLE_SWITCH\n"); | |
227 | ||
228 | return ret; | |
229 | } | |
230 | ||
231 | static void usb_role_switch_release(struct device *dev) | |
232 | { | |
233 | struct usb_role_switch *sw = to_role_switch(dev); | |
234 | ||
235 | kfree(sw); | |
236 | } | |
237 | ||
238 | static const struct device_type usb_role_dev_type = { | |
239 | .name = "usb_role_switch", | |
240 | .groups = usb_role_switch_groups, | |
241 | .uevent = usb_role_switch_uevent, | |
242 | .release = usb_role_switch_release, | |
243 | }; | |
244 | ||
245 | /** | |
246 | * usb_role_switch_register - Register USB Role Switch | |
247 | * @parent: Parent device for the switch | |
248 | * @desc: Description of the switch | |
249 | * | |
250 | * USB Role Switch is a device capable or choosing the role for USB connector. | |
251 | * On platforms where the USB controller is dual-role capable, the controller | |
252 | * driver will need to register the switch. On platforms where the USB host and | |
253 | * USB device controllers behind the connector are separate, there will be a | |
254 | * mux, and the driver for that mux will need to register the switch. | |
255 | * | |
256 | * Returns handle to a new role switch or ERR_PTR. The content of @desc is | |
257 | * copied. | |
258 | */ | |
259 | struct usb_role_switch * | |
260 | usb_role_switch_register(struct device *parent, | |
261 | const struct usb_role_switch_desc *desc) | |
262 | { | |
263 | struct usb_role_switch *sw; | |
264 | int ret; | |
265 | ||
266 | if (!desc || !desc->set) | |
267 | return ERR_PTR(-EINVAL); | |
268 | ||
269 | sw = kzalloc(sizeof(*sw), GFP_KERNEL); | |
270 | if (!sw) | |
271 | return ERR_PTR(-ENOMEM); | |
272 | ||
273 | mutex_init(&sw->lock); | |
274 | ||
275 | sw->allow_userspace_control = desc->allow_userspace_control; | |
276 | sw->usb2_port = desc->usb2_port; | |
277 | sw->usb3_port = desc->usb3_port; | |
278 | sw->udc = desc->udc; | |
279 | sw->set = desc->set; | |
280 | sw->get = desc->get; | |
281 | ||
282 | sw->dev.parent = parent; | |
ec69e953 | 283 | sw->dev.fwnode = desc->fwnode; |
fde0aa6c HK |
284 | sw->dev.class = role_class; |
285 | sw->dev.type = &usb_role_dev_type; | |
286 | dev_set_name(&sw->dev, "%s-role-switch", dev_name(parent)); | |
287 | ||
288 | ret = device_register(&sw->dev); | |
289 | if (ret) { | |
290 | put_device(&sw->dev); | |
291 | return ERR_PTR(ret); | |
292 | } | |
293 | ||
294 | /* TODO: Symlinks for the host port and the device controller. */ | |
295 | ||
296 | return sw; | |
297 | } | |
298 | EXPORT_SYMBOL_GPL(usb_role_switch_register); | |
299 | ||
300 | /** | |
301 | * usb_role_switch_unregister - Unregsiter USB Role Switch | |
302 | * @sw: USB Role Switch | |
303 | * | |
304 | * Unregister switch that was registered with usb_role_switch_register(). | |
305 | */ | |
306 | void usb_role_switch_unregister(struct usb_role_switch *sw) | |
307 | { | |
308 | if (!IS_ERR_OR_NULL(sw)) | |
309 | device_unregister(&sw->dev); | |
310 | } | |
311 | EXPORT_SYMBOL_GPL(usb_role_switch_unregister); | |
312 | ||
313 | static int __init usb_roles_init(void) | |
314 | { | |
315 | role_class = class_create(THIS_MODULE, "usb_role"); | |
316 | return PTR_ERR_OR_ZERO(role_class); | |
317 | } | |
318 | subsys_initcall(usb_roles_init); | |
319 | ||
320 | static void __exit usb_roles_exit(void) | |
321 | { | |
322 | class_destroy(role_class); | |
323 | } | |
324 | module_exit(usb_roles_exit); | |
325 | ||
326 | MODULE_AUTHOR("Heikki Krogerus <heikki.krogerus@linux.intel.com>"); | |
327 | MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>"); | |
328 | MODULE_LICENSE("GPL v2"); | |
329 | MODULE_DESCRIPTION("USB Role Class"); |