[RFC/PATCH] gadget: Introduce the Gadget class

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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


[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux