[RFC/PATCH 2/8] usb: gadget: introduce UDC Class

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

 



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: Felipe Balbi <balbi@xxxxxx>
---
 drivers/usb/gadget/Kconfig    |    3 +
 drivers/usb/gadget/Makefile   |    1 +
 drivers/usb/gadget/udc-core.c |  462 +++++++++++++++++++++++++++++++++++++++++
 include/linux/usb/udc.h       |   55 +++++
 4 files changed, 521 insertions(+), 0 deletions(-)
 create mode 100644 drivers/usb/gadget/udc-core.c
 create mode 100644 include/linux/usb/udc.h

diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig
index cd27f9b..7de6334 100644
--- a/drivers/usb/gadget/Kconfig
+++ b/drivers/usb/gadget/Kconfig
@@ -13,6 +13,9 @@
 # both kinds of controller can also support "USB On-the-Go" (CONFIG_USB_OTG).
 #
 
+config USB_UDC_CORE
+	tristate
+
 menuconfig USB_GADGET
 	tristate "USB Gadget Support"
 	help
diff --git a/drivers/usb/gadget/Makefile b/drivers/usb/gadget/Makefile
index 27283df..42c76a2 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_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..5b3d5e8
--- /dev/null
+++ b/drivers/usb/gadget/udc-core.c
@@ -0,0 +1,462 @@
+/**
+ * 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/udc.h>
+#include <linux/usb/ch9.h>
+#include <linux/usb/gadget.h>
+
+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)
+{
+	return gadget->ops->start(gadget);
+}
+
+/**
+ * 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)
+{
+	gadget->ops->stop(gadget);
+}
+
+/* ------------------------------------------------------------------------- */
+
+static void usb_udc_release(struct device *dev)
+{
+	dev_dbg(dev, "releasing '%s'\n", dev_name(dev));
+	kfree(dev);
+}
+
+static void usb_udc_nop_release(struct device *dev)
+{
+}
+
+/**
+ * usb_add_udc - adds a new udc to the udc class driver list
+ * @parent: the parent device to this udc. Usually the controller
+ * driver's device.
+ * @udc: the udc to be added to the list
+ *
+ * Returns zero on success, negative errno otherwise.
+ */
+int usb_add_udc(struct device *parent, struct usb_udc *udc)
+{
+	struct device		*dev;
+	unsigned long		flags;
+	int			ret;
+
+	if (!udc->gadget || !udc->gadget->ops ||
+			!udc->gadget->ops->start) {
+		dev_dbg(parent, "unitializaed gadget\n");
+		return -EINVAL;
+	}
+
+	dev_vdbg(parent, "registering UDC\n");
+
+	dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+	if (!dev) {
+		ret = -ENOMEM;
+		goto err1;
+	}
+
+	spin_lock_irqsave(&udc_lock, flags);
+
+	device_initialize(dev);
+
+	dev->class = udc_class;
+	dev->type = &udc_device_type;
+	dev->parent = parent;
+	dev->release = usb_udc_release;
+	dev_set_drvdata(dev, udc);
+	udc->dev = dev;
+	udc->gadget->dev.parent = udc->dev;
+	udc->gadget->dev.release = usb_udc_nop_release;
+	udc->gadget->dev.dma_mask = parent->dma_mask;
+
+	ret = dev_set_name(dev, "%s", udc->name);
+	if (ret)
+		goto err2;
+
+	ret = dev_set_name(&udc->gadget->dev, "%s", udc->name);
+	if (ret)
+		goto err2;
+
+	ret = device_add(dev);
+	if (ret)
+		goto err2;
+
+	ret = device_register(&udc->gadget->dev);
+	if (ret)
+		goto err3;
+
+
+	list_add_tail(&udc->list, &udc_list);
+	spin_unlock_irqrestore(&udc_lock, flags);
+
+	return 0;
+
+err3:
+	device_unregister(dev);
+
+err2:
+	kfree(dev);
+	spin_unlock_irqrestore(&udc_lock, flags);
+
+err1:
+	return ret;
+}
+EXPORT_SYMBOL_GPL(usb_add_udc);
+
+/**
+ * usb_del_udc - 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_udc(struct usb_udc *udc)
+{
+	unsigned long		flags;
+
+	dev_vdbg(udc->dev->parent, "unregistering UDC\n");
+
+	spin_lock_irqsave(&udc_lock, flags);
+	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(udc->dev);
+	device_unregister(&udc->gadget->dev);
+	spin_unlock_irqrestore(&udc_lock, flags);
+}
+EXPORT_SYMBOL_GPL(usb_del_udc);
+
+/* ------------------------------------------------------------------------- */
+
+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) {
+		if (!udc->driver) {
+			pr_debug("using UDC '%s'\n", udc->name);
+			break;
+		}
+
+		if (udc->driver)
+			continue;
+
+		pr_debug("couldn't find an available UDC\n");
+		ret = -ENODEV;
+		spin_unlock_irqrestore(&udc_lock, flags);
+		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);
+	if (ret) {
+		dev_err(udc->dev, "failed to start %s --> %d\n",
+				udc->name, ret);
+		goto err1;
+	}
+
+	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;
+	}
+
+	kobject_uevent(&udc->dev->kobj, KOBJ_ADD);
+
+	return 0;
+
+err3:
+	driver->unbind(udc->gadget);
+
+err2:
+	usb_gadget_stop(udc->gadget);
+
+err1:
+	spin_lock_irqsave(&udc_lock, flags);
+	udc->driver = NULL;
+	udc->dev->driver = NULL;
+	spin_unlock_irqrestore(&udc_lock, flags);
+
+err0:
+	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->driver != driver)
+			continue;
+
+		ret = -ENODEV;
+		spin_unlock_irqrestore(&udc_lock, flags);
+		goto out;
+	}
+
+	spin_unlock_irqrestore(&udc_lock, flags);
+
+	(void) 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);
+	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 = dev_get_drvdata(dev);
+	int			ret;
+
+	if (!udc || !udc->dev) {
+		dev_dbg(dev, "No UDC registered yet\n");
+		return 0;
+	}
+
+	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;
+}
+
+/* ------------------------------------------------------------------------- */
+
+MODULE_DESCRIPTION("UDC Framework");
+MODULE_AUTHOR("Felipe Balbi <balbi@xxxxxx>");
+MODULE_LICENSE("GPL v2");
+
+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);
+
diff --git a/include/linux/usb/udc.h b/include/linux/usb/udc.h
new file mode 100644
index 0000000..ce44468
--- /dev/null
+++ b/include/linux/usb/udc.h
@@ -0,0 +1,55 @@
+/**
+ * udc.h - Gadget Controller Header file
+ *
+ * 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/>.
+ */
+
+#ifndef __LINUX_USB_UDC_H
+#define __LINUX_USB_UDC_H
+
+#include <linux/device.h>
+#include <linux/list.h>
+#include <linux/spinlock.h>
+#include <linux/usb/ch9.h>
+#include <linux/usb/gadget.h>
+
+/**
+ * struct usb_udc - describes one usb device controller
+ * @gadget - the gadget. Always provide this.
+ * @driver - the gadget driver pointer. For use by the class code
+ * @dev - the child device to the actual controller
+ * @list - for use by the udc class driver
+ * @name - a human friendly name representation
+ *
+ * a pointer to this structure should be passed to udc class
+ * driver by controller driver (musb, omap_udc, atmel, etc).
+ * @gadget is expected to be already initialized by that time.
+ */
+struct usb_udc {
+	struct usb_gadget		*gadget;
+	struct usb_gadget_driver	*driver;
+	struct device			*dev;
+
+	struct list_head		list;
+
+	const char			*name;
+};
+
+extern int usb_add_udc(struct device *, struct usb_udc *);
+extern void usb_del_udc(struct usb_udc *);
+
+#endif /* __LINUX_USB_UDC_H */
+
-- 
1.7.3.rc0.35.g8ac8c

--
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