SAMA5: Fix HSMCI race condition. Now memory card interface is functional with DMA
This commit is contained in:
parent
6622714c5d
commit
3c38992727
@ -56,6 +56,7 @@
|
||||
#include "chip.h"
|
||||
|
||||
#include "sam_dmac.h"
|
||||
#include "sam_periphclks.h"
|
||||
#include "chip/sam3u_pmc.h"
|
||||
#include "chip/sam3u_dmac.h"
|
||||
|
||||
@ -413,7 +414,7 @@ sam_txctrlabits(struct sam_dma_s *dmach)
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
static size_t sam_maxtxtransfer(struct sam_dmach_s *dmach)
|
||||
static size_t sam_maxtxtransfer(struct sam_dma_s *dmach)
|
||||
{
|
||||
unsigned int srcwidth;
|
||||
size_t maxtransfer;
|
||||
@ -558,7 +559,7 @@ static inline uint32_t sam_rxctrlabits(struct sam_dma_s *dmach)
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
static size_t sam_maxrxtransfer(struct sam_dmach_s *dmach)
|
||||
static size_t sam_maxrxtransfer(struct sam_dma_s *dmach)
|
||||
{
|
||||
unsigned int srcwidth;
|
||||
size_t maxtransfer;
|
||||
@ -1399,7 +1400,7 @@ DMA_HANDLE sam_dmachannel(uint32_t chflags)
|
||||
|
||||
void sam_dmaconfig(DMA_HANDLE handle, uint32_t chflags)
|
||||
{
|
||||
struct sam_dmach_s *dmach = (struct sam_dmach_s *)handle;
|
||||
struct sam_dma_s *dmach = (struct sam_dma_s *)handle;
|
||||
|
||||
/* Set the new DMA channel flags. */
|
||||
|
||||
|
@ -51,7 +51,7 @@
|
||||
/* Helper macros */
|
||||
|
||||
#define sam_enableperipheral(s) putreg32((1 << (s)), SAM_PMC_PCER)
|
||||
#define sam_disableperipheral(s) putreg32((1 << (s)), SAM_PMC_PDER)
|
||||
#define sam_disableperipheral(s) putreg32((1 << (s)), SAM_PMC_PCDR)
|
||||
|
||||
#define sam_supc_enableclk() sam_enableperipheral(SAM_PID_SUPC)
|
||||
#define sam_rstc_enableclk() sam_enableperipheral(SAM_PID_RSTC)
|
||||
|
@ -63,6 +63,7 @@
|
||||
#include "sam_gpio.h"
|
||||
#include "sam_dmac.h"
|
||||
#include "sam_hsmci.h"
|
||||
#include "sam_periphclks.h"
|
||||
#include "chip/sam3u_dmac.h"
|
||||
#include "chip/sam3u_pmc.h"
|
||||
#include "chip/sam_hsmci.h"
|
||||
@ -268,6 +269,7 @@ struct sam_dev_s
|
||||
uint32_t cmdrmask; /* Interrupt enables for this particular cmd/response */
|
||||
volatile sdio_eventset_t wkupevent; /* The event that caused the wakeup */
|
||||
WDOG_ID waitwdog; /* Watchdog that handles event timeouts */
|
||||
bool dmabusy; /* TRUE: DMA is in progress */
|
||||
|
||||
/* Callback support */
|
||||
|
||||
@ -329,11 +331,14 @@ struct sam_xfrregs_s
|
||||
|
||||
static void sam_takesem(struct sam_dev_s *priv);
|
||||
#define sam_givesem(priv) (sem_post(&priv->waitsem))
|
||||
static void sam_enablewaitints(struct sam_dev_s *priv, uint32_t waitmask,
|
||||
|
||||
static void sam_configwaitints(struct sam_dev_s *priv, uint32_t waitmask,
|
||||
sdio_eventset_t waitevents);
|
||||
static void sam_disablewaitints(struct sam_dev_s *priv, sdio_eventset_t wkupevents);
|
||||
static void sam_enablexfrints(struct sam_dev_s *priv, uint32_t xfrmask);
|
||||
static inline void sam_configxfrints(struct sam_dev_s *priv, uint32_t xfrmask);
|
||||
static void sam_disablexfrints(struct sam_dev_s *priv);
|
||||
static void sam_enableints(struct sam_dev_s *priv);
|
||||
|
||||
static inline void sam_disable(void);
|
||||
static inline void sam_enable(void);
|
||||
|
||||
@ -522,10 +527,13 @@ static void sam_takesem(struct sam_dev_s *priv)
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: sam_enablewaitints
|
||||
* Name: sam_configwaitints
|
||||
*
|
||||
* Description:
|
||||
* Enable HSMCI interrupts needed to suport the wait function
|
||||
* Configure HSMCI interrupts needed to support the wait function. Wait
|
||||
* interrupts are configured here, but not enabled until
|
||||
* sam_enableints() is called. Why? Because the XFRDONE interrupt
|
||||
* is always pending until start the data transfer.
|
||||
*
|
||||
* Input Parameters:
|
||||
* priv - A reference to the HSMCI device state structure
|
||||
@ -537,20 +545,17 @@ static void sam_takesem(struct sam_dev_s *priv)
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
static void sam_enablewaitints(struct sam_dev_s *priv, uint32_t waitmask,
|
||||
static void sam_configwaitints(struct sam_dev_s *priv, uint32_t waitmask,
|
||||
sdio_eventset_t waitevents)
|
||||
{
|
||||
irqstate_t flags;
|
||||
|
||||
/* Save all of the data and set the new interrupt mask in one, atomic
|
||||
* operation.
|
||||
*/
|
||||
/* Save all of the data in one, atomic operation. */
|
||||
|
||||
flags = irqsave();
|
||||
priv->waitevents = waitevents;
|
||||
priv->wkupevent = 0;
|
||||
priv->waitmask = waitmask;
|
||||
putreg32(priv->xfrmask | priv->waitmask, SAM_HSMCI_IER);
|
||||
irqrestore(flags);
|
||||
}
|
||||
|
||||
@ -587,10 +592,13 @@ static void sam_disablewaitints(struct sam_dev_s *priv,
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: sam_enablexfrints
|
||||
* Name: sam_configxfrints
|
||||
*
|
||||
* Description:
|
||||
* Enable HSMCI interrupts needed to support the data transfer event
|
||||
* Configure HSMCI interrupts needed to support the data transfer. Data
|
||||
* transfer interrupts are configured here, but not enabled until
|
||||
* sam_enableints() is called. Why? Because the XFRDONE interrupt
|
||||
* is always pending until start the data transfer.
|
||||
*
|
||||
* Input Parameters:
|
||||
* priv - A reference to the HSMCI device state structure
|
||||
@ -601,12 +609,9 @@ static void sam_disablewaitints(struct sam_dev_s *priv,
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
static void sam_enablexfrints(struct sam_dev_s *priv, uint32_t xfrmask)
|
||||
static inline void sam_configxfrints(struct sam_dev_s *priv, uint32_t xfrmask)
|
||||
{
|
||||
irqstate_t flags = irqsave();
|
||||
priv->xfrmask = xfrmask;
|
||||
putreg32(priv->xfrmask | priv->waitmask, SAM_HSMCI_IER);
|
||||
irqrestore(flags);
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
@ -632,6 +637,28 @@ static void sam_disablexfrints(struct sam_dev_s *priv)
|
||||
irqrestore(flags);
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: sam_enableints
|
||||
*
|
||||
* Description:
|
||||
* Enable the previously configured HSMCI interrupts needed to suport the
|
||||
* wait and transfer functions.
|
||||
*
|
||||
* Input Parameters:
|
||||
* priv - A reference to the HSMCI device state structure
|
||||
*
|
||||
* Returned Value:
|
||||
* None
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
static inline void sam_enableints(struct sam_dev_s *priv)
|
||||
{
|
||||
/* Enable all interrupts associated with the waited-for event */
|
||||
|
||||
putreg32(priv->xfrmask | priv->waitmask, SAM_HSMCI_IER);
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: sam_disable
|
||||
*
|
||||
@ -903,10 +930,16 @@ static void sam_cmddump(void)
|
||||
|
||||
static void sam_dmacallback(DMA_HANDLE handle, void *arg, int result)
|
||||
{
|
||||
struct sam_dev_s *priv = (struct sam_dev_s *)arg;
|
||||
|
||||
/* We don't really do anything at the completion of DMA. The termination
|
||||
* of the transfer is driven by the HSMCI interrupts.
|
||||
*
|
||||
* Mark the DMA not busy.
|
||||
*/
|
||||
|
||||
priv->dmabusy = false;
|
||||
|
||||
sam_xfrsample((struct sam_dev_s*)arg, SAMPLENDX_DMA_CALLBACK);
|
||||
}
|
||||
|
||||
@ -1025,6 +1058,7 @@ static void sam_endtransfer(struct sam_dev_s *priv,
|
||||
*/
|
||||
|
||||
sam_dmastop(priv->dma);
|
||||
priv->dmabusy = false;
|
||||
|
||||
/* Disable the DMA handshaking */
|
||||
|
||||
@ -1100,6 +1134,7 @@ static int sam_interrupt(int irq, void *context)
|
||||
|
||||
sr = getreg32(SAM_HSMCI_SR);
|
||||
enabled = sr & getreg32(SAM_HSMCI_IMR);
|
||||
|
||||
if (enabled == 0)
|
||||
{
|
||||
break;
|
||||
@ -1270,6 +1305,7 @@ static void sam_reset(FAR struct sdio_dev_s *dev)
|
||||
priv->waitevents = 0; /* Set of events to be waited for */
|
||||
priv->waitmask = 0; /* Interrupt enables for event waiting */
|
||||
priv->wkupevent = 0; /* The event that caused the wakeup */
|
||||
priv->dmabusy = false; /* No DMA in progress */
|
||||
wd_cancel(priv->waitwdog); /* Cancel any timeouts */
|
||||
|
||||
/* Interrupt mode data transfer support */
|
||||
@ -1620,8 +1656,8 @@ static void sam_blocksetup(FAR struct sdio_dev_s *dev, unsigned int blocklen,
|
||||
* Name: sam_cancel
|
||||
*
|
||||
* Description:
|
||||
* Cancel the data transfer setup of HSMCI_RECVSETUP, HSMCI_SENDSETUP,
|
||||
* HSMCI_DMARECVSETUP or HSMCI_DMASENDSETUP. This must be called to cancel
|
||||
* Cancel the data transfer setup of SDIO_RECVSETUP, SDIO_SENDSETUP,
|
||||
* SDIO_DMARECVSETUP or SDIO_DMASENDSETUP. This must be called to cancel
|
||||
* the data transfer setup if, for some reason, you cannot perform the
|
||||
* transfer.
|
||||
*
|
||||
@ -1660,6 +1696,7 @@ static int sam_cancel(FAR struct sdio_dev_s *dev)
|
||||
*/
|
||||
|
||||
sam_dmastop(priv->dma);
|
||||
priv->dmabusy = false;
|
||||
|
||||
/* Disable the DMA handshaking */
|
||||
|
||||
@ -1947,13 +1984,24 @@ static int sam_recvnotimpl(FAR struct sdio_dev_s *dev,
|
||||
*
|
||||
* Description:
|
||||
* Enable/disable of a set of SDIO wait events. This is part of the
|
||||
* the HSMCI_WAITEVENT sequence. The set of to-be-waited-for events is
|
||||
* configured before calling sam_eventwait. This is done in this way
|
||||
* to help the driver to eliminate race conditions between the command
|
||||
* the SDIO_WAITEVENT sequence. The set of to-be-waited-for events is
|
||||
* configured before calling either calling SDIO_DMARECVSETUP,
|
||||
* SDIO_DMASENDSETUP, or or SDIO_WAITEVENT. This is the recommended
|
||||
* ordering:
|
||||
*
|
||||
* SDIO_WAITENABLE: Discard any pending interrupts, enable event(s)
|
||||
* of interest
|
||||
* SDIO_DMARECVSETUP/
|
||||
* SDIO_DMASENDSETUP: Setup the logic that will trigger the event the
|
||||
* event(s) of interest
|
||||
* SDIO_WAITEVENT: Wait for the event of interest (which might
|
||||
* already have occurred)
|
||||
*
|
||||
* This sequency should eliminate race conditions between the command/trasnfer
|
||||
* setup and the subsequent events.
|
||||
*
|
||||
* The enabled events persist until either (1) HSMCI_WAITENABLE is called
|
||||
* again specifying a different set of wait events, or (2) HSMCI_EVENTWAIT
|
||||
* The enabled events persist until either (1) SDIO_WAITENABLE is called
|
||||
* again specifying a different set of wait events, or (2) SDIO_EVENTWAIT
|
||||
* returns.
|
||||
*
|
||||
* Input Parameters:
|
||||
@ -1988,10 +2036,20 @@ static void sam_waitenable(FAR struct sdio_dev_s *dev,
|
||||
waitmask |= priv->cmdrmask;
|
||||
}
|
||||
|
||||
/* Enable event-related interrupts */
|
||||
/* Clear (most) pending interrupts by reading the status register.
|
||||
* No interrupts should be lost (assuming that interrupts were enabled
|
||||
* before sam_waitenable() was called). Any interrupts that become
|
||||
* pending after this point must be valid event indications.
|
||||
*/
|
||||
|
||||
(void)getreg32(SAM_HSMCI_SR);
|
||||
sam_enablewaitints(priv, waitmask, eventset);
|
||||
|
||||
/* Wait interrupts are configured here, but not enabled until
|
||||
* sam_eventwait() is called. Why? Because the XFRDONE interrupt is
|
||||
* always pending until start the data transfer.
|
||||
*/
|
||||
|
||||
sam_configwaitints(priv, waitmask, eventset);
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
@ -1999,8 +2057,8 @@ static void sam_waitenable(FAR struct sdio_dev_s *dev,
|
||||
*
|
||||
* Description:
|
||||
* Wait for one of the enabled events to occur (or a timeout). Note that
|
||||
* all events enabled by HSMCI_WAITEVENTS are disabled when sam_eventwait
|
||||
* returns. HSMCI_WAITEVENTS must be called again before sam_eventwait
|
||||
* all events enabled by SDIO_WAITEVENTS are disabled when sam_eventwait
|
||||
* returns. SDIO_WAITEVENTS must be called again before sam_eventwait
|
||||
* can be used again.
|
||||
*
|
||||
* Input Parameters:
|
||||
@ -2022,13 +2080,22 @@ static sdio_eventset_t sam_eventwait(FAR struct sdio_dev_s *dev,
|
||||
sdio_eventset_t wkupevent = 0;
|
||||
int ret;
|
||||
|
||||
/* There is a race condition here... the event may have completed before
|
||||
* we get here. In this case waitevents will be zero, but wkupevents will
|
||||
* be non-zero (and, hopefully, the semaphore count will also be non-zero.
|
||||
/* Since interrupts not been enabled to this point, any relevant events
|
||||
* are pending and should not yet have occurred.
|
||||
*/
|
||||
|
||||
DEBUGASSERT((priv->waitevents != 0 && priv->wkupevent == 0) ||
|
||||
(priv->waitevents == 0 && priv->wkupevent != 0));
|
||||
DEBUGASSERT(priv->waitevents != 0 && priv->wkupevent == 0);
|
||||
|
||||
/* Now enable event-related interrupts. If the events are pending, they
|
||||
* may happen immediately here before entering the loop.
|
||||
*/
|
||||
|
||||
sam_enableints(priv);
|
||||
|
||||
/* There is a race condition here... the event may have completed before
|
||||
* we get here. In this case waitevents will be zero, but wkupevents will
|
||||
* be non-zero (and, hopefully, the semaphore count will also be non-zero).
|
||||
*/
|
||||
|
||||
/* Check if the timeout event is specified in the event set */
|
||||
|
||||
@ -2043,7 +2110,16 @@ static sdio_eventset_t sam_eventwait(FAR struct sdio_dev_s *dev,
|
||||
return SDIOWAIT_TIMEOUT;
|
||||
}
|
||||
|
||||
/* Start the watchdog timer */
|
||||
/* Start the watchdog timer. I am not sure why this is, but I am
|
||||
* currently seeing some additional delays when DMA is used (On the
|
||||
* SAMA5, might not be necessary for SAM3/4).
|
||||
*/
|
||||
|
||||
#warning REVISIT: This should not be necessary
|
||||
if (priv->dmabusy)
|
||||
{
|
||||
timeout += 500;
|
||||
}
|
||||
|
||||
delay = (timeout + (MSEC_PER_TICK-1)) / MSEC_PER_TICK;
|
||||
ret = wd_start(priv->waitwdog, delay, (wdentry_t)sam_eventtimeout,
|
||||
@ -2099,7 +2175,7 @@ static sdio_eventset_t sam_eventwait(FAR struct sdio_dev_s *dev,
|
||||
*
|
||||
* Events are automatically disabled once the callback is performed and no
|
||||
* further callback events will occur until they are again enabled by
|
||||
* calling this methos.
|
||||
* calling this methods.
|
||||
*
|
||||
* Input Parameters:
|
||||
* dev - An instance of the SDIO device interface
|
||||
@ -2133,7 +2209,7 @@ static void sam_callbackenable(FAR struct sdio_dev_s *dev,
|
||||
* thread.
|
||||
*
|
||||
* When this method is called, all callbacks should be disabled until they
|
||||
* are enabled via a call to HSMCI_CALLBACKENABLE
|
||||
* are enabled via a call to SDIO_CALLBACKENABLE.
|
||||
*
|
||||
* Input Parameters:
|
||||
* dev - Device-specific state data
|
||||
@ -2216,7 +2292,6 @@ static int sam_dmarecvsetup(FAR struct sdio_dev_s *dev, FAR uint8_t *buffer,
|
||||
|
||||
/* Configure the RX DMA */
|
||||
|
||||
sam_enablexfrints(priv, HSMCI_DMARECV_INTS);
|
||||
sam_dmarxsetup(priv->dma, SAM_HSMCI_RDR, (uint32_t)buffer, buflen);
|
||||
|
||||
/* Enable DMA handshaking */
|
||||
@ -2226,8 +2301,16 @@ static int sam_dmarecvsetup(FAR struct sdio_dev_s *dev, FAR uint8_t *buffer,
|
||||
|
||||
/* Start the DMA */
|
||||
|
||||
priv->dmabusy = true;
|
||||
sam_dmastart(priv->dma, sam_dmacallback, priv);
|
||||
|
||||
/* Configure transfer-related interrupts. Transfer interrupts are not
|
||||
* enabled until after the transfer is stard with an SD command (i.e.,
|
||||
* at the beginning of sam_eventwait().
|
||||
*/
|
||||
|
||||
sam_xfrsample(priv, SAMPLENDX_AFTER_SETUP);
|
||||
sam_configxfrints(priv, HSMCI_DMARECV_INTS);
|
||||
return OK;
|
||||
}
|
||||
|
||||
@ -2274,12 +2357,16 @@ static int sam_dmasendsetup(FAR struct sdio_dev_s *dev,
|
||||
|
||||
/* Start the DMA */
|
||||
|
||||
priv->dmabusy = true;
|
||||
sam_dmastart(priv->dma, sam_dmacallback, priv);
|
||||
|
||||
/* Configure transfer-related interrupts. Transfer interrupts are not
|
||||
* enabled until after the transfer is stard with an SD command (i.e.,
|
||||
* at the beginning of sam_eventwait().
|
||||
*/
|
||||
|
||||
sam_xfrsample(priv, SAMPLENDX_AFTER_SETUP);
|
||||
|
||||
/* Enable TX interrrupts */
|
||||
|
||||
sam_enablexfrints(priv, HSMCI_DMASEND_INTS);
|
||||
sam_configxfrints(priv, HSMCI_DMASEND_INTS);
|
||||
return OK;
|
||||
}
|
||||
|
||||
|
@ -313,6 +313,7 @@ struct sam_dev_s
|
||||
volatile sdio_eventset_t wkupevent; /* The event that caused the wakeup */
|
||||
WDOG_ID waitwdog; /* Watchdog that handles event timeouts */
|
||||
uint8_t hsmci; /* HSMCI (0, 1, or 2) */
|
||||
bool dmabusy; /* TRUE: DMA is in progress */
|
||||
|
||||
/* Callback support */
|
||||
|
||||
@ -375,11 +376,12 @@ static inline uint32_t sam_getreg(struct sam_dev_s *priv,
|
||||
static inline void sam_putreg(struct sam_dev_s *priv, uint32_t value,
|
||||
unsigned int offset);
|
||||
|
||||
static void sam_enablewaitints(struct sam_dev_s *priv, uint32_t waitmask,
|
||||
static inline void sam_configwaitints(struct sam_dev_s *priv, uint32_t waitmask,
|
||||
sdio_eventset_t waitevents);
|
||||
static void sam_disablewaitints(struct sam_dev_s *priv, sdio_eventset_t wkupevents);
|
||||
static void sam_enablexfrints(struct sam_dev_s *priv, uint32_t xfrmask);
|
||||
static inline void sam_configxfrints(struct sam_dev_s *priv, uint32_t xfrmask);
|
||||
static void sam_disablexfrints(struct sam_dev_s *priv);
|
||||
static inline void sam_enableints(struct sam_dev_s *priv);
|
||||
|
||||
static inline void sam_disable(struct sam_dev_s *priv);
|
||||
static inline void sam_enable(struct sam_dev_s *priv);
|
||||
@ -680,10 +682,13 @@ static inline void sam_putreg(struct sam_dev_s *priv, uint32_t value,
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: sam_enablewaitints
|
||||
* Name: sam_configwaitints
|
||||
*
|
||||
* Description:
|
||||
* Enable HSMCI interrupts needed to suport the wait function
|
||||
* Configure HSMCI interrupts needed to support the wait function. Wait
|
||||
* interrupts are configured here, but not enabled until
|
||||
* sam_enableints() is called. Why? Because the XFRDONE interrupt
|
||||
* is always pending until start the data transfer.
|
||||
*
|
||||
* Input Parameters:
|
||||
* priv - A reference to the HSMCI device state structure
|
||||
@ -695,20 +700,18 @@ static inline void sam_putreg(struct sam_dev_s *priv, uint32_t value,
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
static void sam_enablewaitints(struct sam_dev_s *priv, uint32_t waitmask,
|
||||
sdio_eventset_t waitevents)
|
||||
static inline void sam_configwaitints(struct sam_dev_s *priv,
|
||||
uint32_t waitmask,
|
||||
sdio_eventset_t waitevents)
|
||||
{
|
||||
irqstate_t flags;
|
||||
|
||||
/* Save all of the data and set the new interrupt mask in one, atomic
|
||||
* operation.
|
||||
*/
|
||||
/* Save all of the data in one, atomic operation. */
|
||||
|
||||
flags = irqsave();
|
||||
priv->waitevents = waitevents;
|
||||
priv->wkupevent = 0;
|
||||
priv->waitmask = waitmask;
|
||||
sam_putreg(priv, priv->xfrmask | priv->waitmask, SAM_HSMCI_IER_OFFSET);
|
||||
irqrestore(flags);
|
||||
}
|
||||
|
||||
@ -745,10 +748,13 @@ static void sam_disablewaitints(struct sam_dev_s *priv,
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: sam_enablexfrints
|
||||
* Name: sam_configxfrints
|
||||
*
|
||||
* Description:
|
||||
* Enable HSMCI interrupts needed to support the data transfer event
|
||||
* Configure HSMCI interrupts needed to support the data transfer. Data
|
||||
* transfer interrupts are configured here, but not enabled until
|
||||
* sam_enableints() is called. Why? Because the XFRDONE interrupt
|
||||
* is always pending until start the data transfer.
|
||||
*
|
||||
* Input Parameters:
|
||||
* priv - A reference to the HSMCI device state structure
|
||||
@ -759,12 +765,9 @@ static void sam_disablewaitints(struct sam_dev_s *priv,
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
static void sam_enablexfrints(struct sam_dev_s *priv, uint32_t xfrmask)
|
||||
static inline void sam_configxfrints(struct sam_dev_s *priv, uint32_t xfrmask)
|
||||
{
|
||||
irqstate_t flags = irqsave();
|
||||
priv->xfrmask = xfrmask;
|
||||
sam_putreg(priv, priv->xfrmask | priv->waitmask, SAM_HSMCI_IER_OFFSET);
|
||||
irqrestore(flags);
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
@ -790,6 +793,28 @@ static void sam_disablexfrints(struct sam_dev_s *priv)
|
||||
irqrestore(flags);
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: sam_enableints
|
||||
*
|
||||
* Description:
|
||||
* Enable the previously configured HSMCI interrupts needed to suport the
|
||||
* wait and transfer functions.
|
||||
*
|
||||
* Input Parameters:
|
||||
* priv - A reference to the HSMCI device state structure
|
||||
*
|
||||
* Returned Value:
|
||||
* None
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
static inline void sam_enableints(struct sam_dev_s *priv)
|
||||
{
|
||||
/* Enable all interrupts associated with the waited-for event */
|
||||
|
||||
sam_putreg(priv, priv->xfrmask | priv->waitmask, SAM_HSMCI_IER_OFFSET);
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: sam_disable
|
||||
*
|
||||
@ -1068,10 +1093,16 @@ static void sam_cmddump(struct sam_dev_s *priv)
|
||||
|
||||
static void sam_dmacallback(DMA_HANDLE handle, void *arg, int result)
|
||||
{
|
||||
struct sam_dev_s *priv = (struct sam_dev_s *)arg;
|
||||
|
||||
/* We don't really do anything at the completion of DMA. The termination
|
||||
* of the transfer is driven by the HSMCI interrupts.
|
||||
*
|
||||
* Mark the DMA not busy.
|
||||
*/
|
||||
|
||||
priv->dmabusy = false;
|
||||
|
||||
sam_xfrsample((struct sam_dev_s *)arg, SAMPLENDX_DMA_CALLBACK);
|
||||
}
|
||||
|
||||
@ -1204,6 +1235,7 @@ static void sam_endtransfer(struct sam_dev_s *priv,
|
||||
*/
|
||||
|
||||
sam_dmastop(priv->dma);
|
||||
priv->dmabusy = false;
|
||||
|
||||
/* Disable the DMA handshaking */
|
||||
|
||||
@ -1287,6 +1319,7 @@ static int sam_hsmci_interrupt(struct sam_dev_s *priv)
|
||||
|
||||
sr = sam_getreg(priv, SAM_HSMCI_SR_OFFSET);
|
||||
enabled = sr & sam_getreg(priv, SAM_HSMCI_IMR_OFFSET);
|
||||
|
||||
if (enabled == 0)
|
||||
{
|
||||
break;
|
||||
@ -1491,6 +1524,7 @@ static void sam_reset(FAR struct sdio_dev_s *dev)
|
||||
priv->waitevents = 0; /* Set of events to be waited for */
|
||||
priv->waitmask = 0; /* Interrupt enables for event waiting */
|
||||
priv->wkupevent = 0; /* The event that caused the wakeup */
|
||||
priv->dmabusy = false; /* No DMA in progress */
|
||||
wd_cancel(priv->waitwdog); /* Cancel any timeouts */
|
||||
|
||||
/* Interrupt mode data transfer support */
|
||||
@ -1873,8 +1907,8 @@ static void sam_blocksetup(FAR struct sdio_dev_s *dev, unsigned int blocklen,
|
||||
* Name: sam_cancel
|
||||
*
|
||||
* Description:
|
||||
* Cancel the data transfer setup of HSMCI_RECVSETUP, HSMCI_SENDSETUP,
|
||||
* HSMCI_DMARECVSETUP or HSMCI_DMASENDSETUP. This must be called to cancel
|
||||
* Cancel the data transfer setup of SDIO_RECVSETUP, SDIO_SENDSETUP,
|
||||
* SDIO_DMARECVSETUP or SDIO_DMASENDSETUP. This must be called to cancel
|
||||
* the data transfer setup if, for some reason, you cannot perform the
|
||||
* transfer.
|
||||
*
|
||||
@ -1913,6 +1947,7 @@ static int sam_cancel(FAR struct sdio_dev_s *dev)
|
||||
*/
|
||||
|
||||
sam_dmastop(priv->dma);
|
||||
priv->dmabusy = false;
|
||||
|
||||
/* Disable the DMA handshaking */
|
||||
|
||||
@ -2200,13 +2235,24 @@ static int sam_recvnotimpl(FAR struct sdio_dev_s *dev,
|
||||
*
|
||||
* Description:
|
||||
* Enable/disable of a set of SDIO wait events. This is part of the
|
||||
* the HSMCI_WAITEVENT sequence. The set of to-be-waited-for events is
|
||||
* configured before calling sam_eventwait. This is done in this way
|
||||
* to help the driver to eliminate race conditions between the command
|
||||
* the SDIO_WAITEVENT sequence. The set of to-be-waited-for events is
|
||||
* configured before calling either calling SDIO_DMARECVSETUP,
|
||||
* SDIO_DMASENDSETUP, or or SDIO_WAITEVENT. This is the recommended
|
||||
* ordering:
|
||||
*
|
||||
* SDIO_WAITENABLE: Discard any pending interrupts, enable event(s)
|
||||
* of interest
|
||||
* SDIO_DMARECVSETUP/
|
||||
* SDIO_DMASENDSETUP: Setup the logic that will trigger the event the
|
||||
* event(s) of interest
|
||||
* SDIO_WAITEVENT: Wait for the event of interest (which might
|
||||
* already have occurred)
|
||||
*
|
||||
* This sequency should eliminate race conditions between the command/trasnfer
|
||||
* setup and the subsequent events.
|
||||
*
|
||||
* The enabled events persist until either (1) HSMCI_WAITENABLE is called
|
||||
* again specifying a different set of wait events, or (2) HSMCI_EVENTWAIT
|
||||
* The enabled events persist until either (1) SDIO_WAITENABLE is called
|
||||
* again specifying a different set of wait events, or (2) SDIO_EVENTWAIT
|
||||
* returns.
|
||||
*
|
||||
* Input Parameters:
|
||||
@ -2241,10 +2287,20 @@ static void sam_waitenable(FAR struct sdio_dev_s *dev,
|
||||
waitmask |= priv->cmdrmask;
|
||||
}
|
||||
|
||||
/* Enable event-related interrupts */
|
||||
/* Clear (most) pending interrupts by reading the status register.
|
||||
* No interrupts should be lost (assuming that interrupts were enabled
|
||||
* before sam_waitenable() was called). Any interrupts that become
|
||||
* pending after this point must be valid event indications.
|
||||
*/
|
||||
|
||||
(void)sam_getreg(priv, SAM_HSMCI_SR_OFFSET);
|
||||
sam_enablewaitints(priv, waitmask, eventset);
|
||||
|
||||
/* Wait interrupts are configured here, but not enabled until
|
||||
* sam_eventwait() is called. Why? Because the XFRDONE interrupt is
|
||||
* always pending until start the data transfer.
|
||||
*/
|
||||
|
||||
sam_configwaitints(priv, waitmask, eventset);
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
@ -2252,8 +2308,8 @@ static void sam_waitenable(FAR struct sdio_dev_s *dev,
|
||||
*
|
||||
* Description:
|
||||
* Wait for one of the enabled events to occur (or a timeout). Note that
|
||||
* all events enabled by HSMCI_WAITEVENTS are disabled when sam_eventwait
|
||||
* returns. HSMCI_WAITEVENTS must be called again before sam_eventwait
|
||||
* all events enabled by SDIO_WAITEVENTS are disabled when sam_eventwait
|
||||
* returns. SDIO_WAITEVENTS must be called again before sam_eventwait
|
||||
* can be used again.
|
||||
*
|
||||
* Input Parameters:
|
||||
@ -2275,13 +2331,22 @@ static sdio_eventset_t sam_eventwait(FAR struct sdio_dev_s *dev,
|
||||
sdio_eventset_t wkupevent = 0;
|
||||
int ret;
|
||||
|
||||
/* There is a race condition here... the event may have completed before
|
||||
* we get here. In this case waitevents will be zero, but wkupevents will
|
||||
* be non-zero (and, hopefully, the semaphore count will also be non-zero.
|
||||
/* Since interrupts not been enabled to this point, any relevant events
|
||||
* are pending and should not yet have occurred.
|
||||
*/
|
||||
|
||||
DEBUGASSERT((priv->waitevents != 0 && priv->wkupevent == 0) ||
|
||||
(priv->waitevents == 0 && priv->wkupevent != 0));
|
||||
DEBUGASSERT(priv->waitevents != 0 && priv->wkupevent == 0);
|
||||
|
||||
/* Now enable event-related interrupts. If the events are pending, they
|
||||
* may happen immediately here before entering the loop.
|
||||
*/
|
||||
|
||||
sam_enableints(priv);
|
||||
|
||||
/* There is a race condition here... the event may have completed before
|
||||
* we get here. In this case waitevents will be zero, but wkupevents will
|
||||
* be non-zero (and, hopefully, the semaphore count will also be non-zero).
|
||||
*/
|
||||
|
||||
/* Check if the timeout event is specified in the event set */
|
||||
|
||||
@ -2296,7 +2361,15 @@ static sdio_eventset_t sam_eventwait(FAR struct sdio_dev_s *dev,
|
||||
return SDIOWAIT_TIMEOUT;
|
||||
}
|
||||
|
||||
/* Start the watchdog timer */
|
||||
/* Start the watchdog timer. I am not sure why this is, but I am\
|
||||
* currently seeing some additional delays when DMA is used.
|
||||
*/
|
||||
|
||||
#warning REVISIT: This should not be necessary
|
||||
if (priv->dmabusy)
|
||||
{
|
||||
timeout += 500;
|
||||
}
|
||||
|
||||
delay = (timeout + (MSEC_PER_TICK-1)) / MSEC_PER_TICK;
|
||||
ret = wd_start(priv->waitwdog, delay, (wdentry_t)sam_eventtimeout,
|
||||
@ -2352,7 +2425,7 @@ static sdio_eventset_t sam_eventwait(FAR struct sdio_dev_s *dev,
|
||||
*
|
||||
* Events are automatically disabled once the callback is performed and no
|
||||
* further callback events will occur until they are again enabled by
|
||||
* calling this methos.
|
||||
* calling this methods.
|
||||
*
|
||||
* Input Parameters:
|
||||
* dev - An instance of the SDIO device interface
|
||||
@ -2386,7 +2459,7 @@ static void sam_callbackenable(FAR struct sdio_dev_s *dev,
|
||||
* thread.
|
||||
*
|
||||
* When this method is called, all callbacks should be disabled until they
|
||||
* are enabled via a call to HSMCI_CALLBACKENABLE
|
||||
* are enabled via a call to SDIO_CALLBACKENABLE.
|
||||
*
|
||||
* Input Parameters:
|
||||
* dev - Device-specific state data
|
||||
@ -2478,7 +2551,6 @@ static int sam_dmarecvsetup(FAR struct sdio_dev_s *dev, FAR uint8_t *buffer,
|
||||
|
||||
/* Configure the RX DMA */
|
||||
|
||||
sam_enablexfrints(priv, HSMCI_DMARECV_INTS);
|
||||
sam_dmarxsetup(priv->dma, paddr, maddr, buflen);
|
||||
|
||||
/* Enable DMA handshaking */
|
||||
@ -2488,8 +2560,16 @@ static int sam_dmarecvsetup(FAR struct sdio_dev_s *dev, FAR uint8_t *buffer,
|
||||
|
||||
/* Start the DMA */
|
||||
|
||||
priv->dmabusy = true;
|
||||
sam_dmastart(priv->dma, sam_dmacallback, priv);
|
||||
|
||||
/* Configure transfer-related interrupts. Transfer interrupts are not
|
||||
* enabled until after the transfer is stard with an SD command (i.e.,
|
||||
* at the beginning of sam_eventwait().
|
||||
*/
|
||||
|
||||
sam_xfrsample(priv, SAMPLENDX_AFTER_SETUP);
|
||||
sam_configxfrints(priv, HSMCI_DMARECV_INTS);
|
||||
return OK;
|
||||
}
|
||||
|
||||
@ -2543,12 +2623,16 @@ static int sam_dmasendsetup(FAR struct sdio_dev_s *dev,
|
||||
|
||||
/* Start the DMA */
|
||||
|
||||
priv->dmabusy = true;
|
||||
sam_dmastart(priv->dma, sam_dmacallback, priv);
|
||||
|
||||
/* Configure transfer-related interrupts. Transfer interrupts are not
|
||||
* enabled until after the transfer is stard with an SD command (i.e.,
|
||||
* at the beginning of sam_eventwait().
|
||||
*/
|
||||
|
||||
sam_xfrsample(priv, SAMPLENDX_AFTER_SETUP);
|
||||
|
||||
/* Enable TX interrrupts */
|
||||
|
||||
sam_enablexfrints(priv, HSMCI_DMASEND_INTS);
|
||||
sam_configxfrints(priv, HSMCI_DMARECV_INTS);
|
||||
return OK;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user