Commit bb286336 authored by Benjamin Herrenschmidt's avatar Benjamin Herrenschmidt Committed by Felipe Balbi

usb: gadget: aspeed: Workaround memory ordering issue

The Aspeed SoC has a memory ordering issue that (thankfully)
only affects the USB gadget device. A read back is necessary
after writing to memory and before letting the device DMA
from it.
Signed-off-by: default avatarBenjamin Herrenschmidt <benh@kernel.crashing.org>
Signed-off-by: default avatarFelipe Balbi <felipe.balbi@linux.intel.com>
parent 9566a7c7
...@@ -219,6 +219,8 @@ static void ast_vhub_ep0_do_send(struct ast_vhub_ep *ep, ...@@ -219,6 +219,8 @@ static void ast_vhub_ep0_do_send(struct ast_vhub_ep *ep,
if (chunk && req->req.buf) if (chunk && req->req.buf)
memcpy(ep->buf, req->req.buf + req->req.actual, chunk); memcpy(ep->buf, req->req.buf + req->req.actual, chunk);
vhub_dma_workaround(ep->buf);
/* Remember chunk size and trigger send */ /* Remember chunk size and trigger send */
reg = VHUB_EP0_SET_TX_LEN(chunk); reg = VHUB_EP0_SET_TX_LEN(chunk);
writel(reg, ep->ep0.ctlstat); writel(reg, ep->ep0.ctlstat);
......
...@@ -66,11 +66,16 @@ static void ast_vhub_epn_kick(struct ast_vhub_ep *ep, struct ast_vhub_req *req) ...@@ -66,11 +66,16 @@ static void ast_vhub_epn_kick(struct ast_vhub_ep *ep, struct ast_vhub_req *req)
if (!req->req.dma) { if (!req->req.dma) {
/* For IN transfers, copy data over first */ /* 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); 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); 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); writel(req->req.dma + act, ep->epn.regs + AST_VHUB_EP_DESC_BASE);
}
/* Start DMA */ /* Start DMA */
req->active = true; req->active = true;
...@@ -161,6 +166,7 @@ static inline unsigned int ast_vhub_count_free_descs(struct ast_vhub_ep *ep) ...@@ -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, static void ast_vhub_epn_kick_desc(struct ast_vhub_ep *ep,
struct ast_vhub_req *req) struct ast_vhub_req *req)
{ {
struct ast_vhub_desc *desc = NULL;
unsigned int act = req->act_count; unsigned int act = req->act_count;
unsigned int len = req->req.length; unsigned int len = req->req.length;
unsigned int chunk; unsigned int chunk;
...@@ -177,7 +183,6 @@ static void ast_vhub_epn_kick_desc(struct ast_vhub_ep *ep, ...@@ -177,7 +183,6 @@ static void ast_vhub_epn_kick_desc(struct ast_vhub_ep *ep,
/* While we can create descriptors */ /* While we can create descriptors */
while (ast_vhub_count_free_descs(ep) && req->last_desc < 0) { while (ast_vhub_count_free_descs(ep) && req->last_desc < 0) {
struct ast_vhub_desc *desc;
unsigned int d_num; unsigned int d_num;
/* Grab next free descriptor */ /* Grab next free descriptor */
...@@ -227,6 +232,9 @@ static void ast_vhub_epn_kick_desc(struct ast_vhub_ep *ep, ...@@ -227,6 +232,9 @@ static void ast_vhub_epn_kick_desc(struct ast_vhub_ep *ep,
req->act_count = act = act + chunk; req->act_count = act = act + chunk;
} }
if (likely(desc))
vhub_dma_workaround(desc);
/* Tell HW about new descriptors */ /* Tell HW about new descriptors */
writel(VHUB_EP_DMA_SET_CPU_WPTR(ep->epn.d_next), writel(VHUB_EP_DMA_SET_CPU_WPTR(ep->epn.d_next),
ep->epn.regs + AST_VHUB_EP_DESC_STATUS); ep->epn.regs + AST_VHUB_EP_DESC_STATUS);
......
...@@ -462,6 +462,39 @@ enum std_req_rc { ...@@ -462,6 +462,39 @@ enum std_req_rc {
#define DDBG(d, fmt, ...) do { } while(0) #define DDBG(d, fmt, ...) do { } while(0)
#endif #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 */ /* core.c */
void ast_vhub_done(struct ast_vhub_ep *ep, struct ast_vhub_req *req, void ast_vhub_done(struct ast_vhub_ep *ep, struct ast_vhub_req *req,
int status); int status);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment