diff mbox series

[v1,2/3] usb: gadget: udc: gxp-udc: add HPE GXP USB support

Message ID 20230706215910.78772-3-richard.yu@hpe.com
State Superseded
Headers show
Series Add USB driver for HPE GXP Architecture | expand

Commit Message

Yu, Richard July 6, 2023, 9:59 p.m. UTC
From: Richard Yu <richard.yu@hpe.com>

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

Signed-off-by: Richard Yu <richard.yu@hpe.com>
---
 drivers/usb/gadget/udc/Kconfig   |    6 +
 drivers/usb/gadget/udc/Makefile  |    1 +
 drivers/usb/gadget/udc/gxp-udc.c | 1401 ++++++++++++++++++++++++++++++
 3 files changed, 1408 insertions(+)
 create mode 100644 drivers/usb/gadget/udc/gxp-udc.c

Comments

Alan Stern July 7, 2023, 2:16 a.m. UTC | #1
On Thu, Jul 06, 2023 at 04:59:09PM -0500, richard.yu@hpe.com wrote:
> From: Richard Yu <richard.yu@hpe.com>
> 
> The HPE GXP vEHCI controller presents a four port EHCI compatible PCI
> function to host software. Each vEHCI port is logically connected to a
> corresponding set of virtual device registers.

What makes the vEHCI controller virtual?  Presenting a "PCI function"
certainly seems to indicate it is a physical device, indeed, a PCI
device.

> 
> Signed-off-by: Richard Yu <richard.yu@hpe.com>
> ---
>  drivers/usb/gadget/udc/Kconfig   |    6 +
>  drivers/usb/gadget/udc/Makefile  |    1 +
>  drivers/usb/gadget/udc/gxp-udc.c | 1401 ++++++++++++++++++++++++++++++
>  3 files changed, 1408 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 83cae6bb12eb..c01eb2a2c7db 100644
> --- a/drivers/usb/gadget/udc/Kconfig
> +++ b/drivers/usb/gadget/udc/Kconfig
> @@ -461,6 +461,12 @@ config USB_ASPEED_UDC
>  	  dynamically linked module called "aspeed_udc" 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

Now hang on a second.  What sort of driver is this patch adding support 
for: a GXP vEHCI controller driver or a GXP UDC controller driver?  The 
patch description says the first, but the code says the second.

It sounds like this thing actually is a PCI device that appears to the 
OS as an actual EHCI controller, but with virtual (rather than physical) 
downstream ports, and it includes a virtual UDC for each port.  As such, 
it requires a driver for the virtual UDCs, which is what this patch 
provides.  (No new driver is needed for the EHCI controller part, since 
the kernel already has an EHCI driver.)

Is that a correct description?  And if it is, what is the purpose of 
this device?  To act as a testing ground for gadget drivers?

Alan Stern
Greg Kroah-Hartman July 7, 2023, 6:07 a.m. UTC | #2
On Thu, Jul 06, 2023 at 04:59:09PM -0500, richard.yu@hpe.com wrote:
> diff --git a/drivers/usb/gadget/udc/Kconfig b/drivers/usb/gadget/udc/Kconfig
> index 83cae6bb12eb..c01eb2a2c7db 100644
> --- a/drivers/usb/gadget/udc/Kconfig
> +++ b/drivers/usb/gadget/udc/Kconfig
> @@ -461,6 +461,12 @@ config USB_ASPEED_UDC
>  	  dynamically linked module called "aspeed_udc" 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
> +

You need a real help text here please fix this up.

thanks,

greg k-h
Krzysztof Kozlowski July 7, 2023, 8:35 a.m. UTC | #3
On 06/07/2023 23:59, richard.yu@hpe.com wrote:
> From: Richard Yu <richard.yu@hpe.com>
> 
> The HPE GXP vEHCI controller presents a four port EHCI compatible PCI
> function to host software. Each vEHCI port is logically connected to a
> corresponding set of virtual device registers.
> 
> Signed-off-by: Richard Yu <richard.yu@hpe.com>
> ---
>  drivers/usb/gadget/udc/Kconfig   |    6 +
>  drivers/usb/gadget/udc/Makefile  |    1 +
>  drivers/usb/gadget/udc/gxp-udc.c | 1401 ++++++++++++++++++++++++++++++
>  3 files changed, 1408 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 83cae6bb12eb..c01eb2a2c7db 100644
> --- a/drivers/usb/gadget/udc/Kconfig
> +++ b/drivers/usb/gadget/udc/Kconfig
> @@ -461,6 +461,12 @@ config USB_ASPEED_UDC
>  	  dynamically linked module called "aspeed_udc" and force all
>  	  gadget drivers to also be dynamically linked.

