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: Benjamin Herrenschmidt <benh@kernel.crashing.org> Signed-off-by: Felipe Balbi <felipe.balbi@linux.intel.com>
This commit is contained in:
parent
9566a7c72f
commit
bb28633695
|
@ -219,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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue