[PATCH v1 1/7] usb: gadget: udc: gxp_udc: add gxp USB support

From: richard . yu
Date: Thu Nov 03 2022 - 12:08:51 EST


From: Richard Yu <richard.yu@xxxxxxx>

The GXP vEHCI controller presents an 8 port EHCI compatible PCI function
to host software. Each EHCI port is logically connected to a
corresponding set of virtual device registers.

Signed-off-by: Richard Yu <richard.yu@xxxxxxx>
---
drivers/usb/gadget/udc/Kconfig | 6 +
drivers/usb/gadget/udc/Makefile | 1 +
drivers/usb/gadget/udc/gxp_udc.c | 1300 ++++++++++++++++++++++++++++++
3 files changed, 1307 insertions(+)
create mode 100644 drivers/usb/gadget/udc/gxp_udc.c

diff --git a/drivers/usb/gadget/udc/Kconfig b/drivers/usb/gadget/udc/Kconfig
index 5756acb07b8d..5eab5c140edf 100644
--- a/drivers/usb/gadget/udc/Kconfig
+++ b/drivers/usb/gadget/udc/Kconfig
@@ -463,6 +463,12 @@ config USB_TEGRA_XUDC
dynamically linked module called "tegra_xudc" and force all
gadget drivers to also be dynamically linked.

