if (chunk && req->req.buf)
                memcpy(ep->buf, req->req.buf + req->req.actual, chunk);
 
+       vhub_dma_workaround(ep->buf);
+
        /* Remember chunk size and trigger send */
        reg = VHUB_EP0_SET_TX_LEN(chunk);
        writel(reg, ep->ep0.ctlstat);
 
        if (!req->req.dma) {
 
                /* For IN transfers, copy data over first */
-               if (ep->epn.is_in)
+               if (ep->epn.is_in) {
                        memcpy(ep->buf, req->req.buf + act, chunk);
+                       vhub_dma_workaround(ep->buf);
+               }
                writel(ep->buf_dma, ep->epn.regs + AST_VHUB_EP_DESC_BASE);
-       } else
+       } else {
+               if (ep->epn.is_in)
+                       vhub_dma_workaround(req->req.buf);
                writel(req->req.dma + act, ep->epn.regs + AST_VHUB_EP_DESC_BASE);
+       }
 
        /* Start DMA */
        req->active = true;
 static void ast_vhub_epn_kick_desc(struct ast_vhub_ep *ep,
                                   struct ast_vhub_req *req)
 {
+       struct ast_vhub_desc *desc = NULL;
        unsigned int act = req->act_count;
        unsigned int len = req->req.length;
        unsigned int chunk;
 
        /* 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 */
                req->act_count = act = act + chunk;
        }
 
+       if (likely(desc))
+               vhub_dma_workaround(desc);
+
        /* Tell HW about new descriptors */
        writel(VHUB_EP_DMA_SET_CPU_WPTR(ep->epn.d_next),
               ep->epn.regs + AST_VHUB_EP_DESC_STATUS);
 
 #define DDBG(d, fmt, ...)      do { } while(0)
 #endif
 
+static inline void vhub_dma_workaround(void *addr)
+{
+       /*
+        * This works around a confirmed HW issue with the Aspeed chip.
+        *
+        * The core uses a different bus to memory than the AHB going to
+        * the USB device controller. Due to the latter having a higher
+        * priority than the core for arbitration on that bus, it's
+        * possible for an MMIO to the device, followed by a DMA by the
+        * device from memory to all be performed and services before
+        * a previous store to memory gets completed.
+        *
+        * This the following scenario can happen:
+        *
+        *    - Driver writes to a DMA descriptor (Mbus)
+        *    - Driver writes to the MMIO register to start the DMA (AHB)
+        *    - The gadget sees the second write and sends a read of the
+        *      descriptor to the memory controller (Mbus)
+        *    - The gadget hits memory before the descriptor write
+        *      causing it to read an obsolete value.
+        *
+        * Thankfully the problem is limited to the USB gadget device, other
+        * masters in the SoC all have a lower priority than the core, thus
+        * ensuring that the store by the core arrives first.
+        *
+        * The workaround consists of using a dummy read of the memory before
+        * doing the MMIO writes. This will ensure that the previous writes
+        * have been "pushed out".
+        */
+       mb();
+       (void)__raw_readl((void __iomem *)addr);
+}
+
 /* core.c */
 void ast_vhub_done(struct ast_vhub_ep *ep, struct ast_vhub_req *req,
                   int status);