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

Commit 0e6ca199 authored by Pavankumar Kondeti's avatar Pavankumar Kondeti Committed by Greg Kroah-Hartman
Browse files

USB: gadget: Implement hardware queuing in ci13xxx_udc



Chipidea USB controller provides a means (Add dTD TripWire semaphore)
for safely adding a new dTD to an active endpoint's linked list.  Make
use of this feature to improve performance.  Dynamically allocate and
free dTD for supporting zero length packet termination.  Honor
no_interrupt flag set by gadget drivers.

Signed-off-by: default avatarPavankumar Kondeti <pkondeti@codeaurora.org>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@suse.de>
parent 91f58ae6
Loading
Loading
Loading
Loading
+100 −76
Original line number Diff line number Diff line
@@ -434,20 +434,6 @@ static int hw_ep_get_halt(int num, int dir)
	return hw_cread(CAP_ENDPTCTRL + num * sizeof(u32), mask) ? 1 : 0;
}

/**
 * hw_ep_is_primed: test if endpoint is primed (execute without interruption)
 * @num:   endpoint number
 * @dir:   endpoint direction
 *
 * This function returns true if endpoint primed
 */
static int hw_ep_is_primed(int num, int dir)
{
	u32 reg = hw_cread(CAP_ENDPTPRIME, ~0) | hw_cread(CAP_ENDPTSTAT, ~0);

	return test_bit(hw_ep_bit(num, dir), (void *)&reg);
}

/**
 * hw_test_and_clear_setup_status: test & clear setup status (execute without
 *                                 interruption)
@@ -472,10 +458,6 @@ static int hw_ep_prime(int num, int dir, int is_ctrl)
{
	int n = hw_ep_bit(num, dir);

	/* the caller should flush first */
	if (hw_ep_is_primed(num, dir))
		return -EBUSY;

	if (is_ctrl && dir == RX && hw_cread(CAP_ENDPTSETUPSTAT, BIT(num)))
		return -EAGAIN;

