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