Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

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

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
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -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);
+11 −3
Original line number Diff line number Diff line
@@ -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);
+33 −0
Original line number Diff line number Diff line
@@ -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);