dynamically linked module called "udc-xilinx" and force all
          gadget drivers to also be dynamically linked.
 
+source "drivers/usb/gadget/udc/aspeed-vhub/Kconfig"
+
 #
 # LAST -- dummy/emulated controller
 #
 
 obj-$(CONFIG_USB_GR_UDC)       += gr_udc.o
 obj-$(CONFIG_USB_GADGET_XILINX)        += udc-xilinx.o
 obj-$(CONFIG_USB_SNP_UDC_PLAT) += snps_udc_plat.o
+obj-$(CONFIG_USB_ASPEED_VHUB)  += aspeed-vhub/
 obj-$(CONFIG_USB_BDC_UDC)      += bdc/
 
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0+
+config USB_ASPEED_VHUB
+       tristate "Aspeed vHub UDC driver"
+       depends on ARCH_ASPEED || COMPILE_TEST
+       help
+         USB peripheral controller for the Aspeed AST2500 family
+         SoCs supporting the "vHub" functionality and USB2.0
 
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0+
+obj-$(CONFIG_USB_ASPEED_VHUB)  += aspeed-vhub.o
+aspeed-vhub-y  := core.o ep0.o epn.o dev.o hub.o
+
 
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * aspeed-vhub -- Driver for Aspeed SoC "vHub" USB gadget
+ *
+ * core.c - Top level support
+ *
+ * Copyright 2017 IBM Corporation
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/delay.h>
+#include <linux/ioport.h>
+#include <linux/slab.h>
+#include <linux/errno.h>
+#include <linux/list.h>
+#include <linux/interrupt.h>
+#include <linux/proc_fs.h>
+#include <linux/prefetch.h>
+#include <linux/clk.h>
+#include <linux/usb/gadget.h>
+#include <linux/of.h>
+#include <linux/of_gpio.h>
+#include <linux/regmap.h>
+#include <linux/dma-mapping.h>
+
+#include "vhub.h"
+
+void ast_vhub_done(struct ast_vhub_ep *ep, struct ast_vhub_req *req,
+                  int status)
+{
+       bool internal = req->internal;
+
+       EPVDBG(ep, "completing request @%p, status %d\n", req, status);
+
+       list_del_init(&req->queue);
+
+       if (req->req.status == -EINPROGRESS)
+               req->req.status = status;
+
+       if (req->req.dma) {
+               if (!WARN_ON(!ep->dev))
+                       usb_gadget_unmap_request(&ep->dev->gadget,
+                                                &req->req, ep->epn.is_in);
+               req->req.dma = 0;
+       }
+
+       /*
+        * If this isn't an internal EP0 request, call the core
+        * to call the gadget completion.
+        */
+       if (!internal) {
+               spin_unlock(&ep->vhub->lock);
+               usb_gadget_giveback_request(&ep->ep, &req->req);
+               spin_lock(&ep->vhub->lock);
+       }
+}
+
+void ast_vhub_nuke(struct ast_vhub_ep *ep, int status)
+{
+       struct ast_vhub_req *req;
+
+       EPDBG(ep, "Nuking\n");
+
+       /* Beware, lock will be dropped & req-acquired by done() */
+       while (!list_empty(&ep->queue)) {
+               req = list_first_entry(&ep->queue, struct ast_vhub_req, queue);
+               ast_vhub_done(ep, req, status);
+       }
+}
+
+struct usb_request *ast_vhub_alloc_request(struct usb_ep *u_ep,
+                                          gfp_t gfp_flags)
+{
+       struct ast_vhub_req *req;
+
+       req = kzalloc(sizeof(*req), gfp_flags);
+       if (!req)
+               return NULL;
+       return &req->req;
+}
+
+void ast_vhub_free_request(struct usb_ep *u_ep, struct usb_request *u_req)
+{
+       struct ast_vhub_req *req = to_ast_req(u_req);
+
+       kfree(req);
+}
+
+static irqreturn_t ast_vhub_irq(int irq, void *data)
+{
+       struct ast_vhub *vhub = data;
+       irqreturn_t iret = IRQ_NONE;
+       u32 istat;
+
+       /* Stale interrupt while tearing down */
+       if (!vhub->ep0_bufs)
+               return IRQ_NONE;
+
+       spin_lock(&vhub->lock);
+
+       /* Read and ACK interrupts */
+       istat = readl(vhub->regs + AST_VHUB_ISR);
+       if (!istat)
+               goto bail;
+       writel(istat, vhub->regs + AST_VHUB_ISR);
+       iret = IRQ_HANDLED;
+
+       UDCVDBG(vhub, "irq status=%08x, ep_acks=%08x ep_nacks=%08x\n",
+              istat,
+              readl(vhub->regs + AST_VHUB_EP_ACK_ISR),
+              readl(vhub->regs + AST_VHUB_EP_NACK_ISR));
+
+       /* Handle generic EPs first */
+       if (istat & VHUB_IRQ_EP_POOL_ACK_STALL) {
+               u32 i, ep_acks = readl(vhub->regs + AST_VHUB_EP_ACK_ISR);
+               writel(ep_acks, vhub->regs + AST_VHUB_EP_ACK_ISR);
+
+               for (i = 0; ep_acks && i < AST_VHUB_NUM_GEN_EPs; i++) {
+                       u32 mask = VHUB_EP_IRQ(i);
+                       if (ep_acks & mask) {
+                               ast_vhub_epn_ack_irq(&vhub->epns[i]);
+                               ep_acks &= ~mask;
+                       }
+               }
+       }
+
+       /* Handle device interrupts */
+       if (istat & (VHUB_IRQ_DEVICE1 |
+                    VHUB_IRQ_DEVICE2 |
+                    VHUB_IRQ_DEVICE3 |
+                    VHUB_IRQ_DEVICE4 |
+                    VHUB_IRQ_DEVICE5)) {
+               if (istat & VHUB_IRQ_DEVICE1)
+                       ast_vhub_dev_irq(&vhub->ports[0].dev);
+               if (istat & VHUB_IRQ_DEVICE2)
+                       ast_vhub_dev_irq(&vhub->ports[1].dev);
+               if (istat & VHUB_IRQ_DEVICE3)
+                       ast_vhub_dev_irq(&vhub->ports[2].dev);
+               if (istat & VHUB_IRQ_DEVICE4)
+                       ast_vhub_dev_irq(&vhub->ports[3].dev);
+               if (istat & VHUB_IRQ_DEVICE5)
+                       ast_vhub_dev_irq(&vhub->ports[4].dev);
+       }
+
+       /* Handle top-level vHub EP0 interrupts */
+       if (istat & (VHUB_IRQ_HUB_EP0_OUT_ACK_STALL |
+                    VHUB_IRQ_HUB_EP0_IN_ACK_STALL |
+                    VHUB_IRQ_HUB_EP0_SETUP)) {
+               if (istat & VHUB_IRQ_HUB_EP0_IN_ACK_STALL)
+                       ast_vhub_ep0_handle_ack(&vhub->ep0, true);
+               if (istat & VHUB_IRQ_HUB_EP0_OUT_ACK_STALL)
+                       ast_vhub_ep0_handle_ack(&vhub->ep0, false);
+               if (istat & VHUB_IRQ_HUB_EP0_SETUP)
+                       ast_vhub_ep0_handle_setup(&vhub->ep0);
+       }
+
+       /* Various top level bus events */
+       if (istat & (VHUB_IRQ_BUS_RESUME |
+                    VHUB_IRQ_BUS_SUSPEND |
+                    VHUB_IRQ_BUS_RESET)) {
+               if (istat & VHUB_IRQ_BUS_RESUME)
+                       ast_vhub_hub_resume(vhub);
+               if (istat & VHUB_IRQ_BUS_SUSPEND)
+                       ast_vhub_hub_suspend(vhub);
+               if (istat & VHUB_IRQ_BUS_RESET)
+                       ast_vhub_hub_reset(vhub);
+       }
+
+ bail:
+       spin_unlock(&vhub->lock);
+       return iret;
+}
+
+void ast_vhub_init_hw(struct ast_vhub *vhub)
+{
+       u32 ctrl;
+
+       UDCDBG(vhub,"(Re)Starting HW ...\n");
+
+       /* Enable PHY */
+       ctrl = VHUB_CTRL_PHY_CLK |
+               VHUB_CTRL_PHY_RESET_DIS;
+
+       /*
+       * We do *NOT* set the VHUB_CTRL_CLK_STOP_SUSPEND bit
+       * to stop the logic clock during suspend because
+       * it causes the registers to become inaccessible and
+       * we haven't yet figured out a good wayt to bring the
+       * controller back into life to issue a wakeup.
+       */
+
+       /*
+        * Set some ISO & split control bits according to Aspeed
+        * recommendation
+        *
+        * VHUB_CTRL_ISO_RSP_CTRL: When set tells the HW to respond
+        * with 0 bytes data packet to ISO IN endpoints when no data
+        * is available.
+        *
+        * VHUB_CTRL_SPLIT_IN: This makes a SOF complete a split IN
+        * transaction.
+        */
+       ctrl |= VHUB_CTRL_ISO_RSP_CTRL | VHUB_CTRL_SPLIT_IN;
+       writel(ctrl, vhub->regs + AST_VHUB_CTRL);
+       udelay(1);
+
+       /* Set descriptor ring size */
+       if (AST_VHUB_DESCS_COUNT == 256) {
+               ctrl |= VHUB_CTRL_LONG_DESC;
+               writel(ctrl, vhub->regs + AST_VHUB_CTRL);
+       } else {
+               BUILD_BUG_ON(AST_VHUB_DESCS_COUNT != 32);
+       }
+
+       /* Reset all devices */
+       writel(VHUB_SW_RESET_ALL, vhub->regs + AST_VHUB_SW_RESET);
+       udelay(1);
+       writel(0, vhub->regs + AST_VHUB_SW_RESET);
+
+       /* Disable and cleanup EP ACK/NACK interrupts */
+       writel(0, vhub->regs + AST_VHUB_EP_ACK_IER);
+       writel(0, vhub->regs + AST_VHUB_EP_NACK_IER);
+       writel(VHUB_EP_IRQ_ALL, vhub->regs + AST_VHUB_EP_ACK_ISR);
+       writel(VHUB_EP_IRQ_ALL, vhub->regs + AST_VHUB_EP_NACK_ISR);
+
+       /* Default settings for EP0, enable HW hub EP1 */
+       writel(0, vhub->regs + AST_VHUB_EP0_CTRL);
+       writel(VHUB_EP1_CTRL_RESET_TOGGLE |
+              VHUB_EP1_CTRL_ENABLE,
+              vhub->regs + AST_VHUB_EP1_CTRL);
+       writel(0, vhub->regs + AST_VHUB_EP1_STS_CHG);
+
+       /* Configure EP0 DMA buffer */
+       writel(vhub->ep0.buf_dma, vhub->regs + AST_VHUB_EP0_DATA);
+
+       /* Clear address */
+       writel(0, vhub->regs + AST_VHUB_CONF);
+
+       /* Pullup hub (activate on host) */
+       if (vhub->force_usb1)
+               ctrl |= VHUB_CTRL_FULL_SPEED_ONLY;
+
+       ctrl |= VHUB_CTRL_UPSTREAM_CONNECT;
+       writel(ctrl, vhub->regs + AST_VHUB_CTRL);
+
+       /* Enable some interrupts */
+       writel(VHUB_IRQ_HUB_EP0_IN_ACK_STALL |
+              VHUB_IRQ_HUB_EP0_OUT_ACK_STALL |
+              VHUB_IRQ_HUB_EP0_SETUP |
+              VHUB_IRQ_EP_POOL_ACK_STALL |
+              VHUB_IRQ_BUS_RESUME |
+              VHUB_IRQ_BUS_SUSPEND |
+              VHUB_IRQ_BUS_RESET,
+              vhub->regs + AST_VHUB_IER);
+}
+
+static int ast_vhub_remove(struct platform_device *pdev)
+{
+       struct ast_vhub *vhub = platform_get_drvdata(pdev);
+       unsigned long flags;
+       int i;
+
+       if (!vhub || !vhub->regs)
+               return 0;
+
+       /* Remove devices */
+       for (i = 0; i < AST_VHUB_NUM_PORTS; i++)
+               ast_vhub_del_dev(&vhub->ports[i].dev);
+
+       spin_lock_irqsave(&vhub->lock, flags);
+
+       /* Mask & ack all interrupts  */
+       writel(0, vhub->regs + AST_VHUB_IER);
+       writel(VHUB_IRQ_ACK_ALL, vhub->regs + AST_VHUB_ISR);
+
+       /* Pull device, leave PHY enabled */
+       writel(VHUB_CTRL_PHY_CLK |
+              VHUB_CTRL_PHY_RESET_DIS,
+              vhub->regs + AST_VHUB_CTRL);
+
+       if (vhub->clk)
+               clk_disable_unprepare(vhub->clk);
+
+       spin_unlock_irqrestore(&vhub->lock, flags);
+
+       if (vhub->ep0_bufs)
+               dma_free_coherent(&pdev->dev,
+                                 AST_VHUB_EP0_MAX_PACKET *
+                                 (AST_VHUB_NUM_PORTS + 1),
+                                 vhub->ep0_bufs,
+                                 vhub->ep0_bufs_dma);
+       vhub->ep0_bufs = NULL;
+
+       return 0;
+}
+
+static int ast_vhub_probe(struct platform_device *pdev)
+{
+       enum usb_device_speed max_speed;
+       struct ast_vhub *vhub;
+       struct resource *res;
+       int i, rc = 0;
+
+       vhub = devm_kzalloc(&pdev->dev, sizeof(*vhub), GFP_KERNEL);
+       if (!vhub)
+               return -ENOMEM;
+
+       spin_lock_init(&vhub->lock);
+       vhub->pdev = pdev;
+
+       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       vhub->regs = devm_ioremap_resource(&pdev->dev, res);
+       if (IS_ERR(vhub->regs)) {
+               dev_err(&pdev->dev, "Failed to map resources\n");
+               return PTR_ERR(vhub->regs);
+       }
+       UDCDBG(vhub, "vHub@%pR mapped @%p\n", res, vhub->regs);
+
+       platform_set_drvdata(pdev, vhub);
+
+       vhub->clk = devm_clk_get(&pdev->dev, NULL);
+       if (IS_ERR(vhub->clk)) {
+               rc = PTR_ERR(vhub->clk);
+               goto err;
+       }
+       rc = clk_prepare_enable(vhub->clk);
+       if (rc) {
+               dev_err(&pdev->dev, "Error couldn't enable clock (%d)\n", rc);
+               goto err;
+       }
+
+       /* Check if we need to limit the HW to USB1 */
+       max_speed = usb_get_maximum_speed(&pdev->dev);
+       if (max_speed != USB_SPEED_UNKNOWN && max_speed < USB_SPEED_HIGH)
+               vhub->force_usb1 = true;
+
+       /* Mask & ack all interrupts before installing the handler */
+       writel(0, vhub->regs + AST_VHUB_IER);
+       writel(VHUB_IRQ_ACK_ALL, vhub->regs + AST_VHUB_ISR);
+
+       /* Find interrupt and install handler */
+       vhub->irq = platform_get_irq(pdev, 0);
+       if (vhub->irq < 0) {
+               dev_err(&pdev->dev, "Failed to get interrupt\n");
+               rc = vhub->irq;
+               goto err;
+       }
+       rc = devm_request_irq(&pdev->dev, vhub->irq, ast_vhub_irq, 0,
+                             KBUILD_MODNAME, vhub);
+       if (rc) {
+               dev_err(&pdev->dev, "Failed to request interrupt\n");
+               goto err;
+       }
+
+       /*
+        * Allocate DMA buffers for all EP0s in one chunk,
+        * one per port and one for the vHub itself
+        */
+       vhub->ep0_bufs = dma_alloc_coherent(&pdev->dev,
+                                           AST_VHUB_EP0_MAX_PACKET *
+                                           (AST_VHUB_NUM_PORTS + 1),
+                                           &vhub->ep0_bufs_dma, GFP_KERNEL);
+       if (!vhub->ep0_bufs) {
+               dev_err(&pdev->dev, "Failed to allocate EP0 DMA buffers\n");
+               rc = -ENOMEM;
+               goto err;
+       }
+       UDCVDBG(vhub, "EP0 DMA buffers @%p (DMA 0x%08x)\n",
+               vhub->ep0_bufs, (u32)vhub->ep0_bufs_dma);
+
+       /* Init vHub EP0 */
+       ast_vhub_init_ep0(vhub, &vhub->ep0, NULL);
+
+       /* Init devices */
+       for (i = 0; i < AST_VHUB_NUM_PORTS && rc == 0; i++)
+               rc = ast_vhub_init_dev(vhub, i);
+       if (rc)
+               goto err;
+
+       /* Init hub emulation */
+       ast_vhub_init_hub(vhub);
+
+       /* Initialize HW */
+       ast_vhub_init_hw(vhub);
+
+       dev_info(&pdev->dev, "Initialized virtual hub in USB%d mode\n",
+                vhub->force_usb1 ? 1 : 2);
+
+       return 0;
+ err:
+       ast_vhub_remove(pdev);
+       return rc;
+}
+
+static const struct of_device_id ast_vhub_dt_ids[] = {
+       {
+               .compatible = "aspeed,ast2400-usb-vhub",
+       },
+       {
+               .compatible = "aspeed,ast2500-usb-vhub",
+       },
+       { }
+};
+MODULE_DEVICE_TABLE(of, ast_vhub_dt_ids);
+
+static struct platform_driver ast_vhub_driver = {
+       .probe          = ast_vhub_probe,
+       .remove         = ast_vhub_remove,
+       .driver         = {
+               .name   = KBUILD_MODNAME,
+               .of_match_table = ast_vhub_dt_ids,
+       },
+};
+module_platform_driver(ast_vhub_driver);
+
+MODULE_DESCRIPTION("Aspeed vHub udc driver");
+MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>");
+MODULE_LICENSE("GPL");
 
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * aspeed-vhub -- Driver for Aspeed SoC "vHub" USB gadget
+ *
+ * dev.c - Individual device/gadget management (ie, a port = a gadget)
+ *
+ * Copyright 2017 IBM Corporation
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/delay.h>
+#include <linux/ioport.h>
+#include <linux/slab.h>
+#include <linux/errno.h>
+#include <linux/list.h>
+#include <linux/interrupt.h>
+#include <linux/proc_fs.h>
+#include <linux/prefetch.h>
+#include <linux/clk.h>
+#include <linux/usb/gadget.h>
+#include <linux/of.h>
+#include <linux/of_gpio.h>
+#include <linux/regmap.h>
+#include <linux/dma-mapping.h>
+#include <linux/usb.h>
+#include <linux/usb/hcd.h>
+
+#include "vhub.h"
+
+void ast_vhub_dev_irq(struct ast_vhub_dev *d)
+{
+       u32 istat = readl(d->regs + AST_VHUB_DEV_ISR);
+
+       writel(istat, d->regs + AST_VHUB_DEV_ISR);
+
+       if (istat & VHUV_DEV_IRQ_EP0_IN_ACK_STALL)
+               ast_vhub_ep0_handle_ack(&d->ep0, true);
+       if (istat & VHUV_DEV_IRQ_EP0_OUT_ACK_STALL)
+               ast_vhub_ep0_handle_ack(&d->ep0, false);
+       if (istat & VHUV_DEV_IRQ_EP0_SETUP)
+               ast_vhub_ep0_handle_setup(&d->ep0);
+}
+
+static void ast_vhub_dev_enable(struct ast_vhub_dev *d)
+{
+       u32 reg, hmsk;
+
+       if (d->enabled)
+               return;
+
+       /* Enable device and its EP0 interrupts */
+       reg = VHUB_DEV_EN_ENABLE_PORT |
+               VHUB_DEV_EN_EP0_IN_ACK_IRQEN |
+               VHUB_DEV_EN_EP0_OUT_ACK_IRQEN |
+               VHUB_DEV_EN_EP0_SETUP_IRQEN;
+       if (d->gadget.speed == USB_SPEED_HIGH)
+               reg |= VHUB_DEV_EN_SPEED_SEL_HIGH;
+       writel(reg, d->regs + AST_VHUB_DEV_EN_CTRL);
+
+       /* Enable device interrupt in the hub as well */
+       hmsk = VHUB_IRQ_DEVICE1 << d->index;
+       reg = readl(d->vhub->regs + AST_VHUB_IER);
+       reg |= hmsk;
+       writel(reg, d->vhub->regs + AST_VHUB_IER);
+
+       /* Set EP0 DMA buffer address */
+       writel(d->ep0.buf_dma, d->regs + AST_VHUB_DEV_EP0_DATA);
+
+       d->enabled = true;
+}
+
+static void ast_vhub_dev_disable(struct ast_vhub_dev *d)
+{
+       u32 reg, hmsk;
+
+       if (!d->enabled)
+               return;
+
+       /* Disable device interrupt in the hub */
+       hmsk = VHUB_IRQ_DEVICE1 << d->index;
+       reg = readl(d->vhub->regs + AST_VHUB_IER);
+       reg &= ~hmsk;
+       writel(reg, d->vhub->regs + AST_VHUB_IER);
+
+       /* Then disable device */
+       writel(0, d->regs + AST_VHUB_DEV_EN_CTRL);
+       d->gadget.speed = USB_SPEED_UNKNOWN;
+       d->enabled = false;
+       d->suspended = false;
+}
+
+static int ast_vhub_dev_feature(struct ast_vhub_dev *d,
+                               u16 wIndex, u16 wValue,
+                               bool is_set)
+{
+       DDBG(d, "%s_FEATURE(dev val=%02x)\n",
+            is_set ? "SET" : "CLEAR", wValue);
+
+       if (wValue != USB_DEVICE_REMOTE_WAKEUP)
+               return std_req_driver;
+
+       d->wakeup_en = is_set;
+
+       return std_req_complete;
+}
+
+static int ast_vhub_ep_feature(struct ast_vhub_dev *d,
+                              u16 wIndex, u16 wValue, bool is_set)
+{
+       struct ast_vhub_ep *ep;
+       int ep_num;
+
+       ep_num = wIndex & USB_ENDPOINT_NUMBER_MASK;
+       DDBG(d, "%s_FEATURE(ep%d val=%02x)\n",
+            is_set ? "SET" : "CLEAR", ep_num, wValue);
+       if (ep_num == 0)
+               return std_req_complete;
+       if (ep_num >= AST_VHUB_NUM_GEN_EPs || !d->epns[ep_num - 1])
+               return std_req_stall;
+       if (wValue != USB_ENDPOINT_HALT)
+               return std_req_driver;
+
+       ep = d->epns[ep_num - 1];
+       if (WARN_ON(!ep))
+               return std_req_stall;
+
+       if (!ep->epn.enabled || !ep->ep.desc || ep->epn.is_iso ||
+           ep->epn.is_in != !!(wIndex & USB_DIR_IN))
+               return std_req_stall;
+
+       DDBG(d, "%s stall on EP %d\n",
+            is_set ? "setting" : "clearing", ep_num);
+       ep->epn.stalled = is_set;
+       ast_vhub_update_epn_stall(ep);
+
+       return std_req_complete;
+}
+
+static int ast_vhub_dev_status(struct ast_vhub_dev *d,
+                              u16 wIndex, u16 wValue)
+{
+       u8 st0;
+
+       DDBG(d, "GET_STATUS(dev)\n");
+
+       st0 = d->gadget.is_selfpowered << USB_DEVICE_SELF_POWERED;
+       if (d->wakeup_en)
+               st0 |= 1 << USB_DEVICE_REMOTE_WAKEUP;
+
+       return ast_vhub_simple_reply(&d->ep0, st0, 0);
+}
+
+static int ast_vhub_ep_status(struct ast_vhub_dev *d,
+                             u16 wIndex, u16 wValue)
+{
+       int ep_num = wIndex & USB_ENDPOINT_NUMBER_MASK;
+       struct ast_vhub_ep *ep;
+       u8 st0 = 0;
+
+       DDBG(d, "GET_STATUS(ep%d)\n", ep_num);
+
+       if (ep_num >= AST_VHUB_NUM_GEN_EPs)
+               return std_req_stall;
+       if (ep_num != 0) {
+               ep = d->epns[ep_num - 1];
+               if (!ep)
+                       return std_req_stall;
+               if (!ep->epn.enabled || !ep->ep.desc || ep->epn.is_iso ||
+                   ep->epn.is_in != !!(wIndex & USB_DIR_IN))
+                       return std_req_stall;
+               if (ep->epn.stalled)
+                       st0 |= 1 << USB_ENDPOINT_HALT;
+       }
+
+       return ast_vhub_simple_reply(&d->ep0, st0, 0);
+}
+
+static void ast_vhub_dev_set_address(struct ast_vhub_dev *d, u8 addr)
+{
+       u32 reg;
+
+       DDBG(d, "SET_ADDRESS: Got address %x\n", addr);
+
+       reg = readl(d->regs + AST_VHUB_DEV_EN_CTRL);
+       reg &= ~VHUB_DEV_EN_ADDR_MASK;
+       reg |= VHUB_DEV_EN_SET_ADDR(addr);
+       writel(reg, d->regs + AST_VHUB_DEV_EN_CTRL);
+}
+
+int ast_vhub_std_dev_request(struct ast_vhub_ep *ep,
+                            struct usb_ctrlrequest *crq)
+{
+       struct ast_vhub_dev *d = ep->dev;
+       u16 wValue, wIndex;
+
+       /* No driver, we shouldn't be enabled ... */
+       if (!d->driver || !d->enabled || d->suspended) {
+               EPDBG(ep,
+                     "Device is wrong state driver=%p enabled=%d"
+                     " suspended=%d\n",
+                     d->driver, d->enabled, d->suspended);
+               return std_req_stall;
+       }
+
+       /* First packet, grab speed */
+       if (d->gadget.speed == USB_SPEED_UNKNOWN) {
+               d->gadget.speed = ep->vhub->speed;
+               if (d->gadget.speed > d->driver->max_speed)
+                       d->gadget.speed = d->driver->max_speed;
+               DDBG(d, "fist packet, captured speed %d\n",
+                    d->gadget.speed);
+       }
+
+       wValue = le16_to_cpu(crq->wValue);
+       wIndex = le16_to_cpu(crq->wIndex);
+
+       switch ((crq->bRequestType << 8) | crq->bRequest) {
+               /* SET_ADDRESS */
+       case DeviceOutRequest | USB_REQ_SET_ADDRESS:
+               ast_vhub_dev_set_address(d, wValue);
+               return std_req_complete;
+
+               /* GET_STATUS */
+       case DeviceRequest | USB_REQ_GET_STATUS:
+               return ast_vhub_dev_status(d, wIndex, wValue);
+       case InterfaceRequest | USB_REQ_GET_STATUS:
+               return ast_vhub_simple_reply(ep, 0, 0);
+       case EndpointRequest | USB_REQ_GET_STATUS:
+               return ast_vhub_ep_status(d, wIndex, wValue);
+
+               /* SET/CLEAR_FEATURE */
+       case DeviceOutRequest | USB_REQ_SET_FEATURE:
+               return ast_vhub_dev_feature(d, wIndex, wValue, true);
+       case DeviceOutRequest | USB_REQ_CLEAR_FEATURE:
+               return ast_vhub_dev_feature(d, wIndex, wValue, false);
+       case EndpointOutRequest | USB_REQ_SET_FEATURE:
+               return ast_vhub_ep_feature(d, wIndex, wValue, true);
+       case EndpointOutRequest | USB_REQ_CLEAR_FEATURE:
+               return ast_vhub_ep_feature(d, wIndex, wValue, false);
+       }
+       return std_req_driver;
+}
+
+static int ast_vhub_udc_wakeup(struct usb_gadget* gadget)
+{
+       struct ast_vhub_dev *d = to_ast_dev(gadget);
+       unsigned long flags;
+       int rc = -EINVAL;
+
+       spin_lock_irqsave(&d->vhub->lock, flags);
+       if (!d->wakeup_en)
+               goto err;
+
+       DDBG(d, "Device initiated wakeup\n");
+
+       /* Wakeup the host */
+       ast_vhub_hub_wake_all(d->vhub);
+       rc = 0;
+ err:
+       spin_unlock_irqrestore(&d->vhub->lock, flags);
+       return rc;
+}
+
+static int ast_vhub_udc_get_frame(struct usb_gadget* gadget)
+{
+       struct ast_vhub_dev *d = to_ast_dev(gadget);
+
+       return (readl(d->vhub->regs + AST_VHUB_USBSTS) >> 16) & 0x7ff;
+}
+
+static void ast_vhub_dev_nuke(struct ast_vhub_dev *d)
+{
+       unsigned int i;
+
+       for (i = 0; i < AST_VHUB_NUM_GEN_EPs; i++) {
+               if (!d->epns[i])
+                       continue;
+               ast_vhub_nuke(d->epns[i], -ESHUTDOWN);
+       }
+}
+
+static int ast_vhub_udc_pullup(struct usb_gadget* gadget, int on)
+{
+       struct ast_vhub_dev *d = to_ast_dev(gadget);
+       unsigned long flags;
+
+       spin_lock_irqsave(&d->vhub->lock, flags);
+
+       DDBG(d, "pullup(%d)\n", on);
+
+       /* Mark disconnected in the hub */
+       ast_vhub_device_connect(d->vhub, d->index, on);
+
+       /*
+        * If enabled, nuke all requests if any (there shouldn't be)
+        * and disable the port. This will clear the address too.
+        */
+       if (d->enabled) {
+               ast_vhub_dev_nuke(d);
+               ast_vhub_dev_disable(d);
+       }
+
+       spin_unlock_irqrestore(&d->vhub->lock, flags);
+
+       return 0;
+}
+
+static int ast_vhub_udc_start(struct usb_gadget *gadget,
+                             struct usb_gadget_driver *driver)
+{
+       struct ast_vhub_dev *d = to_ast_dev(gadget);
+       unsigned long flags;
+
+       spin_lock_irqsave(&d->vhub->lock, flags);
+
+       DDBG(d, "start\n");
+
+       /* We don't do much more until the hub enables us */
+       d->driver = driver;
+       d->gadget.is_selfpowered = 1;
+
+       spin_unlock_irqrestore(&d->vhub->lock, flags);
+
+       return 0;
+}
+
+static struct usb_ep *ast_vhub_udc_match_ep(struct usb_gadget *gadget,
+                                           struct usb_endpoint_descriptor *desc,
+                                           struct usb_ss_ep_comp_descriptor *ss)
+{
+       struct ast_vhub_dev *d = to_ast_dev(gadget);
+       struct ast_vhub_ep *ep;
+       struct usb_ep *u_ep;
+       unsigned int max, addr, i;
+
+       DDBG(d, "Match EP type %d\n", usb_endpoint_type(desc));
+
+       /*
+        * First we need to look for an existing unclaimed EP as another
+        * configuration may have already associated a bunch of EPs with
+        * this gadget. This duplicates the code in usb_ep_autoconfig_ss()
+        * unfortunately.
+        */
+       list_for_each_entry(u_ep, &gadget->ep_list, ep_list) {
+               if (usb_gadget_ep_match_desc(gadget, u_ep, desc, ss)) {
+                       DDBG(d, " -> using existing EP%d\n",
+                            to_ast_ep(u_ep)->d_idx);
+                       return u_ep;
+               }
+       }
+
+       /*
+        * We didn't find one, we need to grab one from the pool.
+        *
+        * First let's do some sanity checking
+        */
+       switch(usb_endpoint_type(desc)) {
+       case USB_ENDPOINT_XFER_CONTROL:
+               /* Only EP0 can be a control endpoint */
+               return NULL;
+       case USB_ENDPOINT_XFER_ISOC:
+               /* ISO:  limit 1023 bytes full speed, 1024 high/super speed */
+               if (gadget_is_dualspeed(gadget))
+                       max = 1024;
+               else
+                       max = 1023;
+               break;
+       case USB_ENDPOINT_XFER_BULK:
+               if (gadget_is_dualspeed(gadget))
+                       max = 512;
+               else
+                       max = 64;
+               break;
+       case USB_ENDPOINT_XFER_INT:
+               if (gadget_is_dualspeed(gadget))
+                       max = 1024;
+               else
+                       max = 64;
+               break;
+       }
+       if (usb_endpoint_maxp(desc) > max)
+               return NULL;
+
+       /*
+        * Find a free EP address for that device. We can't
+        * let the generic code assign these as it would
+        * create overlapping numbers for IN and OUT which
+        * we don't support, so also create a suitable name
+        * that will allow the generic code to use our
+        * assigned address.
+        */
+       for (i = 0; i < AST_VHUB_NUM_GEN_EPs; i++)
+               if (d->epns[i] == NULL)
+                       break;
+       if (i >= AST_VHUB_NUM_GEN_EPs)
+               return NULL;
+       addr = i + 1;
+
+       /*
+        * Now grab an EP from the shared pool and associate
+        * it with our device
+        */
+       ep = ast_vhub_alloc_epn(d, addr);
+       if (!ep)
+               return NULL;
+       DDBG(d, "Allocated epn#%d for port EP%d\n",
+            ep->epn.g_idx, addr);
+
+       return &ep->ep;
+}
+
+static int ast_vhub_udc_stop(struct usb_gadget *gadget)
+{
+       struct ast_vhub_dev *d = to_ast_dev(gadget);
+       unsigned long flags;
+
+       spin_lock_irqsave(&d->vhub->lock, flags);
+
+       DDBG(d, "stop\n");
+
+       d->driver = NULL;
+       d->gadget.speed = USB_SPEED_UNKNOWN;
+
+       ast_vhub_dev_nuke(d);
+
+       if (d->enabled)
+               ast_vhub_dev_disable(d);
+
+       spin_unlock_irqrestore(&d->vhub->lock, flags);
+
+       return 0;
+}
+
+static struct usb_gadget_ops ast_vhub_udc_ops = {
+       .get_frame      = ast_vhub_udc_get_frame,
+       .wakeup         = ast_vhub_udc_wakeup,
+       .pullup         = ast_vhub_udc_pullup,
+       .udc_start      = ast_vhub_udc_start,
+       .udc_stop       = ast_vhub_udc_stop,
+       .match_ep       = ast_vhub_udc_match_ep,
+};
+
+void ast_vhub_dev_suspend(struct ast_vhub_dev *d)
+{
+       d->suspended = true;
+       if (d->driver) {
+               spin_unlock(&d->vhub->lock);
+               d->driver->suspend(&d->gadget);
+               spin_lock(&d->vhub->lock);
+       }
+}
+
+void ast_vhub_dev_resume(struct ast_vhub_dev *d)
+{
+       d->suspended = false;
+       if (d->driver) {
+               spin_unlock(&d->vhub->lock);
+               d->driver->resume(&d->gadget);
+               spin_lock(&d->vhub->lock);
+       }
+}
+
+void ast_vhub_dev_reset(struct ast_vhub_dev *d)
+{
+       /*
+        * If speed is not set, we enable the port. If it is,
+        * send reset to the gadget and reset "speed".
+        *
+        * Speed is an indication that we have got the first
+        * setup packet to the device.
+        */
+       if (d->gadget.speed == USB_SPEED_UNKNOWN && !d->enabled) {
+               DDBG(d, "Reset at unknown speed of disabled device, enabling...\n");
+               ast_vhub_dev_enable(d);
+               d->suspended = false;
+       }
+       if (d->gadget.speed != USB_SPEED_UNKNOWN && d->driver) {
+               unsigned int i;
+
+               DDBG(d, "Reset at known speed of bound device, resetting...\n");
+               spin_unlock(&d->vhub->lock);
+               d->driver->reset(&d->gadget);
+               spin_lock(&d->vhub->lock);
+
+               /*
+                * Disable/re-enable HW, this will clear the address
+                * and speed setting.
+                */
+               ast_vhub_dev_disable(d);
+               ast_vhub_dev_enable(d);
+
+               /* Clear stall on all EPs */
+               for (i = 0; i < AST_VHUB_NUM_GEN_EPs; i++) {
+                       struct ast_vhub_ep *ep = d->epns[i];
+
+                       if (ep && ep->epn.stalled) {
+                               ep->epn.stalled = false;
+                               ast_vhub_update_epn_stall(ep);
+                       }
+               }
+
+               /* Additional cleanups */
+               d->wakeup_en = false;
+               d->suspended = false;
+       }
+}
+
+void ast_vhub_del_dev(struct ast_vhub_dev *d)
+{
+       unsigned long flags;
+
+       spin_lock_irqsave(&d->vhub->lock, flags);
+       if (!d->registered) {
+               spin_unlock_irqrestore(&d->vhub->lock, flags);
+               return;
+       }
+       d->registered = false;
+       spin_unlock_irqrestore(&d->vhub->lock, flags);
+
+       usb_del_gadget_udc(&d->gadget);
+       device_unregister(d->port_dev);
+}
+
+static void ast_vhub_dev_release(struct device *dev)
+{
+       kfree(dev);
+}
+
+int ast_vhub_init_dev(struct ast_vhub *vhub, unsigned int idx)
+{
+       struct ast_vhub_dev *d = &vhub->ports[idx].dev;
+       struct device *parent = &vhub->pdev->dev;
+       int rc;
+
+       d->vhub = vhub;
+       d->index = idx;
+       d->name = devm_kasprintf(parent, GFP_KERNEL, "port%d", idx+1);
+       d->regs = vhub->regs + 0x100 + 0x10 * idx;
+
+       ast_vhub_init_ep0(vhub, &d->ep0, d);
+
+       /*
+        * 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
+        */
+       d->port_dev = kzalloc(sizeof(struct device), GFP_KERNEL);
+       if (!d->port_dev)
+               return -ENOMEM;
+       device_initialize(d->port_dev);
+       d->port_dev->release = ast_vhub_dev_release;
+       d->port_dev->parent = parent;
+       dev_set_name(d->port_dev, "%s:p%d", dev_name(parent), idx + 1);
+       rc = device_add(d->port_dev);
+       if (rc)
+               goto fail_add;
+
+       /* Populate gadget */
+       INIT_LIST_HEAD(&d->gadget.ep_list);
+       d->gadget.ops = &ast_vhub_udc_ops;
+       d->gadget.ep0 = &d->ep0.ep;
+       d->gadget.name = KBUILD_MODNAME;
+       if (vhub->force_usb1)
+               d->gadget.max_speed = USB_SPEED_FULL;
+       else
+               d->gadget.max_speed = USB_SPEED_HIGH;
+       d->gadget.speed = USB_SPEED_UNKNOWN;
+       d->gadget.dev.of_node = vhub->pdev->dev.of_node;
+
+       rc = usb_add_gadget_udc(d->port_dev, &d->gadget);
+       if (rc != 0)
+               goto fail_udc;
+       d->registered = true;
+
+       return 0;
+ fail_udc:
+       device_del(d->port_dev);
+ fail_add:
+       put_device(d->port_dev);
+
+       return rc;
+}
 
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * aspeed-vhub -- Driver for Aspeed SoC "vHub" USB gadget
+ *
+ * ep0.c - Endpoint 0 handling
+ *
+ * Copyright 2017 IBM Corporation
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/delay.h>
+#include <linux/ioport.h>
+#include <linux/slab.h>
+#include <linux/errno.h>
+#include <linux/list.h>
+#include <linux/interrupt.h>
+#include <linux/proc_fs.h>
+#include <linux/prefetch.h>
+#include <linux/clk.h>
+#include <linux/usb/gadget.h>
+#include <linux/of.h>
+#include <linux/of_gpio.h>
+#include <linux/regmap.h>
+#include <linux/dma-mapping.h>
+
+#include "vhub.h"
+
+int ast_vhub_reply(struct ast_vhub_ep *ep, char *ptr, int len)
+{
+       struct usb_request *req = &ep->ep0.req.req;
+       int rc;
+
+       if (WARN_ON(ep->d_idx != 0))
+               return std_req_stall;
+       if (WARN_ON(!ep->ep0.dir_in))
+               return std_req_stall;
+       if (WARN_ON(len > AST_VHUB_EP0_MAX_PACKET))
+               return std_req_stall;
+       if (WARN_ON(req->status == -EINPROGRESS))
+               return std_req_stall;
+
+       req->buf = ptr;
+       req->length = len;
+       req->complete = NULL;
+       req->zero = true;
+
+       /*
+        * Call internal queue directly after dropping the lock. This is
+        * safe to do as the reply is always the last thing done when
+        * processing a SETUP packet, usually as a tail call
+        */
+       spin_unlock(&ep->vhub->lock);
+       if (ep->ep.ops->queue(&ep->ep, req, GFP_ATOMIC))
+               rc = std_req_stall;
+       else
+               rc = std_req_data;
+       spin_lock(&ep->vhub->lock);
+       return rc;
+}
+
+int __ast_vhub_simple_reply(struct ast_vhub_ep *ep, int len, ...)
+{
+       u8 *buffer = ep->buf;
+       unsigned int i;
+       va_list args;
+
+       va_start(args, len);
+
+       /* Copy data directly into EP buffer */
+       for (i = 0; i < len; i++)
+               buffer[i] = va_arg(args, int);
+       va_end(args);
+
+       /* req->buf NULL means data is already there */
+       return ast_vhub_reply(ep, NULL, len);
+}
+
+void ast_vhub_ep0_handle_setup(struct ast_vhub_ep *ep)
+{
+       struct usb_ctrlrequest crq;
+       enum std_req_rc std_req_rc;
+       int rc = -ENODEV;
+
+       if (WARN_ON(ep->d_idx != 0))
+               return;
+
+       /*
+        * Grab the setup packet from the chip and byteswap
+        * interesting fields
+        */
+       memcpy_fromio(&crq, ep->ep0.setup, sizeof(crq));
+
+       EPDBG(ep, "SETUP packet %02x/%02x/%04x/%04x/%04x [%s] st=%d\n",
+             crq.bRequestType, crq.bRequest,
+              le16_to_cpu(crq.wValue),
+              le16_to_cpu(crq.wIndex),
+              le16_to_cpu(crq.wLength),
+              (crq.bRequestType & USB_DIR_IN) ? "in" : "out",
+              ep->ep0.state);
+
+       /* Check our state, cancel pending requests if needed */
+       if (ep->ep0.state != ep0_state_token) {
+               EPDBG(ep, "wrong state\n");
+               ast_vhub_nuke(ep, 0);
+               goto stall;
+       }
+
+       /* Calculate next state for EP0 */
+       ep->ep0.state = ep0_state_data;
+       ep->ep0.dir_in = !!(crq.bRequestType & USB_DIR_IN);
+
+       /* If this is the vHub, we handle requests differently */
+       std_req_rc = std_req_driver;
+       if (ep->dev == NULL) {
+               if ((crq.bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD)
+                       std_req_rc = ast_vhub_std_hub_request(ep, &crq);
+               else if ((crq.bRequestType & USB_TYPE_MASK) == USB_TYPE_CLASS)
+                       std_req_rc = ast_vhub_class_hub_request(ep, &crq);
+               else
+                       std_req_rc = std_req_stall;
+       } else if ((crq.bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD)
+               std_req_rc = ast_vhub_std_dev_request(ep, &crq);
+
+       /* Act upon result */
+       switch(std_req_rc) {
+       case std_req_complete:
+               goto complete;
+       case std_req_stall:
+               goto stall;
+       case std_req_driver:
+               break;
+       case std_req_data:
+               return;
+       }
+
+       /* Pass request up to the gadget driver */
+       if (WARN_ON(!ep->dev))
+               goto stall;
+       if (ep->dev->driver) {
+               EPDBG(ep, "forwarding to gadget...\n");
+               spin_unlock(&ep->vhub->lock);
+               rc = ep->dev->driver->setup(&ep->dev->gadget, &crq);
+               spin_lock(&ep->vhub->lock);
+               EPDBG(ep, "driver returned %d\n", rc);
+       } else {
+               EPDBG(ep, "no gadget for request !\n");
+       }
+       if (rc >= 0)
+               return;
+
+ stall:
+       EPDBG(ep, "stalling\n");
+       writel(VHUB_EP0_CTRL_STALL, ep->ep0.ctlstat);
+       ep->ep0.state = ep0_state_status;
+       ep->ep0.dir_in = false;
+       return;
+
+ complete:
+       EPVDBG(ep, "sending [in] status with no data\n");
+       writel(VHUB_EP0_TX_BUFF_RDY, ep->ep0.ctlstat);
+       ep->ep0.state = ep0_state_status;
+       ep->ep0.dir_in = false;
+}
+
+
+static void ast_vhub_ep0_do_send(struct ast_vhub_ep *ep,
+                                struct ast_vhub_req *req)
+{
+       unsigned int chunk;
+       u32 reg;
+
+       /* If this is a 0-length request, it's the gadget trying to
+        * send a status on our behalf. We take it from here.
+        */
+       if (req->req.length == 0)
+               req->last_desc = 1;
+
+       /* Are we done ? Complete request, otherwise wait for next interrupt */
+       if (req->last_desc >= 0) {
+               EPVDBG(ep, "complete send %d/%d\n",
+                      req->req.actual, req->req.length);
+               ep->ep0.state = ep0_state_status;
+               writel(VHUB_EP0_RX_BUFF_RDY, ep->ep0.ctlstat);
+               ast_vhub_done(ep, req, 0);
+               return;
+       }
+
+       /*
+        * Next chunk cropped to max packet size. Also check if this
+        * is the last packet
+        */
+       chunk = req->req.length - req->req.actual;
+       if (chunk > ep->ep.maxpacket)
+               chunk = ep->ep.maxpacket;
+       else if ((chunk < ep->ep.maxpacket) || !req->req.zero)
+               req->last_desc = 1;
+
+       EPVDBG(ep, "send chunk=%d last=%d, req->act=%d mp=%d\n",
+              chunk, req->last_desc, req->req.actual, ep->ep.maxpacket);
+
+       /*
+        * Copy data if any (internal requests already have data
+        * in the EP buffer)
+        */
+       if (chunk && req->req.buf)
+               memcpy(ep->buf, req->req.buf + req->req.actual, chunk);
+
+       /* Remember chunk size and trigger send */
+       reg = VHUB_EP0_SET_TX_LEN(chunk);
+       writel(reg, ep->ep0.ctlstat);
+       writel(reg | VHUB_EP0_TX_BUFF_RDY, ep->ep0.ctlstat);
+       req->req.actual += chunk;
+}
+
+static void ast_vhub_ep0_rx_prime(struct ast_vhub_ep *ep)
+{
+       EPVDBG(ep, "rx prime\n");
+
+       /* Prime endpoint for receiving data */
+       writel(VHUB_EP0_RX_BUFF_RDY, ep->ep0.ctlstat + AST_VHUB_EP0_CTRL);
+}
+
+static void ast_vhub_ep0_do_receive(struct ast_vhub_ep *ep, struct ast_vhub_req *req,
+                                   unsigned int len)
+{
+       unsigned int remain;
+       int rc = 0;
+
+       /* We are receiving... grab request */
+       remain = req->req.length - req->req.actual;
+
+       EPVDBG(ep, "receive got=%d remain=%d\n", len, remain);
+
+       /* Are we getting more than asked ? */
+       if (len > remain) {
+               EPDBG(ep, "receiving too much (ovf: %d) !\n",
+                     len - remain);
+               len = remain;
+               rc = -EOVERFLOW;
+       }
+       if (len && req->req.buf)
+               memcpy(req->req.buf + req->req.actual, ep->buf, len);
+       req->req.actual += len;
+
+       /* Done ? */
+       if (len < ep->ep.maxpacket || len == remain) {
+               ep->ep0.state = ep0_state_status;
+               writel(VHUB_EP0_TX_BUFF_RDY, ep->ep0.ctlstat);
+               ast_vhub_done(ep, req, rc);
+       } else
+               ast_vhub_ep0_rx_prime(ep);
+}
+
+void ast_vhub_ep0_handle_ack(struct ast_vhub_ep *ep, bool in_ack)
+{
+       struct ast_vhub_req *req;
+       struct ast_vhub *vhub = ep->vhub;
+       struct device *dev = &vhub->pdev->dev;
+       bool stall = false;
+       u32 stat;
+
+       /* Read EP0 status */
+       stat = readl(ep->ep0.ctlstat);
+
+       /* Grab current request if any */
+       req = list_first_entry_or_null(&ep->queue, struct ast_vhub_req, queue);
+
+       EPVDBG(ep, "ACK status=%08x,state=%d is_in=%d in_ack=%d req=%p\n",
+               stat, ep->ep0.state, ep->ep0.dir_in, in_ack, req);
+
+       switch(ep->ep0.state) {
+       case ep0_state_token:
+               /* There should be no request queued in that state... */
+               if (req) {
+                       dev_warn(dev, "request present while in TOKEN state\n");
+                       ast_vhub_nuke(ep, -EINVAL);
+               }
+               dev_warn(dev, "ack while in TOKEN state\n");
+               stall = true;
+               break;
+       case ep0_state_data:
+               /* Check the state bits corresponding to our direction */
+               if ((ep->ep0.dir_in && (stat & VHUB_EP0_TX_BUFF_RDY)) ||
+                   (!ep->ep0.dir_in && (stat & VHUB_EP0_RX_BUFF_RDY)) ||
+                   (ep->ep0.dir_in != in_ack)) {
+                       dev_warn(dev, "irq state mismatch");
+                       stall = true;
+                       break;
+               }
+               /*
+                * We are in data phase and there's no request, something is
+                * wrong, stall
+                */
+               if (!req) {
+                       dev_warn(dev, "data phase, no request\n");
+                       stall = true;
+                       break;
+               }
+
+               /* We have a request, handle data transfers */
+               if (ep->ep0.dir_in)
+                       ast_vhub_ep0_do_send(ep, req);
+               else
+                       ast_vhub_ep0_do_receive(ep, req, VHUB_EP0_RX_LEN(stat));
+               return;
+       case ep0_state_status:
+               /* Nuke stale requests */
+               if (req) {
+                       dev_warn(dev, "request present while in STATUS state\n");
+                       ast_vhub_nuke(ep, -EINVAL);
+               }
+
+               /*
+                * If the status phase completes with the wrong ack, stall
+                * the endpoint just in case, to abort whatever the host
+                * was doing.
+                */
+               if (ep->ep0.dir_in == in_ack) {
+                       dev_warn(dev, "status direction mismatch\n");
+                       stall = true;
+               }
+       }
+
+       /* Reset to token state */
+       ep->ep0.state = ep0_state_token;
+       if (stall)
+               writel(VHUB_EP0_CTRL_STALL, ep->ep0.ctlstat);
+}
+
+static int ast_vhub_ep0_queue(struct usb_ep* u_ep, struct usb_request *u_req,
+                             gfp_t gfp_flags)
+{
+       struct ast_vhub_req *req = to_ast_req(u_req);
+       struct ast_vhub_ep *ep = to_ast_ep(u_ep);
+       struct ast_vhub *vhub = ep->vhub;
+       struct device *dev = &vhub->pdev->dev;
+       unsigned long flags;
+
+       /* Paranoid cheks */
+       if (!u_req || (!u_req->complete && !req->internal)) {
+               dev_warn(dev, "Bogus EP0 request ! u_req=%p\n", u_req);
+               if (u_req) {
+                       dev_warn(dev, "complete=%p internal=%d\n",
+                                u_req->complete, req->internal);
+               }
+               return -EINVAL;
+       }
+
+       /* Not endpoint 0 ? */
+       if (WARN_ON(ep->d_idx != 0))
+               return -EINVAL;
+
+       /* Disabled device */
+       if (ep->dev && (!ep->dev->enabled || ep->dev->suspended))
+               return -ESHUTDOWN;
+
+       /* Data, no buffer and not internal ? */
+       if (u_req->length && !u_req->buf && !req->internal) {
+               dev_warn(dev, "Request with no buffer !\n");
+               return -EINVAL;
+       }
+
+       EPVDBG(ep, "enqueue req @%p\n", req);
+       EPVDBG(ep, "  l=%d zero=%d noshort=%d is_in=%d\n",
+              u_req->length, u_req->zero,
+              u_req->short_not_ok, ep->ep0.dir_in);
+
+       /* Initialize request progress fields */
+       u_req->status = -EINPROGRESS;
+       u_req->actual = 0;
+       req->last_desc = -1;
+       req->active = false;
+
+       spin_lock_irqsave(&vhub->lock, flags);
+
+       /* EP0 can only support a single request at a time */
+       if (!list_empty(&ep->queue) || ep->ep0.state == ep0_state_token) {
+               dev_warn(dev, "EP0: Request in wrong state\n");
+               spin_unlock_irqrestore(&vhub->lock, flags);
+               return -EBUSY;
+       }
+
+       /* Add request to list and kick processing if empty */
+       list_add_tail(&req->queue, &ep->queue);
+
+       if (ep->ep0.dir_in) {
+               /* IN request, send data */
+               ast_vhub_ep0_do_send(ep, req);
+       } else if (u_req->length == 0) {
+               /* 0-len request, send completion as rx */
+               EPVDBG(ep, "0-length rx completion\n");
+               ep->ep0.state = ep0_state_status;
+               writel(VHUB_EP0_TX_BUFF_RDY, ep->ep0.ctlstat);
+               ast_vhub_done(ep, req, 0);
+       } else {
+               /* OUT request, start receiver */
+               ast_vhub_ep0_rx_prime(ep);
+       }
+
+       spin_unlock_irqrestore(&vhub->lock, flags);
+
+       return 0;
+}
+
+static int ast_vhub_ep0_dequeue(struct usb_ep* u_ep, struct usb_request *u_req)
+{
+       struct ast_vhub_ep *ep = to_ast_ep(u_ep);
+       struct ast_vhub *vhub = ep->vhub;
+       struct ast_vhub_req *req;
+       unsigned long flags;
+       int rc = -EINVAL;
+
+       spin_lock_irqsave(&vhub->lock, flags);
+
+       /* Only one request can be in the queue */
+       req = list_first_entry_or_null(&ep->queue, struct ast_vhub_req, queue);
+
+       /* Is it ours ? */
+       if (req && u_req == &req->req) {
+               EPVDBG(ep, "dequeue req @%p\n", req);
+
+               /*
+                * We don't have to deal with "active" as all
+                * DMAs go to the EP buffers, not the request.
+                */
+               ast_vhub_done(ep, req, -ECONNRESET);
+
+               /* We do stall the EP to clean things up in HW */
+               writel(VHUB_EP0_CTRL_STALL, ep->ep0.ctlstat);
+               ep->ep0.state = ep0_state_status;
+               ep->ep0.dir_in = false;
+               rc = 0;
+       }
+       spin_unlock_irqrestore(&vhub->lock, flags);
+       return rc;
+}
+
+
+static const struct usb_ep_ops ast_vhub_ep0_ops = {
+       .queue          = ast_vhub_ep0_queue,
+       .dequeue        = ast_vhub_ep0_dequeue,
+       .alloc_request  = ast_vhub_alloc_request,
+       .free_request   = ast_vhub_free_request,
+};
+
+void ast_vhub_init_ep0(struct ast_vhub *vhub, struct ast_vhub_ep *ep,
+                      struct ast_vhub_dev *dev)
+{
+       memset(ep, 0, sizeof(*ep));
+
+       INIT_LIST_HEAD(&ep->ep.ep_list);
+       INIT_LIST_HEAD(&ep->queue);
+       ep->ep.ops = &ast_vhub_ep0_ops;
+       ep->ep.name = "ep0";
+       ep->ep.caps.type_control = true;
+       usb_ep_set_maxpacket_limit(&ep->ep, AST_VHUB_EP0_MAX_PACKET);
+       ep->d_idx = 0;
+       ep->dev = dev;
+       ep->vhub = vhub;
+       ep->ep0.state = ep0_state_token;
+       INIT_LIST_HEAD(&ep->ep0.req.queue);
+       ep->ep0.req.internal = true;
+
+       /* Small difference between vHub and devices */
+       if (dev) {
+               ep->ep0.ctlstat = dev->regs + AST_VHUB_DEV_EP0_CTRL;
+               ep->ep0.setup = vhub->regs +
+                       AST_VHUB_SETUP0 + 8 * (dev->index + 1);
+               ep->buf = vhub->ep0_bufs +
+                       AST_VHUB_EP0_MAX_PACKET * (dev->index + 1);
+               ep->buf_dma = vhub->ep0_bufs_dma +
+                       AST_VHUB_EP0_MAX_PACKET * (dev->index + 1);
+       } else {
+               ep->ep0.ctlstat = vhub->regs + AST_VHUB_EP0_CTRL;
+               ep->ep0.setup = vhub->regs + AST_VHUB_SETUP0;
+               ep->buf = vhub->ep0_bufs;
+               ep->buf_dma = vhub->ep0_bufs_dma;
+       }
+}
 
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * aspeed-vhub -- Driver for Aspeed SoC "vHub" USB gadget
+ *
+ * epn.c - Generic endpoints management
+ *
+ * Copyright 2017 IBM Corporation
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/delay.h>
+#include <linux/ioport.h>
+#include <linux/slab.h>
+#include <linux/errno.h>
+#include <linux/list.h>
+#include <linux/interrupt.h>
+#include <linux/proc_fs.h>
+#include <linux/prefetch.h>
+#include <linux/clk.h>
+#include <linux/usb/gadget.h>
+#include <linux/of.h>
+#include <linux/of_gpio.h>
+#include <linux/regmap.h>
+#include <linux/dma-mapping.h>
+
+#include "vhub.h"
+
+#define EXTRA_CHECKS
+
+#ifdef EXTRA_CHECKS
+#define CHECK(ep, expr, fmt...)                                        \
+       do {                                                    \
+               if (!(expr)) EPDBG(ep, "CHECK:" fmt);           \
+       } while(0)
+#else
+#define CHECK(ep, expr, fmt...)        do { } while(0)
+#endif
+
+static void ast_vhub_epn_kick(struct ast_vhub_ep *ep, struct ast_vhub_req *req)
+{
+       unsigned int act = req->req.actual;
+       unsigned int len = req->req.length;
+       unsigned int chunk;
+
+       /* There should be no DMA ongoing */
+       WARN_ON(req->active);
+
+       /* Calculate next chunk size */
+       chunk = len - act;
+       if (chunk > ep->ep.maxpacket)
+               chunk = ep->ep.maxpacket;
+       else if ((chunk < ep->ep.maxpacket) || !req->req.zero)
+               req->last_desc = 1;
+
+       EPVDBG(ep, "kick req %p act=%d/%d chunk=%d last=%d\n",
+              req, act, len, chunk, req->last_desc);
+
+       /* If DMA unavailable, using staging EP buffer */
+       if (!req->req.dma) {
+
+               /* For IN transfers, copy data over first */
+               if (ep->epn.is_in)
+                       memcpy(ep->buf, req->req.buf + act, chunk);
+               writel(ep->buf_dma, ep->epn.regs + AST_VHUB_EP_DESC_BASE);
+       } else
+               writel(req->req.dma + act, ep->epn.regs + AST_VHUB_EP_DESC_BASE);
+
+       /* Start DMA */
+       req->active = true;
+       writel(VHUB_EP_DMA_SET_TX_SIZE(chunk),
+              ep->epn.regs + AST_VHUB_EP_DESC_STATUS);
+       writel(VHUB_EP_DMA_SET_TX_SIZE(chunk) | VHUB_EP_DMA_SINGLE_KICK,
+              ep->epn.regs + AST_VHUB_EP_DESC_STATUS);
+}
+
+static void ast_vhub_epn_handle_ack(struct ast_vhub_ep *ep)
+{
+       struct ast_vhub_req *req;
+       unsigned int len;
+       u32 stat;
+
+       /* Read EP status */
+       stat = readl(ep->epn.regs + AST_VHUB_EP_DESC_STATUS);
+
+       /* Grab current request if any */
+       req = list_first_entry_or_null(&ep->queue, struct ast_vhub_req, queue);
+
+       EPVDBG(ep, "ACK status=%08x is_in=%d, req=%p (active=%d)\n",
+              stat, ep->epn.is_in, req, req ? req->active : 0);
+
+       /* In absence of a request, bail out, must have been dequeued */
+       if (!req)
+               return;
+
+       /*
+        * Request not active, move on to processing queue, active request
+        * was probably dequeued
+        */
+       if (!req->active)
+               goto next_chunk;
+
+       /* Check if HW has moved on */
+       if (VHUB_EP_DMA_RPTR(stat) != 0) {
+               EPDBG(ep, "DMA read pointer not 0 !\n");
+               return;
+       }
+
+       /* No current DMA ongoing */
+       req->active = false;
+
+       /* Grab lenght out of HW */
+       len = VHUB_EP_DMA_TX_SIZE(stat);
+
+       /* If not using DMA, copy data out if needed */
+       if (!req->req.dma && !ep->epn.is_in && len)
+               memcpy(req->req.buf + req->req.actual, ep->buf, len);
+
+       /* Adjust size */
+       req->req.actual += len;
+
+       /* Check for short packet */
+       if (len < ep->ep.maxpacket)
+               req->last_desc = 1;
+
+       /* That's it ? complete the request and pick a new one */
+       if (req->last_desc >= 0) {
+               ast_vhub_done(ep, req, 0);
+               req = list_first_entry_or_null(&ep->queue, struct ast_vhub_req,
+                                              queue);
+
+               /*
+                * Due to lock dropping inside "done" the next request could
+                * already be active, so check for that and bail if needed.
+                */
+               if (!req || req->active)
+                       return;
+       }
+
+ next_chunk:
+       ast_vhub_epn_kick(ep, req);
+}
+
+static inline unsigned int ast_vhub_count_free_descs(struct ast_vhub_ep *ep)
+{
+       /*
+        * d_next == d_last means descriptor list empty to HW,
+        * thus we can only have AST_VHUB_DESCS_COUNT-1 descriptors
+        * in the list
+        */
+       return (ep->epn.d_last + AST_VHUB_DESCS_COUNT - ep->epn.d_next - 1) &
+               (AST_VHUB_DESCS_COUNT - 1);
+}
+
+static void ast_vhub_epn_kick_desc(struct ast_vhub_ep *ep,
+                                  struct ast_vhub_req *req)
+{
+       unsigned int act = req->act_count;
+       unsigned int len = req->req.length;
+       unsigned int chunk;
+
+       /* Mark request active if not already */
+       req->active = true;
+
+       /* If the request was already completely written, do nothing */
+       if (req->last_desc >= 0)
+               return;
+
+       EPVDBG(ep, "kick act=%d/%d chunk_max=%d free_descs=%d\n",
+              act, len, ep->epn.chunk_max, ast_vhub_count_free_descs(ep));
+
+       /* While we can create descriptors */
+       while (ast_vhub_count_free_descs(ep) && req->last_desc < 0) {
+               struct ast_vhub_desc *desc;
+               unsigned int d_num;
+
+               /* Grab next free descriptor */
+               d_num = ep->epn.d_next;
+               desc = &ep->epn.descs[d_num];
+               ep->epn.d_next = (d_num + 1) & (AST_VHUB_DESCS_COUNT - 1);
+
+               /* Calculate next chunk size */
+               chunk = len - act;
+               if (chunk <= ep->epn.chunk_max) {
+                       /*
+                        * Is this the last packet ? Because of having up to 8
+                        * packets in a descriptor we can't just compare "chunk"
+                        * with ep.maxpacket. We have to see if it's a multiple
+                        * of it to know if we have to send a zero packet.
+                        * Sadly that involves a modulo which is a bit expensive
+                        * but probably still better than not doing it.
+                        */
+                       if (!chunk || !req->req.zero || (chunk % ep->ep.maxpacket) != 0)
+                               req->last_desc = d_num;
+               } else {
+                       chunk = ep->epn.chunk_max;
+               }
+
+               EPVDBG(ep, " chunk: act=%d/%d chunk=%d last=%d desc=%d free=%d\n",
+                      act, len, chunk, req->last_desc, d_num,
+                      ast_vhub_count_free_descs(ep));
+
+               /* Populate descriptor */
+               desc->w0 = cpu_to_le32(req->req.dma + act);
+
+               /* Interrupt if end of request or no more descriptors */
+
+               /*
+                * TODO: Be smarter about it, if we don't have enough
+                * descriptors request an interrupt before queue empty
+                * or so in order to be able to populate more before
+                * the HW runs out. This isn't a problem at the moment
+                * as we use 256 descriptors and only put at most one
+                * request in the ring.
+                */
+               desc->w1 = cpu_to_le32(VHUB_DSC1_IN_SET_LEN(chunk));
+               if (req->last_desc >= 0 || !ast_vhub_count_free_descs(ep))
+                       desc->w1 |= cpu_to_le32(VHUB_DSC1_IN_INTERRUPT);
+
+               /* Account packet */
+               req->act_count = act = act + chunk;
+       }
+
+       /* Tell HW about new descriptors */
+       writel(VHUB_EP_DMA_SET_CPU_WPTR(ep->epn.d_next),
+              ep->epn.regs + AST_VHUB_EP_DESC_STATUS);
+
+       EPVDBG(ep, "HW kicked, d_next=%d dstat=%08x\n",
+              ep->epn.d_next, readl(ep->epn.regs + AST_VHUB_EP_DESC_STATUS));
+}
+
+static void ast_vhub_epn_handle_ack_desc(struct ast_vhub_ep *ep)
+{
+       struct ast_vhub_req *req;
+       unsigned int len, d_last;
+       u32 stat, stat1;
+
+       /* Read EP status, workaround HW race */
+       do {
+               stat = readl(ep->epn.regs + AST_VHUB_EP_DESC_STATUS);
+               stat1 = readl(ep->epn.regs + AST_VHUB_EP_DESC_STATUS);
+       } while(stat != stat1);
+
+       /* Extract RPTR */
+       d_last = VHUB_EP_DMA_RPTR(stat);
+
+       /* Grab current request if any */
+       req = list_first_entry_or_null(&ep->queue, struct ast_vhub_req, queue);
+
+       EPVDBG(ep, "ACK status=%08x is_in=%d ep->d_last=%d..%d\n",
+              stat, ep->epn.is_in, ep->epn.d_last, d_last);
+
+       /* Check all completed descriptors */
+       while (ep->epn.d_last != d_last) {
+               struct ast_vhub_desc *desc;
+               unsigned int d_num;
+               bool is_last_desc;
+
+               /* Grab next completed descriptor */
+               d_num = ep->epn.d_last;
+               desc = &ep->epn.descs[d_num];
+               ep->epn.d_last = (d_num + 1) & (AST_VHUB_DESCS_COUNT - 1);
+
+               /* Grab len out of descriptor */
+               len = VHUB_DSC1_IN_LEN(le32_to_cpu(desc->w1));
+
+               EPVDBG(ep, " desc %d len=%d req=%p (act=%d)\n",
+                      d_num, len, req, req ? req->active : 0);
+
+               /* If no active request pending, move on */
+               if (!req || !req->active)
+                       continue;
+
+               /* Adjust size */
+               req->req.actual += len;
+
+               /* Is that the last chunk ? */
+               is_last_desc = req->last_desc == d_num;
+               CHECK(ep, is_last_desc == (len < ep->ep.maxpacket ||
+                                          (req->req.actual >= req->req.length &&
+                                           !req->req.zero)),
+                     "Last packet discrepancy: last_desc=%d len=%d r.act=%d "
+                     "r.len=%d r.zero=%d mp=%d\n",
+                     is_last_desc, len, req->req.actual, req->req.length,
+                     req->req.zero, ep->ep.maxpacket);
+
+               if (is_last_desc) {
+                       /*
+                        * Because we can only have one request at a time
+                        * in our descriptor list in this implementation,
+                        * d_last and ep->d_last should now be equal
+                        */
+                       CHECK(ep, d_last == ep->epn.d_last,
+                             "DMA read ptr mismatch %d vs %d\n",
+                             d_last, ep->epn.d_last);
+
+                       /* Note: done will drop and re-acquire the lock */
+                       ast_vhub_done(ep, req, 0);
+                       req = list_first_entry_or_null(&ep->queue,
+                                                      struct ast_vhub_req,
+                                                      queue);
+                       break;
+               }
+       }
+
+       /* More work ? */
+       if (req)
+               ast_vhub_epn_kick_desc(ep, req);
+}
+
+void ast_vhub_epn_ack_irq(struct ast_vhub_ep *ep)
+{
+       if (ep->epn.desc_mode)
+               ast_vhub_epn_handle_ack_desc(ep);
+       else
+               ast_vhub_epn_handle_ack(ep);
+}
+
+static int ast_vhub_epn_queue(struct usb_ep* u_ep, struct usb_request *u_req,
+                             gfp_t gfp_flags)
+{
+       struct ast_vhub_req *req = to_ast_req(u_req);
+       struct ast_vhub_ep *ep = to_ast_ep(u_ep);
+       struct ast_vhub *vhub = ep->vhub;
+       unsigned long flags;
+       bool empty;
+       int rc;
+
+       /* Paranoid checks */
+       if (!u_req || !u_req->complete || !u_req->buf) {
+               dev_warn(&vhub->pdev->dev, "Bogus EPn request ! u_req=%p\n", u_req);
+               if (u_req) {
+                       dev_warn(&vhub->pdev->dev, "complete=%p internal=%d\n",
+                                u_req->complete, req->internal);
+               }
+               return -EINVAL;
+       }
+
+       /* Endpoint enabled ? */
+       if (!ep->epn.enabled || !u_ep->desc || !ep->dev || !ep->d_idx ||
+           !ep->dev->enabled || ep->dev->suspended) {
+               EPDBG(ep,"Enqueing request on wrong or disabled EP\n");
+               return -ESHUTDOWN;
+       }
+
+       /* Map request for DMA if possible. For now, the rule for DMA is
+        * that:
+        *
+        *  * For single stage mode (no descriptors):
+        *
+        *   - The buffer is aligned to a 8 bytes boundary (HW requirement)
+        *   - For a OUT endpoint, the request size is a multiple of the EP
+        *     packet size (otherwise the controller will DMA past the end
+        *     of the buffer if the host is sending a too long packet).
+        *
+        *  * For descriptor mode (tx only for now), always.
+        *
+        * We could relax the latter by making the decision to use the bounce
+        * buffer based on the size of a given *segment* of the request rather
+        * than the whole request.
+        */
+       if (ep->epn.desc_mode ||
+           ((((unsigned long)u_req->buf & 7) == 0) &&
+            (ep->epn.is_in || !(u_req->length & (u_ep->maxpacket - 1))))) {
+               rc = usb_gadget_map_request(&ep->dev->gadget, u_req,
+                                           ep->epn.is_in);
+               if (rc) {
+                       dev_warn(&vhub->pdev->dev,
+                                "Request mapping failure %d\n", rc);
+                       return rc;
+               }
+       } else
+               u_req->dma = 0;
+
+       EPVDBG(ep, "enqueue req @%p\n", req);
+       EPVDBG(ep, " l=%d dma=0x%x zero=%d noshort=%d noirq=%d is_in=%d\n",
+              u_req->length, (u32)u_req->dma, u_req->zero,
+              u_req->short_not_ok, u_req->no_interrupt,
+              ep->epn.is_in);
+
+       /* Initialize request progress fields */
+       u_req->status = -EINPROGRESS;
+       u_req->actual = 0;
+       req->act_count = 0;
+       req->active = false;
+       req->last_desc = -1;
+       spin_lock_irqsave(&vhub->lock, flags);
+       empty = list_empty(&ep->queue);
+
+       /* Add request to list and kick processing if empty */
+       list_add_tail(&req->queue, &ep->queue);
+       if (empty) {
+               if (ep->epn.desc_mode)
+                       ast_vhub_epn_kick_desc(ep, req);
+               else
+                       ast_vhub_epn_kick(ep, req);
+       }
+       spin_unlock_irqrestore(&vhub->lock, flags);
+
+       return 0;
+}
+
+static void ast_vhub_stop_active_req(struct ast_vhub_ep *ep,
+                                    bool restart_ep)
+{
+       u32 state, reg, loops;
+
+       /* Stop DMA activity */
+       writel(0, ep->epn.regs + AST_VHUB_EP_DMA_CTLSTAT);
+
+       /* Wait for it to complete */
+       for (loops = 0; loops < 1000; loops++) {
+               state = readl(ep->epn.regs + AST_VHUB_EP_DMA_CTLSTAT);
+               state = VHUB_EP_DMA_PROC_STATUS(state);
+               if (state == EP_DMA_PROC_RX_IDLE ||
+                   state == EP_DMA_PROC_TX_IDLE)
+                       break;
+               udelay(1);
+       }
+       if (loops >= 1000)
+               dev_warn(&ep->vhub->pdev->dev, "Timeout waiting for DMA\n");
+
+       /* If we don't have to restart the endpoint, that's it */
+       if (!restart_ep)
+               return;
+
+       /* Restart the endpoint */
+       if (ep->epn.desc_mode) {
+               /*
+                * Take out descriptors by resetting the DMA read
+                * pointer to be equal to the CPU write pointer.
+                *
+                * Note: If we ever support creating descriptors for
+                * requests that aren't the head of the queue, we
+                * may have to do something more complex here,
+                * especially if the request being taken out is
+                * not the current head descriptors.
+                */
+               reg = VHUB_EP_DMA_SET_RPTR(ep->epn.d_next) |
+                       VHUB_EP_DMA_SET_CPU_WPTR(ep->epn.d_next);
+               writel(reg, ep->epn.regs + AST_VHUB_EP_DESC_STATUS);
+
+               /* Then turn it back on */
+               writel(ep->epn.dma_conf,
+                      ep->epn.regs + AST_VHUB_EP_DMA_CTLSTAT);
+       } else {
+               /* Single mode: just turn it back on */
+               writel(ep->epn.dma_conf,
+                      ep->epn.regs + AST_VHUB_EP_DMA_CTLSTAT);
+       }
+}
+
+static int ast_vhub_epn_dequeue(struct usb_ep* u_ep, struct usb_request *u_req)
+{
+       struct ast_vhub_ep *ep = to_ast_ep(u_ep);
+       struct ast_vhub *vhub = ep->vhub;
+       struct ast_vhub_req *req;
+       unsigned long flags;
+       int rc = -EINVAL;
+
+       spin_lock_irqsave(&vhub->lock, flags);
+
+       /* Make sure it's actually queued on this endpoint */
+       list_for_each_entry (req, &ep->queue, queue) {
+               if (&req->req == u_req)
+                       break;
+       }
+
+       if (&req->req == u_req) {
+               EPVDBG(ep, "dequeue req @%p active=%d\n",
+                      req, req->active);
+               if (req->active)
+                       ast_vhub_stop_active_req(ep, true);
+               ast_vhub_done(ep, req, -ECONNRESET);
+               rc = 0;
+       }
+
+       spin_unlock_irqrestore(&vhub->lock, flags);
+       return rc;
+}
+
+void ast_vhub_update_epn_stall(struct ast_vhub_ep *ep)
+{
+       u32 reg;
+
+       if (WARN_ON(ep->d_idx == 0))
+               return;
+       reg = readl(ep->epn.regs + AST_VHUB_EP_CONFIG);
+       if (ep->epn.stalled || ep->epn.wedged)
+               reg |= VHUB_EP_CFG_STALL_CTRL;
+       else
+               reg &= ~VHUB_EP_CFG_STALL_CTRL;
+       writel(reg, ep->epn.regs + AST_VHUB_EP_CONFIG);
+
+       if (!ep->epn.stalled && !ep->epn.wedged)
+               writel(VHUB_EP_TOGGLE_SET_EPNUM(ep->epn.g_idx),
+                      ep->vhub->regs + AST_VHUB_EP_TOGGLE);
+}
+
+static int ast_vhub_set_halt_and_wedge(struct usb_ep* u_ep, bool halt,
+                                     bool wedge)
+{
+       struct ast_vhub_ep *ep = to_ast_ep(u_ep);
+       struct ast_vhub *vhub = ep->vhub;
+       unsigned long flags;
+
+       EPDBG(ep, "Set halt (%d) & wedge (%d)\n", halt, wedge);
+
+       if (!u_ep || !u_ep->desc)
+               return -EINVAL;
+       if (ep->d_idx == 0)
+               return 0;
+       if (ep->epn.is_iso)
+               return -EOPNOTSUPP;
+
+       spin_lock_irqsave(&vhub->lock, flags);
+
+       /* Fail with still-busy IN endpoints */
+       if (halt && ep->epn.is_in && !list_empty(&ep->queue)) {
+               spin_unlock_irqrestore(&vhub->lock, flags);
+               return -EAGAIN;
+       }
+       ep->epn.stalled = halt;
+       ep->epn.wedged = wedge;
+       ast_vhub_update_epn_stall(ep);
+
+       spin_unlock_irqrestore(&vhub->lock, flags);
+
+       return 0;
+}
+
+static int ast_vhub_epn_set_halt(struct usb_ep *u_ep, int value)
+{
+       return ast_vhub_set_halt_and_wedge(u_ep, value != 0, false);
+}
+
+static int ast_vhub_epn_set_wedge(struct usb_ep *u_ep)
+{
+       return ast_vhub_set_halt_and_wedge(u_ep, true, true);
+}
+
+static int ast_vhub_epn_disable(struct usb_ep* u_ep)
+{
+       struct ast_vhub_ep *ep = to_ast_ep(u_ep);
+       struct ast_vhub *vhub = ep->vhub;
+       unsigned long flags;
+       u32 imask, ep_ier;
+
+       EPDBG(ep, "Disabling !\n");
+
+       spin_lock_irqsave(&vhub->lock, flags);
+
+       ep->epn.enabled = false;
+
+       /* Stop active DMA if any */
+       ast_vhub_stop_active_req(ep, false);
+
+       /* Disable endpoint */
+       writel(0, ep->epn.regs + AST_VHUB_EP_CONFIG);
+
+       /* Disable ACK interrupt */
+       imask = VHUB_EP_IRQ(ep->epn.g_idx);
+       ep_ier = readl(vhub->regs + AST_VHUB_EP_ACK_IER);
+       ep_ier &= ~imask;
+       writel(ep_ier, vhub->regs + AST_VHUB_EP_ACK_IER);
+       writel(imask, vhub->regs + AST_VHUB_EP_ACK_ISR);
+
+       /* Nuke all pending requests */
+       ast_vhub_nuke(ep, -ESHUTDOWN);
+
+       /* No more descriptor associated with request */
+       ep->ep.desc = NULL;
+
+       spin_unlock_irqrestore(&vhub->lock, flags);
+
+       return 0;
+}
+
+static int ast_vhub_epn_enable(struct usb_ep* u_ep,
+                              const struct usb_endpoint_descriptor *desc)
+{
+       static const char *ep_type_string[] __maybe_unused = { "ctrl",
+                                                              "isoc",
+                                                              "bulk",
+                                                              "intr" };
+       struct ast_vhub_ep *ep = to_ast_ep(u_ep);
+       struct ast_vhub_dev *dev;
+       struct ast_vhub *vhub;
+       u16 maxpacket, type;
+       unsigned long flags;
+       u32 ep_conf, ep_ier, imask;
+
+       /* Check arguments */
+       if (!u_ep || !desc)
+               return -EINVAL;
+
+       maxpacket = usb_endpoint_maxp(desc);
+       if (!ep->d_idx || !ep->dev ||
+           desc->bDescriptorType != USB_DT_ENDPOINT ||
+           maxpacket == 0 || maxpacket > ep->ep.maxpacket) {
+               EPDBG(ep, "Invalid EP enable,d_idx=%d,dev=%p,type=%d,mp=%d/%d\n",
+                     ep->d_idx, ep->dev, desc->bDescriptorType,
+                     maxpacket, ep->ep.maxpacket);
+               return -EINVAL;
+       }
+       if (ep->d_idx != usb_endpoint_num(desc)) {
+               EPDBG(ep, "EP number mismatch !\n");
+               return -EINVAL;
+       }
+
+       if (ep->epn.enabled) {
+               EPDBG(ep, "Already enabled\n");
+               return -EBUSY;
+       }
+       dev = ep->dev;
+       vhub = ep->vhub;
+
+       /* Check device state */
+       if (!dev->driver) {
+               EPDBG(ep, "Bogus device state: driver=%p speed=%d\n",
+                      dev->driver, dev->gadget.speed);
+               return -ESHUTDOWN;
+       }
+
+       /* Grab some info from the descriptor */
+       ep->epn.is_in = usb_endpoint_dir_in(desc);
+       ep->ep.maxpacket = maxpacket;
+       type = usb_endpoint_type(desc);
+       ep->epn.d_next = ep->epn.d_last = 0;
+       ep->epn.is_iso = false;
+       ep->epn.stalled = false;
+       ep->epn.wedged = false;
+
+       EPDBG(ep, "Enabling [%s] %s num %d maxpacket=%d\n",
+             ep->epn.is_in ? "in" : "out", ep_type_string[type],
+             usb_endpoint_num(desc), maxpacket);
+
+       /* Can we use DMA descriptor mode ? */
+       ep->epn.desc_mode = ep->epn.descs && ep->epn.is_in;
+       if (ep->epn.desc_mode)
+               memset(ep->epn.descs, 0, 8 * AST_VHUB_DESCS_COUNT);
+
+       /*
+        * Large send function can send up to 8 packets from
+        * one descriptor with a limit of 4095 bytes.
+        */
+       ep->epn.chunk_max = ep->ep.maxpacket;
+       if (ep->epn.is_in) {
+               ep->epn.chunk_max <<= 3;
+               while (ep->epn.chunk_max > 4095)
+                       ep->epn.chunk_max -= ep->ep.maxpacket;
+       }
+
+       switch(type) {
+       case USB_ENDPOINT_XFER_CONTROL:
+               EPDBG(ep, "Only one control endpoint\n");
+               return -EINVAL;
+       case USB_ENDPOINT_XFER_INT:
+               ep_conf = VHUB_EP_CFG_SET_TYPE(EP_TYPE_INT);
+               break;
+       case USB_ENDPOINT_XFER_BULK:
+               ep_conf = VHUB_EP_CFG_SET_TYPE(EP_TYPE_BULK);
+               break;
+       case USB_ENDPOINT_XFER_ISOC:
+               ep_conf = VHUB_EP_CFG_SET_TYPE(EP_TYPE_ISO);
+               ep->epn.is_iso = true;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       /* Encode the rest of the EP config register */
+       if (maxpacket < 1024)
+               ep_conf |= VHUB_EP_CFG_SET_MAX_PKT(maxpacket);
+       if (!ep->epn.is_in)
+               ep_conf |= VHUB_EP_CFG_DIR_OUT;
+       ep_conf |= VHUB_EP_CFG_SET_EP_NUM(usb_endpoint_num(desc));
+       ep_conf |= VHUB_EP_CFG_ENABLE;
+       ep_conf |= VHUB_EP_CFG_SET_DEV(dev->index + 1);
+       EPVDBG(ep, "config=%08x\n", ep_conf);
+
+       spin_lock_irqsave(&vhub->lock, flags);
+
+       /* Disable HW and reset DMA */
+       writel(0, ep->epn.regs + AST_VHUB_EP_CONFIG);
+       writel(VHUB_EP_DMA_CTRL_RESET,
+              ep->epn.regs + AST_VHUB_EP_DMA_CTLSTAT);
+
+       /* Configure and enable */
+       writel(ep_conf, ep->epn.regs + AST_VHUB_EP_CONFIG);
+
+       if (ep->epn.desc_mode) {
+               /* Clear DMA status, including the DMA read ptr */
+               writel(0, ep->epn.regs + AST_VHUB_EP_DESC_STATUS);
+
+               /* Set descriptor base */
+               writel(ep->epn.descs_dma,
+                      ep->epn.regs + AST_VHUB_EP_DESC_BASE);
+
+               /* Set base DMA config value */
+               ep->epn.dma_conf = VHUB_EP_DMA_DESC_MODE;
+               if (ep->epn.is_in)
+                       ep->epn.dma_conf |= VHUB_EP_DMA_IN_LONG_MODE;
+
+               /* First reset and disable all operations */
+               writel(ep->epn.dma_conf | VHUB_EP_DMA_CTRL_RESET,
+                      ep->epn.regs + AST_VHUB_EP_DMA_CTLSTAT);
+
+               /* Enable descriptor mode */
+               writel(ep->epn.dma_conf,
+                      ep->epn.regs + AST_VHUB_EP_DMA_CTLSTAT);
+       } else {
+               /* Set base DMA config value */
+               ep->epn.dma_conf = VHUB_EP_DMA_SINGLE_STAGE;
+
+               /* Reset and switch to single stage mode */
+               writel(ep->epn.dma_conf | VHUB_EP_DMA_CTRL_RESET,
+                      ep->epn.regs + AST_VHUB_EP_DMA_CTLSTAT);
+               writel(ep->epn.dma_conf,
+                      ep->epn.regs + AST_VHUB_EP_DMA_CTLSTAT);
+               writel(0, ep->epn.regs + AST_VHUB_EP_DESC_STATUS);
+       }
+
+       /* Cleanup data toggle just in case */
+       writel(VHUB_EP_TOGGLE_SET_EPNUM(ep->epn.g_idx),
+              vhub->regs + AST_VHUB_EP_TOGGLE);
+
+       /* Cleanup and enable ACK interrupt */
+       imask = VHUB_EP_IRQ(ep->epn.g_idx);
+       writel(imask, vhub->regs + AST_VHUB_EP_ACK_ISR);
+       ep_ier = readl(vhub->regs + AST_VHUB_EP_ACK_IER);
+       ep_ier |= imask;
+       writel(ep_ier, vhub->regs + AST_VHUB_EP_ACK_IER);
+
+       /* Woot, we are online ! */
+       ep->epn.enabled = true;
+
+       spin_unlock_irqrestore(&vhub->lock, flags);
+
+       return 0;
+}
+
+static void ast_vhub_epn_dispose(struct usb_ep *u_ep)
+{
+       struct ast_vhub_ep *ep = to_ast_ep(u_ep);
+
+       if (WARN_ON(!ep->dev || !ep->d_idx))
+               return;
+
+       EPDBG(ep, "Releasing endpoint\n");
+
+       /* Take it out of the EP list */
+       list_del_init(&ep->ep.ep_list);
+
+       /* Mark the address free in the device */
+       ep->dev->epns[ep->d_idx - 1] = NULL;
+
+       /* Free name & DMA buffers */
+       kfree(ep->ep.name);
+       ep->ep.name = NULL;
+       dma_free_coherent(&ep->vhub->pdev->dev,
+                         AST_VHUB_EPn_MAX_PACKET +
+                         8 * AST_VHUB_DESCS_COUNT,
+                         ep->buf, ep->buf_dma);
+       ep->buf = NULL;
+       ep->epn.descs = NULL;
+
+       /* Mark free */
+       ep->dev = NULL;
+}
+
+static const struct usb_ep_ops ast_vhub_epn_ops = {
+       .enable         = ast_vhub_epn_enable,
+       .disable        = ast_vhub_epn_disable,
+       .dispose        = ast_vhub_epn_dispose,
+       .queue          = ast_vhub_epn_queue,
+       .dequeue        = ast_vhub_epn_dequeue,
+       .set_halt       = ast_vhub_epn_set_halt,
+       .set_wedge      = ast_vhub_epn_set_wedge,
+       .alloc_request  = ast_vhub_alloc_request,
+       .free_request   = ast_vhub_free_request,
+};
+
+struct ast_vhub_ep *ast_vhub_alloc_epn(struct ast_vhub_dev *d, u8 addr)
+{
+       struct ast_vhub *vhub = d->vhub;
+       struct ast_vhub_ep *ep;
+       unsigned long flags;
+       int i;
+
+       /* Find a free one (no device) */
+       spin_lock_irqsave(&vhub->lock, flags);
+       for (i = 0; i < AST_VHUB_NUM_GEN_EPs; i++)
+               if (vhub->epns[i].dev == NULL)
+                       break;
+       if (i >= AST_VHUB_NUM_GEN_EPs) {
+               spin_unlock_irqrestore(&vhub->lock, flags);
+               return NULL;
+       }
+
+       /* Set it up */
+       ep = &vhub->epns[i];
+       ep->dev = d;
+       spin_unlock_irqrestore(&vhub->lock, flags);
+
+       DDBG(d, "Allocating gen EP %d for addr %d\n", i, addr);
+       INIT_LIST_HEAD(&ep->queue);
+       ep->d_idx = addr;
+       ep->vhub = vhub;
+       ep->ep.ops = &ast_vhub_epn_ops;
+       ep->ep.name = kasprintf(GFP_KERNEL, "ep%d", addr);
+       d->epns[addr-1] = ep;
+       ep->epn.g_idx = i;
+       ep->epn.regs = vhub->regs + 0x200 + (i * 0x10);
+
+       ep->buf = dma_alloc_coherent(&vhub->pdev->dev,
+                                    AST_VHUB_EPn_MAX_PACKET +
+                                    8 * AST_VHUB_DESCS_COUNT,
+                                    &ep->buf_dma, GFP_KERNEL);
+       if (!ep->buf) {
+               kfree(ep->ep.name);
+               ep->ep.name = NULL;
+               return NULL;
+       }
+       ep->epn.descs = ep->buf + AST_VHUB_EPn_MAX_PACKET;
+       ep->epn.descs_dma = ep->buf_dma + AST_VHUB_EPn_MAX_PACKET;
+
+       usb_ep_set_maxpacket_limit(&ep->ep, AST_VHUB_EPn_MAX_PACKET);
+       list_add_tail(&ep->ep.ep_list, &d->gadget.ep_list);
+       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 ep;
+}
 
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * aspeed-vhub -- Driver for Aspeed SoC "vHub" USB gadget
+ *
+ * hub.c - virtual hub handling
+ *
+ * Copyright 2017 IBM Corporation
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/delay.h>
+#include <linux/ioport.h>
+#include <linux/slab.h>
+#include <linux/errno.h>
+#include <linux/list.h>
+#include <linux/interrupt.h>
+#include <linux/proc_fs.h>
+#include <linux/prefetch.h>
+#include <linux/clk.h>
+#include <linux/usb/gadget.h>
+#include <linux/of.h>
+#include <linux/of_gpio.h>
+#include <linux/regmap.h>
+#include <linux/dma-mapping.h>
+#include <linux/bcd.h>
+#include <linux/version.h>
+#include <linux/usb.h>
+#include <linux/usb/hcd.h>
+
+#include "vhub.h"
+
+/* usb 2.0 hub device descriptor
+ *
+ * A few things we may want to improve here:
+ *
+ *    - We may need to indicate TT support
+ *    - We may need a device qualifier descriptor
+ *     as devices can pretend to be usb1 or 2
+ *    - Make vid/did overridable
+ *    - make it look like usb1 if usb1 mode forced
+ */
+#define KERNEL_REL     bin2bcd(((LINUX_VERSION_CODE >> 16) & 0x0ff))
+#define KERNEL_VER     bin2bcd(((LINUX_VERSION_CODE >> 8) & 0x0ff))
+
+enum {
+       AST_VHUB_STR_MANUF = 3,
+       AST_VHUB_STR_PRODUCT = 2,
+       AST_VHUB_STR_SERIAL = 1,
+};
+
+static const struct usb_device_descriptor ast_vhub_dev_desc = {
+       .bLength                = USB_DT_DEVICE_SIZE,
+       .bDescriptorType        = USB_DT_DEVICE,
+       .bcdUSB                 = cpu_to_le16(0x0200),
+       .bDeviceClass           = USB_CLASS_HUB,
+       .bDeviceSubClass        = 0,
+       .bDeviceProtocol        = 1,
+       .bMaxPacketSize0        = 64,
+       .idVendor               = cpu_to_le16(0x1d6b),
+       .idProduct              = cpu_to_le16(0x0107),
+       .bcdDevice              = cpu_to_le16(0x0100),
+       .iManufacturer          = AST_VHUB_STR_MANUF,
+       .iProduct               = AST_VHUB_STR_PRODUCT,
+       .iSerialNumber          = AST_VHUB_STR_SERIAL,
+       .bNumConfigurations     = 1,
+};
+
+/* Patches to the above when forcing USB1 mode */
+static void ast_vhub_patch_dev_desc_usb1(struct usb_device_descriptor *desc)
+{
+       desc->bcdUSB = cpu_to_le16(0x0100);
+       desc->bDeviceProtocol = 0;
+}
+
+/*
+ * Configuration descriptor: same comments as above
+ * regarding handling USB1 mode.
+ */
+
+/*
+ * We don't use sizeof() as Linux definition of
+ * struct usb_endpoint_descriptor contains 2
+ * extra bytes
+ */
+#define AST_VHUB_CONF_DESC_SIZE        (USB_DT_CONFIG_SIZE + \
+                                USB_DT_INTERFACE_SIZE + \
+                                USB_DT_ENDPOINT_SIZE)
+
+static const struct ast_vhub_full_cdesc {
+       struct usb_config_descriptor    cfg;
+       struct usb_interface_descriptor intf;
+       struct usb_endpoint_descriptor  ep;
+} __attribute__ ((packed)) ast_vhub_conf_desc = {
+       .cfg = {
+               .bLength                = USB_DT_CONFIG_SIZE,
+               .bDescriptorType        = USB_DT_CONFIG,
+               .wTotalLength           = cpu_to_le16(AST_VHUB_CONF_DESC_SIZE),
+               .bNumInterfaces         = 1,
+               .bConfigurationValue    = 1,
+               .iConfiguration         = 0,
+               .bmAttributes           = USB_CONFIG_ATT_ONE |
+                                         USB_CONFIG_ATT_SELFPOWER |
+                                         USB_CONFIG_ATT_WAKEUP,
+               .bMaxPower              = 0,
+       },
+       .intf = {
+               .bLength                = USB_DT_INTERFACE_SIZE,
+               .bDescriptorType        = USB_DT_INTERFACE,
+               .bInterfaceNumber       = 0,
+               .bAlternateSetting      = 0,
+               .bNumEndpoints          = 1,
+               .bInterfaceClass        = USB_CLASS_HUB,
+               .bInterfaceSubClass     = 0,
+               .bInterfaceProtocol     = 0,
+               .iInterface             = 0,
+       },
+       .ep = {
+               .bLength                = USB_DT_ENDPOINT_SIZE,
+               .bDescriptorType        = USB_DT_ENDPOINT,
+               .bEndpointAddress       = 0x81,
+               .bmAttributes           = USB_ENDPOINT_XFER_INT,
+               .wMaxPacketSize         = cpu_to_le16(1),
+               .bInterval              = 0x0c,
+       },
+};
+
+#define AST_VHUB_HUB_DESC_SIZE (USB_DT_HUB_NONVAR_SIZE + 2)
+
+static const struct usb_hub_descriptor ast_vhub_hub_desc = {
+       .bDescLength                    = AST_VHUB_HUB_DESC_SIZE,
+       .bDescriptorType                = USB_DT_HUB,
+       .bNbrPorts                      = AST_VHUB_NUM_PORTS,
+       .wHubCharacteristics            = cpu_to_le16(HUB_CHAR_NO_LPSM),
+       .bPwrOn2PwrGood                 = 10,
+       .bHubContrCurrent               = 0,
+       .u.hs.DeviceRemovable[0]        = 0,
+       .u.hs.DeviceRemovable[1]        = 0xff,
+};
+
+/*
+ * These strings converted to UTF-16 must be smaller than
+ * our EP0 buffer.
+ */
+static const struct usb_string ast_vhub_str_array[] = {
+       {
+               .id = AST_VHUB_STR_SERIAL,
+               .s = "00000000"
+       },
+       {
+               .id = AST_VHUB_STR_PRODUCT,
+               .s = "USB Virtual Hub"
+       },
+       {
+               .id = AST_VHUB_STR_MANUF,
+               .s = "Aspeed"
+       },
+       { }
+};
+
+static const struct usb_gadget_strings ast_vhub_strings = {
+       .language = 0x0409,
+       .strings = (struct usb_string *)ast_vhub_str_array
+};
+
+static int ast_vhub_hub_dev_status(struct ast_vhub_ep *ep,
+                                  u16 wIndex, u16 wValue)
+{
+       u8 st0;
+
+       EPDBG(ep, "GET_STATUS(dev)\n");
+
+       /*
+        * Mark it as self-powered, I doubt the BMC is powered off
+        * the USB bus ...
+        */
+       st0 = 1 << USB_DEVICE_SELF_POWERED;
+
+       /*
+        * Need to double check how remote wakeup actually works
+        * on that chip and what triggers it.
+        */
+       if (ep->vhub->wakeup_en)
+               st0 |= 1 << USB_DEVICE_REMOTE_WAKEUP;
+
+       return ast_vhub_simple_reply(ep, st0, 0);
+}
+
+static int ast_vhub_hub_ep_status(struct ast_vhub_ep *ep,
+                                 u16 wIndex, u16 wValue)
+{
+       int ep_num;
+       u8 st0 = 0;
+
+       ep_num = wIndex & USB_ENDPOINT_NUMBER_MASK;
+       EPDBG(ep, "GET_STATUS(ep%d)\n", ep_num);
+
+       /* On the hub we have only EP 0 and 1 */
+       if (ep_num == 1) {
+               if (ep->vhub->ep1_stalled)
+                       st0 |= 1 << USB_ENDPOINT_HALT;
+       } else if (ep_num != 0)
+               return std_req_stall;
+
+       return ast_vhub_simple_reply(ep, st0, 0);
+}
+
+static int ast_vhub_hub_dev_feature(struct ast_vhub_ep *ep,
+                                   u16 wIndex, u16 wValue,
+                                   bool is_set)
+{
+       EPDBG(ep, "%s_FEATURE(dev val=%02x)\n",
+             is_set ? "SET" : "CLEAR", wValue);
+
+       if (wValue != USB_DEVICE_REMOTE_WAKEUP)
+               return std_req_stall;
+
+       ep->vhub->wakeup_en = is_set;
+       EPDBG(ep, "Hub remote wakeup %s\n",
+             is_set ? "enabled" : "disabled");
+
+       return std_req_complete;
+}
+
+static int ast_vhub_hub_ep_feature(struct ast_vhub_ep *ep,
+                                  u16 wIndex, u16 wValue,
+                                  bool is_set)
+{
+       int ep_num;
+       u32 reg;
+
+       ep_num = wIndex & USB_ENDPOINT_NUMBER_MASK;
+       EPDBG(ep, "%s_FEATURE(ep%d val=%02x)\n",
+             is_set ? "SET" : "CLEAR", ep_num, wValue);
+
+       if (ep_num > 1)
+               return std_req_stall;
+       if (wValue != USB_ENDPOINT_HALT)
+               return std_req_stall;
+       if (ep_num == 0)
+               return std_req_complete;
+
+       EPDBG(ep, "%s stall on EP 1\n",
+             is_set ? "setting" : "clearing");
+
+       ep->vhub->ep1_stalled = is_set;
+       reg = readl(ep->vhub->regs + AST_VHUB_EP1_CTRL);
+       if (is_set) {
+               reg |= VHUB_EP1_CTRL_STALL;
+       } else {
+               reg &= ~VHUB_EP1_CTRL_STALL;
+               reg |= VHUB_EP1_CTRL_RESET_TOGGLE;
+       }
+       writel(reg, ep->vhub->regs + AST_VHUB_EP1_CTRL);
+
+       return std_req_complete;
+}
+
+static int ast_vhub_rep_desc(struct ast_vhub_ep *ep,
+                            u8 desc_type, u16 len)
+{
+       size_t dsize;
+
+       EPDBG(ep, "GET_DESCRIPTOR(type:%d)\n", desc_type);
+
+       /*
+        * Copy first to EP buffer and send from there, so
+        * we can do some in-place patching if needed. We know
+        * the EP buffer is big enough but ensure that doesn't
+        * change. We do that now rather than later after we
+        * have checked sizes etc... to avoid a gcc bug where
+        * it thinks len is constant and barfs about read
+        * overflows in memcpy.
+        */
+       switch(desc_type) {
+       case USB_DT_DEVICE:
+               dsize = USB_DT_DEVICE_SIZE;
+               memcpy(ep->buf, &ast_vhub_dev_desc, dsize);
+               BUILD_BUG_ON(dsize > sizeof(ast_vhub_dev_desc));
+               BUILD_BUG_ON(USB_DT_DEVICE_SIZE >= AST_VHUB_EP0_MAX_PACKET);
+               break;
+       case USB_DT_CONFIG:
+               dsize = AST_VHUB_CONF_DESC_SIZE;
+               memcpy(ep->buf, &ast_vhub_conf_desc, dsize);
+               BUILD_BUG_ON(dsize > sizeof(ast_vhub_conf_desc));
+               BUILD_BUG_ON(AST_VHUB_CONF_DESC_SIZE >= AST_VHUB_EP0_MAX_PACKET);
+               break;
+       case USB_DT_HUB:
+               dsize = AST_VHUB_HUB_DESC_SIZE;
+               memcpy(ep->buf, &ast_vhub_hub_desc, dsize);
+               BUILD_BUG_ON(dsize > sizeof(ast_vhub_hub_desc));
+       BUILD_BUG_ON(AST_VHUB_HUB_DESC_SIZE >= AST_VHUB_EP0_MAX_PACKET);
+               break;
+       default:
+               return std_req_stall;
+       }
+
+       /* Crop requested length */
+       if (len > dsize)
+               len = dsize;
+
+       /* Patch it if forcing USB1 */
+       if (desc_type == USB_DT_DEVICE && ep->vhub->force_usb1)
+               ast_vhub_patch_dev_desc_usb1(ep->buf);
+
+       /* Shoot it from the EP buffer */
+       return ast_vhub_reply(ep, NULL, len);
+}
+
+static int ast_vhub_rep_string(struct ast_vhub_ep *ep,
+                              u8 string_id, u16 lang_id,
+                              u16 len)
+{
+       int rc = usb_gadget_get_string (&ast_vhub_strings, string_id, ep->buf);
+
+       /*
+        * This should never happen unless we put too big strings in
+        * the array above
+        */
+       BUG_ON(rc >= AST_VHUB_EP0_MAX_PACKET);
+
+       if (rc < 0)
+               return std_req_stall;
+
+       /* Shoot it from the EP buffer */
+       return ast_vhub_reply(ep, NULL, min_t(u16, rc, len));
+}
+
+enum std_req_rc ast_vhub_std_hub_request(struct ast_vhub_ep *ep,
+                                        struct usb_ctrlrequest *crq)
+{
+       struct ast_vhub *vhub = ep->vhub;
+       u16 wValue, wIndex, wLength;
+
+       wValue = le16_to_cpu(crq->wValue);
+       wIndex = le16_to_cpu(crq->wIndex);
+       wLength = le16_to_cpu(crq->wLength);
+
+       /* First packet, grab speed */
+       if (vhub->speed == USB_SPEED_UNKNOWN) {
+               u32 ustat = readl(vhub->regs + AST_VHUB_USBSTS);
+               if (ustat & VHUB_USBSTS_HISPEED)
+                       vhub->speed = USB_SPEED_HIGH;
+               else
+                       vhub->speed = USB_SPEED_FULL;
+               UDCDBG(vhub, "USB status=%08x speed=%s\n", ustat,
+                      vhub->speed == USB_SPEED_HIGH ? "high" : "full");
+       }
+
+       switch ((crq->bRequestType << 8) | crq->bRequest) {
+               /* SET_ADDRESS */
+       case DeviceOutRequest | USB_REQ_SET_ADDRESS:
+               EPDBG(ep, "SET_ADDRESS: Got address %x\n", wValue);
+               writel(wValue, vhub->regs + AST_VHUB_CONF);
+               return std_req_complete;
+
+               /* GET_STATUS */
+       case DeviceRequest | USB_REQ_GET_STATUS:
+               return ast_vhub_hub_dev_status(ep, wIndex, wValue);
+       case InterfaceRequest | USB_REQ_GET_STATUS:
+               return ast_vhub_simple_reply(ep, 0, 0);
+       case EndpointRequest | USB_REQ_GET_STATUS:
+               return ast_vhub_hub_ep_status(ep, wIndex, wValue);
+
+               /* SET/CLEAR_FEATURE */
+       case DeviceOutRequest | USB_REQ_SET_FEATURE:
+               return ast_vhub_hub_dev_feature(ep, wIndex, wValue, true);
+       case DeviceOutRequest | USB_REQ_CLEAR_FEATURE:
+               return ast_vhub_hub_dev_feature(ep, wIndex, wValue, false);
+       case EndpointOutRequest | USB_REQ_SET_FEATURE:
+               return ast_vhub_hub_ep_feature(ep, wIndex, wValue, true);
+       case EndpointOutRequest | USB_REQ_CLEAR_FEATURE:
+               return ast_vhub_hub_ep_feature(ep, wIndex, wValue, false);
+
+               /* GET/SET_CONFIGURATION */
+       case DeviceRequest | USB_REQ_GET_CONFIGURATION:
+               return ast_vhub_simple_reply(ep, 1);
+       case DeviceOutRequest | USB_REQ_SET_CONFIGURATION:
+               if (wValue != 1)
+                       return std_req_stall;
+               return std_req_complete;
+
+               /* GET_DESCRIPTOR */
+       case DeviceRequest | USB_REQ_GET_DESCRIPTOR:
+               switch (wValue >> 8) {
+               case USB_DT_DEVICE:
+               case USB_DT_CONFIG:
+                       return ast_vhub_rep_desc(ep, wValue >> 8,
+                                                wLength);
+               case USB_DT_STRING:
+                       return ast_vhub_rep_string(ep, wValue & 0xff,
+                                                  wIndex, wLength);
+               }
+               return std_req_stall;
+
+               /* GET/SET_INTERFACE */
+       case DeviceRequest | USB_REQ_GET_INTERFACE:
+               return ast_vhub_simple_reply(ep, 0);
+       case DeviceOutRequest | USB_REQ_SET_INTERFACE:
+               if (wValue != 0 || wIndex != 0)
+                       return std_req_stall;
+               return std_req_complete;
+       }
+       return std_req_stall;
+}
+
+static void ast_vhub_update_hub_ep1(struct ast_vhub *vhub,
+                                   unsigned int port)
+{
+       /* Update HW EP1 response */
+       u32 reg = readl(vhub->regs + AST_VHUB_EP1_STS_CHG);
+       u32 pmask = (1 << (port + 1));
+       if (vhub->ports[port].change)
+               reg |= pmask;
+       else
+               reg &= ~pmask;
+       writel(reg, vhub->regs + AST_VHUB_EP1_STS_CHG);
+}
+
+static void ast_vhub_change_port_stat(struct ast_vhub *vhub,
+                                     unsigned int port,
+                                     u16 clr_flags,
+                                     u16 set_flags,
+                                     bool set_c)
+{
+       struct ast_vhub_port *p = &vhub->ports[port];
+       u16 prev;
+
+       /* Update port status */
+       prev = p->status;
+       p->status = (prev & ~clr_flags) | set_flags;
+       DDBG(&p->dev, "port %d status %04x -> %04x (C=%d)\n",
+            port + 1, prev, p->status, set_c);
+
+       /* Update change bits if needed */
+       if (set_c) {
+               u16 chg = p->status ^ prev;
+
+               /* Only these are relevant for change */
+               chg &= USB_PORT_STAT_C_CONNECTION |
+                      USB_PORT_STAT_C_ENABLE |
+                      USB_PORT_STAT_C_SUSPEND |
+                      USB_PORT_STAT_C_OVERCURRENT |
+                      USB_PORT_STAT_C_RESET |
+                      USB_PORT_STAT_C_L1;
+               p->change |= chg;
+
+               ast_vhub_update_hub_ep1(vhub, port);
+       }
+}
+
+static void ast_vhub_send_host_wakeup(struct ast_vhub *vhub)
+{
+       u32 reg = readl(vhub->regs + AST_VHUB_CTRL);
+       UDCDBG(vhub, "Waking up host !\n");
+       reg |= VHUB_CTRL_MANUAL_REMOTE_WAKEUP;
+       writel(reg, vhub->regs + AST_VHUB_CTRL);
+}
+
+void ast_vhub_device_connect(struct ast_vhub *vhub,
+                            unsigned int port, bool on)
+{
+       if (on)
+               ast_vhub_change_port_stat(vhub, port, 0,
+                                         USB_PORT_STAT_CONNECTION, true);
+       else
+               ast_vhub_change_port_stat(vhub, port,
+                                         USB_PORT_STAT_CONNECTION |
+                                         USB_PORT_STAT_ENABLE,
+                                         0, true);
+
+       /*
+        * If the hub is set to wakup the host on connection events
+        * then send a wakeup.
+        */
+       if (vhub->wakeup_en)
+               ast_vhub_send_host_wakeup(vhub);
+}
+
+static void ast_vhub_wake_work(struct work_struct *work)
+{
+       struct ast_vhub *vhub = container_of(work,
+                                            struct ast_vhub,
+                                            wake_work);
+       unsigned long flags;
+       unsigned int i;
+
+       /*
+        * Wake all sleeping ports. If a port is suspended by
+        * the host suspend (without explicit state suspend),
+        * we let the normal host wake path deal with it later.
+        */
+       spin_lock_irqsave(&vhub->lock, flags);
+       for (i = 0; i < AST_VHUB_NUM_PORTS; i++) {
+               struct ast_vhub_port *p = &vhub->ports[i];
+
+               if (!(p->status & USB_PORT_STAT_SUSPEND))
+                       continue;
+               ast_vhub_change_port_stat(vhub, i,
+                                         USB_PORT_STAT_SUSPEND,
+                                         0, true);
+               ast_vhub_dev_resume(&p->dev);
+       }
+       ast_vhub_send_host_wakeup(vhub);
+       spin_unlock_irqrestore(&vhub->lock, flags);
+}
+
+void ast_vhub_hub_wake_all(struct ast_vhub *vhub)
+{
+       /*
+        * A device is trying to wake the world, because this
+        * can recurse into the device, we break the call chain
+        * using a work queue
+        */
+       schedule_work(&vhub->wake_work);
+}
+
+static void ast_vhub_port_reset(struct ast_vhub *vhub, u8 port)
+{
+       struct ast_vhub_port *p = &vhub->ports[port];
+       u16 set, clr, speed;
+
+       /* First mark disabled */
+       ast_vhub_change_port_stat(vhub, port,
+                                 USB_PORT_STAT_ENABLE |
+                                 USB_PORT_STAT_SUSPEND,
+                                 USB_PORT_STAT_RESET,
+                                 false);
+
+       if (!p->dev.driver)
+               return;
+
+       /*
+        * This will either "start" the port or reset the
+        * device if already started...
+        */
+       ast_vhub_dev_reset(&p->dev);
+
+       /* Grab the right speed */
+       speed = p->dev.driver->max_speed;
+       if (speed == USB_SPEED_UNKNOWN || speed > vhub->speed)
+               speed = vhub->speed;
+
+       switch (speed) {
+       case USB_SPEED_LOW:
+               set = USB_PORT_STAT_LOW_SPEED;
+               clr = USB_PORT_STAT_HIGH_SPEED;
+               break;
+       case USB_SPEED_FULL:
+               set = 0;
+               clr = USB_PORT_STAT_LOW_SPEED |
+                       USB_PORT_STAT_HIGH_SPEED;
+               break;
+       case USB_SPEED_HIGH:
+               set = USB_PORT_STAT_HIGH_SPEED;
+               clr = USB_PORT_STAT_LOW_SPEED;
+               break;
+       default:
+               UDCDBG(vhub, "Unsupported speed %d when"
+                      " connecting device\n",
+                      speed);
+               return;
+       }
+       clr |= USB_PORT_STAT_RESET;
+       set |= USB_PORT_STAT_ENABLE;
+
+       /* This should ideally be delayed ... */
+       ast_vhub_change_port_stat(vhub, port, clr, set, true);
+}
+
+static enum std_req_rc ast_vhub_set_port_feature(struct ast_vhub_ep *ep,
+                                                u8 port, u16 feat)
+{
+       struct ast_vhub *vhub = ep->vhub;
+       struct ast_vhub_port *p;
+
+       if (port == 0 || port > AST_VHUB_NUM_PORTS)
+               return std_req_stall;
+       port--;
+       p = &vhub->ports[port];
+
+       switch(feat) {
+       case USB_PORT_FEAT_SUSPEND:
+               if (!(p->status & USB_PORT_STAT_ENABLE))
+                       return std_req_complete;
+               ast_vhub_change_port_stat(vhub, port,
+                                         0, USB_PORT_STAT_SUSPEND,
+                                         false);
+               ast_vhub_dev_suspend(&p->dev);
+               return std_req_complete;
+       case USB_PORT_FEAT_RESET:
+               EPDBG(ep, "Port reset !\n");
+               ast_vhub_port_reset(vhub, port);
+               return std_req_complete;
+       case USB_PORT_FEAT_POWER:
+               /*
+                * On Power-on, we mark the connected flag changed,
+                * if there's a connected device, some hosts will
+                * otherwise fail to detect it.
+                */
+               if (p->status & USB_PORT_STAT_CONNECTION) {
+                       p->change |= USB_PORT_STAT_C_CONNECTION;
+                       ast_vhub_update_hub_ep1(vhub, port);
+               }
+               return std_req_complete;
+       case USB_PORT_FEAT_TEST:
+       case USB_PORT_FEAT_INDICATOR:
+               /* We don't do anything with these */
+               return std_req_complete;
+       }
+       return std_req_stall;
+}
+
+static enum std_req_rc ast_vhub_clr_port_feature(struct ast_vhub_ep *ep,
+                                                u8 port, u16 feat)
+{
+       struct ast_vhub *vhub = ep->vhub;
+       struct ast_vhub_port *p;
+
+       if (port == 0 || port > AST_VHUB_NUM_PORTS)
+               return std_req_stall;
+       port--;
+       p = &vhub->ports[port];
+
+       switch(feat) {
+       case USB_PORT_FEAT_ENABLE:
+               ast_vhub_change_port_stat(vhub, port,
+                                         USB_PORT_STAT_ENABLE |
+                                         USB_PORT_STAT_SUSPEND, 0,
+                                         false);
+               ast_vhub_dev_suspend(&p->dev);
+               return std_req_complete;
+       case USB_PORT_FEAT_SUSPEND:
+               if (!(p->status & USB_PORT_STAT_SUSPEND))
+                       return std_req_complete;
+               ast_vhub_change_port_stat(vhub, port,
+                                         USB_PORT_STAT_SUSPEND, 0,
+                                         false);
+               ast_vhub_dev_resume(&p->dev);
+               return std_req_complete;
+       case USB_PORT_FEAT_POWER:
+               /* We don't do power control */
+               return std_req_complete;
+       case USB_PORT_FEAT_INDICATOR:
+               /* We don't have indicators */
+               return std_req_complete;
+       case USB_PORT_FEAT_C_CONNECTION:
+       case USB_PORT_FEAT_C_ENABLE:
+       case USB_PORT_FEAT_C_SUSPEND:
+       case USB_PORT_FEAT_C_OVER_CURRENT:
+       case USB_PORT_FEAT_C_RESET:
+               /* Clear state-change feature */
+               p->change &= ~(1u << (feat - 16));
+               ast_vhub_update_hub_ep1(vhub, port);
+               return std_req_complete;
+       }
+       return std_req_stall;
+}
+
+static enum std_req_rc ast_vhub_get_port_stat(struct ast_vhub_ep *ep,
+                                             u8 port)
+{
+       struct ast_vhub *vhub = ep->vhub;
+       u16 stat, chg;
+
+       if (port == 0 || port > AST_VHUB_NUM_PORTS)
+               return std_req_stall;
+       port--;
+
+       stat = vhub->ports[port].status;
+       chg = vhub->ports[port].change;
+
+       /* We always have power */
+       stat |= USB_PORT_STAT_POWER;
+
+       EPDBG(ep, " port status=%04x change=%04x\n", stat, chg);
+
+       return ast_vhub_simple_reply(ep,
+                                    stat & 0xff,
+                                    stat >> 8,
+                                    chg & 0xff,
+                                    chg >> 8);
+}
+
+enum std_req_rc ast_vhub_class_hub_request(struct ast_vhub_ep *ep,
+                                          struct usb_ctrlrequest *crq)
+{
+       u16 wValue, wIndex, wLength;
+
+       wValue = le16_to_cpu(crq->wValue);
+       wIndex = le16_to_cpu(crq->wIndex);
+       wLength = le16_to_cpu(crq->wLength);
+
+       switch ((crq->bRequestType << 8) | crq->bRequest) {
+       case GetHubStatus:
+               EPDBG(ep, "GetHubStatus\n");
+               return ast_vhub_simple_reply(ep, 0, 0, 0, 0);
+       case GetPortStatus:
+               EPDBG(ep, "GetPortStatus(%d)\n", wIndex & 0xff);
+               return ast_vhub_get_port_stat(ep, wIndex & 0xf);
+       case GetHubDescriptor:
+               if (wValue != (USB_DT_HUB << 8))
+                       return std_req_stall;
+               EPDBG(ep, "GetHubDescriptor(%d)\n", wIndex & 0xff);
+               return ast_vhub_rep_desc(ep, USB_DT_HUB, wLength);
+       case SetHubFeature:
+       case ClearHubFeature:
+               EPDBG(ep, "Get/SetHubFeature(%d)\n", wValue);
+               /* No feature, just complete the requests */
+               if (wValue == C_HUB_LOCAL_POWER ||
+                   wValue == C_HUB_OVER_CURRENT)
+                       return std_req_complete;
+               return std_req_stall;
+       case SetPortFeature:
+               EPDBG(ep, "SetPortFeature(%d,%d)\n", wIndex & 0xf, wValue);
+               return ast_vhub_set_port_feature(ep, wIndex & 0xf, wValue);
+       case ClearPortFeature:
+               EPDBG(ep, "ClearPortFeature(%d,%d)\n", wIndex & 0xf, wValue);
+               return ast_vhub_clr_port_feature(ep, wIndex & 0xf, wValue);
+       default:
+               EPDBG(ep, "Unknown class request\n");
+       }
+       return std_req_stall;
+}
+
+void ast_vhub_hub_suspend(struct ast_vhub *vhub)
+{
+       unsigned int i;
+
+       UDCDBG(vhub, "USB bus suspend\n");
+
+       if (vhub->suspended)
+               return;
+
+       vhub->suspended = true;
+
+       /*
+        * Forward to unsuspended ports without changing
+        * their connection status.
+        */
+       for (i = 0; i < AST_VHUB_NUM_PORTS; i++) {
+               struct ast_vhub_port *p = &vhub->ports[i];
+
+               if (!(p->status & USB_PORT_STAT_SUSPEND))
+                       ast_vhub_dev_suspend(&p->dev);
+       }
+}
+
+void ast_vhub_hub_resume(struct ast_vhub *vhub)
+{
+       unsigned int i;
+
+       UDCDBG(vhub, "USB bus resume\n");
+
+       if (!vhub->suspended)
+               return;
+
+       vhub->suspended = false;
+
+       /*
+        * Forward to unsuspended ports without changing
+        * their connection status.
+        */
+       for (i = 0; i < AST_VHUB_NUM_PORTS; i++) {
+               struct ast_vhub_port *p = &vhub->ports[i];
+
+               if (!(p->status & USB_PORT_STAT_SUSPEND))
+                       ast_vhub_dev_resume(&p->dev);
+       }
+}
+
+void ast_vhub_hub_reset(struct ast_vhub *vhub)
+{
+       unsigned int i;
+
+       UDCDBG(vhub, "USB bus reset\n");
+
+       /*
+        * Is the speed known ? If not we don't care, we aren't
+        * initialized yet and ports haven't been enabled.
+        */
+       if (vhub->speed == USB_SPEED_UNKNOWN)
+               return;
+
+       /* We aren't suspended anymore obviously */
+       vhub->suspended = false;
+
+       /* No speed set */
+       vhub->speed = USB_SPEED_UNKNOWN;
+
+       /* Wakeup not enabled anymore */
+       vhub->wakeup_en = false;
+
+       /*
+        * Clear all port status, disable gadgets and "suspend"
+        * them. They will be woken up by a port reset.
+        */
+       for (i = 0; i < AST_VHUB_NUM_PORTS; i++) {
+               struct ast_vhub_port *p = &vhub->ports[i];
+
+               /* Only keep the connected flag */
+               p->status &= USB_PORT_STAT_CONNECTION;
+               p->change = 0;
+
+               /* Suspend the gadget if any */
+               ast_vhub_dev_suspend(&p->dev);
+       }
+
+       /* Cleanup HW */
+       writel(0, vhub->regs + AST_VHUB_CONF);
+       writel(0, vhub->regs + AST_VHUB_EP0_CTRL);
+       writel(VHUB_EP1_CTRL_RESET_TOGGLE |
+              VHUB_EP1_CTRL_ENABLE,
+              vhub->regs + AST_VHUB_EP1_CTRL);
+       writel(0, vhub->regs + AST_VHUB_EP1_STS_CHG);
+}
+
+void ast_vhub_init_hub(struct ast_vhub *vhub)
+{
+       vhub->speed = USB_SPEED_UNKNOWN;
+       INIT_WORK(&vhub->wake_work, ast_vhub_wake_work);
+}
+
 
--- /dev/null
+/* SPDX-License-Identifier: GPL-2.0+ */
+#ifndef __ASPEED_VHUB_H
+#define __ASPEED_VHUB_H
+
+/*****************************
+ *                           *
+ * VHUB register definitions *
+ *                           *
+ *****************************/
+
+#define        AST_VHUB_CTRL           0x00    /* Root Function Control & Status Register */
+#define        AST_VHUB_CONF           0x04    /* Root Configuration Setting Register */
+#define        AST_VHUB_IER            0x08    /* Interrupt Ctrl Register */
+#define        AST_VHUB_ISR            0x0C    /* Interrupt Status Register */
+#define        AST_VHUB_EP_ACK_IER     0x10    /* Programmable Endpoint Pool ACK Interrupt Enable Register */
+#define        AST_VHUB_EP_NACK_IER    0x14    /* Programmable Endpoint Pool NACK Interrupt Enable Register  */
+#define AST_VHUB_EP_ACK_ISR    0x18    /* Programmable Endpoint Pool ACK Interrupt Status Register  */
+#define AST_VHUB_EP_NACK_ISR   0x1C    /* Programmable Endpoint Pool NACK Interrupt Status Register  */
+#define AST_VHUB_SW_RESET      0x20    /* Device Controller Soft Reset Enable Register */
+#define AST_VHUB_USBSTS                0x24    /* USB Status Register */
+#define AST_VHUB_EP_TOGGLE     0x28    /* Programmable Endpoint Pool Data Toggle Value Set */
+#define AST_VHUB_ISO_FAIL_ACC  0x2C    /* Isochronous Transaction Fail Accumulator */
+#define AST_VHUB_EP0_CTRL      0x30    /* Endpoint 0 Contrl/Status Register */
+#define AST_VHUB_EP0_DATA      0x34    /* Base Address of Endpoint 0 In/OUT Data Buffer Register */
+#define AST_VHUB_EP1_CTRL      0x38    /* Endpoint 1 Contrl/Status Register */
+#define AST_VHUB_EP1_STS_CHG   0x3C    /* Endpoint 1 Status Change Bitmap Data */
+#define AST_VHUB_SETUP0                0x80    /* Root Device Setup Data Buffer0 */
+#define AST_VHUB_SETUP1                0x84    /* Root Device Setup Data Buffer1 */
+
+/* Main control reg */
+#define VHUB_CTRL_PHY_CLK                      (1 << 31)
+#define VHUB_CTRL_PHY_LOOP_TEST                        (1 << 25)
+#define VHUB_CTRL_DN_PWN                       (1 << 24)
+#define VHUB_CTRL_DP_PWN                       (1 << 23)
+#define VHUB_CTRL_LONG_DESC                    (1 << 18)
+#define VHUB_CTRL_ISO_RSP_CTRL                 (1 << 17)
+#define VHUB_CTRL_SPLIT_IN                     (1 << 16)
+#define VHUB_CTRL_LOOP_T_RESULT                        (1 << 15)
+#define VHUB_CTRL_LOOP_T_STS                   (1 << 14)
+#define VHUB_CTRL_PHY_BIST_RESULT              (1 << 13)
+#define VHUB_CTRL_PHY_BIST_CTRL                        (1 << 12)
+#define VHUB_CTRL_PHY_RESET_DIS                        (1 << 11)
+#define VHUB_CTRL_SET_TEST_MODE(x)             ((x) << 8)
+#define VHUB_CTRL_MANUAL_REMOTE_WAKEUP         (1 << 4)
+#define VHUB_CTRL_AUTO_REMOTE_WAKEUP           (1 << 3)
+#define VHUB_CTRL_CLK_STOP_SUSPEND             (1 << 2)
+#define VHUB_CTRL_FULL_SPEED_ONLY              (1 << 1)
+#define VHUB_CTRL_UPSTREAM_CONNECT             (1 << 0)
+
+/* IER & ISR */
+#define VHUB_IRQ_USB_CMD_DEADLOCK              (1 << 18)
+#define VHUB_IRQ_EP_POOL_NAK                   (1 << 17)
+#define VHUB_IRQ_EP_POOL_ACK_STALL             (1 << 16)
+#define VHUB_IRQ_DEVICE5                       (1 << 13)
+#define VHUB_IRQ_DEVICE4                       (1 << 12)
+#define VHUB_IRQ_DEVICE3                       (1 << 11)
+#define VHUB_IRQ_DEVICE2                       (1 << 10)
+#define VHUB_IRQ_DEVICE1                       (1 << 9)
+#define VHUB_IRQ_BUS_RESUME                    (1 << 8)
+#define VHUB_IRQ_BUS_SUSPEND                   (1 << 7)
+#define VHUB_IRQ_BUS_RESET                     (1 << 6)
+#define VHUB_IRQ_HUB_EP1_IN_DATA_ACK           (1 << 5)
+#define VHUB_IRQ_HUB_EP0_IN_DATA_NAK           (1 << 4)
+#define VHUB_IRQ_HUB_EP0_IN_ACK_STALL          (1 << 3)
+#define VHUB_IRQ_HUB_EP0_OUT_NAK               (1 << 2)
+#define VHUB_IRQ_HUB_EP0_OUT_ACK_STALL         (1 << 1)
+#define VHUB_IRQ_HUB_EP0_SETUP                 (1 << 0)
+#define VHUB_IRQ_ACK_ALL                       0x1ff
+
+/* SW reset reg */
+#define VHUB_SW_RESET_EP_POOL                  (1 << 9)
+#define VHUB_SW_RESET_DMA_CONTROLLER           (1 << 8)
+#define VHUB_SW_RESET_DEVICE5                  (1 << 5)
+#define VHUB_SW_RESET_DEVICE4                  (1 << 4)
+#define VHUB_SW_RESET_DEVICE3                  (1 << 3)
+#define VHUB_SW_RESET_DEVICE2                  (1 << 2)
+#define VHUB_SW_RESET_DEVICE1                  (1 << 1)
+#define VHUB_SW_RESET_ROOT_HUB                 (1 << 0)
+#define VHUB_SW_RESET_ALL                      (VHUB_SW_RESET_EP_POOL | \
+                                                VHUB_SW_RESET_DMA_CONTROLLER | \
+                                                VHUB_SW_RESET_DEVICE5 | \
+                                                VHUB_SW_RESET_DEVICE4 | \
+                                                VHUB_SW_RESET_DEVICE3 | \
+                                                VHUB_SW_RESET_DEVICE2 | \
+                                                VHUB_SW_RESET_DEVICE1 | \
+                                                VHUB_SW_RESET_ROOT_HUB)
+/* EP ACK/NACK IRQ masks */
+#define VHUB_EP_IRQ(n)                         (1 << (n))
+#define VHUB_EP_IRQ_ALL                                0x7fff  /* 15 EPs */
+
+/* USB status reg */
+#define VHUB_USBSTS_HISPEED                    (1 << 27)
+
+/* EP toggle */
+#define VHUB_EP_TOGGLE_VALUE                   (1 << 8)
+#define VHUB_EP_TOGGLE_SET_EPNUM(x)            ((x) & 0x1f)
+
+/* HUB EP0 control */
+#define VHUB_EP0_CTRL_STALL                    (1 << 0)
+#define VHUB_EP0_TX_BUFF_RDY                   (1 << 1)
+#define VHUB_EP0_RX_BUFF_RDY                   (1 << 2)
+#define VHUB_EP0_RX_LEN(x)                     (((x) >> 16) & 0x7f)
+#define VHUB_EP0_SET_TX_LEN(x)                 (((x) & 0x7f) << 8)
+
+/* HUB EP1 control */
+#define VHUB_EP1_CTRL_RESET_TOGGLE             (1 << 2)
+#define VHUB_EP1_CTRL_STALL                    (1 << 1)
+#define VHUB_EP1_CTRL_ENABLE                   (1 << 0)
+
+/***********************************
+ *                                 *
+ * per-device register definitions *
+ *                                 *
+ ***********************************/
+#define AST_VHUB_DEV_EN_CTRL           0x00
+#define AST_VHUB_DEV_ISR               0x04
+#define AST_VHUB_DEV_EP0_CTRL          0x08
+#define AST_VHUB_DEV_EP0_DATA          0x0c
+
+/* Device enable control */
+#define VHUB_DEV_EN_SET_ADDR(x)                        ((x) << 8)
+#define VHUB_DEV_EN_ADDR_MASK                  ((0xff) << 8)
+#define VHUB_DEV_EN_EP0_NAK_IRQEN              (1 << 6)
+#define VHUB_DEV_EN_EP0_IN_ACK_IRQEN           (1 << 5)
+#define VHUB_DEV_EN_EP0_OUT_NAK_IRQEN          (1 << 4)
+#define VHUB_DEV_EN_EP0_OUT_ACK_IRQEN          (1 << 3)
+#define VHUB_DEV_EN_EP0_SETUP_IRQEN            (1 << 2)
+#define VHUB_DEV_EN_SPEED_SEL_HIGH             (1 << 1)
+#define VHUB_DEV_EN_ENABLE_PORT                        (1 << 0)
+
+/* Interrupt status */
+#define VHUV_DEV_IRQ_EP0_IN_DATA_NACK          (1 << 4)
+#define VHUV_DEV_IRQ_EP0_IN_ACK_STALL          (1 << 3)
+#define VHUV_DEV_IRQ_EP0_OUT_DATA_NACK         (1 << 2)
+#define VHUV_DEV_IRQ_EP0_OUT_ACK_STALL         (1 << 1)
+#define VHUV_DEV_IRQ_EP0_SETUP                 (1 << 0)
+
+/* Control bits.
+ *
+ * Note: The driver relies on the bulk of those bits
+ *       matching corresponding vHub EP0 control bits
+ */
+#define VHUB_DEV_EP0_CTRL_STALL                        VHUB_EP0_CTRL_STALL
+#define VHUB_DEV_EP0_TX_BUFF_RDY               VHUB_EP0_TX_BUFF_RDY
+#define VHUB_DEV_EP0_RX_BUFF_RDY               VHUB_EP0_RX_BUFF_RDY
+#define VHUB_DEV_EP0_RX_LEN(x)                 VHUB_EP0_RX_LEN(x)
+#define VHUB_DEV_EP0_SET_TX_LEN(x)             VHUB_EP0_SET_TX_LEN(x)
+
+/*************************************
+ *                                   *
+ * per-endpoint register definitions *
+ *                                   *
+ *************************************/
+
+#define AST_VHUB_EP_CONFIG             0x00
+#define AST_VHUB_EP_DMA_CTLSTAT                0x04
+#define AST_VHUB_EP_DESC_BASE          0x08
+#define AST_VHUB_EP_DESC_STATUS                0x0C
+
+/* EP config reg */
+#define VHUB_EP_CFG_SET_MAX_PKT(x)     (((x) & 0x3ff) << 16)
+#define VHUB_EP_CFG_AUTO_DATA_DISABLE  (1 << 13)
+#define VHUB_EP_CFG_STALL_CTRL         (1 << 12)
+#define VHUB_EP_CFG_SET_EP_NUM(x)      (((x) & 0xf) << 8)
+#define VHUB_EP_CFG_SET_TYPE(x)                ((x) << 5)
+#define   EP_TYPE_OFF                  0
+#define   EP_TYPE_BULK                 1
+#define   EP_TYPE_INT                  2
+#define   EP_TYPE_ISO                  3
+#define VHUB_EP_CFG_DIR_OUT            (1 << 4)
+#define VHUB_EP_CFG_SET_DEV(x)         ((x) << 1)
+#define VHUB_EP_CFG_ENABLE             (1 << 0)
+
+/* EP DMA control */
+#define VHUB_EP_DMA_PROC_STATUS(x)     (((x) >> 4) & 0xf)
+#define   EP_DMA_PROC_RX_IDLE          0
+#define   EP_DMA_PROC_TX_IDLE          8
+#define VHUB_EP_DMA_IN_LONG_MODE       (1 << 3)
+#define VHUB_EP_DMA_OUT_CONTIG_MODE    (1 << 3)
+#define VHUB_EP_DMA_CTRL_RESET         (1 << 2)
+#define VHUB_EP_DMA_SINGLE_STAGE       (1 << 1)
+#define VHUB_EP_DMA_DESC_MODE          (1 << 0)
+
+/* EP DMA status */
+#define VHUB_EP_DMA_SET_TX_SIZE(x)     ((x) << 16)
+#define VHUB_EP_DMA_TX_SIZE(x)         (((x) >> 16) & 0x7ff)
+#define VHUB_EP_DMA_RPTR(x)            (((x) >> 8) & 0xff)
+#define VHUB_EP_DMA_SET_RPTR(x)                (((x) & 0xff) << 8)
+#define VHUB_EP_DMA_SET_CPU_WPTR(x)    (x)
+#define VHUB_EP_DMA_SINGLE_KICK                (1 << 0) /* WPTR = 1 for single mode */
+
+/*******************************
+ *                             *
+ * DMA descriptors definitions *
+ *                             *
+ *******************************/
+
+/* Desc W1 IN */
+#define VHUB_DSC1_IN_INTERRUPT         (1 << 31)
+#define VHUB_DSC1_IN_SPID_DATA0                (0 << 14)
+#define VHUB_DSC1_IN_SPID_DATA2                (1 << 14)
+#define VHUB_DSC1_IN_SPID_DATA1                (2 << 14)
+#define VHUB_DSC1_IN_SPID_MDATA                (3 << 14)
+#define VHUB_DSC1_IN_SET_LEN(x)                ((x) & 0xfff)
+#define VHUB_DSC1_IN_LEN(x)            ((x) & 0xfff)
+
+/****************************************
+ *                                      *
+ * Data structures and misc definitions *
+ *                                      *
+ ****************************************/
+
+#define AST_VHUB_NUM_GEN_EPs   15      /* Generic non-0 EPs */
+#define AST_VHUB_NUM_PORTS     5       /* vHub ports */
+#define AST_VHUB_EP0_MAX_PACKET        64      /* EP0's max packet size */
+#define AST_VHUB_EPn_MAX_PACKET        1024    /* Generic EPs max packet size */
+#define AST_VHUB_DESCS_COUNT   256     /* Use 256 descriptor mode (valid
+                                        * values are 256 and 32)
+                                        */
+
+struct ast_vhub;
+struct ast_vhub_dev;
+
+/*
+ * DMA descriptor (generic EPs only, currently only used
+ * for IN endpoints
+ */
+struct ast_vhub_desc {
+       __le32  w0;
+       __le32  w1;
+};
+
+/* A transfer request, either core-originated or internal */
+struct ast_vhub_req {
+       struct usb_request      req;
+       struct list_head        queue;
+
+       /* Actual count written to descriptors (desc mode only) */
+       unsigned int            act_count;
+
+       /*
+        * Desc number of the final packet or -1. For non-desc
+        * mode (or ep0), any >= 0 value means "last packet"
+        */
+       int                     last_desc;
+
+       /* Request active (pending DMAs) */
+       bool                    active  : 1;
+
+       /* Internal request (don't call back core) */
+       bool                    internal : 1;
+};
+#define to_ast_req(__ureq) container_of(__ureq, struct ast_vhub_req, req)
+
+/* Current state of an EP0 */
+enum ep0_state {
+       ep0_state_token,
+       ep0_state_data,
+       ep0_state_status,
+};
+
+/*
+ * An endpoint, either generic, ep0, actual gadget EP
+ * or internal use vhub EP0. vhub EP1 doesn't have an
+ * associated structure as it's mostly HW managed.
+ */
+struct ast_vhub_ep {
+       struct usb_ep           ep;
+
+       /* Request queue */
+       struct list_head        queue;
+
+       /* EP index in the device, 0 means this is an EP0 */
+       unsigned int            d_idx;
+
+       /* Dev pointer or NULL for vHub EP0 */
+       struct ast_vhub_dev     *dev;
+
+       /* vHub itself */
+       struct ast_vhub         *vhub;
+
+       /*
+        * DMA buffer for EP0, fallback DMA buffer for misaligned
+        * OUT transfers for generic EPs
+        */
+       void                    *buf;
+       dma_addr_t              buf_dma;
+
+       /* The rest depends on the EP type */
+       union {
+               /* EP0 (either device or vhub) */
+               struct {
+                       /*
+                        * EP0 registers are "similar" for
+                        * vHub and devices but located in
+                        * different places.
+                        */
+                       void __iomem            *ctlstat;
+                       void __iomem            *setup;
+
+                       /* Current state & direction */
+                       enum ep0_state          state;
+                       bool                    dir_in;
+
+                       /* Internal use request */
+                       struct ast_vhub_req     req;
+               } ep0;
+
+               /* Generic endpoint (aka EPn) */
+               struct {
+                       /* Registers */
+                       void __iomem            *regs;
+
+                       /* Index in global pool (0..14) */
+                       unsigned int            g_idx;
+
+                       /* DMA Descriptors */
+                       struct ast_vhub_desc    *descs;
+                       dma_addr_t              descs_dma;
+                       unsigned int            d_next;
+                       unsigned int            d_last;
+                       unsigned int            dma_conf;
+
+                       /* Max chunk size for IN EPs */
+                       unsigned int            chunk_max;
+
+                       /* State flags */
+                       bool                    is_in :  1;
+                       bool                    is_iso : 1;
+                       bool                    stalled : 1;
+                       bool                    wedged : 1;
+                       bool                    enabled : 1;
+                       bool                    desc_mode : 1;
+               } epn;
+       };
+};
+#define to_ast_ep(__uep) container_of(__uep, struct ast_vhub_ep, ep)
+
+/* A device attached to a vHub port */
+struct ast_vhub_dev {
+       struct ast_vhub                 *vhub;
+       void __iomem                    *regs;
+
+       /* Device index (0...4) and name string */
+       unsigned int                    index;
+       const char                      *name;
+
+       /* sysfs enclosure for the gadget gunk */
+       struct device                   *port_dev;
+
+       /* Link to gadget core */
+       struct usb_gadget               gadget;
+       struct usb_gadget_driver        *driver;
+       bool                            registered : 1;
+       bool                            wakeup_en : 1;
+       bool                            suspended : 1;
+       bool                            enabled : 1;
+
+       /* Endpoint structures */
+       struct ast_vhub_ep              ep0;
+       struct ast_vhub_ep              *epns[AST_VHUB_NUM_GEN_EPs];
+
+};
+#define to_ast_dev(__g) container_of(__g, struct ast_vhub_dev, gadget)
+
+/* Per vhub port stateinfo structure */
+struct ast_vhub_port {
+       /* Port status & status change registers */
+       u16                     status;
+       u16                     change;
+
+       /* Associated device slot */
+       struct ast_vhub_dev     dev;
+};
+
+/* Global vhub structure */
+struct ast_vhub {
+       struct platform_device          *pdev;
+       void __iomem                    *regs;
+       int                             irq;
+       spinlock_t                      lock;
+       struct work_struct              wake_work;
+       struct clk                      *clk;
+
+       /* EP0 DMA buffers allocated in one chunk */
+       void                            *ep0_bufs;
+       dma_addr_t                      ep0_bufs_dma;
+
+       /* EP0 of the vhub itself */
+       struct ast_vhub_ep              ep0;
+
+       /* State of vhub ep1 */
+       bool                            ep1_stalled : 1;
+
+       /* Per-port info */
+       struct ast_vhub_port            ports[AST_VHUB_NUM_PORTS];
+
+       /* Generic EP data structures */
+       struct ast_vhub_ep              epns[AST_VHUB_NUM_GEN_EPs];
+
+       /* Upstream bus is suspended ? */
+       bool                            suspended : 1;
+
+       /* Hub itself can signal remote wakeup */
+       bool                            wakeup_en : 1;
+
+       /* Force full speed only */
+       bool                            force_usb1 : 1;
+
+       /* Upstream bus speed captured at bus reset */
+       unsigned int                    speed;
+};
+
+/* Standard request handlers result codes */
+enum std_req_rc {
+       std_req_stall = -1,     /* Stall requested */
+       std_req_complete = 0,   /* Request completed with no data */
+       std_req_data = 1,       /* Request completed with data */
+       std_req_driver = 2,     /* Pass to driver pls */
+};
+
+#ifdef CONFIG_USB_GADGET_VERBOSE
+#define UDCVDBG(u, fmt...)     dev_dbg(&(u)->pdev->dev, fmt)
+
+#define EPVDBG(ep, fmt, ...)   do {                    \
+       dev_dbg(&(ep)->vhub->pdev->dev,                 \
+               "%s:EP%d " fmt,                         \
+               (ep)->dev ? (ep)->dev->name : "hub",    \
+               (ep)->d_idx, ##__VA_ARGS__);            \
+       } while(0)
+
+#define DVDBG(d, fmt, ...)     do {                    \
+       dev_dbg(&(d)->vhub->pdev->dev,                  \
+               "%s " fmt, (d)->name,                   \
+               ##__VA_ARGS__);                         \
+       } while(0)
+
+#else
+#define UDCVDBG(u, fmt...)     do { } while(0)
+#define EPVDBG(ep, fmt, ...)   do { } while(0)
+#define DVDBG(d, fmt, ...)     do { } while(0)
+#endif
+
+#ifdef CONFIG_USB_GADGET_DEBUG
+#define UDCDBG(u, fmt...)      dev_dbg(&(u)->pdev->dev, fmt)
+
+#define EPDBG(ep, fmt, ...)    do {                    \
+       dev_dbg(&(ep)->vhub->pdev->dev,                 \
+               "%s:EP%d " fmt,                         \
+               (ep)->dev ? (ep)->dev->name : "hub",    \
+               (ep)->d_idx, ##__VA_ARGS__);            \
+       } while(0)
+
+#define DDBG(d, fmt, ...)      do {                    \
+       dev_dbg(&(d)->vhub->pdev->dev,                  \
+               "%s " fmt, (d)->name,                   \
+               ##__VA_ARGS__);                         \
+       } while(0)
+#else
+#define UDCDBG(u, fmt...)      do { } while(0)
+#define EPDBG(ep, fmt, ...)    do { } while(0)
+#define DDBG(d, fmt, ...)      do { } while(0)
+#endif
+
+/* core.c */
+void ast_vhub_done(struct ast_vhub_ep *ep, struct ast_vhub_req *req,
+                  int status);
+void ast_vhub_nuke(struct ast_vhub_ep *ep, int status);
+struct usb_request *ast_vhub_alloc_request(struct usb_ep *u_ep,
+                                          gfp_t gfp_flags);
+void ast_vhub_free_request(struct usb_ep *u_ep, struct usb_request *u_req);
+void ast_vhub_init_hw(struct ast_vhub *vhub);
+
+/* ep0.c */
+void ast_vhub_ep0_handle_ack(struct ast_vhub_ep *ep, bool in_ack);
+void ast_vhub_ep0_handle_setup(struct ast_vhub_ep *ep);
+void ast_vhub_init_ep0(struct ast_vhub *vhub, struct ast_vhub_ep *ep,
+                      struct ast_vhub_dev *dev);
+int ast_vhub_reply(struct ast_vhub_ep *ep, char *ptr, int len);
+int __ast_vhub_simple_reply(struct ast_vhub_ep *ep, int len, ...);
+#define ast_vhub_simple_reply(udc, ...)                                               \
+       __ast_vhub_simple_reply((udc),                                         \
+                              sizeof((u8[]) { __VA_ARGS__ })/sizeof(u8),      \
+                              __VA_ARGS__)
+
+/* hub.c */
+void ast_vhub_init_hub(struct ast_vhub *vhub);
+enum std_req_rc ast_vhub_std_hub_request(struct ast_vhub_ep *ep,
+                                        struct usb_ctrlrequest *crq);
+enum std_req_rc ast_vhub_class_hub_request(struct ast_vhub_ep *ep,
+                                          struct usb_ctrlrequest *crq);
+void ast_vhub_device_connect(struct ast_vhub *vhub, unsigned int port,
+                            bool on);
+void ast_vhub_hub_suspend(struct ast_vhub *vhub);
+void ast_vhub_hub_resume(struct ast_vhub *vhub);
+void ast_vhub_hub_reset(struct ast_vhub *vhub);
+void ast_vhub_hub_wake_all(struct ast_vhub *vhub);
+
+/* dev.c */
+int ast_vhub_init_dev(struct ast_vhub *vhub, unsigned int idx);
+void ast_vhub_del_dev(struct ast_vhub_dev *d);
+void ast_vhub_dev_irq(struct ast_vhub_dev *d);
+int ast_vhub_std_dev_request(struct ast_vhub_ep *ep,
+                            struct usb_ctrlrequest *crq);
+
+/* epn.c */
+void ast_vhub_epn_ack_irq(struct ast_vhub_ep *ep);
+void ast_vhub_update_epn_stall(struct ast_vhub_ep *ep);
+struct ast_vhub_ep *ast_vhub_alloc_epn(struct ast_vhub_dev *d, u8 addr);
+void ast_vhub_dev_suspend(struct ast_vhub_dev *d);
+void ast_vhub_dev_resume(struct ast_vhub_dev *d);
+void ast_vhub_dev_reset(struct ast_vhub_dev *d);
+
+#endif /* __ASPEED_VHUB_H */