diff options
Diffstat (limited to 'drivers/usb/gadget/udc/aspeed-vhub')
-rw-r--r-- | drivers/usb/gadget/udc/aspeed-vhub/ep0.c | 11 | ||||
-rw-r--r-- | drivers/usb/gadget/udc/aspeed-vhub/epn.c | 14 | ||||
-rw-r--r-- | drivers/usb/gadget/udc/aspeed-vhub/vhub.h | 33 |
3 files changed, 54 insertions, 4 deletions
diff --git a/drivers/usb/gadget/udc/aspeed-vhub/ep0.c b/drivers/usb/gadget/udc/aspeed-vhub/ep0.c index 20ffb03ff6ac..e2927fb083cf 100644 --- a/drivers/usb/gadget/udc/aspeed-vhub/ep0.c +++ b/drivers/usb/gadget/udc/aspeed-vhub/ep0.c @@ -108,6 +108,13 @@ void ast_vhub_ep0_handle_setup(struct ast_vhub_ep *ep) /* Check our state, cancel pending requests if needed */ if (ep->ep0.state != ep0_state_token) { EPDBG(ep, "wrong state\n"); + ast_vhub_nuke(ep, -EIO); + + /* + * Accept the packet regardless, this seems to happen + * when stalling a SETUP packet that has an OUT data + * phase. + */ ast_vhub_nuke(ep, 0); goto stall; } @@ -212,6 +219,8 @@ static void ast_vhub_ep0_do_send(struct ast_vhub_ep *ep, 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); @@ -224,7 +233,7 @@ 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); + writel(VHUB_EP0_RX_BUFF_RDY, ep->ep0.ctlstat); } static void ast_vhub_ep0_do_receive(struct ast_vhub_ep *ep, struct ast_vhub_req *req, diff --git a/drivers/usb/gadget/udc/aspeed-vhub/epn.c b/drivers/usb/gadget/udc/aspeed-vhub/epn.c index 80c9feac5147..5939eb1e97f2 100644 --- a/drivers/usb/gadget/udc/aspeed-vhub/epn.c +++ b/drivers/usb/gadget/udc/aspeed-vhub/epn.c @@ -66,11 +66,16 @@ static void ast_vhub_epn_kick(struct ast_vhub_ep *ep, struct ast_vhub_req *req) 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; @@ -161,6 +166,7 @@ static inline unsigned int ast_vhub_count_free_descs(struct ast_vhub_ep *ep) 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; @@ -177,7 +183,6 @@ static void ast_vhub_epn_kick_desc(struct ast_vhub_ep *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 */ @@ -227,6 +232,9 @@ static void ast_vhub_epn_kick_desc(struct ast_vhub_ep *ep, 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); diff --git a/drivers/usb/gadget/udc/aspeed-vhub/vhub.h b/drivers/usb/gadget/udc/aspeed-vhub/vhub.h index 2b040257bc1f..4ed03d33a5a9 100644 --- a/drivers/usb/gadget/udc/aspeed-vhub/vhub.h +++ b/drivers/usb/gadget/udc/aspeed-vhub/vhub.h @@ -462,6 +462,39 @@ enum std_req_rc { #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); |