this will allow us to register N gadget controller drivers and register gadget drivers based on whether that is already busy or not. It's good, at a minimum, for development, but it's also a real-life possibility. NYET-Signed-off-by: Felipe Balbi <balbi@xxxxxx> --- **** NOT FOR MERGING **** drivers/usb/gadget/Makefile | 1 + drivers/usb/gadget/gadget.c | 247 ++++++++++++++++++++++++++++++++++++++++ drivers/usb/musb/musb_gadget.c | 194 ++++++++----------------------- include/linux/usb/gadget.h | 33 ++++- 4 files changed, 324 insertions(+), 151 deletions(-) create mode 100644 drivers/usb/gadget/gadget.c diff --git a/drivers/usb/gadget/Makefile b/drivers/usb/gadget/Makefile index 27283df..60ff266 100644 --- a/drivers/usb/gadget/Makefile +++ b/drivers/usb/gadget/Makefile @@ -5,6 +5,7 @@ ifeq ($(CONFIG_USB_GADGET_DEBUG),y) EXTRA_CFLAGS += -DDEBUG endif +obj-$(CONFIG_USB_GADGET) += gadget.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/gadget.c b/drivers/usb/gadget/gadget.c new file mode 100644 index 0000000..5224fd5 --- /dev/null +++ b/drivers/usb/gadget/gadget.c @@ -0,0 +1,247 @@ +/** + * gadget.c - Core Gadget 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> + +static struct class *gadget_class; +static struct device_type gadget_device_type; +static LIST_HEAD(gadget_list); +static spinlock_t gadget_lock; + +static void usb_gadget_release(struct device *dev) +{ + dev_dbg(dev, "releasing '%s'\n", dev_name(dev)); + kfree(dev); +} + +int usb_add_gadget(struct device *parent, struct usb_gadget *gadget) +{ + struct device *dev; + unsigned long flags; + int ret; + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) { + ret = -ENOMEM; + goto err1; + } + + device_initialize(dev); + + dev->class = gadget_class; + dev->type = &gadget_device_type; + dev->parent = parent; + dev->release = usb_gadget_release; + gadget->dev = dev; + + ret = kobject_set_name(&dev->kobj, "%s", gadget->name); + if (ret) + goto err2; + + ret = device_add(dev); + if (ret) + goto err2; + + spin_lock_irqsave(&gadget_lock, flags); + list_add_tail(&gadget->list, &gadget_list); + spin_unlock_irqrestore(&gadget_lock, flags); + + return 0; + +err2: + kfree(dev); + +err1: + return ret; +} +EXPORT_SYMBOL_GPL(usb_add_gadget); + +void usb_del_gadget(struct usb_gadget *gadget) +{ + unsigned long flags; + + device_unregister(gadget->dev); + + spin_lock_irqsave(&gadget_lock, flags); + list_del(&gadget->list); + spin_unlock_irqrestore(&gadget_lock, flags); +} +EXPORT_SYMBOL_GPL(usb_del_gadget); + +int usb_gadget_register_driver(struct usb_gadget_driver *driver) +{ + struct usb_gadget *gadget = NULL; + unsigned long flags; + int ret; + + if (!driver || !driver->bind || !driver->setup) + return -EINVAL; + + spin_lock_irqsave(&gadget_lock, flags); + list_for_each_entry(gadget, &gadget_list, list) { + if (gadget->busy) + continue; + + dev_vdbg(gadget->dev, "registering driver [%s]\n", + driver->function); + + gadget->busy = true; + gadget->driver = driver; + gadget->dev->driver = &driver->driver; + } + spin_unlock_irqrestore(&gadget_lock, flags); + + if (!gadget) + return -EBUSY; + + ret = driver->bind(gadget); + if (ret) { + dev_dbg(gadget->dev, "bind to driver %s failed --> %d\n", + driver->function, ret); + goto err1; + } + + ret = usb_gadget_start(gadget); + if (ret) { + dev_dbg(gadget->dev, "failed to start %s --> %d\n", + gadget->name, ret); + goto err1; + } + + ret = usb_gadget_connect(gadget); + if (ret) { + dev_dbg(gadget->dev, "failed to start %s --> %d\n", + gadget->name, ret); + goto err2; + } + + return 0; + +err2: + usb_gadget_stop(gadget); + +err1: + gadget->busy = false; + gadget->driver = NULL; + gadget->dev->driver = NULL; + + return ret; +} +EXPORT_SYMBOL_GPL(usb_gadget_register_driver); + +int usb_gadget_unregister_driver(struct usb_gadget_driver *driver) +{ + struct usb_gadget *gadget = NULL; + unsigned long flags; + + if (!driver || !driver->unbind) + return -EINVAL; + + spin_lock_irqsave(&gadget_lock, flags); + list_for_each_entry(gadget, &gadget_list, list) { + if (!gadget->busy) + continue; + + if (gadget->driver != driver) + continue; + + gadget->busy = false; + gadget->driver = NULL; + gadget->dev->driver = NULL; + } + spin_unlock_irqrestore(&gadget_lock, flags); + + if (!gadget) + return -ENODEV; + + (void) usb_gadget_vbus_draw(gadget, 0); + + dev_dbg(gadget->dev, "unregistering driver [%s]\n", driver->function); + + driver->unbind(gadget); + usb_gadget_stop(gadget); + usb_gadget_disconnect(gadget); + + return 0; +} +EXPORT_SYMBOL_GPL(usb_gadget_unregister_driver); + +static struct device_attribute usb_gadget_attrs[] = { + __ATTR_NULL, +}; + +static struct attribute *__usb_gadget_attrs[ARRAY_SIZE(usb_gadget_attrs) + 1]; + +static const struct attribute_group usb_gadget_attr_group = { + .attrs = __usb_gadget_attrs, +}; + +static const struct attribute_group *usb_gadget_attr_groups[] = { + &usb_gadget_attr_group, + NULL, +}; + +static void usb_gadget_init_attrs(struct device_type *type) +{ + int i; + + type->groups = usb_gadget_attr_groups; + + for (i = 0; i < ARRAY_SIZE(usb_gadget_attrs); i++) + __usb_gadget_attrs[i] = &usb_gadget_attrs[i].attr; +} + +static int usb_gadget_uevent(struct device *dev, struct kobj_uevent_env *env) +{ + return 0; +} + +MODULE_DESCRIPTION("USB Gadget Framework"); +MODULE_AUTHOR("Felipe Balbi <balbi@xxxxxx>"); +MODULE_LICENSE("GPL v2"); + +static int __init usb_gadget_init(void) +{ + spin_lock_init(&gadget_lock); + + gadget_class = class_create(THIS_MODULE, "gadget"); + if (IS_ERR(gadget_class)) { + return PTR_ERR(gadget_class); + } + + gadget_class->dev_uevent = usb_gadget_uevent; + usb_gadget_init_attrs(&gadget_device_type); + + return 0; +} +subsys_initcall(usb_gadget_init); + +static void __exit usb_gadget_exit(void) +{ + class_destroy(gadget_class); +} +module_exit(usb_gadget_exit); + diff --git a/drivers/usb/musb/musb_gadget.c b/drivers/usb/musb/musb_gadget.c index 6fca870..56548d2 100644 --- a/drivers/usb/musb/musb_gadget.c +++ b/drivers/usb/musb/musb_gadget.c @@ -47,6 +47,8 @@ #include "musb_core.h" +static int musb_gadget_start(struct usb_gadget *gadget); +static void musb_gadget_stop(struct usb_gadget *gadget); /* MUSB PERIPHERAL status 3-mar-2006: * @@ -1552,6 +1554,8 @@ static const struct usb_gadget_ops musb_gadget_operations = { /* .vbus_session = musb_gadget_vbus_session, */ .vbus_draw = musb_gadget_vbus_draw, .pullup = musb_gadget_pullup, + .start = musb_gadget_start, + .stop = musb_gadget_stop, }; /* ----------------------------------------------------------------------- */ @@ -1564,13 +1568,6 @@ static const struct usb_gadget_ops musb_gadget_operations = { */ static struct musb *the_gadget; -static void musb_gadget_release(struct device *dev) -{ - /* kref_put(WHAT) */ - dev_dbg(dev, "%s\n", __func__); -} - - static void __init init_peripheral_ep(struct musb *musb, struct musb_ep *ep, u8 epnum, int is_in) { @@ -1656,12 +1653,6 @@ int __init musb_gadget_setup(struct musb *musb) musb->g.ops = &musb_gadget_operations; musb->g.is_dualspeed = 1; musb->g.speed = USB_SPEED_UNKNOWN; - - /* this "gadget" abstracts/virtualizes the controller */ - dev_set_name(&musb->g.dev, "gadget"); - musb->g.dev.parent = musb->controller; - musb->g.dev.dma_mask = musb->controller->dma_mask; - musb->g.dev.release = musb_gadget_release; musb->g.name = musb_driver_name; if (is_otg_enabled(musb)) @@ -1672,9 +1663,10 @@ int __init musb_gadget_setup(struct musb *musb) musb->is_active = 0; musb_platform_try_idle(musb, 0); - status = device_register(&musb->g.dev); + status = usb_add_gadget(musb->controller, &musb->g); if (status != 0) the_gadget = NULL; + return status; } @@ -1683,7 +1675,7 @@ void musb_gadget_cleanup(struct musb *musb) if (musb != the_gadget) return; - device_unregister(&musb->g.dev); + usb_del_gadget(&musb->g); the_gadget = NULL; } @@ -1698,132 +1690,68 @@ void musb_gadget_cleanup(struct musb *musb) * @param driver the gadget driver * @return <0 if error, 0 if everything is fine */ -int usb_gadget_register_driver(struct usb_gadget_driver *driver) +static int musb_gadget_start(struct usb_gadget *gadget) { - int retval; + struct musb *musb = gadget_to_musb(gadget); unsigned long flags; - struct musb *musb = the_gadget; - - if (!driver - || driver->speed != USB_SPEED_HIGH - || !driver->bind - || !driver->setup) - return -EINVAL; - - /* driver must be initialized to support peripheral mode */ - if (!musb) { - DBG(1, "%s, no dev??\n", __func__); - return -ENODEV; - } + int retval = 0; - DBG(3, "registering driver %s\n", driver->function); spin_lock_irqsave(&musb->lock, flags); - if (musb->gadget_driver) { - DBG(1, "%s is already bound to %s\n", - musb_driver_name, - musb->gadget_driver->driver.name); - retval = -EBUSY; - } else { - musb->gadget_driver = driver; - musb->g.dev.driver = &driver->driver; - driver->driver.bus = NULL; - musb->softconnect = 1; - retval = 0; - } - - spin_unlock_irqrestore(&musb->lock, flags); + otg_set_peripheral(musb->xceiv, gadget); + musb->xceiv->state = OTG_STATE_B_IDLE; + musb->is_active = 1; - if (retval == 0) { - retval = driver->bind(&musb->g); - if (retval != 0) { - DBG(3, "bind to driver %s failed --> %d\n", - driver->driver.name, retval); - musb->gadget_driver = NULL; - musb->g.dev.driver = NULL; - } + /* FIXME this ignores the softconnect flag. Drivers are + * allowed hold the peripheral inactive until for example + * userspace hooks up printer hardware or DSP codecs, so + * hosts only see fully functional devices. + */ - spin_lock_irqsave(&musb->lock, flags); + if (!is_otg_enabled(musb)) + musb_start(musb); - otg_set_peripheral(musb->xceiv, &musb->g); - musb->xceiv->state = OTG_STATE_B_IDLE; - musb->is_active = 1; + if (is_otg_enabled(musb)) { + DBG(3, "OTG startup...\n"); - /* FIXME this ignores the softconnect flag. Drivers are - * allowed hold the peripheral inactive until for example - * userspace hooks up printer hardware or DSP codecs, so - * hosts only see fully functional devices. + /* REVISIT: funcall to other code, which also + * handles power budgeting ... this way also + * ensures HdrcStart is indirectly called. */ - - if (!is_otg_enabled(musb)) - musb_start(musb); - - otg_set_peripheral(musb->xceiv, &musb->g); - - spin_unlock_irqrestore(&musb->lock, flags); - - if (is_otg_enabled(musb)) { - DBG(3, "OTG startup...\n"); - - /* REVISIT: funcall to other code, which also - * handles power budgeting ... this way also - * ensures HdrcStart is indirectly called. - */ - retval = usb_add_hcd(musb_to_hcd(musb), -1, 0); - if (retval < 0) { - DBG(1, "add_hcd failed, %d\n", retval); - spin_lock_irqsave(&musb->lock, flags); - otg_set_peripheral(musb->xceiv, NULL); - musb->gadget_driver = NULL; - musb->g.dev.driver = NULL; - spin_unlock_irqrestore(&musb->lock, flags); - } + retval = usb_add_hcd(musb_to_hcd(musb), -1, 0); + if (retval < 0) { + DBG(1, "add_hcd failed, %d\n", retval); + otg_set_peripheral(musb->xceiv, NULL); } } + spin_unlock_irqrestore(&musb->lock, flags); + return retval; } -EXPORT_SYMBOL(usb_gadget_register_driver); -static void stop_activity(struct musb *musb, struct usb_gadget_driver *driver) +static void stop_activity(struct musb *musb) { int i; struct musb_hw_ep *hw_ep; - /* don't disconnect if it's not connected */ - if (musb->g.speed == USB_SPEED_UNKNOWN) - driver = NULL; - else - musb->g.speed = USB_SPEED_UNKNOWN; - - /* deactivate the hardware */ - if (musb->softconnect) { - musb->softconnect = 0; - musb_pullup(musb, 0); - } musb_stop(musb); /* killing any outstanding requests will quiesce the driver; * then report disconnect */ - if (driver) { - for (i = 0, hw_ep = musb->endpoints; - i < musb->nr_endpoints; - i++, hw_ep++) { - musb_ep_select(musb->mregs, i); - if (hw_ep->is_shared_fifo /* || !epnum */) { + for (i = 0, hw_ep = musb->endpoints; + i < musb->nr_endpoints; + i++, hw_ep++) { + musb_ep_select(musb->mregs, i); + if (hw_ep->is_shared_fifo /* || !epnum */) { + nuke(&hw_ep->ep_in, -ESHUTDOWN); + } else { + if (hw_ep->max_packet_sz_tx) nuke(&hw_ep->ep_in, -ESHUTDOWN); - } else { - if (hw_ep->max_packet_sz_tx) - nuke(&hw_ep->ep_in, -ESHUTDOWN); - if (hw_ep->max_packet_sz_rx) - nuke(&hw_ep->ep_out, -ESHUTDOWN); - } + if (hw_ep->max_packet_sz_rx) + nuke(&hw_ep->ep_out, -ESHUTDOWN); } - - spin_unlock(&musb->lock); - driver->disconnect(&musb->g); - spin_lock(&musb->lock); } } @@ -1833,14 +1761,10 @@ static void stop_activity(struct musb *musb, struct usb_gadget_driver *driver) * * @param driver the gadget driver to unregister */ -int usb_gadget_unregister_driver(struct usb_gadget_driver *driver) +static void musb_gadget_stop(struct usb_gadget *gadget) { + struct musb *musb = gadget_to_musb(gadget); unsigned long flags; - int retval = 0; - struct musb *musb = the_gadget; - - if (!driver || !driver->unbind || !musb) - return -EINVAL; /* REVISIT always use otg_set_peripheral() here too; * this needs to shut down the OTG engine. @@ -1852,40 +1776,22 @@ int usb_gadget_unregister_driver(struct usb_gadget_driver *driver) musb_hnp_stop(musb); #endif - if (musb->gadget_driver == driver) { - - (void) musb_gadget_vbus_draw(&musb->g, 0); - - musb->xceiv->state = OTG_STATE_UNDEFINED; - stop_activity(musb, driver); - otg_set_peripheral(musb->xceiv, NULL); - - DBG(3, "unregistering driver %s\n", driver->function); - spin_unlock_irqrestore(&musb->lock, flags); - driver->unbind(&musb->g); - spin_lock_irqsave(&musb->lock, flags); - - musb->gadget_driver = NULL; - musb->g.dev.driver = NULL; + musb->xceiv->state = OTG_STATE_UNDEFINED; + stop_activity(musb); + otg_set_peripheral(musb->xceiv, NULL); - musb->is_active = 0; - musb_platform_try_idle(musb, 0); - } else - retval = -EINVAL; + musb->is_active = 0; + musb_platform_try_idle(musb, 0); spin_unlock_irqrestore(&musb->lock, flags); - if (is_otg_enabled(musb) && retval == 0) { + if (is_otg_enabled(musb)) { usb_remove_hcd(musb_to_hcd(musb)); /* FIXME we need to be able to register another * gadget driver here and have everything work; * that currently misbehaves. */ } - - return retval; } -EXPORT_SYMBOL(usb_gadget_unregister_driver); - /* ----------------------------------------------------------------------- */ diff --git a/include/linux/usb/gadget.h b/include/linux/usb/gadget.h index d3ef42d..42e3d6f 100644 --- a/include/linux/usb/gadget.h +++ b/include/linux/usb/gadget.h @@ -18,6 +18,7 @@ #include <linux/slab.h> struct usb_ep; +struct usb_gadget_driver; /** * struct usb_request - describes one i/o request @@ -430,6 +431,8 @@ struct usb_gadget_ops { int (*pullup) (struct usb_gadget *, int is_on); int (*ioctl)(struct usb_gadget *, unsigned code, unsigned long param); + int (*start)(struct usb_gadget *); + void (*stop)(struct usb_gadget *); }; /** @@ -479,6 +482,8 @@ struct usb_gadget { const struct usb_gadget_ops *ops; struct usb_ep *ep0; struct list_head ep_list; /* of usb_ep */ + struct usb_gadget_driver *driver; + struct list_head list; enum usb_device_speed speed; unsigned is_dualspeed:1; unsigned is_otg:1; @@ -486,18 +491,15 @@ struct usb_gadget { unsigned b_hnp_enable:1; unsigned a_hnp_support:1; unsigned a_alt_hnp_support:1; + unsigned busy:1; const char *name; - struct device dev; + struct device *dev; }; static inline void set_gadget_data(struct usb_gadget *gadget, void *data) - { dev_set_drvdata(&gadget->dev, data); } + { dev_set_drvdata(gadget->dev, data); } static inline void *get_gadget_data(struct usb_gadget *gadget) - { return dev_get_drvdata(&gadget->dev); } -static inline struct usb_gadget *dev_to_usb_gadget(struct device *dev) -{ - return container_of(dev, struct usb_gadget, dev); -} + { return dev_get_drvdata(gadget->dev); } /* iterates the non-control endpoints; 'tmp' is a struct usb_ep pointer */ #define gadget_for_each_ep(tmp, gadget) \ @@ -640,6 +642,20 @@ static inline int usb_gadget_vbus_draw(struct usb_gadget *gadget, unsigned mA) return gadget->ops->vbus_draw(gadget, mA); } +static inline int usb_gadget_start(struct usb_gadget *gadget) +{ + if (!gadget->ops->start) + return -EOPNOTSUPP; + return gadget->ops->start(gadget); +} + +static inline void usb_gadget_stop(struct usb_gadget *gadget) +{ + if (!gadget->ops->stop) + return; + gadget->ops->stop(gadget); +} + /** * usb_gadget_vbus_disconnect - notify controller about VBUS session end * @gadget:the device whose VBUS supply is being described @@ -898,4 +914,7 @@ extern struct usb_ep *usb_ep_autoconfig(struct usb_gadget *, extern void usb_ep_autoconfig_reset(struct usb_gadget *) __devinit; +extern int usb_add_gadget(struct device *, struct usb_gadget *); +extern void usb_del_gadget(struct usb_gadget *); + #endif /* __LINUX_USB_GADGET_H */ -- 1.7.3.rc0.6.g7505a -- 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