...

> +
> +static void gxp_udc_dev_release(struct device *dev)
> +{
> +	kfree(dev);
> +}
> +
> +int gxp_udc_init_dev(struct gxp_udcg_drvdata *udcg, unsigned int idx)
> +{
> +	struct gxp_udc_drvdata *drvdata;
> +	struct device *parent = &udcg->pdev->dev;
> +	int rc;
> +
> +	drvdata = devm_kzalloc(parent, sizeof(struct gxp_udc_drvdata),
> +			       GFP_KERNEL);

sizeof(*...)

> +	if (!drvdata)
> +		return -ENOMEM;
> +
> +	udcg->udc_drvdata[idx] = drvdata;
> +	drvdata->pdev = udcg->pdev;

Why do you store platform_device? It's usually useless because dev is used.

> +	drvdata->vdevnum = idx;
> +	drvdata->base = udcg->base + 0x1000 * idx;
> +	drvdata->udcg_map = udcg->udcg_map;
> +	drvdata->irq = udcg->irq;
> +
> +	/*
> +	 * we setup USB device can have up to 4 endpoints besides control
> +	 * endpoint 0.
> +	 */
> +	drvdata->fepnum = min_t(u32, udcg->max_fepnum, 4);
> +
> +	drvdata->vdevnum = idx;
> +
> +	/*
> +	 * The UDC core really needs us to have separate and uniquely
> +	 * named "parent" devices for each port so we create a sub device
> +	 * here for that purpose
> +	 */
> +	drvdata->port_dev = kzalloc(sizeof(*drvdata->port_dev), GFP_KERNEL);
> +	if (!drvdata->port_dev) {
> +		rc = -ENOMEM;
> +		goto fail_alloc;
> +	}
> +	device_initialize(drvdata->port_dev);
> +	drvdata->port_dev->release = gxp_udc_dev_release;
> +	drvdata->port_dev->parent = parent;
> +	dev_set_name(drvdata->port_dev, "%s:p%d", dev_name(parent), idx + 1);
> +
> +	/* DMA setting */
> +	drvdata->port_dev->dma_mask = parent->dma_mask;
> +	drvdata->port_dev->coherent_dma_mask = parent->coherent_dma_mask;
> +	drvdata->port_dev->bus_dma_limit = parent->bus_dma_limit;
> +	drvdata->port_dev->dma_range_map = parent->dma_range_map;
> +	drvdata->port_dev->dma_parms = parent->dma_parms;
> +	drvdata->port_dev->dma_pools = parent->dma_pools;
> +
> +	rc = device_add(drvdata->port_dev);
> +	if (rc)
> +		goto fail_add;
> +
> +	/* Populate gadget */
> +	gxp_udc_init(drvdata);
> +
> +	rc = usb_add_gadget_udc(drvdata->port_dev, &drvdata->gadget);
> +	if (rc != 0) {
> +		dev_err(drvdata->port_dev, "add gadget failed\n");

return dev_err_probe

> +		goto fail_udc;
> +	}
> +	rc = devm_request_irq(drvdata->port_dev,
> +			      drvdata->irq,
> +			      gxp_udc_irq,
> +			      IRQF_SHARED,
> +			      gxp_udc_name[drvdata->vdevnum],
> +			      drvdata);

Shared interrupt usually does not work with devm() and causes later
issues. You need to triple check your unbind and error paths, because
usually this just does not work. Usually we just don't allow devm for
this case. If you are sure this is 100% correct, it could stay...

> +
> +	if (rc < 0) {
> +		dev_err(drvdata->port_dev, "irq request failed\n");

return dev_err_probe

> +		goto fail_udc;
> +	}
> +
> +	return 0;
> +
> +fail_udc:
> +	device_del(drvdata->port_dev);
> +fail_add:
> +	put_device(drvdata->port_dev);
> +fail_alloc:
> +	kfree(drvdata);

Are you sure you tested it? I see here double free.

> +
> +	return rc;
> +}
> +
> +static int gxp_udcg_probe(struct platform_device *pdev)
> +{
> +	struct resource *res;
> +	struct gxp_udcg_drvdata *drvdata;
> +	int ret, rc, err;
> +	u32 vdevnum;
> +	u32 fepnum, i;
> +
> +	drvdata = devm_kzalloc(&pdev->dev, sizeof(struct gxp_udcg_drvdata),

sizeof(*...)

> +			       GFP_KERNEL);
> +	platform_set_drvdata(pdev, drvdata);
> +	drvdata->pdev = pdev;

Why do you store platform_device? It's usually useless because dev is used.

> +
> +	spin_lock_init(&drvdata->lock);
> +
> +	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "udcg");
> +	drvdata->udcg_base = devm_ioremap_resource(&pdev->dev, res);

Use helper for these two.

> +	if (IS_ERR(drvdata->udcg_base)) {
> +		err = PTR_ERR(drvdata->udcg_base);

Drop

> +		return dev_err_probe(&pdev->dev, err, "failed to get udcg resource\n");
> +	}
> +
> +	drvdata->udcg_map = devm_regmap_init_mmio(&pdev->dev, drvdata->udcg_base, &regmap_cfg);
> +
> +	if (IS_ERR(drvdata->udcg_map)) {
> +		dev_err(&pdev->dev, "failed to map udcg regs");

return dev_err_probe

> +		return -ENODEV;

Wrong error code

> +	}
> +
> +	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "udc");
> +	drvdata->base = devm_ioremap_resource(&pdev->dev, res);
> +	if (IS_ERR(drvdata->base)) {
> +		err = PTR_ERR(drvdata->base);

Drop

> +		return dev_err_probe(&pdev->dev, err, "failed to get udc resource\n");
> +	}
> +
> +	ret = platform_get_irq(pdev, 0);
> +	if (ret < 0) {
> +		dev_err(&pdev->dev, "unable to obtain IRQ number\n");

return dev_err_probe

> +		return ret;
> +	}
> +	drvdata->irq = ret;
> +
> +	if (of_property_read_u32(pdev->dev.of_node, "hpe,vehci-downstream-ports", &vdevnum)) {
> +		dev_err(&pdev->dev, "property ports number is undefined\n");
> +		return -EINVAL;
> +	}
> +
> +	if (vdevnum > GXP_UDC_MAX_NUM_VDEVICE) {
> +		dev_err(&pdev->dev, "property max port number(%d) is invalid\n", vdevnum);
> +		return -EINVAL;
> +	}
> +	drvdata->max_vdevnum = vdevnum;
> +
> +	if (of_property_read_u32(pdev->dev.of_node, "hpe,vehci-generic-endpoints", &fepnum)) {
> +		dev_err(&pdev->dev, "property generic endpoints is undefined\n");
> +		return -EINVAL;

Why? Bindings stated it has default, so should not be required.

> +	}
> +
> +	if (fepnum > GXP_UDC_MAX_NUM_FLEX_EP) {
> +		dev_err(&pdev->dev, "property fepnum(%d) is invalid\n", fepnum);
> +		return -EINVAL;
> +	}
> +	drvdata->max_fepnum = fepnum;
> +
> +	gxp_udcg_init(drvdata);
> +
> +	/* Init child devices */
> +	rc = 0;
> +	for (i = 0; i < drvdata->max_vdevnum && rc == 0; i++)
> +		rc = gxp_udc_init_dev(drvdata, i);
> +
> +	return rc;
> +}
> +
> +static const struct of_device_id gxp_udc_of_match[] = {
> +	{ .compatible = "hpe,gxp-udcg" },
> +	{},
> +};
> +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),

Drop of_match_ptr(), you will have warnings here.

> +	},
> +	.probe = gxp_udcg_probe,
> +};
> +module_platform_driver(gxp_udc_driver);
> +
> +MODULE_AUTHOR("Richard Yu <richard.yu@hpe.com>");
> +MODULE_DESCRIPTION("HPE GXP vEHCI UDC Driver");
> +MODULE_LICENSE("GPL");

Best regards,
Krzysztof
Greg Kroah-Hartman July 7, 2023, 10:11 a.m. UTC | #4
On Thu, Jul 06, 2023 at 04:59:09PM -0500, richard.yu@hpe.com wrote:
> From: Richard Yu <richard.yu@hpe.com>
> 
> The HPE GXP vEHCI controller presents a four port EHCI compatible PCI
> function to host software. Each vEHCI port is logically connected to a
> corresponding set of virtual device registers.
> 
> Signed-off-by: Richard Yu <richard.yu@hpe.com>
> ---
>  drivers/usb/gadget/udc/Kconfig   |    6 +
>  drivers/usb/gadget/udc/Makefile  |    1 +
>  drivers/usb/gadget/udc/gxp-udc.c | 1401 ++++++++++++++++++++++++++++++
>  3 files changed, 1408 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 83cae6bb12eb..c01eb2a2c7db 100644
> --- a/drivers/usb/gadget/udc/Kconfig
> +++ b/drivers/usb/gadget/udc/Kconfig
> @@ -461,6 +461,12 @@ config USB_ASPEED_UDC
>  	  dynamically linked module called "aspeed_udc" 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
> +
>  source "drivers/usb/gadget/udc/aspeed-vhub/Kconfig"
>  
>  #
> diff --git a/drivers/usb/gadget/udc/Makefile b/drivers/usb/gadget/udc/Makefile
> index ee569f63c74a..63fa262f31c5 100644
> --- a/drivers/usb/gadget/udc/Makefile
> +++ b/drivers/usb/gadget/udc/Makefile
> @@ -42,3 +42,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..97d198128c06
> --- /dev/null
> +++ b/drivers/usb/gadget/udc/gxp-udc.c
> @@ -0,0 +1,1401 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/* Copyright (C) 2023 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>

Are you sure you need all of these?  I don't see any syscon_* calls in
this file.  Please audit the list of includes here and remove all
unneeded ones.

thanks,

greg k-h
Yu, Richard Aug. 1, 2023, 6:50 p.m. UTC | #5
-----Original Message-----
From: Alan Stern <stern@rowland.harvard.edu> 
Sent: Thursday, July 6, 2023 9:17 PM
To: Yu, Richard <richard.yu@hpe.com>
Cc: Verdun, Jean-Marie <verdun@hpe.com>; Hawkins, Nick <nick.hawkins@hpe.com>; gregkh@linuxfoundation.org; robh+dt@kernel.org; krzysztof.kozlowski+dt@linaro.org; conor+dt@kernel.org; linux-usb@vger.kernel.org; devicetree@vger.kernel.org; linux-kernel@vger.kernel.org
Subject: Re: [PATCH v1 2/3] usb: gadget: udc: gxp-udc: add HPE GXP USB support

On Thu, Jul 06, 2023 at 04:59:09PM -0500, richard.yu@hpe.com wrote:
>> From: Richard Yu <richard.yu@hpe.com>
>> 
>> The HPE GXP vEHCI controller presents a four port EHCI compatible PCI 
>> function to host software. Each vEHCI port is logically connected to a 
>> corresponding set of virtual device registers.

> What makes the vEHCI controller virtual?  Presenting a "PCI function"
> certainly seems to indicate it is a physical device, indeed, a PCI device.

To the host and system OS, GXP is indeed to present a physical standard 
EHCI controller. The standard Linux EHCI driver will be used. What virtual
Here are the ports. We will remove references to EHCI in code and
Documentation. It has been modeled to following ASPEEDs v-hub approach.

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

> Now hang on a second.  What sort of driver is this patch adding support
> for: a GXP vEHCI controller driver or a GXP UDC controller driver?  
> The patch description says the first, but the code says the second.

I will modify the patch description as a GXP virtual UDC driver.

> It sounds like this thing actually is a PCI device that appears to the OS as 
> an actual EHCI controller, but with virtual (rather than physical) downstream ports, 
> and it includes a virtual UDC for each port.  As such, it requires a driver for the virtual UDCs, 
> which is what this patch provides.  (No new driver is needed for the EHCI controller part, 
> since the kernel already has an EHCI driver.)

> Is that a correct description?  And if it is, what is the purpose of this device?  
> To act as a testing ground for gadget drivers?

That is a correct description. The purpose of this device is for us to create virtual USB devices,
such as virtual keyboard/ mouse using in OpenBmc KVM, virtual CD, virtual NIC...

> Alan Stern

Thank you very much for your feedback.

Richard.
diff mbox series

Patch

diff --git a/drivers/usb/gadget/udc/Kconfig b/drivers/usb/gadget/udc/Kconfig
index 83cae6bb12eb..c01eb2a2c7db 100644
--- a/drivers/usb/gadget/udc/Kconfig
+++ b/drivers/usb/gadget/udc/Kconfig
@@ -461,6 +461,12 @@  config USB_ASPEED_UDC
 	  dynamically linked module called "aspeed_udc" 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
+
 source "drivers/usb/gadget/udc/aspeed-vhub/Kconfig"
 
 #
diff --git a/drivers/usb/gadget/udc/Makefile b/drivers/usb/gadget/udc/Makefile
index ee569f63c74a..63fa262f31c5 100644
--- a/drivers/usb/gadget/udc/Makefile
+++ b/drivers/usb/gadget/udc/Makefile
@@ -42,3 +42,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..97d198128c06
--- /dev/null
+++ b/drivers/usb/gadget/udc/gxp-udc.c
@@ -0,0 +1,1401 @@ 
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (C) 2023 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 VUHCBASE	0x80400000
+#define FRINDEX		0x0000002c
+
+#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;
+
+	/* sysfs enclosure for the gadget gunk */
+	struct device *port_dev;
+
+	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;
+};
+
+struct gxp_udcg_drvdata {
+	void __iomem *base;
+	struct platform_device *pdev;
+	void __iomem *udcg_base;
+	struct regmap *udcg_map;
+	struct gxp_udc_ep ep[GXP_UDC_MAX_NUM_EP];
+
+	int irq;
+
+	u32 max_vdevnum;
+	u32 max_fepnum;
+	u32 devaddr;
+	struct gxp_udc_drvdata *udc_drvdata[GXP_UDC_MAX_NUM_VDEVICE];
+
+	/* this lock is used to protect the udcg drvdata structure access */
+	spinlock_t lock;
+};
+
+struct regmap_config regmap_cfg = {
+	.reg_bits = 32,
+	.reg_stride = 4,
+	.val_bits = 32,
+};
+
+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);
+
+	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;
+
+	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:
+			/*
+			 * 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:
+			handled = gxp_udc_set_feature(drvdata, &ctrl);
+			break;
+
+		case USB_REQ_CLEAR_FEATURE:
+			handled = gxp_udc_clear_feature(drvdata, &ctrl);
+			break;
+
+		case USB_REQ_GET_STATUS:
+			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_udcg_drvdata *drvdata)
+{
+	int i;
+
+	if (!udcg_init_done)	{
+		/* disable interrupt */
+		regmap_write(drvdata->udcg_map, EVGIEN, 0);
+		writel(0, drvdata->base + EVGIEN);
+
+		/* 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 void gxp_udc_dev_release(struct device *dev)
+{
+	kfree(dev);
+}
+
+int gxp_udc_init_dev(struct gxp_udcg_drvdata *udcg, unsigned int idx)
+{
+	struct gxp_udc_drvdata *drvdata;
+	struct device *parent = &udcg->pdev->dev;
+	int rc;
+
+	drvdata = devm_kzalloc(parent, sizeof(struct gxp_udc_drvdata),
+			       GFP_KERNEL);
+	if (!drvdata)
+		return -ENOMEM;
+
+	udcg->udc_drvdata[idx] = drvdata;
+	drvdata->pdev = udcg->pdev;
+	drvdata->vdevnum = idx;
+	drvdata->base = udcg->base + 0x1000 * idx;
+	drvdata->udcg_map = udcg->udcg_map;
+	drvdata->irq = udcg->irq;
+
+	/*
+	 * we setup USB device can have up to 4 endpoints besides control
+	 * endpoint 0.
+	 */
+	drvdata->fepnum = min_t(u32, udcg->max_fepnum, 4);
+
+	drvdata->vdevnum = idx;
+
+	/*
+	 * The UDC core really needs us to have separate and uniquely
+	 * named "parent" devices for each port so we create a sub device
+	 * here for that purpose
+	 */
+	drvdata->port_dev = kzalloc(sizeof(*drvdata->port_dev), GFP_KERNEL);
+	if (!drvdata->port_dev) {
+		rc = -ENOMEM;
+		goto fail_alloc;
+	}
+	device_initialize(drvdata->port_dev);
+	drvdata->port_dev->release = gxp_udc_dev_release;
+	drvdata->port_dev->parent = parent;
+	dev_set_name(drvdata->port_dev, "%s:p%d", dev_name(parent), idx + 1);
+
+	/* DMA setting */
+	drvdata->port_dev->dma_mask = parent->dma_mask;
+	drvdata->port_dev->coherent_dma_mask = parent->coherent_dma_mask;
+	drvdata->port_dev->bus_dma_limit = parent->bus_dma_limit;
+	drvdata->port_dev->dma_range_map = parent->dma_range_map;
+	drvdata->port_dev->dma_parms = parent->dma_parms;
+	drvdata->port_dev->dma_pools = parent->dma_pools;
+
+	rc = device_add(drvdata->port_dev);
+	if (rc)
+		goto fail_add;
+
+	/* Populate gadget */
+	gxp_udc_init(drvdata);
+
+	rc = usb_add_gadget_udc(drvdata->port_dev, &drvdata->gadget);
+	if (rc != 0) {
+		dev_err(drvdata->port_dev, "add gadget failed\n");
+		goto fail_udc;
+	}
+	rc = devm_request_irq(drvdata->port_dev,
+			      drvdata->irq,
+			      gxp_udc_irq,
+			      IRQF_SHARED,
+			      gxp_udc_name[drvdata->vdevnum],
+			      drvdata);
+
+	if (rc < 0) {
+		dev_err(drvdata->port_dev, "irq request failed\n");
+		goto fail_udc;
+	}
+
+	return 0;
+
+fail_udc:
+	device_del(drvdata->port_dev);
+fail_add:
+	put_device(drvdata->port_dev);
+fail_alloc:
+	kfree(drvdata);
+
+	return rc;
+}
+
+static int gxp_udcg_probe(struct platform_device *pdev)
+{
+	struct resource *res;
+	struct gxp_udcg_drvdata *drvdata;
+	int ret, rc, err;
+	u32 vdevnum;
+	u32 fepnum, i;
+
+	drvdata = devm_kzalloc(&pdev->dev, sizeof(struct gxp_udcg_drvdata),
+			       GFP_KERNEL);
+	platform_set_drvdata(pdev, drvdata);
+	drvdata->pdev = pdev;
+
+	spin_lock_init(&drvdata->lock);
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "udcg");
+	drvdata->udcg_base = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(drvdata->udcg_base)) {
+		err = PTR_ERR(drvdata->udcg_base);
+		return dev_err_probe(&pdev->dev, err, "failed to get udcg resource\n");
+	}
+
+	drvdata->udcg_map = devm_regmap_init_mmio(&pdev->dev, drvdata->udcg_base, &regmap_cfg);
+
+	if (IS_ERR(drvdata->udcg_map)) {
+		dev_err(&pdev->dev, "failed to map udcg regs");
+		return -ENODEV;
+	}
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "udc");
+	drvdata->base = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(drvdata->base)) {
+		err = PTR_ERR(drvdata->base);
+		return dev_err_probe(&pdev->dev, err, "failed to get udc resource\n");
+	}
+
+	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, "hpe,vehci-downstream-ports", &vdevnum)) {
+		dev_err(&pdev->dev, "property ports number is undefined\n");
+		return -EINVAL;
+	}
+
+	if (vdevnum > GXP_UDC_MAX_NUM_VDEVICE) {
+		dev_err(&pdev->dev, "property max port number(%d) is invalid\n", vdevnum);
+		return -EINVAL;
+	}
+	drvdata->max_vdevnum = vdevnum;
+
+	if (of_property_read_u32(pdev->dev.of_node, "hpe,vehci-generic-endpoints", &fepnum)) {
+		dev_err(&pdev->dev, "property generic endpoints is undefined\n");
+		return -EINVAL;
+	}
+
+	if (fepnum > GXP_UDC_MAX_NUM_FLEX_EP) {
+		dev_err(&pdev->dev, "property fepnum(%d) is invalid\n", fepnum);
+		return -EINVAL;
+	}
+	drvdata->max_fepnum = fepnum;
+
+	gxp_udcg_init(drvdata);
+
+	/* Init child devices */
+	rc = 0;
+	for (i = 0; i < drvdata->max_vdevnum && rc == 0; i++)
+		rc = gxp_udc_init_dev(drvdata, i);
+
+	return rc;
+}
+
+static const struct of_device_id gxp_udc_of_match[] = {
+	{ .compatible = "hpe,gxp-udcg" },
+	{},
+};
+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_udcg_probe,
+};
+module_platform_driver(gxp_udc_driver);
+
+MODULE_AUTHOR("Richard Yu <richard.yu@hpe.com>");
+MODULE_DESCRIPTION("HPE GXP vEHCI UDC Driver");
+MODULE_LICENSE("GPL");