summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGregory Nutt <gnutt@nuttx.org>2013-08-22 10:27:46 -0600
committerGregory Nutt <gnutt@nuttx.org>2013-08-22 10:27:46 -0600
commitbb2ce35412d6b1520131861c68b3c8b7fa5da534 (patch)
tree081e0bde7c634756530ba5688f302ac5195068ff
parent22c63aadad496c1575b9edc42385cc2808f211e5 (diff)
downloadnuttx-bb2ce35412d6b1520131861c68b3c8b7fa5da534.tar.gz
nuttx-bb2ce35412d6b1520131861c68b3c8b7fa5da534.tar.bz2
nuttx-bb2ce35412d6b1520131861c68b3c8b7fa5da534.zip
SAMA5 EHCI: Add data transfer logic for asynchronous endpoints
-rwxr-xr-xnuttx/arch/arm/src/sama5/sam_ehci.c508
1 files changed, 488 insertions, 20 deletions
diff --git a/nuttx/arch/arm/src/sama5/sam_ehci.c b/nuttx/arch/arm/src/sama5/sam_ehci.c
index 03fc1db4a..2a510f3f3 100755
--- a/nuttx/arch/arm/src/sama5/sam_ehci.c
+++ b/nuttx/arch/arm/src/sama5/sam_ehci.c
@@ -295,6 +295,13 @@ static int sam_qh_flush(struct sam_qh_s *qh);
static int sam_ioc_setup(struct sam_rhport_s *rhport, struct sam_epinfo_s *epinfo);
static int sam_ioc_wait(struct sam_epinfo_s *epinfo);
static void sam_qh_enqueue(struct sam_qh_s *qh);
+static struct sam_qh_s *sam_qh_create(struct sam_rhport_s *rhport,
+ struct sam_epinfo_s *epinfo);
+static int sam_qtd_addbpl(struct sam_qtd_s *qtd, const void *buffer, size_t buflen);
+static struct sam_qtd_s *sam_qtd_setupphase(const struct usb_ctrlreq_s *req);
+static struct sam_qtd_s *sam_qtd_dataphase(void *buffer, int buflen,
+ uint32_t tokenbits);
+static struct sam_qtd_s *sam_qtd_statusphase(uint32_t tokenbits);
static int sam_async_transfer(struct sam_rhport_s *rhport,
struct sam_epinfo_s *epinfo, const struct usb_ctrlreq_s *req,
uint8_t *buffer, size_t buflen);
@@ -874,26 +881,25 @@ static int sam_qh_foreach(struct sam_qh_s *qh, uint32_t **bp, foreach_qh_t handl
* Setup and call sam_qh_foreach to that every element of the asynchronous
* queue is examined.
*
+ * Assumption: The caller holds the EHCI exclsem
+ *
*******************************************************************************/
static int sam_qh_forall(foreach_qh_t handler, void *arg)
{
struct sam_qh_s *qh;
uint32_t *bp;
- int ret;
- /* Preemption is disabled to prevent concurrent modification of the queue
- * head by the other threads.
+ /* Set the back pointer to the forward qTD pointer of the asynchronous
+ * queue head.
*/
- bp = (uint32_t *)&qh->hw.hlp;
-
- sched_lock();
+ bp = (uint32_t *)&g_asynchead.hw.hlp;
qh = (struct sam_qh_s *)sam_virtramaddr(sam_swap32(*bp) & QH_HLP_MASK);
- sam_qh_foreach(qh, &bp, handler, arg);
- sched_unlock();
- return ret;
+ /* Then traverse and operate on every QH and qTD in the list */
+
+ return sam_qh_foreach(qh, &bp, handler, arg);
}
/*******************************************************************************
@@ -1186,20 +1192,15 @@ static int sam_ioc_wait(struct sam_epinfo_s *epinfo)
* Description:
* Add a new, ready-to-go QH w/attached qTDs to the asynchonous queue.
*
+ * Assumptions: The caller holds the EHCI exclsem
+ *
*******************************************************************************/
static void sam_qh_enqueue(struct sam_qh_s *qh)
{
uintptr_t physaddr;
- /* Add the new QH to the head of the asynchronous queue list. Preemption
- * is disabled momentarily to prevent concurrent modification of the queue
- * head by the worker thread.
- */
-
- physaddr = (uintptr_t)sam_physramaddr((uintptr_t)qh);
- sched_lock();
-
+ /* Add the new QH to the head of the asynchronous queue list. */
/* Attach the old head as the new QH HLP and flush the new QH and its attached
* qTDs to RAM.
*/
@@ -1211,10 +1212,330 @@ static void sam_qh_enqueue(struct sam_qh_s *qh)
* modified head to RAM.
*/
+ physaddr = (uintptr_t)sam_physramaddr((uintptr_t)qh);
g_asynchead.hw.hlp = sam_swap32(physaddr | QH_HLP_TYP_QH);
cp15_coherent_dcache((uintptr_t)&g_asynchead,
(uintptr_t)&g_asynchead + sizeof(struct ehci_qh_s));
- sched_unlock();
+}
+
+/*******************************************************************************
+ * Name: sam_qh_create
+ *
+ * Description:
+ * Create a new Queue Head (QH)
+ *
+ *******************************************************************************/
+
+static struct sam_qh_s *sam_qh_create(struct sam_rhport_s *rhport,
+ struct sam_epinfo_s *epinfo)
+{
+ struct sam_qh_s *qh;
+ uint32_t regval;
+
+ /* Allocate a new queue head structure */
+
+ qh = sam_qh_alloc();
+ if (qh == NULL)
+ {
+ udbg("ERROR: Failed to allocate a QH\n");
+ return NULL;
+ }
+
+ /* Save the endpoint information with the QH itself */
+
+ qh->epinfo = epinfo;
+
+ /* Write QH endpoint characteristics:
+ *
+ * FIELD DESCRIPTION VALUE/SOURCE
+ * -------- ------------------------------- --------------------
+ * DEVADDR Device address Endpoint structure
+ * I Inactivate on Next Transaction 0
+ * ENDPT Endpoint number Endpoint structure
+ * EPS Endpoint speed Endpoint structure
+ * DTC Data toggle control 1
+ * MAXPKT Max packet size Endpoint structure
+ * C Control endpoint Calculated
+ * RL NAK count reloaded 8
+ */
+
+ regval = ((uint32_t)epinfo->devaddr << QH_EPCHAR_DEVADDR_SHIFT) |
+ ((uint32_t)epinfo->epno << QH_EPCHAR_ENDPT_SHIFT) |
+ ((uint32_t)epinfo->speed << QH_EPCHAR_EPS_SHIFT) |
+ QH_EPCHAR_DTC |
+ ((uint32_t)epinfo->maxpacket << QH_EPCHAR_MAXPKT_SHIFT) |
+ ((uint32_t)8 << QH_EPCHAR_RL_SHIFT);
+
+ if (epinfo->speed != EHCI_FULL_SPEED && epinfo->epno == 0)
+ {
+ regval |= QH_EPCHAR_C;
+ }
+
+ qh->hw.epchar = sam_swap32(regval);
+
+ /* Write QH endpoint capabilities
+ *
+ * FIELD DESCRIPTION VALUE/SOURCE
+ * -------- ------------------------------- --------------------
+ * SSMASK Interrupt Schedule Mask 0
+ * SCMASK Split Completion Mask 0
+ * HUBADDR Hub Address Always 0 for now
+ * PORT Port number RH port index + 1
+ * MULT High band width multiplier 1
+ *
+ * REVISIT: Future HUB support will require the HUB port number
+ * and HUB device address to be included here.
+ */
+
+ regval = ((uint32_t)0 << QH_EPCAPS_HUBADDR_SHIFT) |
+ ((uint32_t)(rhport->rhpndx + 1) << QH_EPCAPS_PORT_SHIFT) |
+ ((uint32_t)1 << QH_EPCAPS_MULT_SHIFT);
+
+ qh->hw.epcaps = sam_swap32(regval);
+
+ /* Mark this as the end of this list. This will be overwritten if/when the
+ * next qTD is added to the queue.
+ */
+
+ qh->hw.overlay.nqp = sam_swap32(QH_NQP_T);
+}
+
+/*******************************************************************************
+ * Name: sam_qtd_addbpl
+ *
+ * Description:
+ * Add a buffer pointer list to a qTD.
+ *
+ *******************************************************************************/
+
+static int sam_qtd_addbpl(struct sam_qtd_s *qtd, const void *buffer, size_t buflen)
+{
+ uint32_t physaddr;
+ uint32_t nbytes;
+ uint32_t next;
+ int ndx;
+
+ physaddr = (uint32_t)sam_physramaddr((uintptr_t)buffer);
+
+ for (ndx = 0; ndx < 5; ndx++)
+ {
+ /* Write the physical address of the buffer into the qTD buffer pointer
+ * list.
+ */
+
+ qtd->hw.bpl[ndx] = sam_swap32(physaddr);
+
+ /* Get the next buffer pointer (in the case where we will have to transfer
+ * more then on 4KB chunks.
+ */
+
+ next = (physaddr + 4096) & ~4095;
+
+ /* How many bytes were included in the last buffer? Was the the whole
+ * thing?
+ */
+
+ nbytes = next - physaddr;
+ if (nbytes >= buflen)
+ {
+ /* Yes... it was the whole thing. Break out of the loop early. */
+
+ break;
+ }
+
+ /* Adjust the buffer length and physical address for the next time
+ * through the loop.
+ */
+
+ buflen -= nbytes;
+ physaddr = next;
+ }
+
+ /* Handle the case of a huge buffer > 4*4KB = 16KB */
+
+ if (ndx >= 5)
+ {
+ uvdbg("ERROR: Buffer too big. Remaining %d\n", buflen);
+ return -EFBIG;
+ }
+
+ return OK;
+}
+
+/*******************************************************************************
+ * Name: sam_qtd_setupphase
+ *
+ * Description:
+ * Create a SETUP phase request qTD.
+ *
+ *******************************************************************************/
+
+static struct sam_qtd_s *sam_qtd_setupphase(const struct usb_ctrlreq_s *req)
+{
+ struct sam_qtd_s *qtd;
+ uint32_t regval;
+ int ret;
+
+ /* Allocate a new Queue Element Transfer Descriptor (qTD) */
+
+ qtd = sam_qtd_alloc();
+ if (qtd == NULL)
+ {
+ udbg("ERROR: Failed to allocate request qTD");
+ return NULL;
+ }
+
+ /* Mark this as the end of the list (this will be overwritten if another
+ * qTD is added after this one.
+ */
+
+ qtd->hw.nqp = sam_swap32(QTD_NQP_T);
+ qtd->hw.alt = sam_swap32(QTD_AQP_T);
+
+ /* Write qTD token:
+ *
+ * FIELD DESCRIPTION VALUE/SOURCE
+ * -------- ------------------------------- --------------------
+ * STATUS Status QTD_TOKEN_ACTIVE
+ * PID PID Code QTD_TOKEN_PID_SETUP
+ * CERR Error Counter 3
+ * CPAGE Current Page 0
+ * IOC Interrupt on complete 0
+ * NBYTES Total Bytes to Transfer USB_SIZEOF_CTRLREQ
+ * TOGGLE Data Toggle 0
+ */
+
+ regval = QTD_TOKEN_ACTIVE | QTD_TOKEN_PID_SETUP |
+ ((uint32_t)3 << QTD_TOKEN_CERR_SHIFT) |
+ ((uint32_t)USB_SIZEOF_CTRLREQ << QTD_TOKEN_NBYTES_SHIFT);
+
+ qtd->hw.token = sam_swap32(regval);
+
+ /* Add the buffer data */
+
+ ret = sam_qtd_addbpl(qtd, req, USB_SIZEOF_CTRLREQ);
+ if (ret < 0)
+ {
+ uvdbg("ERROR: sam_qtd_addbpl failed: %d\n", ret);
+ sam_qtd_free(qtd);
+ return NULL;
+ }
+
+ return qtd;
+}
+
+/*******************************************************************************
+ * Name: sam_qtd_dataphase
+ *
+ * Description:
+ * Create a data transfer or SET data phase qTD.
+ *
+ *******************************************************************************/
+
+static struct sam_qtd_s *sam_qtd_dataphase(void *buffer, int buflen,
+ uint32_t tokenbits)
+{
+ struct sam_qtd_s *qtd;
+ uint32_t regval;
+ int ret;
+
+ /* Allocate a new Queue Element Transfer Descriptor (qTD) */
+
+ qtd = sam_qtd_alloc();
+ if (qtd == NULL)
+ {
+ udbg("ERROR: Failed to allocate data buffer qTD");
+ return NULL;
+ }
+
+ /* Mark this as the end of the list (this will be overwritten if another
+ * qTD is added after this one.
+ */
+
+ qtd->hw.nqp = sam_swap32(QTD_NQP_T);
+ qtd->hw.alt = sam_swap32(QTD_AQP_T);
+
+ /* Write qTD token:
+ *
+ * FIELD DESCRIPTION VALUE/SOURCE
+ * -------- ------------------------------- --------------------
+ * STATUS Status QTD_TOKEN_ACTIVE
+ * PID PID Code Contained in tokenbits
+ * CERR Error Counter 3
+ * CPAGE Current Page 0
+ * IOC Interrupt on complete Contained in tokenbits
+ * NBYTES Total Bytes to Transfer buflen
+ * TOGGLE Data Toggle Contained in tokenbits
+ */
+
+ regval = tokenbits | QTD_TOKEN_ACTIVE |
+ ((uint32_t)3 << QTD_TOKEN_CERR_SHIFT) |
+ ((uint32_t)buflen << QTD_TOKEN_NBYTES_SHIFT);
+
+ qtd->hw.token = sam_swap32(regval);
+
+ /* Add the buffer information to the bufffer pointer list */
+
+ ret = sam_qtd_addbpl(qtd, buffer, buflen);
+ if (ret < 0)
+ {
+ udbg("ERROR: sam_qtd_addbpl failed: %d\n", ret);
+ sam_qtd_free(qtd);
+ return NULL;
+ }
+
+ return qtd;
+}
+
+/*******************************************************************************
+ * Name: sam_qtd_statusphase
+ *
+ * Description:
+ * Create a STATUS phase request qTD.
+ *
+ *******************************************************************************/
+
+static struct sam_qtd_s *sam_qtd_statusphase(uint32_t tokenbits)
+{
+ struct sam_qtd_s *qtd;
+ uint32_t regval;
+
+ /* Allocate a new Queue Element Transfer Descriptor (qTD) */
+
+ qtd = sam_qtd_alloc();
+ if (qtd == NULL)
+ {
+ udbg("ERROR: Failed to allocate request qTD");
+ return NULL;
+ }
+
+ /* Mark this as the end of the list (this will be overwritten if another
+ * qTD is added after this one.
+ */
+
+ qtd->hw.nqp = sam_swap32(QTD_NQP_T);
+ qtd->hw.alt = sam_swap32(QTD_AQP_T);
+
+ /* Write qTD token:
+ *
+ * FIELD DESCRIPTION VALUE/SOURCE
+ * -------- ------------------------------- --------------------
+ * STATUS Status QTD_TOKEN_ACTIVE
+ * PID PID Code Contained in tokenbits
+ * CERR Error Counter 3
+ * CPAGE Current Page 0
+ * IOC Interrupt on complete QH_TOKEN_IOC
+ * NBYTES Total Bytes to Transfer 0
+ * TOGGLE Data Toggle Contained in tokenbits
+ */
+
+ regval = tokenbits | QTD_TOKEN_ACTIVE |
+ ((uint32_t)3 << QTD_TOKEN_CERR_SHIFT) |
+ QH_TOKEN_IOC |
+ ((uint32_t)USB_SIZEOF_CTRLREQ << QTD_TOKEN_NBYTES_SHIFT);
+
+ qtd->hw.token = sam_swap32(regval);
+ return qtd;
}
/*******************************************************************************
@@ -1222,11 +1543,15 @@ static void sam_qh_enqueue(struct sam_qh_s *qh)
*
* Description:
* Process a IN or OUT request on any asynchronous endpoint (bulk or control).
- * This function will enqueue the request and wait for it to complete.
+ * This function will enqueue the request and wait for it to complete. Bulk
+ * data transfers differ in that req == NULL and there are not SETUP or STATUS
+ * phases.
*
* This is a blocking function; it will not return until the control transfer
* has completed.
*
+ * Assumption: The caller holds the EHCI exclsem
+ *
*******************************************************************************/
static int sam_async_transfer(struct sam_rhport_s *rhport,
@@ -1234,11 +1559,19 @@ static int sam_async_transfer(struct sam_rhport_s *rhport,
const struct usb_ctrlreq_s *req,
uint8_t *buffer, size_t buflen)
{
+ struct sam_qh_s *qh;
+ struct sam_qtd_s *qtd;
+ uintptr_t physaddr;
+ uint32_t *flink;
+ uint32_t toggle;
+ uint32_t datapid;
int ret;
uvdbg("RHport%d EP%d: buffer=%p, buflen=%d, req=%p\n",
rhport->rhpndx+1, epinfo->epno, buffer, buflen, req);
+ DEBUGASSERT(rhport && epinfo);
+
if (req != NULL)
{
uvdbg("req=%02x type=%02x value=%04x index=%04x\n",
@@ -1246,6 +1579,12 @@ static int sam_async_transfer(struct sam_rhport_s *rhport,
sam_read16(req->index));
}
+ /* A buffer may or may be supplied with an EP0 SETUP transfer. A buffer will
+ * always be present for normal endpoint data transfers.
+ */
+
+ DEBUGASSERT(req || (buffer && buflen > 0));
+
/* Set the request for the IOC event well BEFORE enabling the transfer. */
ret = sam_ioc_setup(rhport, epinfo);
@@ -1255,7 +1594,132 @@ static int sam_async_transfer(struct sam_rhport_s *rhport,
return ret;
}
-#warning "Missing logic"
+ /* Get the data token direction */
+
+ datapid = QTD_TOKEN_PID_OUT;
+ if (req)
+ {
+ if ((req->req & USB_REQ_DIR_MASK) == USB_REQ_DIR_IN)
+ {
+ datapid = QTD_TOKEN_PID_IN;
+ }
+ }
+ else if (epinfo->dirin)
+ {
+ datapid = QTD_TOKEN_PID_IN;
+ }
+
+ /* Create and initialize a Queue Head (QH) structure for this transfer */
+
+ qh = sam_qh_create(rhport, epinfo);
+ if (qh == NULL)
+ {
+ udbg("ERROR: sam_qh_create failed\n");
+ ret = -ENOMEM;
+ goto errout_with_iocwait;
+ }
+
+ /* Initialize the QH link and get the next data toggle (not used for SETUP
+ * transfers)
+ */
+
+ flink = &qh->hw.overlay.nqp;
+ toggle = epinfo->toggle ? 0 : QTD_TOKEN_TOGGLE;
+ ret = -EIO;
+
+ /* Is the an EP0 SETUP request? If req will be non-NULL */
+
+ if (req != NULL)
+ {
+ /* Allocate a new Queue Element Transfer Descriptor (qTD) for the SETUP
+ * phase of the request sequence.
+ */
+
+ qtd = sam_qtd_setupphase(req);
+ if (qtd == NULL)
+ {
+ udbg("ERROR: sam_qtd_setupphase failed\n");
+ goto errout_with_qh;
+ }
+
+ /* Link the new qTD to the QH head. */
+
+ physaddr = sam_physramaddr((uintptr_t)qtd);
+ *flink = sam_swap32(physaddr);
+
+ /* Get the new forward link pointer and data toggle */
+
+ flink = &qtd->hw.nqp;
+ toggle = QTD_TOKEN_TOGGLE;
+ }
+
+ /* A buffer may or may be supplied with an EP0 SETUP transfer. A buffer will
+ * always be present for normal endpoint data transfers.
+ */
+
+ if (buffer && buflen > 0)
+ {
+ /* Extra TOKEN bits include the data toggle, the data PID, and if there
+ * is no request, and indication to interrupt at the end of this
+ * transfer.
+ */
+
+ uint32_t tokenbits = toggle | datapid;
+
+ /* If this is not an EP0 SETUP request, then nothing follows the data and
+ * we want the IOC interrupt when the data transfer completes.
+ */
+
+ if (!req)
+ {
+ tokenbits |= QTD_TOKEN_IOC;
+ }
+
+ /* Allocate a new Queue Element Transfer Descriptor (qTD) for the data
+ * buffer.
+ */
+
+ qtd = sam_qtd_dataphase(buffer, buflen, tokenbits);
+ if (qtd == NULL)
+ {
+ udbg("ERROR: sam_qtd_dataphase failed\n");
+ goto errout_with_qh;
+ }
+
+ /* Link the new qTD to either QH head of the SETUP qTD. */
+
+ physaddr = sam_physramaddr((uintptr_t)qtd);
+ *flink = sam_swap32(physaddr);
+
+ /* Set the forward link pointer to this new qTD */
+
+ flink = &qtd->hw.nqp;
+ }
+
+ if (req != NULL)
+ {
+ /* Extra TOKEN bits include the data toggle and the data PID. */
+
+ uint32_t tokenbits = toggle | datapid ;
+
+ /* Allocate a new Queue Element Transfer Descriptor (qTD) for the status */
+
+ qtd = sam_qtd_statusphase(tokenbits);
+ if (qtd == NULL)
+ {
+ udbg("ERROR: sam_qtd_statusphase failed\n");
+ goto errout_with_qh;
+ }
+
+ /* Link the new qTD to either the SETUP or data qTD. */
+
+ physaddr = sam_physramaddr((uintptr_t)qtd);
+ *flink = sam_swap32(physaddr);
+ }
+
+ /* Add the new QH to the head of the asynchronous queue list */
+
+ sam_qh_enqueue(qh);
/* Wait for the IOC completion event */
@@ -1270,6 +1734,10 @@ static int sam_async_transfer(struct sam_rhport_s *rhport,
return OK;
+ /* Clean-up after an error */
+
+errout_with_qh:
+ sam_qh_discard(qh);
errout_with_iocwait:
epinfo->iocwait = false;
return ret;