@@ -1434,6 +1416,8 @@ static inline u8 _usb_addr(struct ci13xxx_ep *ep)
static int _hardware_enqueue(struct ci13xxx_ep *mEp, struct ci13xxx_req *mReq)
{
	unsigned i;
	int ret = 0;
	unsigned length = mReq->req.length;

	trace("%p, %p", mEp, mReq);

@@ -1441,53 +1425,91 @@ static int _hardware_enqueue(struct ci13xxx_ep *mEp, struct ci13xxx_req *mReq)
	if (mReq->req.status == -EALREADY)
		return -EALREADY;

	if (hw_ep_is_primed(mEp->num, mEp->dir))
		return -EBUSY;

	mReq->req.status = -EALREADY;

	if (mReq->req.length && !mReq->req.dma) {
	if (length && !mReq->req.dma) {
		mReq->req.dma = \
			dma_map_single(mEp->device, mReq->req.buf,
				       mReq->req.length, mEp->dir ?
				       DMA_TO_DEVICE : DMA_FROM_DEVICE);
				       length, mEp->dir ? DMA_TO_DEVICE :
				       DMA_FROM_DEVICE);
		if (mReq->req.dma == 0)
			return -ENOMEM;

		mReq->map = 1;
	}

	if (mReq->req.zero && length && (length % mEp->ep.maxpacket == 0)) {
		mReq->zptr = dma_pool_alloc(mEp->td_pool, GFP_ATOMIC,
					   &mReq->zdma);
		if (mReq->zptr == NULL) {
			if (mReq->map) {
				dma_unmap_single(mEp->device, mReq->req.dma,
					length, mEp->dir ? DMA_TO_DEVICE :
					DMA_FROM_DEVICE);
				mReq->req.dma = 0;
				mReq->map     = 0;
			}
			return -ENOMEM;
		}
		memset(mReq->zptr, 0, sizeof(*mReq->zptr));
		mReq->zptr->next    = TD_TERMINATE;
		mReq->zptr->token   = TD_STATUS_ACTIVE;
		if (!mReq->req.no_interrupt)
			mReq->zptr->token   |= TD_IOC;
	}
	/*
	 * TD configuration
	 * TODO - handle requests which spawns into several TDs
	 */
	memset(mReq->ptr, 0, sizeof(*mReq->ptr));
	mReq->ptr->next    |= TD_TERMINATE;
	mReq->ptr->token    = mReq->req.length << ffs_nr(TD_TOTAL_BYTES);
	mReq->ptr->token    = length << ffs_nr(TD_TOTAL_BYTES);
	mReq->ptr->token   &= TD_TOTAL_BYTES;
	mReq->ptr->token   |= TD_IOC;
	mReq->ptr->token   |= TD_STATUS_ACTIVE;
	if (mReq->zptr) {
		mReq->ptr->next    = mReq->zdma;
	} else {
		mReq->ptr->next    = TD_TERMINATE;
		if (!mReq->req.no_interrupt)
			mReq->ptr->token  |= TD_IOC;
	}
	mReq->ptr->page[0]  = mReq->req.dma;
	for (i = 1; i < 5; i++)
		mReq->ptr->page[i] =
			(mReq->req.dma + i * CI13XXX_PAGE_SIZE) & ~TD_RESERVED_MASK;

	/*
	 *  QH configuration
	 *  At this point it's guaranteed exclusive access to qhead
	 *  (endpt is not primed) so it's no need to use tripwire
	 */
	if (!list_empty(&mEp->qh.queue)) {
		struct ci13xxx_req *mReqPrev;
		int n = hw_ep_bit(mEp->num, mEp->dir);
		int tmp_stat;

		mReqPrev = list_entry(mEp->qh.queue.prev,
				struct ci13xxx_req, queue);
		if (mReqPrev->zptr)
			mReqPrev->zptr->next = mReq->dma & TD_ADDR_MASK;
		else
			mReqPrev->ptr->next = mReq->dma & TD_ADDR_MASK;
		wmb();
		if (hw_cread(CAP_ENDPTPRIME, BIT(n)))
			goto done;
		do {
			hw_cwrite(CAP_USBCMD, USBCMD_ATDTW, USBCMD_ATDTW);
			tmp_stat = hw_cread(CAP_ENDPTSTAT, BIT(n));
		} while (!hw_cread(CAP_USBCMD, USBCMD_ATDTW));
		hw_cwrite(CAP_USBCMD, USBCMD_ATDTW, 0);
		if (tmp_stat)
			goto done;
	}

	/*  QH configuration */
	mEp->qh.ptr->td.next   = mReq->dma;    /* TERMINATE = 0 */
	mEp->qh.ptr->td.token &= ~TD_STATUS;   /* clear status */
	if (mReq->req.zero == 0)
	mEp->qh.ptr->cap |=  QH_ZLT;
	else
		mEp->qh.ptr->cap &= ~QH_ZLT;

	wmb();   /* synchronize before ep prime */

	return hw_ep_prime(mEp->num, mEp->dir,
	ret = hw_ep_prime(mEp->num, mEp->dir,
			   mEp->type == USB_ENDPOINT_XFER_CONTROL);
done:
	return ret;
}

/**
@@ -1504,8 +1526,15 @@ static int _hardware_dequeue(struct ci13xxx_ep *mEp, struct ci13xxx_req *mReq)
	if (mReq->req.status != -EALREADY)
		return -EINVAL;

	if (hw_ep_is_primed(mEp->num, mEp->dir))
		hw_ep_flush(mEp->num, mEp->dir);
	if ((TD_STATUS_ACTIVE & mReq->ptr->token) != 0)
		return -EBUSY;

	if (mReq->zptr) {
		if ((TD_STATUS_ACTIVE & mReq->zptr->token) != 0)
			return -EBUSY;
		dma_pool_free(mEp->td_pool, mReq->zptr, mReq->zdma);
		mReq->zptr = NULL;
	}

	mReq->req.status = 0;

@@ -1517,9 +1546,7 @@ static int _hardware_dequeue(struct ci13xxx_ep *mEp, struct ci13xxx_req *mReq)
	}

	mReq->req.status = mReq->ptr->token & TD_STATUS;
	if      ((TD_STATUS_ACTIVE & mReq->req.status) != 0)
		mReq->req.status = -ECONNRESET;
	else if ((TD_STATUS_HALTED & mReq->req.status) != 0)
	if ((TD_STATUS_HALTED & mReq->req.status) != 0)
		mReq->req.status = -1;
	else if ((TD_STATUS_DT_ERR & mReq->req.status) != 0)
		mReq->req.status = -1;
@@ -1783,7 +1810,7 @@ static int isr_tr_complete_low(struct ci13xxx_ep *mEp)
__releases(mEp->lock)
__acquires(mEp->lock)
{
	struct ci13xxx_req *mReq;
	struct ci13xxx_req *mReq, *mReqTemp;
	int retval;

	trace("%p", mEp);
@@ -1791,34 +1818,25 @@ __acquires(mEp->lock)
	if (list_empty(&mEp->qh.queue))
		return -EINVAL;

	/* pop oldest request */
	mReq = list_entry(mEp->qh.queue.next,
			  struct ci13xxx_req, queue);
	list_del_init(&mReq->queue);

	list_for_each_entry_safe(mReq, mReqTemp, &mEp->qh.queue,
			queue) {
		retval = _hardware_dequeue(mEp, mReq);
	if (retval < 0) {
		dbg_event(_usb_addr(mEp), "DONE", retval);
		goto done;
	}

		if (retval < 0)
			break;
		list_del_init(&mReq->queue);
		dbg_done(_usb_addr(mEp), mReq->ptr->token, retval);

	if (!list_empty(&mEp->qh.queue)) {
		struct ci13xxx_req* mReqEnq;

		mReqEnq = list_entry(mEp->qh.queue.next,
				  struct ci13xxx_req, queue);
		_hardware_enqueue(mEp, mReqEnq);
	}

		if (mReq->req.complete != NULL) {
			spin_unlock(mEp->lock);
			mReq->req.complete(&mEp->ep, &mReq->req);
			spin_lock(mEp->lock);
		}
	}

	if (retval == EBUSY)
		retval = 0;
	if (retval < 0)
		dbg_event(_usb_addr(mEp), "DONE", retval);

 done:
	return retval;
}