+config USB_GXP_UDC
+ bool "GXP UDC Driver"
+ depends on ARCH_HPE_GXP || COMPILE_TEST
+ help
+ Say "y" to add support for GXP UDC driver
+
config USB_ASPEED_UDC
tristate "Aspeed UDC driver support"
depends on ARCH_ASPEED || COMPILE_TEST
diff --git a/drivers/usb/gadget/udc/Makefile b/drivers/usb/gadget/udc/Makefile
index 12f9e4c9eb0c..e1d6e0711b4d 100644
--- a/drivers/usb/gadget/udc/Makefile
+++ b/drivers/usb/gadget/udc/Makefile
@@ -43,3 +43,4 @@ obj-$(CONFIG_USB_ASPEED_VHUB) += aspeed-vhub/
obj-$(CONFIG_USB_ASPEED_UDC) += aspeed_udc.o
obj-$(CONFIG_USB_BDC_UDC) += bdc/
obj-$(CONFIG_USB_MAX3420_UDC) += max3420_udc.o
+obj-$(CONFIG_USB_GXP_UDC) += gxp_udc.o
diff --git a/drivers/usb/gadget/udc/gxp_udc.c b/drivers/usb/gadget/udc/gxp_udc.c
new file mode 100644
index 000000000000..2e046f88af6c
--- /dev/null
+++ b/drivers/usb/gadget/udc/gxp_udc.c
@@ -0,0 +1,1300 @@
+// SPDX-License-Identifier: GPL-2.0=or-later
+/* Copyright (C) 2022 Hewlett-Packard Enterprise Development Company, L.P. */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/spinlock.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/of_platform.h>
+#include <linux/dma-mapping.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/usb/ch9.h>
+#include <linux/usb/gadget.h>
+#include <linux/usb/otg.h>
+#include <linux/prefetch.h>
+#include <linux/regmap.h>
+#include <linux/mfd/syscon.h>
+
+#define EVGBASE 0x0800
+#define EVGISTAT 0x0000
+#define EVGIEN 0x0004
+#define EVDBG 0x0008
+#define EVFEMAP 0x0010
+#define EVFEMAP_VDEVNUM_MASK 0x07
+#define EVFEMAP_VDEVNUM(val) val
+#define EVFEMAP_EPNUM_MASK 0x70
+#define EVFEMAP_EPNUM(val) ((val) << 4)
+
+#define EVDBASE 0x1000
+#define EVDISTAT 0x0000
+#define EVDISTAT_ALL_EP_INT 0x000000ff
+#define EVDISTAT_EP0_INT 0x00000001
+#define EVDISTAT_EP1_INT 0x00000002
+#define EVDISTAT_EP2_INT 0x00000004
+#define EVDISTAT_EP3_INT 0x00000008
+#define EVDISTAT_EP4_INT 0x00000010
+#define EVDISTAT_EP5_INT 0x00000020
+#define EVDISTAT_EP6_INT 0x00000040
+#define EVDISTAT_EP7_INT 0x00000080
+#define EVDISTAT_SETUP_DAT_RCV 0x00010000
+#define EVDISTAT_PORTEN_CHANGE 0x00100000
+#define EVDISTAT_PORTRST 0x80000000
+
+#define EVDIEN 0x0004
+
+#define EVDISTAT_MASK (EVDISTAT_SETUP_DAT_RCV | EVDISTAT_PORTEN_CHANGE | \
+ EVDISTAT_PORTRST | EVDISTAT_ALL_EP_INT)
+#define EVDIEN_MASK (EVDISTAT_SETUP_DAT_RCV | EVDISTAT_PORTEN_CHANGE | \
+ EVDISTAT_PORTRST | EVDISTAT_EP0_INT)
+
+#define EVDCS 0x0008
+#define EVDCS_CONNECT 0x00000001
+#define EVDCS_PORTENABLESTS 0x01000000
+
+#define EVDADDR 0x000c
+#define EVDSETUPL 0x0010
+#define EVDSETUPH 0x0014
+
+#define EVDEPCC 0x0000
+#define EVDEPCC_EPENABLE 0x00000001
+#define EVDEPCC_CFGINOUT 0x00000002
+#define EVDEPCC_DT_RESET 0x00000004
+#define EVDEPCC_DISARMCMD 0x00000020
+#define EVDEPCC_STALL 0x00000080
+#define EVDEPCC_TXN_PER_UFRAME_MASK 0x00030000
+#define EVDEPCC_TXN_PER_UFRAME(val) ((val) << 16)
+#define EVDEPCC_EP_TYPE_MASK 0x000C0000
+#define EVDEPCC_EP_TYPE(val) ((val) << 18)
+#define EVDEPCC_EP_SIZE_MASK 0x00700000
+#define EVDEPCC_EP_SIZE(val) ((val) << 20)
+
+#define EP_TYPE_CONTROL 0x00
+#define EP_TYPE_BULK 0x01
+#define EP_TYPE_INT 0x02
+#define EP_TYPE_ISOC 0x03
+
+#define EP_IN 0x01
+#define EP_OUT 0x00
+
+#define EVDEPSTS 0x0004
+#define EVDEPSTS_DONE 0x00000001
+#define EVDEPSTS_SHORT 0x00000002
+#define EVDEPSTS_STALLDET 0x00000004
+#define EVDEPSTS_BUFARMED 0x00010000
+
+#define EVDEPEVTEN 0x0008
+#define EVDEPEVTEN_DONE 0x00000001
+#define EVDEPEVTEN_SHORT 0x00000002
+#define EVDEPEVTEN_STALLDET 0x00000004
+#define EVDEPADDR 0x000c
+#define EVDEPLEN 0x0010
+#define EVDEPNTX 0x0014
+
+#define GXP_UDC_MAX_NUM_EP 8
+#define GXP_UDC_MAX_NUM_FLEX_EP 16
+#define GXP_UDC_MAX_NUM_VDEVICE 8
+#define GXP_UDC_MAX_NUM_FLEX_EP_PER_VDEV 7
+
+static bool udcg_init_done;
+
+static const char * const gxp_udc_name[] = {
+ "gxp-udc0", "gxp-udc1", "gxp-udc2", "gxp-udc3",
+ "gxp-udc4", "gxp-udc5", "gxp-udc6", "gxp-udc7"};
+
+static const char * const gxp_udc_ep_name[] = {
+ "ep0", "ep1", "ep2", "ep3", "ep4", "ep5", "ep6", "ep7"};
+
+struct gxp_udc_req {
+ struct usb_request req;
+ struct list_head queue;
+};
+
+struct gxp_udc_ep {
+ void __iomem *base;
+ struct gxp_udc_drvdata *drvdata;
+
+ struct usb_ep ep;
+ struct list_head queue;
+ unsigned char epnum;
+ unsigned char type;
+ bool stall;
+ bool wedged;
+ int is_in;
+};
+
+struct gxp_udc_drvdata {
+ void __iomem *base;
+ struct platform_device *pdev;
+ struct regmap *udcg_map;
+ struct gxp_udc_ep ep[GXP_UDC_MAX_NUM_EP];
+
+ int irq;
+ struct usb_gadget gadget;
+ struct usb_gadget_driver *driver;
+
+ u32 vdevnum;
+ u32 fepnum;
+ u32 devaddr;
+
+ __le16 ep0_data;
+ struct usb_request *ep0_req;
+ bool deffer_set_address;
+
+ /* This lock is used to protect the drvdata structure access */
+ spinlock_t lock;
+};
+
+static void gxp_udc_out_0byte_status(struct gxp_udc_ep *ep);
+static void gxp_udc_in_0byte_status(struct gxp_udc_ep *ep);
+
+static unsigned char gxp_udc_femap_read(struct gxp_udc_drvdata *drvdata,
+ int fep)
+{
+ unsigned int value;
+ int i = fep & 0x03;
+
+ regmap_read(drvdata->udcg_map, EVFEMAP + (fep & ~0x03), &value);
+ value = (value >> (8 * i)) & 0xff;
+
+ return (unsigned char)value;
+}
+
+static void gxp_udc_femap_write(struct gxp_udc_drvdata *drvdata, int fep,
+ unsigned char value)
+{
+ unsigned int reg;
+ unsigned int tmp = value;
+ int i = fep & 0x03;
+
+ regmap_read(drvdata->udcg_map, EVFEMAP + (fep & ~0x03), &reg);
+ tmp = tmp << (8 * i);
+ reg &= ~(0xff << (8 * i));
+ reg |= tmp;
+ regmap_write(drvdata->udcg_map, EVFEMAP + (fep & ~0x03), reg);
+}
+
+static int gxp_udc_start_dma(struct gxp_udc_ep *ep, struct gxp_udc_req *req)
+{
+ writel(req->req.dma, ep->base + EVDEPADDR);
+ writel(req->req.length, ep->base + EVDEPLEN);
+
+ return 0;
+}
+
+static int gxp_udc_done_dma(struct gxp_udc_ep *ep, struct gxp_udc_req *req)
+{
+ u32 len;
+
+ len = readl(ep->base + EVDEPLEN);
+ req->req.actual = req->req.length - len;
+
+ if (len && ep->is_in) {
+ /* remaining data to send, rearm again */
+ writel(len, ep->base + EVDEPLEN);
+ return 0;
+ }
+ return 1;
+}
+
+static u32 evdepcc_ep_type(int value)
+{
+ switch (value) {
+ case USB_ENDPOINT_XFER_CONTROL:
+ /* control */
+ return EP_TYPE_CONTROL;
+
+ case USB_ENDPOINT_XFER_ISOC:
+ /* isochronous */
+ return EP_TYPE_ISOC;
+
+ case USB_ENDPOINT_XFER_BULK:
+ /* bulk */
+ return EP_TYPE_BULK;
+
+ case USB_ENDPOINT_XFER_INT:
+ /* interrupt */
+ return EP_TYPE_INT;
+
+ default:
+ return 0;
+ }
+}
+
+static u32 evdepcc_ep_size(int value)
+{
+ switch (value) {
+ case 8:
+ return 0x00;
+ case 16:
+ return 0x01;
+ case 32:
+ return 0x02;
+ case 64:
+ return 0x03;
+ case 128:
+ return 0x04;
+ case 256:
+ return 0x05;
+ case 512:
+ return 0x06;
+ case 1024:
+ default:
+ return 0x07;
+ }
+}
+
+static int gxp_udc_ep_init(struct gxp_udc_ep *ep, int type, int dir_in,
+ int size)
+{
+ u32 val;
+ u32 tmp;
+
+ ep->is_in = dir_in;
+ ep->type = type;
+ ep->ep.maxpacket = size;
+
+ val = readl(ep->base + EVDEPCC);
+ if (dir_in) {
+ /* to host */
+ val |= EVDEPCC_CFGINOUT;
+ } else {
+ /* to device */
+ val &= ~EVDEPCC_CFGINOUT;
+ }
+
+ tmp = evdepcc_ep_type(type);
+ val &= ~EVDEPCC_EP_TYPE_MASK;
+ val |= EVDEPCC_EP_TYPE(tmp);
+
+ tmp = evdepcc_ep_size(size);
+ val &= ~EVDEPCC_EP_SIZE_MASK;
+ val |= EVDEPCC_EP_SIZE(tmp);
+
+ val &= ~EVDEPCC_TXN_PER_UFRAME_MASK;
+ if (type == USB_ENDPOINT_XFER_INT ||
+ type == USB_ENDPOINT_XFER_ISOC) {
+ val |= EVDEPCC_TXN_PER_UFRAME(1);
+ }
+
+ val |= EVDEPCC_EPENABLE;
+ writel(val, ep->base + EVDEPCC);
+
+ val = readl(ep->base + EVDEPSTS);
+ val &= ~(EVDEPSTS_DONE | EVDEPSTS_SHORT | EVDEPSTS_STALLDET);
+ writel(val, ep->base + EVDEPSTS);
+
+ val = readl(ep->base + EVDEPEVTEN);
+ val |= EVDEPEVTEN_DONE;
+ val |= EVDEPEVTEN_SHORT;
+ val |= EVDEPEVTEN_STALLDET;
+ writel(val, ep->base + EVDEPEVTEN);
+
+ return 0;
+}
+
+static struct usb_request *gxp_udc_ep_alloc_request(struct usb_ep *_ep,
+ gfp_t gfp_flags)
+{
+ struct gxp_udc_req *req;
+ struct gxp_udc_ep *ep;
+
+ req = kzalloc(sizeof(*req), gfp_flags);
+ if (!req)
+ return NULL;
+
+ ep = container_of(_ep, struct gxp_udc_ep, ep);
+ pr_debug("%s: ep%d\n", __func__, ep->epnum);
+
+ INIT_LIST_HEAD(&req->queue);
+
+ return &req->req;
+}
+
+static void gxp_udc_ep_free_request(struct usb_ep *_ep,
+ struct usb_request *_req)
+{
+ struct gxp_udc_req *req;
+ struct gxp_udc_ep *ep;
+
+ if (!_req)
+ return;
+
+ ep = container_of(_ep, struct gxp_udc_ep, ep);
+ pr_debug("%s: ep%d\n", __func__, ep->epnum);
+
+ req = container_of(_req, struct gxp_udc_req, req);
+ WARN_ON(!list_empty(&req->queue));
+ kfree(req);
+}
+
+static int gxp_udc_ep_queue(struct usb_ep *_ep, struct usb_request *_req,
+ gfp_t gfp_flags)
+{
+ struct gxp_udc_req *req;
+ struct gxp_udc_ep *ep;
+ unsigned long flags;
+ u32 val;
+ int ret;
+
+ if (!_req || !_ep)
+ return -EINVAL;
+
+ ep = container_of(_ep, struct gxp_udc_ep, ep);
+ req = container_of(_req, struct gxp_udc_req, req);
+
+ if (!ep->drvdata->driver ||
+ ep->drvdata->gadget.speed == USB_SPEED_UNKNOWN)
+ return -ESHUTDOWN;
+
+ pr_debug("%s: vdev%d ep%d\n",
+ __func__, ep->drvdata->vdevnum, ep->epnum);
+
+ spin_lock_irqsave(&ep->drvdata->lock, flags);
+
+ if (ep->epnum == 0) {
+ val = readl(ep->base + EVDEPCC);
+ ep->is_in = (val & EVDEPCC_CFGINOUT) ? 1 : 0;
+
+ if (!_req->length) {
+ /* ep0 control write transaction no data stage. */
+ _req->status = 0;
+
+ if (_req->complete) {
+ spin_unlock(&ep->drvdata->lock);
+ usb_gadget_giveback_request(_ep, _req);
+ spin_lock(&ep->drvdata->lock);
+ }
+
+ gxp_udc_in_0byte_status(ep);
+ return 0;
+ }
+ }
+
+ ret = usb_gadget_map_request(&ep->drvdata->gadget,
+ &req->req, ep->is_in);
+ if (ret) {
+ dev_dbg(&ep->drvdata->pdev->dev,
+ "usb_gadget_map_request failed ep%d\n", ep->epnum);
+ spin_unlock_irqrestore(&ep->drvdata->lock, flags);
+ return ret;
+ }
+
+ req->req.actual = 0;
+ req->req.status = -EINPROGRESS;
+
+ if (list_empty(&ep->queue)) {
+ list_add_tail(&req->queue, &ep->queue);
+ gxp_udc_start_dma(ep, req);
+ } else {
+ list_add_tail(&req->queue, &ep->queue);
+ }
+
+ spin_unlock_irqrestore(&ep->drvdata->lock, flags);
+
+ return 0;
+}
+
+static int gxp_udc_ep_dequeue(struct usb_ep *_ep, struct usb_request *_req)
+{
+ struct gxp_udc_req *req;
+ struct gxp_udc_ep *ep;
+ unsigned long flags;
+
+ if (!_ep || !_req)
+ return -EINVAL;
+
+ ep = container_of(_ep, struct gxp_udc_ep, ep);
+ req = container_of(_req, struct gxp_udc_req, req);
+
+ pr_debug("%s: vdev%d ep%d\n",
+ __func__, ep->drvdata->vdevnum, ep->epnum);
+
+ spin_lock_irqsave(&ep->drvdata->lock, flags);
+
+ /* ensure the req is still queued on this ep */
+ list_for_each_entry(req, &ep->queue, queue) {
+ if (&req->req == _req)
+ break;
+ }
+ if (&req->req != _req) {
+ pr_debug("%s: vdev%d ep%d _req is not queued in this ep\n",
+ __func__, ep->drvdata->vdevnum, ep->epnum);
+ spin_unlock_irqrestore(&ep->drvdata->lock, flags);
+ return -EINVAL;
+ }
+
+ if (ep->drvdata->gadget.speed == USB_SPEED_UNKNOWN)
+ _req->status = -ESHUTDOWN;
+ else
+ _req->status = -ECONNRESET;
+
+ /* remove req from queue */
+ list_del_init(&req->queue);
+ usb_gadget_unmap_request(&ep->drvdata->gadget, &req->req, ep->is_in);
+ if (req->req.complete) {
+ spin_unlock(&ep->drvdata->lock);
+ usb_gadget_giveback_request(_ep, _req);
+ spin_lock(&ep->drvdata->lock);
+ }
+
+ spin_unlock_irqrestore(&ep->drvdata->lock, flags);
+
+ return 0;
+}
+
+static int gxp_udc_ep_enable(struct usb_ep *_ep,
+ const struct usb_endpoint_descriptor *desc)
+{
+ struct gxp_udc_ep *ep;
+ struct gxp_udc_drvdata *drvdata;
+ u32 val;
+
+ if (!_ep)
+ return -EINVAL;
+
+ ep = container_of(_ep, struct gxp_udc_ep, ep);
+ drvdata = ep->drvdata;
+
+ pr_debug("%s vdev%d ep%d\n", __func__, drvdata->vdevnum, ep->epnum);
+
+ if (!drvdata->driver || drvdata->gadget.speed == USB_SPEED_UNKNOWN) {
+ dev_err(&drvdata->pdev->dev,
+ "vdev%d ep enable failed no driver or speed unknown\n",
+ drvdata->vdevnum);
+ return -ESHUTDOWN;
+ }
+
+ if (usb_endpoint_num(desc) > GXP_UDC_MAX_NUM_EP) {
+ dev_err(&drvdata->pdev->dev,
+ "vdev%d ep enable failed ep%d is out of range(%d)\n",
+ drvdata->vdevnum, usb_endpoint_num(desc),
+ GXP_UDC_MAX_NUM_EP);
+ return -EINVAL;
+ }
+
+ ep->ep.desc = desc;
+
+ ep->stall = false;
+ ep->wedged = false;
+
+ /* setup ep according to the setting of desc */
+ gxp_udc_ep_init(ep, usb_endpoint_type(desc), usb_endpoint_dir_in(desc),
+ usb_endpoint_maxp(desc));
+
+ /* enable ep interrupt */
+ val = readl(drvdata->base + EVDIEN);
+ val |= 1 << ep->epnum;
+ writel(val, drvdata->base + EVDIEN);
+
+ return 0;
+}
+
+static int gxp_udc_ep_disable(struct usb_ep *_ep)
+{
+ struct gxp_udc_ep *ep;
+ struct gxp_udc_req *req;
+ unsigned long flags;
+ u32 val;
+
+ if (!_ep)
+ return -EINVAL;
+
+ if (!_ep->desc)
+ return -EINVAL;
+
+ _ep->desc = NULL;
+
+ ep = container_of(_ep, struct gxp_udc_ep, ep);
+ ep->stall = false;
+ ep->wedged = false;
+
+ pr_debug("%s vdev%d ep%d\n",
+ __func__, ep->drvdata->vdevnum, ep->epnum);
+ spin_lock_irqsave(&ep->drvdata->lock, flags);
+
+ /* clean queue */
+ while (!list_empty(&ep->queue)) {
+ req = list_entry(ep->queue.next, struct gxp_udc_req, queue);
+ gxp_udc_ep_dequeue(&ep->ep, &req->req);
+ }
+
+ /* disable ep */
+ val = readl(ep->base + EVDEPCC);
+ val &= ~EVDEPCC_EPENABLE;
+ writel(val, ep->base + EVDEPCC);
+
+ /* disable interrupt */
+ val = readl(ep->drvdata->base + EVDIEN);
+ val &= ~(1 << ep->epnum);
+ writel(val, ep->drvdata->base + EVDIEN);
+
+ spin_unlock_irqrestore(&ep->drvdata->lock, flags);
+
+ return 0;
+}
+
+static int gxp_udc_ep_set_halt_and_wedge(struct usb_ep *_ep,
+ int value, int wedge)
+{
+ struct gxp_udc_ep *ep;
+ struct gxp_udc_req *req;
+ u32 val;
+
+ if (!_ep)
+ return -EINVAL;
+
+ ep = container_of(_ep, struct gxp_udc_ep, ep);
+
+ pr_debug("vdev%d ep%d gxp_udc_ep_set_halt(%d)\n",
+ ep->drvdata->vdevnum, ep->epnum, value);
+
+ val = readl(ep->base + EVDEPCC);
+
+ /* value = 1, halt on endpoint. 0, clear halt */
+ if (value) {
+ ep->stall = true;
+ if (wedge)
+ ep->wedged = true;
+
+ val |= EVDEPCC_STALL;
+ writel(val, ep->base + EVDEPCC);
+ } else {
+ ep->stall = false;
+ ep->wedged = false;
+
+ val &= ~EVDEPCC_STALL;
+ val |= EVDEPCC_DT_RESET;
+ writel(val, ep->base + EVDEPCC);
+
+ /*
+ * resume dma if it is in endpoint and there is
+ * request in queue when clear halt
+ */
+ if (ep->is_in && !list_empty(&ep->queue) && !value) {
+ req = list_entry(ep->queue.next,
+ struct gxp_udc_req, queue);
+ if (req)
+ gxp_udc_start_dma(ep, req);
+ }
+ }
+
+ return 0;
+}
+
+static int gxp_udc_ep_set_halt(struct usb_ep *_ep, int value)
+{
+ struct gxp_udc_ep *ep;
+
+ ep = container_of(_ep, struct gxp_udc_ep, ep);
+
+ pr_debug("%s: vdev%d ep%d value:%d\n",
+ __func__, ep->drvdata->vdevnum, ep->epnum, value);
+ return gxp_udc_ep_set_halt_and_wedge(_ep, value, 0);
+}
+
+static int gxp_udc_ep_set_wedge(struct usb_ep *_ep)
+{
+ struct gxp_udc_ep *ep;
+
+ ep = container_of(_ep, struct gxp_udc_ep, ep);
+
+ pr_debug("%s: vdev%d ep%d\n",
+ __func__, ep->drvdata->vdevnum, ep->epnum);
+ return gxp_udc_ep_set_halt_and_wedge(_ep, 1, 1);
+}
+
+static const struct usb_ep_ops gxp_udc_ep_ops = {
+ .enable = gxp_udc_ep_enable,
+ .disable = gxp_udc_ep_disable,
+
+ .alloc_request = gxp_udc_ep_alloc_request,
+ .free_request = gxp_udc_ep_free_request,
+
+ .queue = gxp_udc_ep_queue,
+ .dequeue = gxp_udc_ep_dequeue,
+
+ .set_halt = gxp_udc_ep_set_halt,
+ .set_wedge = gxp_udc_ep_set_wedge,
+};
+
+static int gxp_udc_connect(struct gxp_udc_drvdata *drvdata)
+{
+ unsigned int val;
+ unsigned char femap;
+ int i;
+ int j;
+
+ /* clear event before enable int */
+ val = readl(drvdata->base + EVDISTAT);
+ val |= EVDISTAT_MASK;
+ val |= (EVDISTAT_SETUP_DAT_RCV | EVDISTAT_PORTEN_CHANGE | EVDISTAT_PORTRST);
+ writel(val, drvdata->base + EVDISTAT);
+
+ /* ep0 init */
+ gxp_udc_ep_init(&drvdata->ep[0], USB_ENDPOINT_XFER_CONTROL, EP_IN, 64);
+
+ /* enable interrupt */
+ val = readl(drvdata->base + EVDIEN);
+ val |= EVDIEN_MASK;
+ writel(val, drvdata->base + EVDIEN);
+
+ /* enable vdevice interrupt */
+ regmap_update_bits(drvdata->udcg_map, EVGIEN, BIT(drvdata->vdevnum),
+ BIT(drvdata->vdevnum));
+
+ /* initial flex ep */
+ for (j = 1; j < drvdata->fepnum + 1; j++) {
+ for (i = 0; i < GXP_UDC_MAX_NUM_FLEX_EP; i++) {
+ femap = gxp_udc_femap_read(drvdata, i);
+ /* if the flex ep is free (ep==0) */
+ if (!(femap & EVFEMAP_EPNUM_MASK)) {
+ femap &= ~EVFEMAP_EPNUM_MASK;
+ femap |= EVFEMAP_EPNUM(j);
+ femap &= ~EVFEMAP_VDEVNUM_MASK;
+ femap |= EVFEMAP_VDEVNUM(drvdata->vdevnum);
+ gxp_udc_femap_write(drvdata, i, femap);
+ break;
+ }
+ }
+ if (i == GXP_UDC_MAX_NUM_FLEX_EP) {
+ dev_err(&drvdata->pdev->dev,
+ "vdev%d cannot find a free flex ep\n",
+ drvdata->vdevnum);
+ return -ENODEV;
+ }
+ }
+
+ /* set connect bit, EVDCS */
+ val = readl(drvdata->base + EVDCS);
+
+ val |= EVDCS_CONNECT;
+ writel(val, drvdata->base + EVDCS);
+
+ return 0;
+}
+
+static int gxp_udc_disconnect(struct gxp_udc_drvdata *drvdata)
+{
+ unsigned int val;
+ unsigned char femap;
+ int i;
+
+ /* disable interrupt */
+ val = readl(drvdata->base + EVDIEN);
+ val &= ~EVDIEN_MASK;
+ writel(val, drvdata->base + EVDIEN);
+
+ /* disable vdevice interrupt */
+ regmap_update_bits(drvdata->udcg_map, EVGIEN, BIT(drvdata->vdevnum), 0);
+
+ /* clear connect bit, EVDCS */
+ val = readl(drvdata->base + EVDCS);
+ val &= ~EVDCS_CONNECT;
+ writel(val, drvdata->base + EVDCS);
+
+ /* free flex ep */
+ for (i = 0; i < GXP_UDC_MAX_NUM_FLEX_EP; i++) {
+ femap = gxp_udc_femap_read(drvdata, i);
+ if (EVFEMAP_VDEVNUM(drvdata->vdevnum) ==
+ (femap & EVFEMAP_VDEVNUM_MASK)) {
+ gxp_udc_femap_write(drvdata, i, 0);
+ }
+ }
+
+ return 0;
+}
+
+static int gxp_udc_start(struct usb_gadget *gadget,
+ struct usb_gadget_driver *driver)
+{
+ struct gxp_udc_drvdata *drvdata;
+
+ if (!gadget)
+ return -ENODEV;
+
+ drvdata = container_of(gadget, struct gxp_udc_drvdata, gadget);
+ spin_lock(&drvdata->lock);
+
+ if (drvdata->driver) {
+ dev_err(&drvdata->pdev->dev,
+ "vdev%d start failed driver!=NULL\n", drvdata->vdevnum);
+
+ spin_unlock(&drvdata->lock);
+ return -EBUSY;
+ }
+
+ drvdata->deffer_set_address = false;
+
+ /* hook up the driver */
+ driver->driver.bus = NULL;
+ drvdata->driver = driver;
+
+ gxp_udc_connect(drvdata);
+
+ spin_unlock(&drvdata->lock);
+ return 0;
+}
+
+static int gxp_udc_stop(struct usb_gadget *gadget)
+{
+ struct gxp_udc_drvdata *drvdata;
+
+ if (!gadget)
+ return -ENODEV;
+
+ drvdata = container_of(gadget, struct gxp_udc_drvdata, gadget);
+ spin_lock(&drvdata->lock);
+
+ gxp_udc_disconnect(drvdata);
+ drvdata->gadget.speed = USB_SPEED_UNKNOWN;
+ drvdata->driver = NULL;
+
+ spin_unlock(&drvdata->lock);
+ return 0;
+}
+
+static const struct usb_gadget_ops gxp_udc_gadget_ops = {
+ .udc_start = gxp_udc_start,
+ .udc_stop = gxp_udc_stop,
+};
+
+static void gxp_udc_out_0byte_status(struct gxp_udc_ep *ep)
+{
+ u32 evdepcc;
+
+ /* out */
+ evdepcc = readl(ep->base + EVDEPCC);
+ evdepcc &= ~EVDEPCC_CFGINOUT;
+ writel(evdepcc, ep->base + EVDEPCC);
+
+ /* 0 len */
+ writel(0, ep->base + EVDEPLEN);
+}
+
+static void gxp_udc_in_0byte_status(struct gxp_udc_ep *ep)
+{
+ u32 evdepcc;
+
+ /* in */
+ evdepcc = readl(ep->base + EVDEPCC);
+ evdepcc |= EVDEPCC_CFGINOUT;
+ writel(evdepcc, ep->base + EVDEPCC);
+
+ /* 0 len */
+ writel(0, ep->base + EVDEPLEN);
+}
+
+static int gxp_udc_ep_nuke(struct gxp_udc_ep *ep)
+{
+ u32 evdepcc;
+ struct gxp_udc_req *req;
+
+ if (!ep)
+ return -EINVAL;
+
+ /* disarm dma */
+ evdepcc = readl(ep->base + EVDEPCC);
+ evdepcc |= EVDEPCC_DISARMCMD;
+ writel(evdepcc, ep->base + EVDEPCC);
+
+ while (!list_empty(&ep->queue)) {
+ req = list_first_entry(&ep->queue, struct gxp_udc_req, queue);
+ gxp_udc_ep_dequeue(&ep->ep, &req->req);
+ }
+
+ return 0;
+}
+
+static int gxp_udc_set_feature(struct gxp_udc_drvdata *drvdata,
+ struct usb_ctrlrequest *ctrl)
+{
+ struct gxp_udc_ep *ep;
+
+ if (ctrl->bRequestType == USB_RECIP_ENDPOINT) {
+ ep = &drvdata->ep[ctrl->wIndex & USB_ENDPOINT_NUMBER_MASK];
+ if (le16_to_cpu(ctrl->wValue) == USB_ENDPOINT_HALT) {
+ gxp_udc_ep_set_halt(&ep->ep, 1);
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static int gxp_udc_clear_feature(struct gxp_udc_drvdata *drvdata,
+ struct usb_ctrlrequest *ctrl)
+{
+ struct gxp_udc_ep *ep;
+
+ if (ctrl->bRequestType == USB_RECIP_ENDPOINT) {
+ ep = &drvdata->ep[ctrl->wIndex & USB_ENDPOINT_NUMBER_MASK];
+ if (le16_to_cpu(ctrl->wValue) == USB_ENDPOINT_HALT) {
+ if (ep->wedged)
+ gxp_udc_ep_set_halt(&ep->ep, 0);
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static int gxp_udc_get_status(struct gxp_udc_drvdata *drvdata,
+ struct usb_ctrlrequest *ctrl)
+{
+ int ret;
+ u32 evdepcc;
+
+ switch (ctrl->bRequestType & USB_RECIP_MASK) {
+ case USB_RECIP_DEVICE:
+ /* fill the req to send */
+ drvdata->ep0_data = 1;
+ drvdata->ep0_req->length = 2;
+ drvdata->ep0_req->buf = &drvdata->ep0_data;
+ gxp_udc_ep_queue(&drvdata->ep[0].ep, drvdata->ep0_req,
+ GFP_KERNEL);
+ ret = 1;
+ break;
+
+ case USB_RECIP_ENDPOINT:
+ evdepcc = readl(drvdata->ep[ctrl->wIndex &
+ USB_ENDPOINT_NUMBER_MASK].base + EVDEPCC);
+ /* fill the req to send */
+ if (evdepcc & EVDEPCC_STALL)
+ drvdata->ep0_data = 1;
+ else
+ drvdata->ep0_data = 0;
+ drvdata->ep0_req->length = 2;
+ drvdata->ep0_req->buf = &drvdata->ep0_data;
+ gxp_udc_ep_queue(&drvdata->ep[0].ep, drvdata->ep0_req,
+ GFP_KERNEL);
+ ret = 1;
+ break;
+
+ default:
+ ret = 0;
+ break;
+ }
+
+ return ret;
+}
+
+static void gxp_udc_handle_ep0_int(struct gxp_udc_ep *ep)
+{
+ struct gxp_udc_drvdata *drvdata = ep->drvdata;
+ struct gxp_udc_req *req;
+ u32 evdepsts;
+ u32 evdepcc;
+
+ evdepcc = readl(ep->base + EVDEPCC);
+ evdepsts = readl(ep->base + EVDEPSTS);
+ writel(evdepsts, ep->base + EVDEPSTS); /* write 1 to clear status */
+
+ if (evdepsts & EVDEPSTS_STALLDET) {
+ pr_debug("ep%d get stalldet int\n", ep->epnum);
+ evdepcc &= ~EVDEPCC_STALL;
+ evdepcc |= EVDEPCC_DT_RESET;
+ writel(evdepcc, ep->base + EVDEPCC);
+ }
+
+ if (evdepsts & EVDEPSTS_DONE || evdepsts & EVDEPSTS_SHORT) {
+ if (drvdata->deffer_set_address) {
+ writel(drvdata->devaddr, drvdata->base + EVDADDR);
+ drvdata->deffer_set_address = false;
+ } else if (!list_empty(&ep->queue)) {
+ req = list_entry(ep->queue.next,
+ struct gxp_udc_req, queue);
+ if (gxp_udc_done_dma(ep, req)) {
+ /* req is done */
+ list_del_init(&req->queue);
+ usb_gadget_unmap_request(&ep->drvdata->gadget,
+ &req->req, ep->is_in);
+ if (req->req.complete) {
+ req->req.status = 0;
+ usb_gadget_giveback_request(&ep->ep,
+ &req->req);
+ }
+
+ if (!list_empty(&ep->queue)) {
+ /* process next req in queue */
+ req = list_entry(ep->queue.next,
+ struct gxp_udc_req,
+ queue);
+ gxp_udc_start_dma(ep, req);
+ pr_debug("ep0 queue is not empty after done a req!!\n");
+ } else {
+ /* last req completed -> status stage */
+ if (evdepcc & EVDEPCC_CFGINOUT) {
+ /* data in -> out status */
+ gxp_udc_out_0byte_status(ep);
+ } else {
+ /* data out ->in status */
+ gxp_udc_in_0byte_status(ep);
+ }
+ }
+ }
+ }
+ }
+}
+
+static void gxp_udc_handle_ep_int(struct gxp_udc_ep *ep)
+{
+ struct gxp_udc_req *req;
+ u32 evdepsts;
+ u32 evdepcc;
+
+ evdepsts = readl(ep->base + EVDEPSTS);
+ writel(evdepsts, ep->base + EVDEPSTS); /* write 1 to clear status */
+
+ if (evdepsts & EVDEPSTS_STALLDET) {
+ pr_debug("ep%d get stalldet int\n", ep->epnum);
+ evdepcc = readl(ep->base + EVDEPCC);
+ evdepcc &= ~EVDEPCC_STALL;
+ evdepcc |= EVDEPCC_DT_RESET;
+ writel(evdepcc, ep->base + EVDEPCC);
+ }
+
+ if (evdepsts & EVDEPSTS_DONE || evdepsts & EVDEPSTS_SHORT) {
+ if (evdepsts & EVDEPSTS_SHORT)
+ pr_debug("ep%d get short int\n", ep->epnum);
+ if (evdepsts & EVDEPSTS_DONE)
+ pr_debug("ep%d get done int\n", ep->epnum);
+
+ if (!list_empty(&ep->queue)) {
+ req = list_entry(ep->queue.next,
+ struct gxp_udc_req, queue);
+ if (gxp_udc_done_dma(ep, req)) {
+ /* req is done */
+ list_del_init(&req->queue);
+ usb_gadget_unmap_request(&ep->drvdata->gadget,
+ &req->req, ep->is_in);
+ if (req->req.complete) {
+ req->req.status = 0;
+ usb_gadget_giveback_request(&ep->ep,
+ &req->req);
+ }
+
+ if (!list_empty(&ep->queue)) {
+ /* process next req in queue */
+ req = list_entry(ep->queue.next,
+ struct gxp_udc_req,
+ queue);
+ gxp_udc_start_dma(ep, req);
+ }
+ }
+ }
+ }
+}
+
+static void gxp_udc_handle_setup_data_rcv(struct gxp_udc_drvdata *drvdata)
+{
+ struct usb_ctrlrequest ctrl = {0};
+ u32 *ptr;
+ u32 val;
+ int handled = 0;
+ int ret;
+
+ ptr = (u32 *)&ctrl;
+
+ ptr[0] = readl(drvdata->base + EVDSETUPL);
+ ptr[1] = readl(drvdata->base + EVDSETUPH);
+
+ if ((ctrl.bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD) {
+ switch (ctrl.bRequest) {
+ case USB_REQ_SET_ADDRESS:
+ pr_debug("vdev%d get setup data USB_REQ_SET_ADDRESS\n",
+ drvdata->vdevnum);
+ /*
+ * this request is unique in that the device does
+ * not set its address until after the completion
+ * of the status stage.(ref: usb nutshell)
+ */
+ drvdata->devaddr = ctrl.wValue & 0x7f;
+ drvdata->deffer_set_address = true;
+ gxp_udc_in_0byte_status(&drvdata->ep[0]);
+ handled = 1;
+ break;
+
+ case USB_REQ_SET_FEATURE:
+ pr_debug("vdev%d get setup data USB_REQ_SET_FEATURE\n",
+ drvdata->vdevnum);
+ handled = gxp_udc_set_feature(drvdata, &ctrl);
+ break;
+
+ case USB_REQ_CLEAR_FEATURE:
+ pr_debug("vdev%d get setup data USB_REQ_CLEAR_FEATURE\n",
+ drvdata->vdevnum);
+ handled = gxp_udc_clear_feature(drvdata, &ctrl);
+ break;
+
+ case USB_REQ_GET_STATUS:
+ pr_debug("vdev%d get setup data USB_REQ_GET_STATUS\n",
+ drvdata->vdevnum);
+ handled = gxp_udc_get_status(drvdata, &ctrl);
+ break;
+
+ default:
+ handled = 0;
+ break;
+ }
+ }
+
+ /*
+ * pass the request to upper layer driver code(composite.c)
+ * if it is not handled by the code above.
+ */
+ if (drvdata->driver && !handled) {
+ ret = drvdata->driver->setup(&drvdata->gadget, &ctrl);
+ if (ret < 0) {
+ dev_err(&drvdata->pdev->dev,
+ "setup failed, return %d\n", ret);
+ /* set ep0 to stall */
+ val = readl(drvdata->ep[0].base + EVDEPCC);
+ val |= EVDEPCC_STALL;
+ writel(val, drvdata->ep[0].base + EVDEPCC);
+ return;
+ }
+ }
+}
+
+static irqreturn_t gxp_udc_irq(int irq, void *_drvdata)
+{
+ struct gxp_udc_drvdata *drvdata = _drvdata;
+
+ u32 evgistat;
+ u32 evdistat;
+ u32 evdcs;
+ u32 i;
+
+ regmap_read(drvdata->udcg_map, EVGISTAT, &evgistat);
+ if (evgistat & (1 << drvdata->vdevnum)) {
+ evdistat = readl(drvdata->base + EVDISTAT);
+ while (evdistat & EVDISTAT_MASK) {
+ pr_debug("vdev%d get int = 0x%08x\n",
+ drvdata->vdevnum, evdistat);
+
+ if (evdistat & EVDISTAT_PORTEN_CHANGE) {
+ evdcs = readl(drvdata->base + EVDCS);
+ drvdata->gadget.speed =
+ (evdcs & EVDCS_PORTENABLESTS) ?
+ USB_SPEED_HIGH : USB_SPEED_UNKNOWN;
+
+ pr_debug("vdev%d get port enable change int speed = %d\n",
+ drvdata->vdevnum,
+ drvdata->gadget.speed);
+
+ /* write 1 to clear */
+ writel(EVDISTAT_PORTEN_CHANGE,
+ drvdata->base + EVDISTAT);
+ }
+
+ if (evdistat & EVDISTAT_PORTRST) {
+ pr_debug("vdev%d get port rst int\n",
+ drvdata->vdevnum);
+ writel(0, drvdata->base + EVDADDR);
+
+ for (i = 0; i < drvdata->fepnum + 1; i++)
+ gxp_udc_ep_nuke(&drvdata->ep[i]);
+
+ usb_gadget_udc_reset(&drvdata->gadget,
+ drvdata->driver);
+
+ /* write 1 to clear */
+ writel(EVDISTAT_PORTRST,
+ drvdata->base + EVDISTAT);
+ }
+
+ if (evdistat & EVDISTAT_SETUP_DAT_RCV) {
+ pr_debug("vdev%d get setup data rcv int\n",
+ drvdata->vdevnum);
+ gxp_udc_handle_setup_data_rcv(drvdata);
+ /* write 1 to clear */
+ writel(EVDISTAT_SETUP_DAT_RCV,
+ drvdata->base + EVDISTAT);
+ }
+ if (evdistat & 0x01)
+ gxp_udc_handle_ep0_int(&drvdata->ep[0]);
+
+ for (i = 1; i < GXP_UDC_MAX_NUM_EP; i++)
+ if (evdistat & (1 << i))
+ gxp_udc_handle_ep_int(&drvdata->ep[i]);
+
+ evdistat = readl(drvdata->base + EVDISTAT);
+ }
+ return IRQ_HANDLED;
+ }
+ return IRQ_NONE;
+}
+
+static int gxp_udc_init(struct gxp_udc_drvdata *drvdata)
+{
+ int i;
+ struct gxp_udc_ep *ep;
+
+ /* Disable device interrupt */
+ writel(0, drvdata->base + EVDIEN);
+
+ drvdata->gadget.max_speed = USB_SPEED_HIGH;
+ drvdata->gadget.speed = USB_SPEED_UNKNOWN;
+ drvdata->gadget.ops = &gxp_udc_gadget_ops;
+ drvdata->gadget.name = gxp_udc_name[drvdata->vdevnum];
+ drvdata->gadget.is_otg = 0;
+ drvdata->gadget.is_a_peripheral = 0;
+
+ drvdata->gadget.ep0 = &drvdata->ep[0].ep;
+ drvdata->ep0_req = gxp_udc_ep_alloc_request(&drvdata->ep[0].ep,
+ GFP_KERNEL);
+
+ INIT_LIST_HEAD(&drvdata->gadget.ep_list);
+
+ for (i = 0; i < drvdata->fepnum + 1; i++) {
+ ep = &drvdata->ep[i];
+
+ ep->drvdata = drvdata;
+
+ INIT_LIST_HEAD(&ep->queue);
+ INIT_LIST_HEAD(&ep->ep.ep_list);
+ if (i > 0)
+ list_add_tail(&ep->ep.ep_list,
+ &drvdata->gadget.ep_list);
+
+ usb_ep_set_maxpacket_limit(&ep->ep, (i > 0) ? 512 : 64);
+ ep->epnum = i;
+ ep->ep.name = gxp_udc_ep_name[i];
+ ep->ep.ops = &gxp_udc_ep_ops;
+ ep->base = drvdata->base + 0x20 * (i + 1);
+ if (i == 0) {
+ ep->ep.caps.type_control = true;
+ } else {
+ ep->ep.caps.type_iso = true;
+ ep->ep.caps.type_bulk = true;
+ ep->ep.caps.type_int = true;
+ }
+
+ ep->ep.caps.dir_in = true;
+ ep->ep.caps.dir_out = true;
+ }
+
+ return 0;
+}
+
+static void gxp_udcg_init(struct gxp_udc_drvdata *drvdata)
+{
+ int i;
+
+ if (!udcg_init_done) {
+ /* disable interrupt */
+ regmap_write(drvdata->udcg_map, EVGIEN, 0);
+
+ /* disable all flex ep map */
+ for (i = 0; i < GXP_UDC_MAX_NUM_FLEX_EP; i += 4)
+ regmap_write(drvdata->udcg_map, EVFEMAP + i, 0);
+
+ udcg_init_done = true;
+ }
+}
+
+static int gxp_udc_probe(struct platform_device *pdev)
+{
+ struct resource *res;
+ struct gxp_udc_drvdata *drvdata;
+ int ret;
+ u32 vdevnum;
+ u32 fepnum;
+
+ drvdata = devm_kzalloc(&pdev->dev, sizeof(struct gxp_udc_drvdata),
+ GFP_KERNEL);
+ if (!drvdata)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, drvdata);
+ drvdata->pdev = pdev;
+
+ spin_lock_init(&drvdata->lock);
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ drvdata->base = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(drvdata->base))
+ return PTR_ERR(drvdata->base);
+
+ ret = platform_get_irq(pdev, 0);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "unable to obtain IRQ number\n");
+ return ret;
+ }
+ drvdata->irq = ret;
+
+ if (of_property_read_u32(pdev->dev.of_node, "vdevnum", &vdevnum)) {
+ dev_err(&pdev->dev,
+ "property vdevnum is undefined\n");
+ return -EINVAL;
+ }
+
+ if (vdevnum > GXP_UDC_MAX_NUM_VDEVICE) {
+ dev_err(&pdev->dev,
+ "property vdevnum(%d) is invalid\n", vdevnum);
+ return -EINVAL;
+ }
+ drvdata->vdevnum = vdevnum;
+
+ if (of_property_read_u32(pdev->dev.of_node, "fepnum", &fepnum)) {
+ dev_err(&pdev->dev, "property fepnum is undefined\n");
+ return -EINVAL;
+ }
+
+ if (fepnum > GXP_UDC_MAX_NUM_FLEX_EP_PER_VDEV) {
+ dev_err(&pdev->dev, "property fepnum(%d) is invalid\n", fepnum);
+ return -EINVAL;
+ }
+ drvdata->fepnum = fepnum;
+
+ drvdata->udcg_map = syscon_regmap_lookup_by_phandle(pdev->dev.of_node,
+ "hpe,syscon-phandle");
+ if (IS_ERR(drvdata->udcg_map)) {
+ dev_err(&pdev->dev, "failed to map udcg_handle\n");
+ return -ENODEV;
+ }
+ gxp_udcg_init(drvdata);
+
+ gxp_udc_init(drvdata);
+ ret = usb_add_gadget_udc(&pdev->dev, &drvdata->gadget);
+ if (ret) {
+ dev_err(&pdev->dev, "add gadget failed\n");
+ return ret;
+ }
+
+ ret = devm_request_irq(&pdev->dev,
+ drvdata->irq,
+ gxp_udc_irq,
+ IRQF_SHARED,
+ gxp_udc_name[vdevnum],
+ drvdata);
+
+ if (ret < 0) {
+ dev_err(&pdev->dev, "irq request failed\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct of_device_id gxp_udc_of_match[] = {
+ { .compatible = "hpe,gxp-udc" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, gxp_udc_of_match);
+
+static struct platform_driver gxp_udc_driver = {
+ .driver = {
+ .name = "gxp-udc",
+ .of_match_table = of_match_ptr(gxp_udc_of_match),
+ },
+ .probe = gxp_udc_probe,
+};
+module_platform_driver(gxp_udc_driver);
+
+MODULE_AUTHOR("Richard Yu <richard.yu@xxxxxxx>");
+MODULE_DESCRIPTION("HPE GXP UDC Driver");
+MODULE_LICENSE("GPL");
--
2.17.1