SAMA5 UDPHS: Major changes to DMA interrupt and request handling to better handle DMA

This commit is contained in:
Gregory Nutt 2013-09-05 14:33:27 -06:00
parent 9861f18ee1
commit 54d5e1a49d

View File

@ -155,20 +155,21 @@
#define SAM_TRACEERR_BADSETFEATURE 0x000c
#define SAM_TRACEERR_BINDFAILED 0x000d
#define SAM_TRACEERR_DISPATCHSTALL 0x000e
#define SAM_TRACEERR_DRIVER 0x000f
#define SAM_TRACEERR_DRIVERREGISTERED 0x0010
#define SAM_TRACEERR_EP0SETUPOUTSIZE 0x0011
#define SAM_TRACEERR_EP0SETUPSTALLED 0x0012
#define SAM_TRACEERR_EPINBUSY 0x0013
#define SAM_TRACEERR_EPOUTNULLPACKET 0x0014
#define SAM_TRACEERR_EPRESERVE 0x0015
#define SAM_TRACEERR_EPTCFGMAPD 0x0016
#define SAM_TRACEERR_INVALIDCTRLREQ 0x0017
#define SAM_TRACEERR_INVALIDPARMS 0x0018
#define SAM_TRACEERR_IRQREGISTRATION 0x0019
#define SAM_TRACEERR_NOTCONFIGURED 0x001a
#define SAM_TRACEERR_REQABORTED 0x001b
#define SAM_TRACEERR_TXRDYERR 0x001c
#define SAM_TRACEERR_DMAERR 0x000f
#define SAM_TRACEERR_DRIVER 0x0010
#define SAM_TRACEERR_DRIVERREGISTERED 0x0011
#define SAM_TRACEERR_ENDBUFST 0x0012
#define SAM_TRACEERR_EP0SETUPOUTSIZE 0x0013
#define SAM_TRACEERR_EP0SETUPSTALLED 0x0014
#define SAM_TRACEERR_EPOUTNULLPACKET 0x0015
#define SAM_TRACEERR_EPRESERVE 0x0016
#define SAM_TRACEERR_EPTCFGMAPD 0x0017
#define SAM_TRACEERR_INVALIDCTRLREQ 0x0018
#define SAM_TRACEERR_INVALIDPARMS 0x0019
#define SAM_TRACEERR_IRQREGISTRATION 0x001a
#define SAM_TRACEERR_NOTCONFIGURED 0x001b
#define SAM_TRACEERR_REQABORTED 0x001c
#define SAM_TRACEERR_TXRDYERR 0x001d
/* Trace interrupt codes */
@ -180,33 +181,32 @@
#define SAM_TRACEINTID_DMA 0x0006
#define SAM_TRACEINTID_DMAEOB 0x0007
#define SAM_TRACEINTID_DMAEOC 0x0008
#define SAM_TRACEINTID_DMAERR 0x0009
#define SAM_TRACEINTID_ENDRESET 0x000a
#define SAM_TRACEINTID_EP 0x000b
#define SAM_TRACEINTID_EP0SETUPIN 0x000c
#define SAM_TRACEINTID_EP0SETUPOUT 0x000d
#define SAM_TRACEINTID_EP0SETUPSETADDRESS 0x000e
#define SAM_TRACEINTID_EPGETSTATUS 0x000f
#define SAM_TRACEINTID_EPINQEMPTY 0x0010
#define SAM_TRACEINTID_EPOUTQEMPTY 0x0011
#define SAM_TRACEINTID_GETCONFIG 0x0012
#define SAM_TRACEINTID_GETSETDESC 0x0013
#define SAM_TRACEINTID_GETSETIF 0x0014
#define SAM_TRACEINTID_GETSTATUS 0x0015
#define SAM_TRACEINTID_IFGETSTATUS 0x0016
#define SAM_TRACEINTID_INTERRUPT 0x0017
#define SAM_TRACEINTID_INTSOF 0x0018
#define SAM_TRACEINTID_NOSTDREQ 0x0019
#define SAM_TRACEINTID_PENDING 0x001a
#define SAM_TRACEINTID_RXRDY 0x001b
#define SAM_TRACEINTID_RXSETUP 0x001c
#define SAM_TRACEINTID_SETCONFIG 0x001d
#define SAM_TRACEINTID_SETFEATURE 0x001e
#define SAM_TRACEINTID_STALLSNT 0x001f
#define SAM_TRACEINTID_SYNCHFRAME 0x0020
#define SAM_TRACEINTID_TXRDY 0x0021
#define SAM_TRACEINTID_UPSTRRES 0x0022
#define SAM_TRACEINTID_WAKEUP 0x0023
#define SAM_TRACEINTID_ENDRESET 0x0009
#define SAM_TRACEINTID_EP 0x0001
#define SAM_TRACEINTID_EP0SETUPIN 0x000b
#define SAM_TRACEINTID_EP0SETUPOUT 0x000c
#define SAM_TRACEINTID_EP0SETUPSETADDRESS 0x000d
#define SAM_TRACEINTID_EPGETSTATUS 0x000e
#define SAM_TRACEINTID_EPINQEMPTY 0x000f
#define SAM_TRACEINTID_EPOUTQEMPTY 0x0010
#define SAM_TRACEINTID_GETCONFIG 0x0011
#define SAM_TRACEINTID_GETSETDESC 0x0012
#define SAM_TRACEINTID_GETSETIF 0x0013
#define SAM_TRACEINTID_GETSTATUS 0x0014
#define SAM_TRACEINTID_IFGETSTATUS 0x0015
#define SAM_TRACEINTID_INTERRUPT 0x0016
#define SAM_TRACEINTID_INTSOF 0x0017
#define SAM_TRACEINTID_NOSTDREQ 0x0018
#define SAM_TRACEINTID_PENDING 0x0019
#define SAM_TRACEINTID_RXRDY 0x001a
#define SAM_TRACEINTID_RXSETUP 0x001b
#define SAM_TRACEINTID_SETCONFIG 0x001c
#define SAM_TRACEINTID_SETFEATURE 0x001d
#define SAM_TRACEINTID_STALLSNT 0x001e
#define SAM_TRACEINTID_SYNCHFRAME 0x001f
#define SAM_TRACEINTID_TXRDY 0x0020
#define SAM_TRACEINTID_UPSTRRES 0x0021
#define SAM_TRACEINTID_WAKEUP 0x0022
/* Ever-present MIN and MAX macros */
@ -355,7 +355,6 @@ struct sam_usbdev_s
uint8_t devstate; /* State of the device (see enum sam_devstate_e) */
uint8_t prevstate; /* Previous state of the device before SUSPEND */
uint8_t devaddr; /* Assigned device address */
uint8_t rxpending:1; /* 1: OUT data in the FIFO, but no read requests */
uint8_t selfpowered:1; /* 1: Device is self powered */
uint16_t epavail; /* Bitset of available endpoints */
@ -414,9 +413,9 @@ static void sam_dtd_free(struct sam_usbdev_s *priv, struct sam_dtd_s *dtd);
#endif
static void sam_dma_single(uint8_t epno, struct sam_req_s *privreq,
uint32_t dmacontrol);
static int sam_req_wrdma(struct sam_usbdev_s *priv,
static void sam_dma_wrsetup(struct sam_usbdev_s *priv,
struct sam_ep_s *privep, struct sam_req_s *privreq);
static int sam_req_rddma(struct sam_usbdev_s *priv,
static void sam_dma_rdsetup(struct sam_usbdev_s *priv,
struct sam_ep_s *privep, struct sam_req_s *privreq);
/* Request Helpers **********************************************************/
@ -430,15 +429,17 @@ static inline void
struct sam_req_s *privreq, int16_t result);
static void sam_req_complete(struct sam_ep_s *privep, int16_t result);
static void sam_ep_txrdy(unsigned int epno);
static int sam_req_wrnodma(struct sam_usbdev_s *priv,
static void sam_req_wrsetup(struct sam_usbdev_s *priv,
struct sam_ep_s *privep, struct sam_req_s *privreq);
static int sam_req_write(struct sam_usbdev_s *priv,
struct sam_ep_s *privep);
static int sam_req_rdnodma(struct sam_usbdev_s *priv,
static void sam_req_rddone(struct sam_usbdev_s *priv,
struct sam_ep_s *privep, struct sam_req_s *privreq,
uint16_t pktsize);
uint16_t recvsize);
static void sam_req_rdenable(uint8_t epno);
static void sam_req_rddisable(uint8_t epno);
static int sam_req_read(struct sam_usbdev_s *priv,
struct sam_ep_s *privep, uint16_t pktsize);
struct sam_ep_s *privep, uint16_t recvsize);
static void sam_req_cancel(struct sam_ep_s *privep, int16_t status);
/* Interrupt level processing ***********************************************/
@ -582,11 +583,12 @@ const struct trace_msg_t g_usb_trace_strings_deverror[] =
TRACE_STR(SAM_TRACEERR_BADSETFEATURE),
TRACE_STR(SAM_TRACEERR_BINDFAILED),
TRACE_STR(SAM_TRACEERR_DISPATCHSTALL),
TRACE_STR(SAM_TRACEERR_DMAERR),
TRACE_STR(SAM_TRACEERR_DRIVER),
TRACE_STR(SAM_TRACEERR_DRIVERREGISTERED),
TRACE_STR(SAM_TRACEERR_ENDBUFST),
TRACE_STR(SAM_TRACEERR_EP0SETUPOUTSIZE),
TRACE_STR(SAM_TRACEERR_EP0SETUPSTALLED),
TRACE_STR(SAM_TRACEERR_EPINBUSY),
TRACE_STR(SAM_TRACEERR_EPOUTNULLPACKET),
TRACE_STR(SAM_TRACEERR_EPRESERVE),
TRACE_STR(SAM_TRACEERR_EPTCFGMAPD),
@ -615,7 +617,6 @@ const struct trace_msg_t g_usb_trace_strings_intdecode[] =
TRACE_STR(SAM_TRACEINTID_DMA),
TRACE_STR(SAM_TRACEINTID_DMAEOB),
TRACE_STR(SAM_TRACEINTID_DMAEOC),
TRACE_STR(SAM_TRACEINTID_DMAERR),
TRACE_STR(SAM_TRACEINTID_ENDRESET),
TRACE_STR(SAM_TRACEINTID_EP),
TRACE_STR(SAM_TRACEINTID_EP0SETUPIN),
@ -928,28 +929,20 @@ static void sam_dma_single(uint8_t epno, struct sam_req_s *privreq,
}
/****************************************************************************
* Name: sam_req_wrdma
* Name: sam_dma_wrsetup
*
* Description:
* Process the next queued write request for an endpoint that supports DMA.
*
****************************************************************************/
static int sam_req_wrdma(struct sam_usbdev_s *priv, struct sam_ep_s *privep,
struct sam_req_s *privreq)
static void sam_dma_wrsetup(struct sam_usbdev_s *priv, struct sam_ep_s *privep,
struct sam_req_s *privreq)
{
uint32_t regval;
int remaining;
int epno;
/* The endpoint must be IDLE and ready to begin the next transfer */
if (privep->epstate != UDPHS_EPSTATE_IDLE)
{
usbtrace(TRACE_DEVERROR(SAM_TRACEERR_EPINBUSY), privep->epstate);
return -EBUSY;
}
/* Switch to the sending state */
privep->epstate = UDPHS_EPSTATE_SENDING;
@ -961,7 +954,7 @@ static int sam_req_wrdma(struct sam_usbdev_s *priv, struct sam_ep_s *privep,
/* How many bytes remain to be transferred in the request? */
remaining = privreq->req.len - privreq->req.xfrd - privreq->inflight;
remaining = privreq->req.len - privreq->req.xfrd;
/* If there are no bytes to send, then send a zero length packet */
@ -994,7 +987,6 @@ static int sam_req_wrdma(struct sam_usbdev_s *priv, struct sam_ep_s *privep,
sam_dma_single(epno, privreq,
UDPHS_DMACONTROL_ENDBEN | UDPHS_DMACONTROL_ENDBUFFIT |
UDPHS_DMACONTROL_CHANNENB);
return OK;
}
/* Enable the endpoint interrupt */
@ -1002,94 +994,67 @@ static int sam_req_wrdma(struct sam_usbdev_s *priv, struct sam_ep_s *privep,
regval = sam_getreg(SAM_UDPHS_IEN);
regval |= UDPHS_INT_EPT(epno);
sam_putreg(regval, SAM_UDPHS_IEN);
return OK;
}
/****************************************************************************
* Name: sam_req_rddma
* Name: sam_dma_rdsetup
*
* Description:
* Process the next queued read request for an endpoint that supports DMA.
*
****************************************************************************/
static int sam_req_rddma(struct sam_usbdev_s *priv, struct sam_ep_s *privep,
struct sam_req_s *privreq)
static void sam_dma_rdsetup(struct sam_usbdev_s *priv,
struct sam_ep_s *privep,
struct sam_req_s *privreq)
{
uint32_t regval;
int remaining;
int epno;
/* The endpoint must be IDLE and ready to begin the next transfer */
if (privep->epstate != UDPHS_EPSTATE_IDLE)
{
usbtrace(TRACE_DEVERROR(SAM_TRACEERR_EPINBUSY), privep->epstate);
return -EBUSY;
}
/* Get the endpoint number */
epno = USB_EPNO(privep->ep.eplog);
/* Switch to the receiving state */
privep->epstate = UDPHS_EPSTATE_RECEIVING;
privep->zlpneeded = false;
privep->zlpsent = false;
privreq->inflight = 0;
privreq->req.xfrd = 0;
/* How many more bytes can we append to the request buffer? */
remaining = privreq->req.len - privreq->req.xfrd;
if (remaining > 0)
{
/* Clip the DMA transfer size to the size available in the user buffer */
DEBUGASSERT(remaining > 0 && privep->epstate == UDPHS_EPSTATE_RECEIVING);
/* Clip the DMA transfer size to the size available in the user buffer */
#if USBDEV_MAXREQUEUST > DMA_MAX_FIFO_SIZE
if (remaining > DMA_MAX_FIFO_SIZE)
{
privreq->inflight = DMA_MAX_FIFO_SIZE;
}
else
if (remaining > DMA_MAX_FIFO_SIZE)
{
privreq->inflight = DMA_MAX_FIFO_SIZE;
}
else
#endif
{
privreq->inflight = remaining;
}
/* And perform the single DMA transfer.
*
* 32.6.10.12 Bulk OUT or Interrupt OUT: Sending a Buffer Using DMA
* - END_B_EN: Can be used for OUT packet truncation (discarding of
* unbuffered packet data) at the end of DMA buffer.
* - END_BUFFIT: Generate an interrupt when BUFF_COUNT in the
* UDPHS_DMASTATUSx register reaches 0.
* - END_TR_EN: End of transfer enable, the UDPHS device can put an
* end to the current DMA transfer, in case of a short packet.
* - END_TR_IT: End of transfer interrupt enable, an interrupt is sent
* after the last USB packet has been transferred by the DMA, if the
* USB transfer ended with a short packet. (Beneficial when the
* receive size is unknown.)
* - CHANN_ENB: Run and stop at end of buffer.
*/
regval = UDPHS_DMACONTROL_ENDBEN | UDPHS_DMACONTROL_ENDBUFFIT |
UDPHS_DMACONTROL_ENDTREN | UDPHS_DMACONTROL_ENDTRIT |
UDPHS_DMACONTROL_CHANNENB;
sam_dma_single(epno, privreq, regval);
return OK;
{
privreq->inflight = remaining;
}
/* Enable the endpoint interrupt */
/* And perform the single DMA transfer.
*
* 32.6.10.12 Bulk OUT or Interrupt OUT: Sending a Buffer Using DMA
* - END_B_EN: Can be used for OUT packet truncation (discarding of
* unbuffered packet data) at the end of DMA buffer.
* - END_BUFFIT: Generate an interrupt when BUFF_COUNT in the
* UDPHS_DMASTATUSx register reaches 0.
* - END_TR_EN: End of transfer enable, the UDPHS device can put an
* end to the current DMA transfer, in case of a short packet.
* - END_TR_IT: End of transfer interrupt enable, an interrupt is sent
* after the last USB packet has been transferred by the DMA, if the
* USB transfer ended with a short packet. (Beneficial when the
* receive size is unknown.)
* - CHANN_ENB: Run and stop at end of buffer.
*/
regval = sam_getreg(SAM_UDPHS_IEN);
regval |= UDPHS_INT_EPT(epno);
sam_putreg(regval, SAM_UDPHS_IEN);
regval = UDPHS_DMACONTROL_ENDBEN | UDPHS_DMACONTROL_ENDBUFFIT |
UDPHS_DMACONTROL_ENDTREN | UDPHS_DMACONTROL_ENDTRIT |
UDPHS_DMACONTROL_CHANNENB;
sam_putreg(UDPHS_EPTCTL_RXRDYTXKL, SAM_UDPHS_EPTCTLENB(epno));
return OK;
sam_dma_single(epno, privreq, regval);
}
/****************************************************************************
@ -1220,7 +1185,7 @@ static void sam_ep_txrdy(unsigned int epno)
}
/****************************************************************************
* Name: sam_req_wrnodma
* Name: sam_req_wrsetup
*
* Description:
* Process the next queued write request for an endpoint that does not
@ -1228,13 +1193,13 @@ static void sam_ep_txrdy(unsigned int epno)
*
****************************************************************************/
static int sam_req_wrnodma(struct sam_usbdev_s *priv, struct sam_ep_s *privep,
struct sam_req_s *privreq)
static void sam_req_wrsetup(struct sam_usbdev_s *priv,
struct sam_ep_s *privep,
struct sam_req_s *privreq)
{
const uint8_t *buf;
uint8_t *fifo;
uint8_t epno;
int committed;
int nbytes;
int bytesleft;
@ -1246,13 +1211,9 @@ static int sam_req_wrnodma(struct sam_usbdev_s *priv, struct sam_ep_s *privep,
DEBUGASSERT((sam_getreg(SAM_UDPHS_EPTSTA(epno)) & UDPHS_EPTSTA_TXRDY) == 0);
/* Get the number of bytes to send. The totals bytes remaining to be sent
* is the the total size of the buffer, minus the number of bytes
* successfully transferred, minus the number of bytes in-flight.
*/
/* Get the number of bytes remaining to be sent. */
committed = privreq->req.xfrd + privreq->inflight;
bytesleft = privreq->req.len - committed;
bytesleft = privreq->req.len - privreq->req.xfrd;
/* Clip the requested transfer size to the number of bytes actually
* available
@ -1268,7 +1229,6 @@ static int sam_req_wrnodma(struct sam_usbdev_s *priv, struct sam_ep_s *privep,
* maxpacket and check if we need to send a following zero length packet.
*/
nbytes = bytesleft;
if (nbytes > 0)
{
/* Either send the maxpacketsize or all of the remaining data in
@ -1282,7 +1242,7 @@ static int sam_req_wrnodma(struct sam_usbdev_s *priv, struct sam_ep_s *privep,
/* This is the new number of bytes "in-flight" */
privreq->inflight += nbytes;
privreq->inflight = nbytes;
usbtrace(TRACE_WRITE(USB_EPNO(privep->ep.eplog)), nbytes);
/* The new buffer pointer is the started of the buffer plus the number
@ -1290,7 +1250,7 @@ static int sam_req_wrnodma(struct sam_usbdev_s *priv, struct sam_ep_s *privep,
* "in-flight".
*/
buf = privreq->req.buf + committed;
buf = privreq->req.buf + privreq->req.xfrd;
/* Write packet in the FIFO buffer */
@ -1314,7 +1274,6 @@ static int sam_req_wrnodma(struct sam_usbdev_s *priv, struct sam_ep_s *privep,
*/
sam_ep_txrdy(epno);
return OK;
}
/****************************************************************************
@ -1326,6 +1285,20 @@ static int sam_req_wrnodma(struct sam_usbdev_s *priv, struct sam_ep_s *privep,
* interrupts disabled, (2) from interrupt handling when a previous
* transfer completes, or (3) resuming a stalled IN endpoint.
*
* Calling rules:
*
* The transfer state must IDLE
*
* When a request is queued, the request 'len' is the number of bytes
* to transfer and 'xfrd' and 'inflight' must be zero.
*
* When this function starts a tranfer it will update the request
* 'inflight' field to indicate the size of the transfer.
*
* When the transfer completes, the the 'inflight' field must hold the
* number of bytes that have completed the transfer. This function will
* update 'xfrd' with the new size of the transfer.
*
****************************************************************************/
static int sam_req_write(struct sam_usbdev_s *priv, struct sam_ep_s *privep)
@ -1335,7 +1308,6 @@ static int sam_req_write(struct sam_usbdev_s *priv, struct sam_ep_s *privep)
uint32_t eptype;
uint8_t epno;
int bytesleft;
int ret;
/* Get the unadorned endpoint number */
@ -1418,18 +1390,11 @@ static int sam_req_write(struct sam_usbdev_s *priv, struct sam_ep_s *privep)
if ((SAM_EPSET_DMA & SAM_EP_BIT(epno)) != 0)
{
ret = sam_req_wrdma(priv, privep, privreq);
sam_dma_wrsetup(priv, privep, privreq);
}
else
{
ret = sam_req_wrnodma(priv, privep, privreq);
}
/* Check if the transfer was successfully initiated */
if (ret < 0)
{
return ret;
sam_req_wrsetup(priv, privep, privreq);
}
}
@ -1488,16 +1453,17 @@ static int sam_req_write(struct sam_usbdev_s *priv, struct sam_ep_s *privep)
}
/****************************************************************************
* Name: sam_req_rdnodma
* Name: sam_req_rddone
*
* Description:
* Process the next queued write request for an endpoint that does not
* support DMA.
* The last non-DMA OUT transfer has completed. Read 'recvsize' byts from
* the FIFO into the read request buffer.
*
****************************************************************************/
static int sam_req_rdnodma(struct sam_usbdev_s *priv, struct sam_ep_s *privep,
struct sam_req_s *privreq, uint16_t pktsize)
static void sam_req_rddone(struct sam_usbdev_s *priv,
struct sam_ep_s *privep,
struct sam_req_s *privreq, uint16_t recvsize)
{
const uint8_t *fifo;
uint8_t *dest;
@ -1505,9 +1471,6 @@ static int sam_req_rdnodma(struct sam_usbdev_s *priv, struct sam_ep_s *privep,
int readlen;
int epno;
privep->epstate = UDPHS_EPSTATE_IDLE;
privreq->inflight = 0;
/* Get the number of bytes that can be received. This is the size of the
* user-provided request buffer, minus the number of bytes already
* transferred to the user-buffer.
@ -1519,7 +1482,7 @@ static int sam_req_rdnodma(struct sam_usbdev_s *priv, struct sam_ep_s *privep,
* size remaining in the request buffer provided by the caller.
*/
readlen = MIN(remaining, pktsize);
readlen = MIN(remaining, recvsize);
privreq->req.xfrd += readlen;
/* Get the source and destination transfer addresses */
@ -1535,8 +1498,45 @@ static int sam_req_rdnodma(struct sam_usbdev_s *priv, struct sam_ep_s *privep,
{
*dest++ = *fifo++;
}
}
return OK;
/****************************************************************************
* Name: sam_req_rdenable
*
* Description:
* Make sure that the endpoint RXRDY_TXTK interrupt is enabled in order
* to receive the next incoming packet
*
****************************************************************************/
static void sam_req_rdenable(uint8_t epno)
{
uint32_t regval;
regval = sam_getreg(SAM_UDPHS_IEN);
regval |= UDPHS_INT_EPT(epno);
sam_putreg(regval, SAM_UDPHS_IEN);
sam_putreg(UDPHS_EPTCTL_RXRDYTXKL, SAM_UDPHS_EPTCTLENB(epno));
}
/****************************************************************************
* Name: sam_req_rddisable
*
* Description:
* Disable endpoint interrupts
*
****************************************************************************/
static void sam_req_rddisable(uint8_t epno)
{
uint32_t regval;
regval = sam_getreg(SAM_UDPHS_IEN);
regval &= ~UDPHS_INT_EPT(epno);
sam_putreg(regval, SAM_UDPHS_IEN);
sam_putreg(UDPHS_EPTCTL_RXRDYTXKL, SAM_UDPHS_EPTCTLDIS(epno));
}
/****************************************************************************
@ -1546,67 +1546,130 @@ static int sam_req_rdnodma(struct sam_usbdev_s *priv, struct sam_ep_s *privep,
* Called only from interrupt handling logic when on OUT packet is received
* on an endpoint in the RECEIVING state.
*
* There is a fundamental difference between receiving packets via DMA and
* via the FIFO:
*
* - When receiving data via DMA, then data has already been transferred
* and this function is called on the terminating event. The transfer
* is complete and we just need to check for end of request events and
* if we need to setup the next tranfer.
* - When receiving via the FIFO, then transfer is not complete. The
* data is in the FIFO and must be transferred from the FIFO to the
* request buffer. No setup is needed for the next transfer.
*
* Calling rules:
*
* The transfer state must IDLE
*
* When a request is queued, the request 'len' is size of the request
* buffer. Any OUT request can be received that will fit in this
* buffer. 'xfrd' and 'inflight' in the request must be zero
* If sam_req_read() is called to start a new transfer, the recvsize
* parameter must be zero.
*
* When this function starts a DMA transfer it will update the request
* 'inflight' field to hold the maximum size of the transfer; but
* 'inflight' is not used with FIFO transfers.
*
* When the transfer completes, the 'recvsize' paramter must be the
* size of the transfer that just completed. For the case of DMA,
* that is the size of the DMA transfer that has just been written to
* memory; for the FIFO transfer, recvsize is the number of bytes
* waiting in the FIFO to be read.
*
****************************************************************************/
static int sam_req_read(struct sam_usbdev_s *priv, struct sam_ep_s *privep,
uint16_t pktsize)
uint16_t recvsize)
{
struct sam_req_s *privreq;
uint32_t regval;
uint32_t eptype;
uint8_t epno;
int ret;
/* Check the request from the head of the endpoint request queue */
DEBUGASSERT(priv && privep && privep->epstate == UDPHS_EPSTATE_IDLE);
epno = USB_EPNO(privep->ep.eplog);
privreq = sam_rqpeek(&privep->reqq);
if (!privreq)
/* Loop in case we need to handle multiple read requests */
while (privep->epstate == UDPHS_EPSTATE_IDLE)
{
/* Incoming data available in the FIFO, but no packet to receive the data.
* Mark that the RX data is pending and hope that a packet is returned
* soon.
/* Check the request from the head of the endpoint request queue */
epno = USB_EPNO(privep->ep.eplog);
privreq = sam_rqpeek(&privep->reqq);
if (!privreq)
{
/* No packet to receive data */
usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_EPOUTQEMPTY), epno);
return -ENOENT;
}
ullvdbg("EP%d: len=%d xfrd=%d\n",
epno, privreq->req.len, privreq->req.xfrd);
/* Ignore any attempt to receive a zero length packet */
if (privreq->req.len == 0)
{
usbtrace(TRACE_DEVERROR(SAM_TRACEERR_EPOUTNULLPACKET), 0);
sam_req_complete(privep, OK);
recvsize = 0;
continue;
}
usbtrace(TRACE_READ(USB_EPNO(privep->ep.eplog)), recvsize);
/* Update the number of bytes transferred with the received size */
privreq->req.xfrd += recvsize;
privreq->inflight = 0;
/* If this was not a DMA transfer, read the incoming data from the FIFO */
if ((SAM_EPSET_DMA & SAM_EP_BIT(epno)) == 0)
{
sam_req_rddone(priv, privep, privreq, recvsize);
}
/* In case we go through the loop again */
recvsize = 0;
/* If nothing has yet be transferred into the read request, then
* indicate that we are in the RECEIVING state and, if the endpoint
* supports DMA, setup the receive DMA.
*/
usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_EPOUTQEMPTY), epno);
priv->rxpending = true;
return -ENOENT;
}
if (privreq->req.xfrd == 0)
{
/* Set the RECEIVING state */
ullvdbg("EP%d: len=%d xfrd=%d\n", epno, privreq->req.len, privreq->req.xfrd);
privep->epstate = UDPHS_EPSTATE_RECEIVING;
/* Ignore any attempt to receive a zero length packet */
/* If the endpoint supports DMA, set up the DMA now */
if (privreq->req.len == 0)
{
usbtrace(TRACE_DEVERROR(SAM_TRACEERR_EPOUTNULLPACKET), 0);
sam_req_complete(privep, OK);
return OK;
}
if ((SAM_EPSET_DMA & SAM_EP_BIT(epno)) != 0)
{
/* Set up the next DMA */
usbtrace(TRACE_READ(USB_EPNO(privep->ep.eplog)), privreq->req.xfrd);
sam_dma_rdsetup(priv, privep, privreq);
}
else
{
/* Enable endpoint RXRDY_TXTK interrupts */
/* The way that we handle the transfer is going to depend on whether
* or not this endpoint supports DMA.
*/
sam_req_rdenable(epno);
}
}
if ((SAM_EPSET_DMA & SAM_EP_BIT(epno)) != 0)
{
ret = sam_req_rddma(priv, privep, privreq);
}
else
{
ret = sam_req_rdnodma(priv, privep, privreq, pktsize);
}
if (ret == OK)
{
/* If the receive buffer is full or this is a partial packet,
* then we are finished with the request buffer).
/* We will not try to accumulate packet data here. If anything
* has been received, we will complete the transfer immediately and
* give the data to the class driver. The idea is that we will let the
* receiving be in-charge if incoming buffer.
*/
if (privreq->inflight < privep->ep.maxpacket ||
privreq->req.xfrd >= privreq->req.len)
else
{
/* Return the read request to the class driver. */
@ -1618,13 +1681,11 @@ static int sam_req_read(struct sam_usbdev_s *priv, struct sam_ep_s *privep,
regval = sam_getreg(SAM_UDPHS_EPTCFG(epno));
eptype = regval & UDPHS_EPTCFG_TYPE_MASK;
/* Disable interrupt if not control EP */
/* Disable endpoint interrupts if not the control endpoint */
if (UDPHS_EPTCFG_TYPE_CTRL8 != eptype)
if (eptype != UDPHS_EPTCFG_TYPE_CTRL8)
{
regval = sam_getreg(SAM_UDPHS_IEN);
regval &= ~UDPHS_INT_EPT(epno);
sam_putreg(regval, SAM_UDPHS_IEN);
sam_req_rddisable(epno);
}
/* And complete the request */
@ -2274,8 +2335,7 @@ static void sam_dma_interrupt(struct sam_usbdev_s *priv, int epno)
uint32_t dmastatus;
uint8_t *buf;
int bufcnt;
int xfrd;
int16_t result = OK;
int xfrsize;
/* Not all endpoints support DMA */
@ -2291,13 +2351,6 @@ static void sam_dma_interrupt(struct sam_usbdev_s *priv, int epno)
privreq = sam_rqpeek(&privep->reqq);
DEBUGASSERT(privreq);
/* Invalidate the data cache for region that just completed DMA.
* This will force the buffer data to be reloaded from RAM.
*/
buf = &privreq->req.buf[privreq->req.xfrd];
cp15_invalidate_dcache((uintptr_t)buf, (uintptr_t)buf + privreq->inflight);
/* Get the result of the DMA operation */
dmastatus = sam_getreg(SAM_UDPHS_DMASTATUS(epno));
@ -2310,8 +2363,8 @@ static void sam_dma_interrupt(struct sam_usbdev_s *priv, int epno)
regval &= ~(UDPHS_DMACONTROL_ENDTREN | UDPHS_DMACONTROL_ENDBEN);
sam_putreg(regval, regaddr);
/* Check for end of the buffer. Set by hardware when the
* BUFF_COUNT downcount reach zero.
/* Check for end of the buffer. Set by hardware when the BUFF_COUNT
* downcount reaches zero. This could be either an IN or OUT transfer.
*/
if ((dmastatus & UDPHS_DMASTATUS_ENDBFST) != 0)
@ -2319,77 +2372,118 @@ static void sam_dma_interrupt(struct sam_usbdev_s *priv, int epno)
usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_DMAEOB), (uint16_t)dmastatus);
/* BUFF_COUNT holds the number of untransmitted bytes. BUFF_COUNT is
* equal to zero in case of good transfer
* equal to zero in case of good transfer.
*
* BUFF_COUNT was set to the 'inflight' count when the DMA started and
* the BUFF_COUNT has now decremented to zero
*/
xfrsize = privreq->inflight;
privreq->inflight = 0;
/* This is just debug logic that only does any if USB debug or tracing
* are enabled. This just verifies taht BUFF_COUNT is zero.
*/
bufcnt = (dmastatus & UDPHS_DMASTATUS_BUFCNT_MASK)
>> UDPHS_DMASTATUS_BUFCNT_SHIFT;
xfrd = privreq->inflight - bufcnt;
privreq->req.xfrd += xfrd;
privreq->inflight = bufcnt;
/* Is there more data to send? */
bufcnt = privreq->req.len - privreq->req.xfrd - privreq->inflight;
if (bufcnt > 0)
if (bufcnt != 0)
{
/* Yes, clip to the size of the DMA FIFO */
usbtrace(TRACE_DEVERROR(SAM_TRACEERR_ENDBUFST), bufcnt);
}
#if USBDEV_MAXREQUEUST > DMA_MAX_FIFO_SIZE
if (bufcnt > DMA_MAX_FIFO_SIZE)
{
privreq->inflight = DMA_MAX_FIFO_SIZE;
}
else
#endif
{
privreq->inflight = bufcnt;
}
/* Were we sending? Or receiving? */
/* And perform the DMA transfer */
if (privep->epstate == UDPHS_EPSTATE_SENDING)
{
/* This is an IN endpoint. Continuing processing the write
* request
*/
regval = UDPHS_DMACONTROL_ENDTREN | UDPHS_DMACONTROL_ENDTRIT |
UDPHS_DMACONTROL_ENDBEN | UDPHS_DMACONTROL_ENDBUFFIT |
UDPHS_DMACONTROL_CHANNENB;
DEBUGASSERT(USB_ISEPIN(privep->ep.eplog));
privep->epstate = UDPHS_EPSTATE_IDLE;
(void)sam_req_write(priv, privep);
}
else
{
/* This is an OUT endpoint. Invalidate the data cache for
* region that just completed DMA. This will force the
* buffer data to be reloaded from RAM. when it is accessed
*/
sam_dma_single(epno, privreq, regval);
DEBUGASSERT(USB_ISEPOUT(privep->ep.eplog));
buf = &privreq->req.buf[privreq->req.xfrd];
cp15_invalidate_dcache((uintptr_t)buf, (uintptr_t)buf + xfrsize);
/* Continuing processing the read request */
privep->epstate = UDPHS_EPSTATE_IDLE;
(void)sam_req_read(priv, privep, xfrsize);
}
}
/* Check for end of channel transfer. Set by hardware when the last
* packet transfer is complete
/* Check for end of channel transfer. END_TR_ST is set by hardware when
* the last packet transfer is complete iff END_TR_EN is set in the
* DMACONTROL rgister. The request is complete.
*
* "Used for OUT transfers only.
*
* "0 = USB end of transfer is ignored.
* "1 = UDPHS device can put an end to the current buffer transfer.
*
* "When set, a BULK or INTERRUPT short packet or the last packet of
* an ISOCHRONOUS (micro) frame (DATAX) will close the current buffer
* and the UDPHS_DMASTATUSx register END_TR_ST flag will be raised."
*/
else if ((dmastatus & UDPHS_DMASTATUS_ENDTRST) != 0)
{
usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_DMAEOC), (uint16_t)dmastatus);
DEBUGASSERT(privep->epstate == UDPHS_EPSTATE_RECEIVING &&
USB_ISEPOUT(privep->ep.eplog));
/* Get the number of btyes transferred from the DMA status */
/* Get the number of bytes transferred from the DMA status.
*
* BUFF_COUNT holds the number of untransmitted bytes. In this case,
* BUFF_COUNT should not be zero. BUFF_COUNT was set to the
* 'inflight' count when the DMA started so the difference will
* give us the actual size of the transfer.
*/
bufcnt = ((dmastatus & UDPHS_DMASTATUS_BUFCNT_MASK)
>> UDPHS_DMASTATUS_BUFCNT_SHIFT);
bufcnt = ((dmastatus & UDPHS_DMASTATUS_BUFCNT_MASK)
>> UDPHS_DMASTATUS_BUFCNT_SHIFT);
xfrsize = privreq->inflight - bufcnt;
xfrd = privreq->inflight - bufcnt;
privreq->req.xfrd += xfrd;
privreq->inflight -= bufcnt;
/* Invalidate the data cache for region that just completed DMA.
* This will force the buffer data to be reloaded from RAM.
*/
buf = &privreq->req.buf[privreq->req.xfrd];
cp15_invalidate_dcache((uintptr_t)buf, (uintptr_t)buf + xfrsize);
/* Complete this transfer now and return the request to the class
* implementation.
*/
privep->epstate = UDPHS_EPSTATE_IDLE;
privreq->req.xfrd += xfrsize;
privreq->inflight = 0;
sam_req_complete(privep, OK);
/* Now, try to start the next, queue read request */
(void)sam_req_read(priv, privep, 0);
}
else
{
usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_DMAERR), (uint16_t)dmastatus);
result = -EIO;
}
usbtrace(TRACE_DEVERROR(SAM_TRACEERR_DMAERR), (uint16_t)dmastatus);
/* Check if we are finished with this requrest */
/* Return the request buffer to the class implementation with the I/O
* error indication.
*/
if (privreq->req.len == privreq->req.xfrd)
{
/* Return the request buffer to the class implementation */
sam_req_complete(privep, result);
/* Try to start the transfer for the next request */
#warning Missing logic
sam_req_complete(privep, -EIO);
}
}
@ -2575,17 +2669,15 @@ static void sam_ep_interrupt(struct sam_usbdev_s *priv, int epno)
if (eptype == UDPHS_EPTCFG_TYPE_ISO)
{
privep->epstate = UDPHS_EPSTATE_IDLE;
sam_req_complete(privep, -EIO);
}
/* If EP is not halted, clear STALL */
else
else if (privep->epstate != UDPHS_EPSTATE_STALLED)
{
if (privep->epstate != UDPHS_EPSTATE_STALLED)
{
sam_putreg(UDPHS_EPTSTA_FRCESTALL, SAM_UDPHS_EPTCLRSTA(epno));
}
sam_putreg(UDPHS_EPTSTA_FRCESTALL, SAM_UDPHS_EPTCLRSTA(epno));
}
}
@ -3500,7 +3592,7 @@ static int sam_ep_submit(struct usbdev_ep_s *ep, struct usbdev_req_s *req)
sam_req_enqueue(&privep->reqq, privreq);
usbtrace(TRACE_INREQQUEUED(epno), req->len);
/* If the IN endpoint FIFO is available, then transfer the data now */
/* If the IN endpoint is IDLE, then transfer the data now */
if (privep->epstate == UDPHS_EPSTATE_IDLE)
{
@ -3517,16 +3609,11 @@ static int sam_ep_submit(struct usbdev_ep_s *ep, struct usbdev_req_s *req)
sam_req_enqueue(&privep->reqq, privreq);
usbtrace(TRACE_OUTREQQUEUED(epno), req->len);
/* This there a incoming data pending the availability of a request? */
/* If the OUT endpoint IDLE, then setup the read */
if (priv->rxpending)
if (privep->epstate == UDPHS_EPSTATE_IDLE)
{
/* NAK any OUT request addressed to the endpoint */
#warning Missing logic
/* Data is no longer pending */
priv->rxpending = false;
ret = sam_req_read(priv, privep, 0);
}
}
@ -3975,7 +4062,6 @@ static void sam_reset(struct sam_usbdev_s *priv)
sam_setdevaddr(priv, 0);
priv->devstate = UDPHS_DEVSTATE_DEFAULT;
priv->rxpending = false;
/* Reset and disable all endpoints other. Then re-configure EP0 */