this class will be used to abstract away several of the duplicated operations scatered among the USB gadget controller drivers. Later, we can add an atomic notifer to tell interested drivers about what's happening with the controller. Notifications such as suspend, resume, enumerated, etc will be useful, at a minimum, for implementing usb charger detection. Signed-off-by: Sebastian Andrzej Siewior <bigeasy@xxxxxxxxxxxxx> Signed-off-by: Felipe Balbi <balbi@xxxxxx> --- drivers/Makefile | 2 +- drivers/usb/gadget/Kconfig | 6 + drivers/usb/gadget/Makefile | 1 + drivers/usb/gadget/udc-core.c | 461 +++++++++++++++++++++++++++++++++++++++++ include/linux/usb/gadget.h | 13 +- 5 files changed, 477 insertions(+), 6 deletions(-) create mode 100644 drivers/usb/gadget/udc-core.c diff --git a/drivers/Makefile b/drivers/Makefile index 3f135b6..fa964cb 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -68,7 +68,7 @@ obj-$(CONFIG_USB_OTG_UTILS) += usb/otg/ obj-$(CONFIG_USB) += usb/ obj-$(CONFIG_USB_MUSB_HDRC) += usb/musb/ obj-$(CONFIG_PCI) += usb/ -obj-$(CONFIG_USB_GADGET) += usb/gadget/ +obj-y += usb/gadget/ obj-$(CONFIG_SERIO) += input/serio/ obj-$(CONFIG_GAMEPORT) += input/gameport/ obj-$(CONFIG_INPUT) += input/ diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig index bc5123c..6078cc6 100644 --- a/drivers/usb/gadget/Kconfig +++ b/drivers/usb/gadget/Kconfig @@ -43,6 +43,12 @@ menuconfig USB_GADGET if USB_GADGET +config USB_UDC_CORE + bool "USB UDC Core Support" + help + This is the new style UDC Core Layer. If your driver has been + converted to this new layer, then you should say Y or M here. + config USB_GADGET_DEBUG boolean "Debugging messages (DEVELOPMENT)" depends on DEBUG_KERNEL diff --git a/drivers/usb/gadget/Makefile b/drivers/usb/gadget/Makefile index 1ea15ee..74e8d9f 100644 --- a/drivers/usb/gadget/Makefile +++ b/drivers/usb/gadget/Makefile @@ -3,6 +3,7 @@ # ccflags-$(CONFIG_USB_GADGET_DEBUG) := -DDEBUG +obj-$(CONFIG_USB_UDC_CORE) += udc-core.o obj-$(CONFIG_USB_DUMMY_HCD) += dummy_hcd.o obj-$(CONFIG_USB_NET2280) += net2280.o obj-$(CONFIG_USB_AMD5536UDC) += amd5536udc.o diff --git a/drivers/usb/gadget/udc-core.c b/drivers/usb/gadget/udc-core.c new file mode 100644 index 0000000..dedf3adb --- /dev/null +++ b/drivers/usb/gadget/udc-core.c @@ -0,0 +1,461 @@ +/** + * udc.c - Core UDC Framework + * + * Copyright (C) 2010 Texas Instruments + * Author: Felipe Balbi <balbi@xxxxxx> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 of + * the License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/list.h> +#include <linux/err.h> + +#include <linux/usb/ch9.h> +#include <linux/usb/gadget.h> + +/** + * struct usb_udc - describes one usb device controller + * @driver - the gadget driver pointer. For use by the class code + * @dev - the child device to the actual controller + * @gadget - the gadget. For use by the class code + * @list - for use by the udc class driver + * @name - a human friendly name representation + * + * This represents the internal data structure which is used by the UDC-class + * to hold information about udc driver and gadget together. + */ +struct usb_udc { + struct usb_gadget_driver *driver; + struct usb_gadget *gadget; + struct device dev; + struct list_head list; + char *name; +}; + +static struct class *udc_class; +static struct device_type udc_device_type; +static LIST_HEAD(udc_list); +static spinlock_t udc_lock; + +/* ------------------------------------------------------------------------- */ + +/** + * usb_gadget_start - tells usb device controller to start up + * @gadget: The device we want to get started + * + * This call is issued by the UDC Class driver when it's about + * to register a gadget driver to the device controller, before + * calling gadget driver's bind() method. + * + * It allows the controller to be powered off until extrictly + * necessary to have it powered on. + * + * Returns zero on success, else negative errno. + */ +static inline int usb_gadget_start(struct usb_gadget *gadget, + struct usb_gadget_driver *drv) +{ + return gadget->ops->start(gadget, drv); +} + +/** + * usb_gadget_stop - tells usb device controller we don't need it anymore + * @gadget: The device we want to stop activity + * + * This call is issued by the UDC Class driver after calling + * gadget driver's unbind() method. + * + * The details are implementation specific, but it can go as + * far as powering off UDC completely and disable its data + * line pullups. + */ +static inline void usb_gadget_stop(struct usb_gadget *gadget, + struct usb_gadget_driver *driver) +{ + gadget->ops->stop(gadget, driver); +} + +/* ------------------------------------------------------------------------- */ + +static void usb_udc_release(struct device *dev) +{ + struct usb_udc *udc; + + udc = container_of(dev, struct usb_udc, dev); + dev_dbg(dev, "releasing '%s'\n", dev_name(dev)); + kfree(udc); +} + +/** + * usb_add_gadget - adds a new gadget to the udc class driver list + * @parent: the parent device to this udc. Usually the controller + * driver's device. + * @gadget: the gadget to be added to the list + * + * Returns zero on success, negative errno otherwise. + */ +int usb_add_gadget(struct device *parent, struct usb_gadget *gadget) +{ + struct usb_udc *udc; + unsigned long flags; + int ret = -ENOMEM; + + udc = kzalloc(sizeof(*udc), GFP_KERNEL); + if (!udc) + goto err1; + + device_initialize(&udc->dev); + udc->dev.release = usb_udc_release; + udc->dev.class = udc_class; + udc->dev.parent = parent; + ret = dev_set_name(&udc->dev, "%s", kobject_name(&parent->kobj)); + if (ret) + goto err2; + + udc->gadget = gadget; + + spin_lock_irqsave(&udc_lock, flags); + list_add_tail(&udc->list, &udc_list); + spin_unlock_irqrestore(&udc_lock, flags); + + ret = device_add(&udc->dev); + if (ret) + goto err2; + + return 0; + +err2: + put_device(&udc->dev); + +err1: + return ret; +} +EXPORT_SYMBOL_GPL(usb_add_gadget); + +/** + * usb_del_gadget - deletes @udc from udc_list + * @udc: the udc to be removed. + * + * This, will call usb_gadget_unregister_driver() if + * the @udc is still busy. + */ +void usb_del_gadget(struct usb_gadget *gadget) +{ + struct usb_udc *udc = NULL; + unsigned long flags; + + dev_vdbg(gadget->dev.parent, "unregistering gadget\n"); + + spin_lock_irqsave(&udc_lock, flags); + list_for_each_entry(udc, &udc_list, list) { + if (udc->gadget == gadget) + break; + } + + if (!udc) + return; + if (udc->gadget != gadget) { + dev_err(gadget->dev.parent, "gadget not registered.\n"); + return; + } + + list_del(&udc->list); + + if (udc->driver) { + spin_unlock_irqrestore(&udc_lock, flags); + usb_gadget_unregister_driver(udc->driver); + spin_lock_irqsave(&udc_lock, flags); + } + + device_unregister(&gadget->dev); + spin_unlock_irqrestore(&udc_lock, flags); +} +EXPORT_SYMBOL_GPL(usb_del_gadget); + +/* ------------------------------------------------------------------------- */ + +int usb_gadget_register_driver(struct usb_gadget_driver *driver) +{ + struct usb_udc *udc = NULL; + unsigned long flags; + int ret; + + if (!driver || !driver->bind || !driver->setup) + return -EINVAL; + + spin_lock_irqsave(&udc_lock, flags); + list_for_each_entry(udc, &udc_list, list) { + /* For now we take the first one */ + if (!udc->driver) { + pr_debug("using UDC '%s'\n", udc->name); + break; + } + udc = NULL; + } + + if (!udc) { + pr_debug("couldn't find an available UDC\n"); + ret = -ENODEV; + goto err0; + } + + dev_dbg(&udc->dev, "registering UDC driver [%s]\n", + driver->function); + + udc->driver = driver; + udc->dev.driver = &driver->driver; + + spin_unlock_irqrestore(&udc_lock, flags); + + ret = usb_gadget_start(udc->gadget, driver); + if (ret) { + dev_err(&udc->dev, "failed to start %s --> %d\n", + udc->name, ret); + goto err1; + } + +#if 0 + /* + * in long term we try to move same/simmilar steps from every gadget's + * ->start() callback into here. ->start() should only change some bits + * in the HW if possible. + */ + ret = driver->bind(udc->gadget); + if (ret) { + dev_err(&udc->dev, "bind to driver %s failed --> %d\n", + driver->function, ret); + goto err2; + } + + ret = usb_gadget_connect(udc->gadget); + if (ret) { + dev_err(&udc->dev, "failed to start %s --> %d\n", + udc->name, ret); + goto err3; + } +#endif + + kobject_uevent(&udc->dev.kobj, KOBJ_ADD); + + return 0; + +#if 0 +err3: + driver->unbind(udc->gadget); + +err2: + usb_gadget_stop(udc->gadget); + +#endif +err1: + spin_lock_irqsave(&udc_lock, flags); + udc->driver = NULL; + udc->dev.driver = NULL; +err0: + spin_unlock_irqrestore(&udc_lock, flags); + return ret; +} +EXPORT_SYMBOL_GPL(usb_gadget_register_driver); + +int usb_gadget_unregister_driver(struct usb_gadget_driver *driver) +{ + struct usb_udc *udc = NULL; + unsigned long flags; + int ret = 0; + + if (!driver || !driver->unbind) + return -EINVAL; + + spin_lock_irqsave(&udc_lock, flags); + list_for_each_entry(udc, &udc_list, list) { + if (udc->driver == driver) + break; + } + + if (!udc || udc->driver != driver) { + ret = -ENODEV; + goto out; + } + + spin_unlock_irqrestore(&udc_lock, flags); + + usb_gadget_vbus_draw(udc->gadget, 0); + + dev_dbg(&udc->dev, "unregistering UDC driver [%s]\n", driver->function); + kobject_uevent(&udc->dev.kobj, KOBJ_REMOVE); + + driver->unbind(udc->gadget); + usb_gadget_stop(udc->gadget, udc->driver); + usb_gadget_disconnect(udc->gadget); + + spin_lock_irqsave(&udc_lock, flags); + udc->driver = NULL; + udc->dev.driver = NULL; + spin_unlock_irqrestore(&udc_lock, flags); + +out: + return ret; +} +EXPORT_SYMBOL_GPL(usb_gadget_unregister_driver); + +/* ------------------------------------------------------------------------- */ + +static ssize_t usb_udc_srp_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t n) +{ + struct usb_udc *udc = dev_get_drvdata(dev); + + if (sysfs_streq(buf, "1")) + usb_gadget_wakeup(udc->gadget); + + return n; +} +static DEVICE_ATTR(srp, S_IWUSR, NULL, usb_udc_srp_store); + +static ssize_t usb_udc_softconn_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t n) +{ + struct usb_udc *udc = dev_get_drvdata(dev); + + if (sysfs_streq(buf, "connect")) { + usb_gadget_connect(udc->gadget); + } else if (sysfs_streq(buf, "disconnect")) { + usb_gadget_disconnect(udc->gadget); + } else { + dev_err(dev, "unsupported command '%s'\n", buf); + return -EINVAL; + } + + return n; +} +static DEVICE_ATTR(soft_connect, S_IWUSR, NULL, usb_udc_softconn_store); + +static ssize_t usb_udc_speed_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_udc *udc = dev_get_drvdata(dev); + struct usb_gadget *gadget = udc->gadget; + + switch (gadget->speed) { + case USB_SPEED_LOW: + return snprintf(buf, PAGE_SIZE, "low-speed\n"); + case USB_SPEED_FULL: + return snprintf(buf, PAGE_SIZE, "full-speed\n"); + case USB_SPEED_HIGH: + return snprintf(buf, PAGE_SIZE, "high-speed\n"); + case USB_SPEED_WIRELESS: + return snprintf(buf, PAGE_SIZE, "wireless\n"); + case USB_SPEED_SUPER: + return snprintf(buf, PAGE_SIZE, "super-speed\n"); + case USB_SPEED_UNKNOWN: /* FALLTHROUGH */ + default: + return snprintf(buf, PAGE_SIZE, "UNKNOWN\n"); + } +} +static DEVICE_ATTR(speed, S_IRUSR, usb_udc_speed_show, NULL); + +#define USB_UDC_ATTR(name) \ +ssize_t usb_udc_##name##_show(struct device *dev, \ + struct device_attribute *attr, char *buf) \ +{ \ + struct usb_udc *udc = dev_get_drvdata(dev); \ + struct usb_gadget *gadget = udc->gadget; \ + \ + return snprintf(buf, PAGE_SIZE, "%d\n", gadget->name); \ +} \ +static DEVICE_ATTR(name, S_IRUSR, usb_udc_##name##_show, NULL) + +static USB_UDC_ATTR(is_dualspeed); +static USB_UDC_ATTR(is_otg); +static USB_UDC_ATTR(is_a_peripheral); +static USB_UDC_ATTR(b_hnp_enable); +static USB_UDC_ATTR(a_hnp_support); +static USB_UDC_ATTR(a_alt_hnp_support); + +static struct attribute *usb_udc_attrs[] = { + &dev_attr_srp.attr, + &dev_attr_soft_connect.attr, + &dev_attr_speed.attr, + + &dev_attr_is_dualspeed.attr, + &dev_attr_is_otg.attr, + &dev_attr_is_a_peripheral.attr, + &dev_attr_b_hnp_enable.attr, + &dev_attr_a_hnp_support.attr, + &dev_attr_a_alt_hnp_support.attr, + NULL, +}; + +static const struct attribute_group usb_udc_attr_group = { + .attrs = usb_udc_attrs, +}; + +static const struct attribute_group *usb_udc_attr_groups[] = { + &usb_udc_attr_group, + NULL, +}; + +static int usb_udc_uevent(struct device *dev, struct kobj_uevent_env *env) +{ + struct usb_udc *udc = container_of(dev, struct usb_udc, dev); + int ret; + + ret = add_uevent_var(env, "USB_UDC_NAME=%s\n", udc->name); + if (ret) { + dev_err(dev, "failed to add uevent USB_UDC_NAME\n"); + return ret; + } + + if (udc->driver) { + ret = add_uevent_var(env, "USB_UDC_DRIVER=%s\n", + udc->driver->function); + if (ret) { + dev_err(dev, "failed to add uevent USB_UDC_DRIVER\n"); + return ret; + } + } + + return 0; +} + +static int __init usb_udc_init(void) +{ + spin_lock_init(&udc_lock); + + udc_class = class_create(THIS_MODULE, "udc"); + if (IS_ERR(udc_class)) { + pr_err("failed to create udc class --> %ld\n", + PTR_ERR(udc_class)); + return PTR_ERR(udc_class); + } + + udc_class->dev_uevent = usb_udc_uevent; + udc_device_type.groups = usb_udc_attr_groups; + + return 0; +} +subsys_initcall(usb_udc_init); + +static void __exit usb_udc_exit(void) +{ + class_destroy(udc_class); + udc_class = NULL; +} +module_exit(usb_udc_exit); + +MODULE_DESCRIPTION("UDC Framework"); +MODULE_AUTHOR("Felipe Balbi <balbi@xxxxxx>"); +MODULE_LICENSE("GPL v2"); diff --git a/include/linux/usb/gadget.h b/include/linux/usb/gadget.h index 34d47f5..c408752 100644 --- a/include/linux/usb/gadget.h +++ b/include/linux/usb/gadget.h @@ -797,19 +797,19 @@ struct usb_gadget_driver { */ /** - * usb_gadget_probe_driver - probe a gadget driver + * usb_gadget_register_driver - register a gadget driver * @driver: the driver being registered - * @bind: the driver's bind callback * Context: can sleep * * Call this in your gadget driver's module initialization function, * to tell the underlying usb controller driver about your driver. - * The @bind() function will be called to bind it to a gadget before this - * registration call returns. It's expected that the @bind() function will - * be in init sections. + * The @driver->bind() function will be called to bind it to a gadget before + * this registration call returns. It's expected that the @driver->bind() + * function will be in init sections. */ int usb_gadget_probe_driver(struct usb_gadget_driver *driver, int (*bind)(struct usb_gadget *)); +int usb_gadget_register_driver(struct usb_gadget_driver *driver); /** * usb_gadget_unregister_driver - unregister a gadget driver @@ -826,6 +826,9 @@ int usb_gadget_probe_driver(struct usb_gadget_driver *driver, */ int usb_gadget_unregister_driver(struct usb_gadget_driver *driver); +extern int usb_add_gadget(struct device *parent, struct usb_gadget *gadget); +extern void usb_del_gadget(struct usb_gadget *gadget); + /*-------------------------------------------------------------------------*/ /* utility to simplify dealing with string descriptors */ -- 1.7.4.1.343.ga91df -- To unsubscribe from this list: send the line "unsubscribe linux-usb" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html