diff --git a/arch/arm/src/stm32h7/stm32_sdmmc.c b/arch/arm/src/stm32h7/stm32_sdmmc.c index 77b30c804cc..45fbd10485e 100644 --- a/arch/arm/src/stm32h7/stm32_sdmmc.c +++ b/arch/arm/src/stm32h7/stm32_sdmmc.c @@ -43,7 +43,6 @@ #include #include -#include #include #include #include @@ -55,12 +54,13 @@ #include #include #include +#include #include - #include +#include + #include -#include #include "chip.h" #include "up_arch.h" @@ -98,6 +98,12 @@ * be monitored off the an HP work thread for a residual of less than * FIFO_SIZE_IN_BYTES / 2. * + * HW Issues when using IDMA + * + * The DMA buffer must be located in a zone accessible via IDMA. + * For SDMMC1, IDMA cannot access SRAM123 or SRAM4. Refer to ST AN5200. + * Buffer validity is checked when CONFIG_ARCH_HAVE_SDIO_PREFLIGHT is set. + * * MDMA is only available on for SDMMC1 and Not supported at this time. * * Required system configuration options: @@ -110,13 +116,13 @@ * APIs to manage concurrent accesses on the SDMMC bus. This is not * needed for the simple case of a single SD card, for example. * CONFIG_STM32H7_SDMMC_IDMA - Enable SDMMC IDMA. - * DMA support for SDMMC. If disabled disabled, the SDMMC will work in + * DMA support for SDMMC. If disabled, the SDMMC will work in * interrupt mode and still use the IDMA to a local buffer for data * lengths less the 32 bytes due to the FIFO limitations. * CONFIG_SDMMC1/2_WIDTH_D1_ONLY - This may be selected to force the driver * operate with only a single data line (the default is to use all * 4 SD data lines). - * CONFIG_CONFIG_STM32H7_SDMMC_XFRDEBUG - Enables some very low-level debug + * CONFIG_STM32H7_SDMMC_XFRDEBUG - Enables some very low-level debug * output This also requires CONFIG_DEBUG_FS and CONFIG_DEBUG_INFO * CONFIG_SDMMC1/2_SDIO_MODE * Build ins additional support needed only for SDIO cards (vs. SD memory @@ -141,6 +147,11 @@ #if !defined(CONFIG_STM32H7_SDMMC_IDMA) # warning "Large Non-DMA transfer may result in RX overrun failures" +#elif defined(CONFIG_STM32H7_SDMMC1) +# define SRAM123_START STM32_SRAM123_BASE +# define SRAM123_END (SRAM123_START + STM32H7_SRAM123_SIZE) +# define SRAM4_START STM32_SRAM4_BASE +# define SRAM4_END (SRAM4_START + STM32H7_SRAM4_SIZE) #endif #ifndef CONFIG_SCHED_WORKQUEUE @@ -153,7 +164,7 @@ #endif #if !defined(CONFIG_DEBUG_FS) || !defined(CONFIG_DEBUG_FEATURES) -# undef CONFIG_CONFIG_STM32H7_SDMMC_XFRDEBUG +# undef CONFIG_STM32H7_SDMMC_XFRDEBUG #endif #ifdef CONFIG_SDMMC1_SDIO_PULLUP @@ -217,6 +228,10 @@ #define SDMMC_DTIMER_DATATIMEOUT (6250000) /* 250 ms @ 25 MHz */ +/* Block size for multi-block transfers */ + +#define SDMMC_MAX_BLOCK_SIZE (512) + /* Data transfer interrupt mask bits */ /* DMA interrupts */ @@ -305,7 +320,7 @@ struct stm32_dev_s { - struct sdio_dev_s dev; /* Standard, base SDIO interface */ + struct sdio_dev_s dev; /* Standard, base SDIO interface */ /* STM32-specific extensions */ @@ -317,30 +332,30 @@ struct stm32_dev_s /* Event support */ - sem_t waitsem; /* Implements event waiting */ - sdio_eventset_t waitevents; /* Set of events to be waited for */ - uint32_t waitmask; /* Interrupt enables for event waiting */ + sem_t waitsem; /* Implements event waiting */ + sdio_eventset_t waitevents; /* Set of events to be waited for */ + uint32_t waitmask; /* Interrupt enables for event waiting */ volatile sdio_eventset_t wkupevent; /* The event that caused the wakeup */ - WDOG_ID waitwdog; /* Watchdog that handles event timeouts */ + WDOG_ID waitwdog; /* Watchdog that handles event timeouts */ /* Callback support */ - sdio_statset_t cdstatus; /* Card status */ - sdio_eventset_t cbevents; /* Set of events to be cause callbacks */ - worker_t callback; /* Registered callback function */ - void *cbarg; /* Registered callback argument */ - struct work_s cbwork; /* Callback work queue structure */ + sdio_statset_t cdstatus; /* Card status */ + sdio_eventset_t cbevents; /* Set of events to be cause callbacks */ + worker_t callback; /* Registered callback function */ + void *cbarg; /* Registered callback argument */ + struct work_s cbwork; /* Callback work queue structure */ /* Interrupt mode data transfer support */ - uint32_t *buffer; /* Address of current R/W buffer */ - size_t remaining; /* Number of bytes remaining in the transfer */ - uint32_t xfrmask; /* Interrupt enables for data transfer */ + uint32_t *buffer; /* Address of current R/W buffer */ + size_t remaining; /* Number of bytes remaining in the transfer */ + uint32_t xfrmask; /* Interrupt enables for data transfer */ /* DMA data transfer support */ - bool widebus; /* Required for DMA support */ - bool onebit; /* true: Only 1-bit transfers are supported */ + bool widebus; /* Required for DMA support */ + bool onebit; /* true: Only 1-bit transfers are supported */ #if defined(HAVE_SDMMC_SDIO_MODE) /* Interrupt at SDIO_D1 pin, only for SDIO cards */ @@ -353,13 +368,17 @@ struct stm32_dev_s /* Misc */ - uint32_t blocksize; /* Current block size */ - uint32_t receivecnt; /* Real count to receive */ + uint32_t blocksize; /* Current block size */ + uint32_t receivecnt; /* Real count to receive */ #if !defined(CONFIG_STM32H7_SDMMC_IDMA) - struct work_s cbfifo; /* Monitor for Lame FIFO */ + struct work_s cbfifo; /* Monitor for Lame FIFO */ #endif uint8_t rxfifo[FIFO_SIZE_IN_BYTES] /* To offload with IDMA */ __attribute__((aligned(ARMV7M_DCACHE_LINESIZE))); +#if defined(CONFIG_ARMV7M_DCACHE) && defined(CONFIG_STM32H7_SDMMC_IDMA) && \ + !defined(CONFIG_ARCH_HAVE_SDIO_DELAYED_INVLDT) + bool unaligned_rx; /* read buffer is not cache-line aligned */ +#endif }; /* Register logging support */ @@ -392,7 +411,7 @@ struct stm32_sampleregs_s static inline void sdmmc_putreg32(struct stm32_dev_s *priv, uint32_t value, int offset); static inline uint32_t sdmmc_getreg32(struct stm32_dev_s *priv, int offset); -static void stm32_takesem(struct stm32_dev_s *priv); +static int stm32_takesem(struct stm32_dev_s *priv); #define stm32_givesem(priv) (nxsem_post(&priv->waitsem)) static inline void stm32_setclkcr(struct stm32_dev_s *priv, uint32_t clkcr); static void stm32_configwaitints(struct stm32_dev_s *priv, uint32_t waitmask, @@ -402,7 +421,7 @@ static void stm32_configxfrints(struct stm32_dev_s *priv, uint32_t xfrmask); static void stm32_setpwrctrl(struct stm32_dev_s *priv, uint32_t pwrctrl); static inline uint32_t stm32_getpwrctrl(struct stm32_dev_s *priv); -/* Debug Helpers **************************************************************/ +/* Debug Helpers ************************************************************/ #if defined(CONFIG_STM32H7_SDMMC_XFRDEBUG) static void stm32_sampleinit(void); @@ -424,13 +443,16 @@ static void stm32_dumpsamples(struct stm32_dev_s *priv); static uint8_t stm32_log2(uint16_t value); static void stm32_dataconfig(struct stm32_dev_s *priv, uint32_t timeout, - uint32_t dlen, uint32_t dctrl); + uint32_t dlen, bool receive); static void stm32_datadisable(struct stm32_dev_s *priv); #ifndef CONFIG_STM32H7_SDMMC_IDMA static void stm32_sendfifo(struct stm32_dev_s *priv); static void stm32_recvfifo(struct stm32_dev_s *priv); +#elif defined(CONFIG_ARMV7M_DCACHE) && \ + !defined(CONFIG_ARCH_HAVE_SDIO_DELAYED_INVLDT) +static void stm32_recvdma(struct stm32_dev_s *priv); #endif -static void stm32_eventtimeout(int argc, uint32_t arg); +static void stm32_eventtimeout(int argc, uint32_t arg, ...); static void stm32_endwait(struct stm32_dev_s *priv, sdio_eventset_t wkupevent); static void stm32_endtransfer(struct stm32_dev_s *priv, @@ -467,10 +489,12 @@ static int stm32_sendcmd(FAR struct sdio_dev_s *dev, uint32_t cmd, uint32_t arg); static void stm32_blocksetup(FAR struct sdio_dev_s *dev, unsigned int blocksize, unsigned int nblocks); +#ifndef CONFIG_STM32H7_SDMMC_IDMA static int stm32_recvsetup(FAR struct sdio_dev_s *dev, FAR uint8_t *buffer, size_t nbytes); static int stm32_sendsetup(FAR struct sdio_dev_s *dev, FAR const uint8_t *buffer, uint32_t nbytes); +#endif static int stm32_cancel(FAR struct sdio_dev_s *dev); static int stm32_waitresponse(FAR struct sdio_dev_s *dev, uint32_t cmd); @@ -533,8 +557,13 @@ struct stm32_dev_s g_sdmmcdev1 = .attach = stm32_attach, .sendcmd = stm32_sendcmd, .blocksetup = stm32_blocksetup, +#if defined(CONFIG_STM32H7_SDMMC_IDMA) + .recvsetup = stm32_dmarecvsetup, + .sendsetup = stm32_dmasendsetup, +#else .recvsetup = stm32_recvsetup, .sendsetup = stm32_sendsetup, +#endif .cancel = stm32_cancel, .waitresponse = stm32_waitresponse, .recvR1 = stm32_recvshortcrc, @@ -585,8 +614,13 @@ struct stm32_dev_s g_sdmmcdev2 = .attach = stm32_attach, .sendcmd = stm32_sendcmd, .blocksetup = stm32_blocksetup, +#if defined(CONFIG_STM32H7_SDMMC_IDMA) + .recvsetup = stm32_dmarecvsetup, + .sendsetup = stm32_dmasendsetup, +#else .recvsetup = stm32_recvsetup, .sendsetup = stm32_sendsetup, +#endif .cancel = stm32_cancel, .waitresponse = stm32_waitresponse, .recvR1 = stm32_recvshortcrc, @@ -627,6 +661,13 @@ struct stm32_dev_s g_sdmmcdev2 = static struct stm32_sampleregs_s g_sampleregs[DEBUG_NSAMPLES]; #endif +/* Input dma buffer for unaligned transfers */ +#if defined(CONFIG_ARMV7M_DCACHE) && defined(CONFIG_STM32H7_SDMMC_IDMA) && \ + !defined(CONFIG_ARCH_HAVE_SDIO_DELAYED_INVLDT) +static uint8_t sdmmc_rxbuffer[SDMMC_MAX_BLOCK_SIZE] +__attribute__((aligned(ARMV7M_DCACHE_LINESIZE))); +#endif + /**************************************************************************** * Private Functions ****************************************************************************/ @@ -679,27 +720,14 @@ static inline void sdmmc_modifyreg32(struct stm32_dev_s *priv, int offset, * priv - Instance of the SDMMC private state structure. * * Returned Value: - * None + * Normally OK, but may return -ECANCELED in the rare event that the task + * has been canceled. * ****************************************************************************/ -static void stm32_takesem(struct stm32_dev_s *priv) +static int stm32_takesem(struct stm32_dev_s *priv) { - int ret; - - do - { - /* Take the semaphore (perhaps waiting) */ - - ret = nxsem_wait(&priv->waitsem); - - /* The only case that an error should occur here is if the wait was - * awakened by a signal. - */ - - DEBUGASSERT(ret == OK || ret == -EINTR); - } - while (ret == -EINTR); + return nxsem_wait_uninterruptible(&priv->waitsem); } /**************************************************************************** @@ -795,16 +823,16 @@ static void stm32_configwaitints(struct stm32_dev_s *priv, uint32_t waitmask, /* Arm the SDMMC_D Ready and install Isr */ - (void)stm32_gpiosetevent(pinset, true, false, false, - stm32_sdmmc_rdyinterrupt, priv); + stm32_gpiosetevent(pinset, true, false, false, + stm32_sdmmc_rdyinterrupt, priv); } /* Disarm SDMMC_D ready */ if ((wkupevent & SDIOWAIT_WRCOMPLETE) != 0) { - (void)stm32_gpiosetevent(priv->d0_gpio, false, false, false, - NULL, NULL); + stm32_gpiosetevent(priv->d0_gpio, false, false, false, + NULL, NULL); stm32_configgpio(priv->d0_gpio); } #endif @@ -816,7 +844,8 @@ static void stm32_configwaitints(struct stm32_dev_s *priv, uint32_t waitmask, #if defined(HAVE_SDMMC_SDIO_MODE) if (priv->sdiomode == true) { - sdmmc_putreg32(priv, priv->xfrmask | priv->waitmask | priv->sdiointmask, + sdmmc_putreg32(priv, + priv->xfrmask | priv->waitmask | priv->sdiointmask, STM32_SDMMC_MASK_OFFSET); } else @@ -854,7 +883,8 @@ static void stm32_configxfrints(struct stm32_dev_s *priv, uint32_t xfrmask) #if defined(HAVE_SDMMC_SDIO_MODE) if (priv->sdiomode == true) { - sdmmc_putreg32(priv, priv->xfrmask | priv->waitmask | priv->sdiointmask, + sdmmc_putreg32(priv, + priv->xfrmask | priv->waitmask | priv->sdiointmask, STM32_SDMMC_MASK_OFFSET); } else @@ -915,10 +945,6 @@ static inline uint32_t stm32_getpwrctrl(struct stm32_dev_s *priv) STM32_SDMMC_POWER_PWRCTRL_MASK; } -/**************************************************************************** - * Debug Helpers - ****************************************************************************/ - /**************************************************************************** * Name: stm32_sampleinit * @@ -1074,42 +1100,80 @@ static uint8_t stm32_log2(uint16_t value) ****************************************************************************/ static void stm32_dataconfig(struct stm32_dev_s *priv, uint32_t timeout, - uint32_t dlen, uint32_t dctrl) + uint32_t dlen, bool receive) { - uint32_t regval = 0; - - /* Enable data path */ - - /* Set DTIMER */ + uint32_t dctrl = 0; - sdmmc_putreg32(priv, timeout, STM32_SDMMC_DTIMER_OFFSET); - - /* Set DLEN */ - - sdmmc_putreg32(priv, dlen, STM32_SDMMC_DLEN_OFFSET); + DEBUGASSERT((sdmmc_getreg32(priv, STM32_SDMMC_IDMACTRLR_OFFSET) & + STM32_SDMMC_IDMACTRLR_IDMAEN) == 0); + DEBUGASSERT((sdmmc_getreg32(priv, STM32_SDMMC_STA_OFFSET) & + STM32_SDMMC_STA_DPSMACT) == 0); /* Configure DCTRL DTDIR, DTMODE, and DBLOCKSIZE fields. * Note: the DTEN is not used, DPSM, and CPSM are used. */ - regval = sdmmc_getreg32(priv, STM32_SDMMC_DCTRL_OFFSET); - regval &= ~(STM32_SDMMC_DCTRL_DTDIR | STM32_SDMMC_DCTRL_DTMODE_MASK | + dctrl = sdmmc_getreg32(priv, STM32_SDMMC_DCTRL_OFFSET); + dctrl &= ~(STM32_SDMMC_DCTRL_DTDIR | STM32_SDMMC_DCTRL_DTMODE_MASK | STM32_SDMMC_DCTRL_DBLOCKSIZE_MASK | STM32_SDMMC_DCTRL_DTEN | STM32_SDMMC_DCTRL_BOOTACKEN); dctrl &= (STM32_SDMMC_DCTRL_DTDIR | STM32_SDMMC_DCTRL_DTMODE_MASK | STM32_SDMMC_DCTRL_DBLOCKSIZE_MASK); - regval |= dctrl; + /* Configure the data direction */ + + if (receive) + { + dctrl |= STM32_SDMMC_DCTRL_DTDIR; + } + + /* Set SDIO_MODE */ #if defined(HAVE_SDMMC_SDIO_MODE) if (priv->sdiomode == true) { - regval |= STM32_SDMMC_DCTRL_SDIOEN | STM32_SDMMC_DCTRL_DTMODE_SDIO; + dctrl |= STM32_SDMMC_DCTRL_SDIOEN | STM32_SDMMC_DCTRL_DTMODE_SDIO; } #endif - sdmmc_putreg32(priv, regval, STM32_SDMMC_DCTRL_OFFSET); + /* if dlen > priv->blocksize we assume that this is a multi-block transfer + * and that the len is multiple of priv->blocksize. + */ + + if (dlen > priv->blocksize) + { + DEBUGASSERT((dlen % priv->blocksize) == 0); + +#if defined(CONFIG_STM32H7_SDMMC_IDMA) && defined(CONFIG_ARMV7M_DCACHE) && \ + !defined(CONFIG_ARCH_HAVE_SDIO_DELAYED_INVLDT) + /* If cache is enabled, and this is an unaligned receive, + * receive one block at a time to the internal buffer + */ + + if (priv->unaligned_rx) + { + DEBUGASSERT(priv->blocksize <= sizeof(sdmmc_rxbuffer)); + dlen = priv->blocksize; + } +#endif + } + + dctrl |= stm32_log2(priv->blocksize) << STM32_SDMMC_DCTRL_DBLOCKSIZE_SHIFT; + + /* Enable data path */ + + /* Set DTIMER */ + + sdmmc_putreg32(priv, timeout, STM32_SDMMC_DTIMER_OFFSET); + + /* Set DLEN */ + + sdmmc_putreg32(priv, dlen, STM32_SDMMC_DLEN_OFFSET); + + /* Set DCTRL */ + + sdmmc_putreg32(priv, dctrl, STM32_SDMMC_DCTRL_OFFSET); } /**************************************************************************** @@ -1270,6 +1334,81 @@ static void stm32_recvfifo(struct stm32_dev_s *priv) } #endif +/**************************************************************************** + * Name: stm32_recvdma + * + * Description: + * Receive SDIO data in dma mode + * + * Input Parameters: + * priv - Instance of the SDMMC private state structure. + * + * Returned Value: + * None + * + ****************************************************************************/ + +#if defined (CONFIG_STM32H7_SDMMC_IDMA) && defined(CONFIG_ARMV7M_DCACHE) && \ + !defined(CONFIG_ARCH_HAVE_SDIO_DELAYED_INVLDT) +static void stm32_recvdma(struct stm32_dev_s *priv) +{ + uint32_t dctrl; + + if (priv->unaligned_rx) + { + /* If we are receiving multiple blocks to an unaligned buffers, + * we receive them one-by-one + */ + + /* Copy the received data to client buffer */ + + memcpy(priv->buffer, sdmmc_rxbuffer, priv->blocksize); + + /* Invalidate the cache before receiving next block */ + + up_invalidate_dcache((uintptr_t)sdmmc_rxbuffer, + (uintptr_t)sdmmc_rxbuffer + priv->blocksize); + + /* Update how much there is left to receive */ + + priv->remaining -= priv->blocksize; + } + else + { + /* In an aligned case, we have always received all blocks */ + + priv->remaining = 0; + } + + if (priv->remaining == 0) + { + /* no data remaining, end the transfer */ + + stm32_endtransfer(priv, SDIOWAIT_TRANSFERDONE); + } + else + { + /* We end up here only in unaligned rx-buffers case, and are receiving + * the data one block at a time + */ + + /* Update where to receive the following block */ + + priv->buffer = (uint32_t *)((uintptr_t)priv->buffer + priv->blocksize); + + /* Clear pending interrupt status */ + + sdmmc_putreg32(priv, STM32_SDMMC_XFRDONE_ICR, STM32_SDMMC_ICR_OFFSET); + + /* Re-enable datapath and wait for next block */ + + dctrl = sdmmc_getreg32(priv, STM32_SDMMC_DCTRL_OFFSET); + dctrl |= STM32_SDMMC_DCTRL_DTEN; + sdmmc_putreg32(priv, dctrl, STM32_SDMMC_DCTRL_OFFSET); + } +} +#endif + /**************************************************************************** * Name: stm32_eventtimeout * @@ -1290,7 +1429,7 @@ static void stm32_recvfifo(struct stm32_dev_s *priv) * ****************************************************************************/ -static void stm32_eventtimeout(int argc, uint32_t arg) +static void stm32_eventtimeout(int argc, uint32_t arg, ...) { struct stm32_dev_s *priv = (struct stm32_dev_s *)arg; @@ -1332,11 +1471,12 @@ static void stm32_eventtimeout(int argc, uint32_t arg) * ****************************************************************************/ -static void stm32_endwait(struct stm32_dev_s *priv, sdio_eventset_t wkupevent) +static void stm32_endwait(struct stm32_dev_s *priv, + sdio_eventset_t wkupevent) { /* Cancel the watchdog timeout */ - (void)wd_cancel(priv->waitwdog); + wd_cancel(priv->waitwdog); /* Disable event-related interrupts */ @@ -1391,16 +1531,9 @@ static void stm32_endtransfer(struct stm32_dev_s *priv, sdmmc_putreg32(priv, STM32_SDMMC_XFRDONE_ICR, STM32_SDMMC_ICR_OFFSET); -#if defined(CONFIG_STM32H7_SDMMC_IDMA) && \ - !defined(CONFIG_ARCH_HAVE_SDIO_DELAYED_INVLDT) - /* invalidate dcache in case of DMA receive. */ + /* DMA debug instrumentation */ - if (priv->receivecnt) - { - up_invalidate_dcache((uintptr_t)priv->buffer, - (uintptr_t)priv->buffer + priv->remaining); - } -#endif + stm32_sample(priv, SAMPLENDX_END_TRANSFER); /* Mark the transfer finished */ @@ -1447,18 +1580,15 @@ static void stm32_sdmmc_fifo_monitor(FAR void *arg) /* Check for the lame FIFO condition */ if (sdmmc_getreg32(priv, STM32_SDMMC_DCOUNT_OFFSET) != 0 && - sdmmc_getreg32(priv, STM32_SDMMC_STA_OFFSET) == STM32_SDMMC_STA_DPSMACT) + sdmmc_getreg32(priv, STM32_SDMMC_STA_OFFSET) == + STM32_SDMMC_STA_DPSMACT) { - (void)work_queue(HPWORK, &priv->cbfifo, - (worker_t)stm32_sdmmc_fifo_monitor, arg, 1); + work_queue(HPWORK, &priv->cbfifo, + (worker_t)stm32_sdmmc_fifo_monitor, arg, 1); } } #endif -/**************************************************************************** - * Interrupt Handling - ****************************************************************************/ - /**************************************************************************** * Name: stm32_sdmmc[1|2]_rdyinterrupt * @@ -1533,10 +1663,8 @@ static int stm32_sdmmc_interrupt(int irq, void *context, void *arg) /* Receive data from the RX FIFO */ stm32_recvfifo(priv); -#if !defined(CONFIG_STM32H7_SDMMC_IDMA) - (void)work_queue(HPWORK, &priv->cbfifo, - (worker_t)stm32_sdmmc_fifo_monitor, arg, 1); -#endif + work_queue(HPWORK, &priv->cbfifo, + (worker_t)stm32_sdmmc_fifo_monitor, arg, 1); } /* Otherwise, Is the transmit FIFO half empty or less? If so @@ -1571,18 +1699,38 @@ static int stm32_sdmmc_interrupt(int irq, void *context, void *arg) * half-full interrupt will be received. */ - /* If the tranfer would not trigger fifo half full +#ifndef CONFIG_STM32H7_SDMMC_IDMA + + /* If the transfer would not trigger fifo half full * we used IDMA to manage the lame fifo */ - if (priv->remaining && priv->remaining < FIFO_SIZE_IN_BYTES / 2) + if (priv->remaining && priv->remaining < + FIFO_SIZE_IN_BYTES / 2) { memcpy(priv->buffer, priv->rxfifo, priv->remaining); } +#else +# if defined(CONFIG_ARMV7M_DCACHE) && \ + !defined(CONFIG_ARCH_HAVE_SDIO_DELAYED_INVLDT) + if (priv->receivecnt) + { + /* Invalidate dcache, and copy the received data into + * client buffers in unaligned case + */ - /* Then terminate the transfer Sets STM32_SDMMC_ICR_DATAENDC */ + stm32_recvdma(priv); + } + else +# endif +#endif + { + /* Then terminate the transfer. + * Sets STM32_SDMMC_ICR_DATAENDC + */ - stm32_endtransfer(priv, SDIOWAIT_TRANSFERDONE); + stm32_endtransfer(priv, SDIOWAIT_TRANSFERDONE); + } } /* Handle data block send/receive CRC failure */ @@ -1594,8 +1742,10 @@ static int stm32_sdmmc_interrupt(int irq, void *context, void *arg) */ mcerr("ERROR: Data block CRC failure, remaining: %d\n", - priv->remaining); - stm32_endtransfer(priv, SDIOWAIT_TRANSFERDONE | SDIOWAIT_ERROR); + priv->remaining); + + stm32_endtransfer(priv, + SDIOWAIT_TRANSFERDONE | SDIOWAIT_ERROR); } /* Handle data timeout error */ @@ -1608,6 +1758,7 @@ static int stm32_sdmmc_interrupt(int irq, void *context, void *arg) mcerr("ERROR: Data timeout, remaining: %d\n", priv->remaining); + stm32_endtransfer(priv, SDIOWAIT_TRANSFERDONE | SDIOWAIT_TIMEOUT); } @@ -1622,6 +1773,7 @@ static int stm32_sdmmc_interrupt(int irq, void *context, void *arg) mcerr("ERROR: RX FIFO overrun, remaining: %d\n", priv->remaining); + stm32_endtransfer(priv, SDIOWAIT_TRANSFERDONE | SDIOWAIT_ERROR); } @@ -1636,6 +1788,7 @@ static int stm32_sdmmc_interrupt(int irq, void *context, void *arg) mcerr("ERROR: TX FIFO underrun, remaining: %d\n", priv->remaining); + stm32_endtransfer(priv, SDIOWAIT_TRANSFERDONE | SDIOWAIT_ERROR); } @@ -1713,10 +1866,6 @@ static int stm32_sdmmc_interrupt(int irq, void *context, void *arg) return OK; } -/**************************************************************************** - * SDIO Interface Methods - ****************************************************************************/ - /**************************************************************************** * Name: stm32_lock * @@ -1787,11 +1936,11 @@ static void stm32_reset(FAR struct sdio_dev_s *dev) regval = getreg32(regaddress); putreg32(regval | restval, regaddress); - usleep(2); + nxsig_usleep(2); putreg32(regval, regaddress); stm32_setpwrctrl(priv, STM32_SDMMC_POWER_PWRCTRL_CYCLE); - usleep(1000); + nxsig_usleep(1000); /* Put SDIO registers in their default, reset state */ @@ -1816,7 +1965,7 @@ static void stm32_reset(FAR struct sdio_dev_s *dev) /* Configure the SDIO peripheral */ stm32_setpwrctrl(priv, STM32_SDMMC_POWER_PWRCTRL_OFF); - usleep(1000); + nxsig_usleep(1000); stm32_setpwrctrl(priv, STM32_SDMMC_POWER_PWRCTRL_ON); stm32_setclkcr(priv, STM32_CLCKCR_INIT); @@ -2147,11 +2296,11 @@ static void stm32_blocksetup(FAR struct sdio_dev_s *dev, * ****************************************************************************/ +#ifndef CONFIG_STM32H7_SDMMC_IDMA static int stm32_recvsetup(FAR struct sdio_dev_s *dev, FAR uint8_t *buffer, size_t nbytes) { struct stm32_dev_s *priv = (struct stm32_dev_s *)dev; - uint32_t dblksize; DEBUGASSERT(priv != NULL && buffer != NULL && nbytes > 0); DEBUGASSERT(((uint32_t)buffer & 3) == 0); @@ -2175,10 +2324,8 @@ static int stm32_recvsetup(FAR struct sdio_dev_s *dev, FAR uint8_t *buffer, /* Then set up the SDIO data path */ - dblksize = stm32_log2(priv->blocksize) << - STM32_SDMMC_DCTRL_DBLOCKSIZE_SHIFT; stm32_dataconfig(priv, SDMMC_DTIMER_DATATIMEOUT * ((nbytes + 511) >> 9), - nbytes, dblksize | STM32_SDMMC_DCTRL_DTDIR); + nbytes, true); /* Workaround the FIFO data available issue */ @@ -2205,6 +2352,7 @@ static int stm32_recvsetup(FAR struct sdio_dev_s *dev, FAR uint8_t *buffer, return OK; } +#endif /**************************************************************************** * Name: stm32_sendsetup @@ -2225,11 +2373,11 @@ static int stm32_recvsetup(FAR struct sdio_dev_s *dev, FAR uint8_t *buffer, * ****************************************************************************/ +#ifndef CONFIG_STM32H7_SDMMC_IDMA static int stm32_sendsetup(FAR struct sdio_dev_s *dev, FAR const uint8_t *buffer, size_t nbytes) { struct stm32_dev_s *priv = (struct stm32_dev_s *)dev; - uint32_t dblksize; DEBUGASSERT(priv != NULL && buffer != NULL && nbytes > 0); DEBUGASSERT(((uint32_t)buffer & 3) == 0); @@ -2251,10 +2399,8 @@ static int stm32_sendsetup(FAR struct sdio_dev_s *dev, FAR const /* Then set up the SDIO data path */ - dblksize = stm32_log2(priv->blocksize) << - STM32_SDMMC_DCTRL_DBLOCKSIZE_SHIFT; stm32_dataconfig(priv, SDMMC_DTIMER_DATATIMEOUT * ((nbytes + 511) >> 9), - nbytes, dblksize); + nbytes, false); /* Enable TX interrupts */ @@ -2262,6 +2408,7 @@ static int stm32_sendsetup(FAR struct sdio_dev_s *dev, FAR const stm32_sample(priv, SAMPLENDX_AFTER_SETUP); return OK; } +#endif /**************************************************************************** * Name: stm32_cancel @@ -2306,7 +2453,7 @@ static int stm32_cancel(FAR struct sdio_dev_s *dev) /* Cancel any watchdog timeout */ - (void)wd_cancel(priv->waitwdog); + wd_cancel(priv->waitwdog); /* Mark no transfer in progress */ @@ -2380,7 +2527,7 @@ static int stm32_waitresponse(FAR struct sdio_dev_s *dev, uint32_t cmd) } /**************************************************************************** - * Name: stm32_recvRx + * Name: stm32_recv* * * Description: * Receive response to SDIO command. Only the critical payload is @@ -2482,7 +2629,9 @@ static int stm32_recvshortcrc(FAR struct sdio_dev_s *dev, uint32_t cmd, #endif } - /* Clear all pending message completion events and return the R1/R6 response */ + /* Clear all pending message completion events and return the R1/R6 + * response. + */ sdmmc_putreg32(priv, STM32_SDMMC_RESPDONE_ICR | STM32_SDMMC_CMDDONE_ICR, STM32_SDMMC_ICR_OFFSET); @@ -2714,7 +2863,7 @@ static sdio_eventset_t stm32_eventwait(FAR struct sdio_dev_s *dev, if (priv->waitevents == 0 && priv->wkupevent == 0) { wkupevent = SDIOWAIT_ERROR; - goto erroutdisable; + goto errout_with_waitints; } #else @@ -2742,7 +2891,7 @@ static sdio_eventset_t stm32_eventwait(FAR struct sdio_dev_s *dev, /* Start the watchdog timer */ delay = MSEC2TICK(timeout); - ret = wd_start(priv->waitwdog, delay, (wdentry_t)stm32_eventtimeout, + ret = wd_start(priv->waitwdog, delay, stm32_eventtimeout, 1, (uint32_t)priv); if (ret < OK) { @@ -2753,8 +2902,8 @@ static sdio_eventset_t stm32_eventwait(FAR struct sdio_dev_s *dev, #if defined(CONFIG_MMCSD_SDIOWAIT_WRCOMPLETE) if ((priv->waitevents & SDIOWAIT_WRCOMPLETE) != 0) { - /* Atomically read pin to see if ready (true) and determine if ISR fired - * If Pin is ready and if ISR did NOT fire end the wait here + /* Atomically read pin to see if ready (true) and determine if ISR + * fired. If Pin is ready and if ISR did NOT fire end the wait here */ if (stm32_gpioread(priv->d0_gpio) && @@ -2765,10 +2914,10 @@ static sdio_eventset_t stm32_eventwait(FAR struct sdio_dev_s *dev, } #endif - /* Loop until the event (or the timeout occurs). Race conditions are avoided - * by calling stm32_waitenable prior to triggering the logic that will cause - * the wait to terminate. Under certain race conditions, the waited-for - * may have already occurred before this function was called! + /* Loop until the event (or the timeout occurs). Race conditions are + * avoided by calling stm32_waitenable prior to triggering the logic that + * will cause the wait to terminate. Under certain race conditions, the + * waited-for may have already occurred before this function was called! */ for (; ; ) @@ -2778,7 +2927,18 @@ static sdio_eventset_t stm32_eventwait(FAR struct sdio_dev_s *dev, * incremented and there will be no wait. */ - stm32_takesem(priv); + ret = stm32_takesem(priv); + if (ret < 0) + { + /* Task canceled. Cancel the wdog (assuming it was started) and + * return an SDIO error. + */ + + wd_cancel(priv->waitwdog); + wkupevent = SDIOWAIT_ERROR; + goto errout_with_waitints; + } + wkupevent = priv->wkupevent; /* Check if the event has occurred. When the event has occurred, then @@ -2796,9 +2956,7 @@ static sdio_eventset_t stm32_eventwait(FAR struct sdio_dev_s *dev, /* Disable event-related interrupts */ -#if defined(CONFIG_MMCSD_SDIOWAIT_WRCOMPLETE) -erroutdisable: -#endif +errout_with_waitints: stm32_configwaitints(priv, 0, 0, 0); @@ -2818,7 +2976,7 @@ static sdio_eventset_t stm32_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 method. * * Input Parameters: * dev - An instance of the SDIO device interface @@ -2856,7 +3014,7 @@ static void stm32_callbackenable(FAR struct sdio_dev_s *dev, * * Input Parameters: * dev - Device-specific state data - * callback - The funtion to call on the media change + * callback - The function to call on the media change * arg - A caller provided value to return with the callback * * Returned Value: @@ -2900,27 +3058,45 @@ static int stm32_registercallback(FAR struct sdio_dev_s *dev, static int stm32_dmapreflight(FAR struct sdio_dev_s *dev, FAR const uint8_t *buffer, size_t buflen) { - /* DMA must be possible to the buffer */ + struct stm32_dev_s *priv = (struct stm32_dev_s *)dev; + + DEBUGASSERT(priv != NULL && buffer != NULL && buflen > 0); + + /* IDMA must be possible to the buffer */ + +#if defined(CONFIG_STM32H7_SDMMC1) + if (priv->base == STM32_SDMMC1_BASE) + { + /* For SDMMC1, IDMA cannot access SRAM123 or SRAM4. */ + + if (((uintptr_t)buffer >= SRAM123_START && + (uintptr_t)buffer + buflen <= SRAM123_END) || + ((uintptr_t)buffer >= SRAM4_START && + (uintptr_t)buffer + buflen <= SRAM4_END)) + { + mcerr("invalid IDMA address " + "buffer:0x%08x end:0x%08x\n", + buffer, buffer + buflen - 1); + return -EFAULT; + } + } +#endif # if defined(CONFIG_ARMV7M_DCACHE) && !defined(CONFIG_ARMV7M_DCACHE_WRITETHROUGH) /* buffer alignment is required for DMA transfers with dcache in buffered * mode (not write-through) because a) arch_invalidate_dcache could lose - * buffered writes and b) arch_flush_dcache could corrupt adjacent memory if - * the maddr and the mend+1, the next next address are not on + * buffered writes and b) arch_flush_dcache could corrupt adjacent memory + * if the maddr and the mend+1, the next next address are not on * ARMV7M_DCACHE_LINESIZE boundaries. */ - struct stm32_dev_s *priv = (struct stm32_dev_s *)dev; - - DEBUGASSERT(priv != NULL && buffer != NULL && buflen > 0); - if (buffer != priv->rxfifo && (((uintptr_t)buffer & (ARMV7M_DCACHE_LINESIZE - 1)) != 0 || ((uintptr_t)(buffer + buflen) & (ARMV7M_DCACHE_LINESIZE - 1)) != 0)) { - dmainfo("stm32_dmapreflight: dcache unaligned " - "buffer:0x%08x end:0x%08x\n", - buffer, buffer + buflen - 1); + mcerr("dcache unaligned " + "buffer:0x%08x end:0x%08x\n", + buffer, buffer + buflen - 1); return -EFAULT; } # endif @@ -2949,31 +3125,38 @@ static int stm32_dmapreflight(FAR struct sdio_dev_s *dev, ****************************************************************************/ #if defined(CONFIG_STM32H7_SDMMC_IDMA) -static int stm32_dmarecvsetup(FAR struct sdio_dev_s *dev, FAR uint8_t *buffer, - size_t buflen) +static int stm32_dmarecvsetup(FAR struct sdio_dev_s *dev, + FAR uint8_t *buffer, size_t buflen) { struct stm32_dev_s *priv = (struct stm32_dev_s *)dev; - uint32_t dblksize; DEBUGASSERT(priv != NULL && buffer != NULL && buflen > 0); -#if defined(CONFIG_ARCH_HAVE_SDIO_PREFLIGHT) +# if defined(CONFIG_ARCH_HAVE_SDIO_PREFLIGHT) DEBUGASSERT(stm32_dmapreflight(dev, buffer, buflen) == 0); -#else -# if defined(CONFIG_ARMV7M_DCACHE) && !defined(CONFIG_ARMV7M_DCACHE_WRITETHROUGH) - /* buffer alignment is required for DMA transfers with dcache in buffered - * mode (not write-through) because the up_invalidate_dcache could lose - * buffered buffered writes if the buffer alignment and sizes are not on - * ARMV7M_DCACHE_LINESIZE boundaries. - */ - +# else +# if defined(CONFIG_ARMV7M_DCACHE) && \ + !defined(CONFIG_ARCH_HAVE_SDIO_DELAYED_INVLDT) if (((uintptr_t)buffer & (ARMV7M_DCACHE_LINESIZE - 1)) != 0 || - (buflen & (ARMV7M_DCACHE_LINESIZE - 1)) != 0) + (buflen & (ARMV7M_DCACHE_LINESIZE - 1)) != 0) { - return -EFAULT; + /* The read buffer is not cache-line aligned. Read to an internal + * buffer instead. + */ + + up_invalidate_dcache((uintptr_t)sdmmc_rxbuffer, + (uintptr_t)sdmmc_rxbuffer + priv->blocksize); + + priv->unaligned_rx = true; } -# endif + else + { + up_invalidate_dcache((uintptr_t)buffer, + (uintptr_t)buffer + buflen); -#endif + priv->unaligned_rx = false; + } +# endif +# endif /* Reset the DPSM configuration */ @@ -2984,7 +3167,9 @@ static int stm32_dmarecvsetup(FAR struct sdio_dev_s *dev, FAR uint8_t *buffer, stm32_sampleinit(); stm32_sample(priv, SAMPLENDX_BEFORE_SETUP); - /* Save the destination buffer information for use by the interrupt handler */ + /* Save the destination buffer information for use by the interrupt + * handler. + */ priv->buffer = (uint32_t *)buffer; priv->remaining = buflen; @@ -2992,27 +3177,27 @@ static int stm32_dmarecvsetup(FAR struct sdio_dev_s *dev, FAR uint8_t *buffer, /* Then set up the SDIO data path */ - dblksize = stm32_log2(priv->blocksize) << - STM32_SDMMC_DCTRL_DBLOCKSIZE_SHIFT; stm32_dataconfig(priv, SDMMC_DTIMER_DATATIMEOUT * ((buflen + 511) >> 9), - buflen, dblksize | STM32_SDMMC_DCTRL_DTDIR); + buflen, true); /* Configure the RX DMA */ - sdmmc_putreg32(priv, (uintptr_t)priv->buffer, - STM32_SDMMC_IDMABASE0R_OFFSET); - - sdmmc_putreg32(priv, STM32_SDMMC_IDMACTRLR_IDMAEN, - STM32_SDMMC_IDMACTRLR_OFFSET); - - /* Force RAM reread */ - -# if !defined(CONFIG_ARCH_HAVE_SDIO_DELAYED_INVLDT) - if ((uintptr_t)buffer < DTCM_START || (uintptr_t)buffer + buflen > DTCM_END) +# if defined(CONFIG_ARMV7M_DCACHE) && \ + !defined(CONFIG_ARCH_HAVE_SDIO_DELAYED_INVLDT) + if (priv->unaligned_rx) { - up_invalidate_dcache((uintptr_t)buffer, (uintptr_t)buffer + buflen); + sdmmc_putreg32(priv, (uintptr_t)sdmmc_rxbuffer, + STM32_SDMMC_IDMABASE0R_OFFSET); } + else # endif + { + sdmmc_putreg32(priv, (uintptr_t)priv->buffer, + STM32_SDMMC_IDMABASE0R_OFFSET); + } + + sdmmc_putreg32(priv, STM32_SDMMC_IDMACTRLR_IDMAEN, + STM32_SDMMC_IDMACTRLR_OFFSET); /* And enable interrupts */ @@ -3047,27 +3232,16 @@ static int stm32_dmasendsetup(FAR struct sdio_dev_s *dev, FAR const uint8_t *buffer, size_t buflen) { struct stm32_dev_s *priv = (struct stm32_dev_s *)dev; - uint32_t dblksize; DEBUGASSERT(priv != NULL && buffer != NULL && buflen > 0); -#if defined(CONFIG_ARCH_HAVE_SDIO_PREFLIGHT) +# if defined(CONFIG_ARCH_HAVE_SDIO_PREFLIGHT) DEBUGASSERT(stm32_dmapreflight(dev, buffer, buflen) == 0); -#else -# if defined(CONFIG_ARMV7M_DCACHE) && \ - !defined(CONFIG_ARMV7M_DCACHE_WRITETHROUGH) - /* buffer alignment is required for DMA transfers with dcache in buffered - * mode (not write-through) because the up_flush_dcache would corrupt - * adjacent memory if the buffer alignment and sizes are not on - * ARMV7M_DCACHE_LINESIZE boundaries. - */ +# endif - if (((uintptr_t)buffer & (ARMV7M_DCACHE_LINESIZE - 1)) != 0 || - (buflen & (ARMV7M_DCACHE_LINESIZE - 1)) != 0) - { - return -EFAULT; - } +# if defined(CONFIG_ARMV7M_DCACHE) && \ + !defined(CONFIG_ARCH_HAVE_SDIO_DELAYED_INVLDT) + priv->unaligned_rx = false; # endif -#endif /* Reset the DPSM configuration */ @@ -3082,11 +3256,12 @@ static int stm32_dmasendsetup(FAR struct sdio_dev_s *dev, # if defined(CONFIG_ARMV7M_DCACHE) && \ !defined(CONFIG_ARMV7M_DCACHE_WRITETHROUGH) - if ((uintptr_t)buffer < DTCM_START || (uintptr_t)buffer + buflen > DTCM_END) + if ((uintptr_t)buffer < DTCM_START || + (uintptr_t)buffer + buflen > DTCM_END) { up_clean_dcache((uintptr_t)buffer, (uintptr_t)buffer + buflen); } -#endif +# endif /* Save the source buffer information for use by the interrupt handler */ @@ -3096,10 +3271,8 @@ static int stm32_dmasendsetup(FAR struct sdio_dev_s *dev, /* Then set up the SDIO data path */ - dblksize = stm32_log2(priv->blocksize) << - STM32_SDMMC_DCTRL_DBLOCKSIZE_SHIFT; stm32_dataconfig(priv, SDMMC_DTIMER_DATATIMEOUT * ((buflen + 511) >> 9), - buflen, dblksize); + buflen, false); /* Configure the TX DMA */ @@ -3141,7 +3314,7 @@ static int stm32_dmasendsetup(FAR struct sdio_dev_s *dev, static int stm32_dmadelydinvldt(FAR struct sdio_dev_s *dev, FAR const uint8_t *buffer, size_t buflen) { - /* Invaliate cache to physical memory when not in DTCM memory. */ + /* Invalidate cache to physical memory when not in DTCM memory. */ if ((uintptr_t)buffer < DTCM_START || (uintptr_t)buffer + buflen > DTCM_END) @@ -3154,10 +3327,6 @@ static int stm32_dmadelydinvldt(FAR struct sdio_dev_s *dev, } #endif -/**************************************************************************** - * Initialization/uninitialization/reset - ****************************************************************************/ - /**************************************************************************** * Name: stm32_callback * @@ -3214,18 +3383,20 @@ static void stm32_callback(void *arg) priv->cbevents = 0; - /* Callbacks cannot be performed in the context of an interrupt handler. - * If we are in an interrupt handler, then queue the callback to be - * performed later on the work thread. + /* Callbacks cannot be performed in the context of an interrupt + * handler. If we are in an interrupt handler, then queue the + * callback to be performed later on the work thread. */ if (up_interrupt_context()) { /* Yes.. queue it */ - mcinfo("Queuing callback to %p(%p)\n", priv->callback, priv->cbarg); - (void)work_queue(HPWORK, &priv->cbwork, (worker_t)priv->callback, - priv->cbarg, 0); + mcinfo("Queuing callback to %p(%p)\n", + priv->callback, priv->cbarg); + + work_queue(HPWORK, &priv->cbwork, (worker_t)priv->callback, + priv->cbarg, 0); } else {