469 lines · 11.1 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0/* * USB Role Switch Support * * Copyright (C) 2018 Intel Corporation * Author: Heikki Krogerus <heikki.krogerus@linux.intel.com> *         Hans de Goede <hdegoede@redhat.com> */ #include <linux/component.h>#include <linux/usb/role.h>#include <linux/property.h>#include <linux/device.h>#include <linux/lockdep.h>#include <linux/module.h>#include <linux/mutex.h>#include <linux/slab.h> static const struct class role_class = {	.name = "usb_role",}; struct usb_role_switch {	struct device dev;	struct lock_class_key key;	struct mutex lock; /* device lock*/	struct module *module; /* the module this device depends on */	enum usb_role role;	bool registered; 	/* From descriptor */	struct device *usb2_port;	struct device *usb3_port;	struct device *udc;	usb_role_switch_set_t set;	usb_role_switch_get_t get;	bool allow_userspace_control;}; #define to_role_switch(d)	container_of(d, struct usb_role_switch, dev) static int connector_bind(struct device *dev, struct device *connector, void *data){	int ret; 	ret = sysfs_create_link(&dev->kobj, &connector->kobj, "connector");	if (ret)		return ret; 	ret = sysfs_create_link(&connector->kobj, &dev->kobj, "usb-role-switch");	if (ret)		sysfs_remove_link(&dev->kobj, "connector"); 	return ret;} static void connector_unbind(struct device *dev, struct device *connector, void *data){	sysfs_remove_link(&connector->kobj, "usb-role-switch");	sysfs_remove_link(&dev->kobj, "connector");} static const struct component_ops connector_ops = {	.bind = connector_bind,	.unbind = connector_unbind,}; /** * usb_role_switch_set_role - Set USB role for a switch * @sw: USB role switch * @role: USB role to be switched to * * Set USB role @role for @sw. */int usb_role_switch_set_role(struct usb_role_switch *sw, enum usb_role role){	int ret; 	if (IS_ERR_OR_NULL(sw))		return 0; 	if (!sw->registered)		return -EOPNOTSUPP; 	mutex_lock(&sw->lock); 	ret = sw->set(sw, role);	if (!ret) {		sw->role = role;		kobject_uevent(&sw->dev.kobj, KOBJ_CHANGE);	} 	mutex_unlock(&sw->lock); 	return ret;}EXPORT_SYMBOL_GPL(usb_role_switch_set_role); /** * usb_role_switch_get_role - Get the USB role for a switch * @sw: USB role switch * * Depending on the role-switch-driver this function returns either a cached * value of the last set role, or reads back the actual value from the hardware. */enum usb_role usb_role_switch_get_role(struct usb_role_switch *sw){	enum usb_role role; 	if (IS_ERR_OR_NULL(sw) || !sw->registered)		return USB_ROLE_NONE; 	mutex_lock(&sw->lock); 	if (sw->get)		role = sw->get(sw);	else		role = sw->role; 	mutex_unlock(&sw->lock); 	return role;}EXPORT_SYMBOL_GPL(usb_role_switch_get_role); static void *usb_role_switch_match(const struct fwnode_handle *fwnode, const char *id,				   void *data){	struct device *dev; 	if (id && !fwnode_property_present(fwnode, id))		return NULL; 	dev = class_find_device_by_fwnode(&role_class, fwnode); 	return dev ? to_role_switch(dev) : ERR_PTR(-EPROBE_DEFER);} static struct usb_role_switch *usb_role_switch_is_parent(struct fwnode_handle *fwnode){	struct fwnode_handle *parent;	struct device *dev; 	if (!fwnode_device_is_compatible(fwnode, "usb-b-connector"))		return NULL; 	parent = fwnode_get_parent(fwnode); 	if (!fwnode_property_present(parent, "usb-role-switch")) {		fwnode_handle_put(parent);		return NULL;	} 	dev = class_find_device_by_fwnode(&role_class, parent);	fwnode_handle_put(parent);	return dev ? to_role_switch(dev) : ERR_PTR(-EPROBE_DEFER);} /** * usb_role_switch_get - Find USB role switch linked with the caller * @dev: The caller device * * Finds and returns role switch linked with @dev. The reference count for the * found switch is incremented. */struct usb_role_switch *usb_role_switch_get(struct device *dev){	struct usb_role_switch *sw; 	sw = usb_role_switch_is_parent(dev_fwnode(dev));	if (!sw)		sw = device_connection_find_match(dev, "usb-role-switch", NULL,						  usb_role_switch_match); 	if (!IS_ERR_OR_NULL(sw))		WARN_ON(!try_module_get(sw->module)); 	return sw;}EXPORT_SYMBOL_GPL(usb_role_switch_get); /** * fwnode_usb_role_switch_get - Find USB role switch linked with the caller * @fwnode: The caller device node * * This is similar to the usb_role_switch_get() function above, but it searches * the switch using fwnode instead of device entry. */struct usb_role_switch *fwnode_usb_role_switch_get(struct fwnode_handle *fwnode){	struct usb_role_switch *sw; 	sw = usb_role_switch_is_parent(fwnode);	if (!sw)		sw = fwnode_connection_find_match(fwnode, "usb-role-switch",						  NULL, usb_role_switch_match);	if (!IS_ERR_OR_NULL(sw))		WARN_ON(!try_module_get(sw->module)); 	return sw;}EXPORT_SYMBOL_GPL(fwnode_usb_role_switch_get); /** * usb_role_switch_put - Release handle to a switch * @sw: USB Role Switch * * Decrement reference count for @sw. */void usb_role_switch_put(struct usb_role_switch *sw){	if (!IS_ERR_OR_NULL(sw)) {		module_put(sw->module);		put_device(&sw->dev);	}}EXPORT_SYMBOL_GPL(usb_role_switch_put); /** * usb_role_switch_find_by_fwnode - Find USB role switch with its fwnode * @fwnode: fwnode of the USB Role Switch * * Finds and returns role switch with @fwnode. The reference count for the * found switch is incremented. */struct usb_role_switch *usb_role_switch_find_by_fwnode(const struct fwnode_handle *fwnode){	struct device *dev;	struct usb_role_switch *sw = NULL; 	if (!fwnode)		return NULL; 	dev = class_find_device_by_fwnode(&role_class, fwnode);	if (dev) {		sw = to_role_switch(dev);		WARN_ON(!try_module_get(sw->module));	} 	return sw;}EXPORT_SYMBOL_GPL(usb_role_switch_find_by_fwnode); static umode_tusb_role_switch_is_visible(struct kobject *kobj, struct attribute *attr, int n){	struct device *dev = kobj_to_dev(kobj);	struct usb_role_switch *sw = to_role_switch(dev); 	if (sw->allow_userspace_control)		return attr->mode; 	return 0;} static const char * const usb_roles[] = {	[USB_ROLE_NONE]		= "none",	[USB_ROLE_HOST]		= "host",	[USB_ROLE_DEVICE]	= "device",}; const char *usb_role_string(enum usb_role role){	if (role < 0 || role >= ARRAY_SIZE(usb_roles))		return "unknown"; 	return usb_roles[role];}EXPORT_SYMBOL_GPL(usb_role_string); static ssize_trole_show(struct device *dev, struct device_attribute *attr, char *buf){	struct usb_role_switch *sw = to_role_switch(dev);	enum usb_role role = usb_role_switch_get_role(sw); 	return sprintf(buf, "%s\n", usb_roles[role]);} static ssize_t role_store(struct device *dev, struct device_attribute *attr,			  const char *buf, size_t size){	struct usb_role_switch *sw = to_role_switch(dev);	int ret; 	ret = sysfs_match_string(usb_roles, buf);	if (ret < 0) {		bool res; 		/* Extra check if the user wants to disable the switch */		ret = kstrtobool(buf, &res);		if (ret || res)			return -EINVAL;	} 	ret = usb_role_switch_set_role(sw, ret);	if (ret)		return ret; 	return size;}static DEVICE_ATTR_RW(role); static struct attribute *usb_role_switch_attrs[] = {	&dev_attr_role.attr,	NULL,}; static const struct attribute_group usb_role_switch_group = {	.is_visible = usb_role_switch_is_visible,	.attrs = usb_role_switch_attrs,}; static const struct attribute_group *usb_role_switch_groups[] = {	&usb_role_switch_group,	NULL,}; static int usb_role_switch_uevent(const struct device *dev, struct kobj_uevent_env *env){	int ret; 	ret = add_uevent_var(env, "USB_ROLE_SWITCH=%s", dev_name(dev));	if (ret)		dev_err(dev, "failed to add uevent USB_ROLE_SWITCH\n"); 	return ret;} static void usb_role_switch_release(struct device *dev){	struct usb_role_switch *sw = to_role_switch(dev); 	mutex_destroy(&sw->lock);	lockdep_unregister_key(&sw->key);	kfree(sw);} static const struct device_type usb_role_dev_type = {	.name = "usb_role_switch",	.groups = usb_role_switch_groups,	.uevent = usb_role_switch_uevent,	.release = usb_role_switch_release,}; /** * usb_role_switch_register - Register USB Role Switch * @parent: Parent device for the switch * @desc: Description of the switch * * USB Role Switch is a device capable or choosing the role for USB connector. * On platforms where the USB controller is dual-role capable, the controller * driver will need to register the switch. On platforms where the USB host and * USB device controllers behind the connector are separate, there will be a * mux, and the driver for that mux will need to register the switch. * * Returns handle to a new role switch or ERR_PTR. The content of @desc is * copied. */struct usb_role_switch *usb_role_switch_register(struct device *parent,			 const struct usb_role_switch_desc *desc){	struct usb_role_switch *sw;	int ret; 	if (!desc || !desc->set)		return ERR_PTR(-EINVAL); 	sw = kzalloc_obj(*sw);	if (!sw)		return ERR_PTR(-ENOMEM); 	lockdep_register_key(&sw->key);	mutex_init_with_key(&sw->lock, &sw->key); 	sw->allow_userspace_control = desc->allow_userspace_control;	sw->usb2_port = desc->usb2_port;	sw->usb3_port = desc->usb3_port;	sw->udc = desc->udc;	sw->set = desc->set;	sw->get = desc->get; 	sw->module = parent->driver->owner;	sw->dev.parent = parent;	sw->dev.fwnode = desc->fwnode;	sw->dev.class = &role_class;	sw->dev.type = &usb_role_dev_type;	dev_set_drvdata(&sw->dev, desc->driver_data);	dev_set_name(&sw->dev, "%s-role-switch",		     desc->name ? desc->name : dev_name(parent)); 	sw->registered = true; 	ret = device_register(&sw->dev);	if (ret) {		sw->registered = false;		put_device(&sw->dev);		return ERR_PTR(ret);	} 	if (dev_fwnode(&sw->dev)) {		ret = component_add(&sw->dev, &connector_ops);		if (ret)			dev_warn(&sw->dev, "failed to add component\n");	} 	/* TODO: Symlinks for the host port and the device controller. */ 	return sw;}EXPORT_SYMBOL_GPL(usb_role_switch_register); /** * usb_role_switch_unregister - Unregsiter USB Role Switch * @sw: USB Role Switch * * Unregister switch that was registered with usb_role_switch_register(). */void usb_role_switch_unregister(struct usb_role_switch *sw){	if (IS_ERR_OR_NULL(sw))		return;	sw->registered = false;	if (dev_fwnode(&sw->dev))		component_del(&sw->dev, &connector_ops);	device_unregister(&sw->dev);}EXPORT_SYMBOL_GPL(usb_role_switch_unregister); /** * usb_role_switch_set_drvdata - Assign private data pointer to a switch * @sw: USB Role Switch * @data: Private data pointer */void usb_role_switch_set_drvdata(struct usb_role_switch *sw, void *data){	dev_set_drvdata(&sw->dev, data);}EXPORT_SYMBOL_GPL(usb_role_switch_set_drvdata); /** * usb_role_switch_get_drvdata - Get the private data pointer of a switch * @sw: USB Role Switch */void *usb_role_switch_get_drvdata(struct usb_role_switch *sw){	return dev_get_drvdata(&sw->dev);}EXPORT_SYMBOL_GPL(usb_role_switch_get_drvdata); static int __init usb_roles_init(void){	return class_register(&role_class);}subsys_initcall(usb_roles_init); static void __exit usb_roles_exit(void){	class_unregister(&role_class);}module_exit(usb_roles_exit); MODULE_AUTHOR("Heikki Krogerus <heikki.krogerus@linux.intel.com>");MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");MODULE_LICENSE("GPL v2");MODULE_DESCRIPTION("USB Role Class");