@@ -2178,15 +2196,15 @@ static int ep_queue(struct usb_ep *ep, struct usb_request *req,
	/* push request */
	mReq->req.status = -EINPROGRESS;
	mReq->req.actual = 0;
	list_add_tail(&mReq->queue, &mEp->qh.queue);

	if (list_is_singular(&mEp->qh.queue))
	retval = _hardware_enqueue(mEp, mReq);

	if (retval == -EALREADY) {
		dbg_event(_usb_addr(mEp), "QUEUE", retval);
		retval = 0;
	}
	if (!retval)
		list_add_tail(&mReq->queue, &mEp->qh.queue);

 done:
	spin_unlock_irqrestore(mEp->lock, flags);
@@ -2206,19 +2224,25 @@ static int ep_dequeue(struct usb_ep *ep, struct usb_request *req)

	trace("%p, %p", ep, req);

	if (ep == NULL || req == NULL || mEp->desc == NULL ||
	    list_empty(&mReq->queue)  || list_empty(&mEp->qh.queue))
	if (ep == NULL || req == NULL || mReq->req.status != -EALREADY ||
		mEp->desc == NULL || list_empty(&mReq->queue) ||
		list_empty(&mEp->qh.queue))
		return -EINVAL;

	spin_lock_irqsave(mEp->lock, flags);

	dbg_event(_usb_addr(mEp), "DEQUEUE", 0);

	if (mReq->req.status == -EALREADY)
		_hardware_dequeue(mEp, mReq);
	hw_ep_flush(mEp->num, mEp->dir);

	/* pop request */
	list_del_init(&mReq->queue);
	if (mReq->map) {
		dma_unmap_single(mEp->device, mReq->req.dma, mReq->req.length,
				 mEp->dir ? DMA_TO_DEVICE : DMA_FROM_DEVICE);
		mReq->req.dma = 0;
		mReq->map     = 0;
	}
	req->status = -ECONNRESET;

	if (mReq->req.complete != NULL) {
+4 −0
Original line number Diff line number Diff line
@@ -33,6 +33,7 @@ struct ci13xxx_td {
	/* 0 */
	u32 next;
#define TD_TERMINATE          BIT(0)
#define TD_ADDR_MASK          (0xFFFFFFEUL << 5)
	/* 1 */
	u32 token;
#define TD_STATUS             (0x00FFUL <<  0)
@@ -74,6 +75,8 @@ struct ci13xxx_req {
	struct list_head     queue;
	struct ci13xxx_td   *ptr;
	dma_addr_t           dma;
	struct ci13xxx_td   *zptr;
	dma_addr_t           zdma;
};

/* Extension of usb_ep */
@@ -152,6 +155,7 @@ struct ci13xxx {
#define USBCMD_RS             BIT(0)
#define USBCMD_RST            BIT(1)
#define USBCMD_SUTW           BIT(13)
#define USBCMD_ATDTW          BIT(14)

/* USBSTS & USBINTR */
#define USBi_UI               BIT(0)