summaryrefslogtreecommitdiffstats
path: root/drivers/usb/common
diff options
context:
space:
mode:
authorHeikki Krogerus <heikki.krogerus@linux.intel.com>2018-03-20 08:57:04 -0400
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2018-03-22 08:40:10 -0400
commitfde0aa6c175a4d8aa19e82b86ae0f9278bc8563b (patch)
tree3fc526a1e5440059b25bbbac42deed590a7f3114 /drivers/usb/common
parentbdecb33af34f79cbfbb656661210f77c8b8b5b5f (diff)
usb: common: Small class for USB role switches
USB role switch is a device that can be used to choose the data role for USB connector. With dual-role capable USB controllers, the controller itself will be the switch, but on some platforms the USB host and device controllers are separate IPs and there is a mux between them and the connector. On those platforms the mux driver will need to register the switch. With USB Type-C connectors, the host-to-device relationship is negotiated over the Configuration Channel (CC). That means the USB Type-C drivers need to be in control of the role switch. The class provides a simple API for the USB Type-C drivers for the control. For other types of USB connectors (mainly microAB) the class provides user space control via sysfs attribute file that can be used to request role swapping from the switch. Reviewed-by: Hans de Goede <hdegoede@redhat.com> Reviewed-by: Andy Shevchenko <andy.shevchenko@gmail.com> Signed-off-by: Heikki Krogerus <heikki.krogerus@linux.intel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers/usb/common')
-rw-r--r--drivers/usb/common/Makefile1
-rw-r--r--drivers/usb/common/roles.c305
2 files changed, 306 insertions, 0 deletions
diff --git a/drivers/usb/common/Makefile b/drivers/usb/common/Makefile
index 0a7c45e85481..fb4d5ef4165c 100644
--- a/drivers/usb/common/Makefile
+++ b/drivers/usb/common/Makefile
@@ -9,3 +9,4 @@ usb-common-$(CONFIG_USB_LED_TRIG) += led.o
9 9
10obj-$(CONFIG_USB_OTG_FSM) += usb-otg-fsm.o 10obj-$(CONFIG_USB_OTG_FSM) += usb-otg-fsm.o
11obj-$(CONFIG_USB_ULPI_BUS) += ulpi.o 11obj-$(CONFIG_USB_ULPI_BUS) += ulpi.o
12obj-$(CONFIG_USB_ROLE_SWITCH) += roles.o
diff --git a/drivers/usb/common/roles.c b/drivers/usb/common/roles.c
new file mode 100644
index 000000000000..15cc76e22123
--- /dev/null
+++ b/drivers/usb/common/roles.c
@@ -0,0 +1,305 @@
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
16static struct class *role_class;
17
18struct 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 */
41int 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}
58EXPORT_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 */
67enum 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}
85EXPORT_SYMBOL_GPL(usb_role_switch_get_role);
86
87static int __switch_match(struct device *dev, const void *name)
88{
89 return !strcmp((const char *)name, dev_name(dev));
90}
91
92static 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 */
110struct usb_role_switch *usb_role_switch_get(struct device *dev)
111{
112 return device_connection_find_match(dev, "usb-role-switch", NULL,
113 usb_role_switch_match);
114}
115EXPORT_SYMBOL_GPL(usb_role_switch_get);
116
117/**
118 * usb_role_switch_put - Release handle to a switch
119 * @sw: USB Role Switch
120 *
121 * Decrement reference count for @sw.
122 */
123void usb_role_switch_put(struct usb_role_switch *sw)
124{
125 if (!IS_ERR_OR_NULL(sw))
126 put_device(&sw->dev);
127}
128EXPORT_SYMBOL_GPL(usb_role_switch_put);
129
130static umode_t
131usb_role_switch_is_visible(struct kobject *kobj, struct attribute *attr, int n)
132{
133 struct device *dev = container_of(kobj, typeof(*dev), kobj);
134 struct usb_role_switch *sw = to_role_switch(dev);
135
136 if (sw->allow_userspace_control)
137 return attr->mode;
138
139 return 0;
140}
141
142static const char * const usb_roles[] = {
143 [USB_ROLE_NONE] = "none",
144 [USB_ROLE_HOST] = "host",
145 [USB_ROLE_DEVICE] = "device",
146};
147
148static ssize_t
149role_show(struct device *dev, struct device_attribute *attr, char *buf)
150{
151 struct usb_role_switch *sw = to_role_switch(dev);
152 enum usb_role role = usb_role_switch_get_role(sw);
153
154 return sprintf(buf, "%s\n", usb_roles[role]);
155}
156
157static ssize_t role_store(struct device *dev, struct device_attribute *attr,
158 const char *buf, size_t size)
159{
160 struct usb_role_switch *sw = to_role_switch(dev);
161 int ret;
162
163 ret = sysfs_match_string(usb_roles, buf);
164 if (ret < 0) {
165 bool res;
166
167 /* Extra check if the user wants to disable the switch */
168 ret = kstrtobool(buf, &res);
169 if (ret || res)
170 return -EINVAL;
171 }
172
173 ret = usb_role_switch_set_role(sw, ret);
174 if (ret)
175 return ret;
176
177 return size;
178}
179static DEVICE_ATTR_RW(role);
180
181static struct attribute *usb_role_switch_attrs[] = {
182 &dev_attr_role.attr,
183 NULL,
184};
185
186static const struct attribute_group usb_role_switch_group = {
187 .is_visible = usb_role_switch_is_visible,
188 .attrs = usb_role_switch_attrs,
189};
190
191static const struct attribute_group *usb_role_switch_groups[] = {
192 &usb_role_switch_group,
193 NULL,
194};
195
196static int
197usb_role_switch_uevent(struct device *dev, struct kobj_uevent_env *env)
198{
199 int ret;
200
201 ret = add_uevent_var(env, "USB_ROLE_SWITCH=%s", dev_name(dev));
202 if (ret)
203 dev_err(dev, "failed to add uevent USB_ROLE_SWITCH\n");
204
205 return ret;
206}
207
208static void usb_role_switch_release(struct device *dev)
209{
210 struct usb_role_switch *sw = to_role_switch(dev);
211
212 kfree(sw);
213}
214
215static const struct device_type usb_role_dev_type = {
216 .name = "usb_role_switch",
217 .groups = usb_role_switch_groups,
218 .uevent = usb_role_switch_uevent,
219 .release = usb_role_switch_release,
220};
221
222/**
223 * usb_role_switch_register - Register USB Role Switch
224 * @parent: Parent device for the switch
225 * @desc: Description of the switch
226 *
227 * USB Role Switch is a device capable or choosing the role for USB connector.
228 * On platforms where the USB controller is dual-role capable, the controller
229 * driver will need to register the switch. On platforms where the USB host and
230 * USB device controllers behind the connector are separate, there will be a
231 * mux, and the driver for that mux will need to register the switch.
232 *
233 * Returns handle to a new role switch or ERR_PTR. The content of @desc is
234 * copied.
235 */
236struct usb_role_switch *
237usb_role_switch_register(struct device *parent,
238 const struct usb_role_switch_desc *desc)
239{
240 struct usb_role_switch *sw;
241 int ret;
242
243 if (!desc || !desc->set)
244 return ERR_PTR(-EINVAL);
245
246 sw = kzalloc(sizeof(*sw), GFP_KERNEL);
247 if (!sw)
248 return ERR_PTR(-ENOMEM);
249
250 mutex_init(&sw->lock);
251
252 sw->allow_userspace_control = desc->allow_userspace_control;
253 sw->usb2_port = desc->usb2_port;
254 sw->usb3_port = desc->usb3_port;
255 sw->udc = desc->udc;
256 sw->set = desc->set;
257 sw->get = desc->get;
258
259 sw->dev.parent = parent;
260 sw->dev.class = role_class;
261 sw->dev.type = &usb_role_dev_type;
262 dev_set_name(&sw->dev, "%s-role-switch", dev_name(parent));
263
264 ret = device_register(&sw->dev);
265 if (ret) {
266 put_device(&sw->dev);
267 return ERR_PTR(ret);
268 }
269
270 /* TODO: Symlinks for the host port and the device controller. */
271
272 return sw;
273}
274EXPORT_SYMBOL_GPL(usb_role_switch_register);
275
276/**
277 * usb_role_switch_unregister - Unregsiter USB Role Switch
278 * @sw: USB Role Switch
279 *
280 * Unregister switch that was registered with usb_role_switch_register().
281 */
282void usb_role_switch_unregister(struct usb_role_switch *sw)
283{
284 if (!IS_ERR_OR_NULL(sw))
285 device_unregister(&sw->dev);
286}
287EXPORT_SYMBOL_GPL(usb_role_switch_unregister);
288
289static int __init usb_roles_init(void)
290{
291 role_class = class_create(THIS_MODULE, "usb_role");
292 return PTR_ERR_OR_ZERO(role_class);
293}
294subsys_initcall(usb_roles_init);
295
296static void __exit usb_roles_exit(void)
297{
298 class_destroy(role_class);
299}
300module_exit(usb_roles_exit);
301
302MODULE_AUTHOR("Heikki Krogerus <heikki.krogerus@linux.intel.com>");
303MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
304MODULE_LICENSE("GPL v2");
305MODULE_DESCRIPTION("USB Role Class");