From c392115fbd6d0767d32784c506229f2854d1a760 Mon Sep 17 00:00:00 2001 From: Gregory Nutt Date: Sun, 26 Oct 2014 11:22:16 -0600 Subject: [PATCH] EFM32: Integrate SPI DMA capability --- arch/arm/src/efm32/Kconfig | 34 ++ arch/arm/src/efm32/chip/efm32_usart.h | 2 +- arch/arm/src/efm32/efm32_dma.c | 38 +- arch/arm/src/efm32/efm32_dma.h | 32 +- arch/arm/src/efm32/efm32_spi.c | 551 ++++++++++++++++++++++---- 5 files changed, 553 insertions(+), 104 deletions(-) diff --git a/arch/arm/src/efm32/Kconfig b/arch/arm/src/efm32/Kconfig index 114ac5b198..4287b81253 100644 --- a/arch/arm/src/efm32/Kconfig +++ b/arch/arm/src/efm32/Kconfig @@ -340,4 +340,38 @@ config LEUART1_2STOP endmenu # LEUART1 Configuration +if EFM32_USART_ISSPI +menu "SPI Configuration" + +config EFM32_SPI_DMA + bool "SPI DMA support" + default n + depends on EFM32_DMA + ---help--- + Select to enable DMA SPI transfers + +if EFM32_SPI_DMA + +config EFM32_SPI_DMA_TIMEO_NSEC + int "Per word timer (nsec)" + default 500 + ---help--- + A timeout will be be used to detect hung DMA transfers. The timeout + will vary as a function of the number of words transferred. This + value provides the per-word timeout value in nanoseconds. + +config EFM32_SPI_DMA_MINSIZE + int "Minimum DMA size" + default 16 + ---help--- + DMA is particularly helpful for the case of large SPI transfers. + Smaller SPI transfer may be more efficiently performed without DMA. + This option determines a threshold: For transfers of this size and + below, DMA will not be used. A value of zero will force all DMA- + based transfers. + +endif # EFM32_SPI_DMA +endmenu # SPI Configuration +endif # EFM32_USART_ISSPI + endif # ARCH_CHIP_EFM32 diff --git a/arch/arm/src/efm32/chip/efm32_usart.h b/arch/arm/src/efm32/chip/efm32_usart.h index cca38a7f66..e2d8fa8f96 100644 --- a/arch/arm/src/efm32/chip/efm32_usart.h +++ b/arch/arm/src/efm32/chip/efm32_usart.h @@ -1,6 +1,6 @@ /******************************************************************************************************************************** * arch/arm/src/efm32/EFM32GG/efm32_usart.h - * + * * (C) Copyright 2014 Silicon Labs, http://www.silabs.com * * Permission is granted to anyone to use this software for any purpose, diff --git a/arch/arm/src/efm32/efm32_dma.c b/arch/arm/src/efm32/efm32_dma.c index 31fd11c8db..1c3f50bf21 100644 --- a/arch/arm/src/efm32/efm32_dma.c +++ b/arch/arm/src/efm32/efm32_dma.c @@ -457,6 +457,7 @@ void efm32_rxdmasetup(DMA_HANDLE handle, uintptr_t paddr, uintptr_t maddr, unsigned int xfersize; unsigned int shift; uint32_t regval; + uint32_t incr; uint32_t mask; DEBUGASSERT(dmach != NULL && dmach->inuse); @@ -500,18 +501,28 @@ void efm32_rxdmasetup(DMA_HANDLE handle, uintptr_t paddr, uintptr_t maddr, { default: case 0: /* Byte transfer */ - regval = DMA_CTRL_DST_INC_BYTE | DMA_CTRL_DST_SIZE_BYTE | DMA_CTRL_SRC_SIZE_BYTE; + regval |= DMA_CTRL_DST_SIZE_BYTE | DMA_CTRL_SRC_SIZE_BYTE; + incr = DMA_CTRL_DST_INC_BYTE; break; case 1: /* Half word transfer */ - regval = DMA_CTRL_DST_INC_HALFWORD | DMA_CTRL_DST_SIZE_HALFWORD | DMA_CTRL_SRC_SIZE_HALFWORD; + regval |= DMA_CTRL_DST_SIZE_HALFWORD | DMA_CTRL_SRC_SIZE_HALFWORD; + incr = DMA_CTRL_DST_INC_HALFWORD; break; case 2: /* Word transfer */ - regval = DMA_CTRL_DST_INC_WORD | DMA_CTRL_DST_SIZE_WORD | DMA_CTRL_SRC_SIZE_WORD; + regval |= DMA_CTRL_DST_SIZE_WORD | DMA_CTRL_SRC_SIZE_WORD; + incr = DMA_CTRL_DST_INC_WORD; break; } + /* Do we need to increment the memory address? */ + + if ((config & EFM32_DMA_MEMINCR_MASK) == EFM32_DMA_MEMINCR) + { + regval |= incr; + } + /* Set the number of transfers (minus 1) */ DEBUGASSERT((nbytes >> shift) < 1024); @@ -543,6 +554,7 @@ void efm32_txdmasetup(DMA_HANDLE handle, uintptr_t paddr, uintptr_t maddr, unsigned int xfersize; unsigned int shift; uint32_t regval; + uint32_t incr; uint32_t mask; DEBUGASSERT(dmach != NULL && dmach->inuse); @@ -579,25 +591,35 @@ void efm32_txdmasetup(DMA_HANDLE handle, uintptr_t paddr, uintptr_t maddr, */ regval = DMA_CTRL_DST_INC_NONE | DMA_CTRL_DST_PROT_NON_PRIVILEGED | - DMA_CTRL_SRC_PROT_NON_PRIVILEGED | DMA_CTRL_R_POWER_1 | - (0 << _DMA_CTRL_NEXT_USEBURST_SHIFT) | _DMA_CTRL_CYCLE_CTRL_BASIC; + DMA_CTRL_SRC_PROT_NON_PRIVILEGED | DMA_CTRL_R_POWER_1 | + (0 << _DMA_CTRL_NEXT_USEBURST_SHIFT) | _DMA_CTRL_CYCLE_CTRL_BASIC; switch (shift) { default: case 0: /* Byte transfer */ - regval = DMA_CTRL_DST_SIZE_BYTE | DMA_CTRL_SRC_INC_BYTE | DMA_CTRL_SRC_SIZE_BYTE; + regval |= DMA_CTRL_DST_SIZE_BYTE | DMA_CTRL_SRC_SIZE_BYTE; + incr = DMA_CTRL_SRC_INC_BYTE; break; case 1: /* Half word transfer */ - regval = DMA_CTRL_DST_SIZE_HALFWORD | DMA_CTRL_SRC_INC_HALFWORD | DMA_CTRL_SRC_SIZE_HALFWORD; + regval |= DMA_CTRL_DST_SIZE_HALFWORD | DMA_CTRL_SRC_SIZE_HALFWORD; + incr = DMA_CTRL_SRC_INC_HALFWORD; break; case 2: /* Word transfer */ - regval = DMA_CTRL_DST_SIZE_WORD | DMA_CTRL_SRC_INC_WORD | DMA_CTRL_SRC_SIZE_WORD; + regval |= DMA_CTRL_DST_SIZE_WORD | DMA_CTRL_SRC_SIZE_WORD; + incr = DMA_CTRL_SRC_INC_WORD; break; } + /* Do we need to increment the memory address? */ + + if ((config & EFM32_DMA_MEMINCR_MASK) == EFM32_DMA_MEMINCR) + { + regval |= incr; + } + /* Set the number of transfers (minus 1) */ DEBUGASSERT((nbytes >> shift) < 1024); diff --git a/arch/arm/src/efm32/efm32_dma.h b/arch/arm/src/efm32/efm32_dma.h index ee3fb02aa5..2cedb881f3 100644 --- a/arch/arm/src/efm32/efm32_dma.h +++ b/arch/arm/src/efm32/efm32_dma.h @@ -63,23 +63,27 @@ * - Memory address is always incremented. */ -#define EFM32_DMA_SIGSEL_SHIFT (0) /* Bits 0-3: _DMA_CH_CTRL_ * value */ -#define EFM32_DMA_SIGSEL_MASK (15 << EFM32_DMA_SIGSEL_SHIFT) -# define EFM32_DMA_SIGSEL(n) ((dma_config_t)(n) << EFM32_DMA_SIGSEL_SHIFT) +#define EFM32_DMA_SIGSEL_SHIFT (0) /* Bits 0-3: _DMA_CH_CTRL_ * value */ +#define EFM32_DMA_SIGSEL_MASK (15 << EFM32_DMA_SIGSEL_SHIFT) +# define EFM32_DMA_SIGSEL(n) ((dma_config_t)(n) << EFM32_DMA_SIGSEL_SHIFT) -#define EFM32_DMA_SOURCSEL_SHIFT (4) /* Bits 4-9: _DMA_CH_SOURCESEL_* value */ -#define EFM32_DMA_SOURCSEL_MASK (63 << EFM32_DMA_SOURCSEL_SHIFT) -# define EFM32_DMA_SOURCSEL(n) (dma_config_t)(n) << EFM32_DMA_SOURCSEL_SHIFT) +#define EFM32_DMA_SOURCSEL_SHIFT (4) /* Bits 4-9: _DMA_CH_SOURCESEL_* value */ +#define EFM32_DMA_SOURCSEL_MASK (63 << EFM32_DMA_SOURCSEL_SHIFT) +# define EFM32_DMA_SOURCSEL(n) ((dma_config_t)(n) << EFM32_DMA_SOURCSEL_SHIFT) -#define EFM32_DMA_XFERSIZE_SHIFT (10) /* Bits 10-11: Transfer size */ -#define EFM32_DMA_XFERSIZE_MASK (3 << EFM32_DMA_XFERSIZE_SHIFT) -# define EFM32_DMA_XFERSIZE_SHIFT_BYTE (0 << EFM32_DMA_SOURCSEL_SHIFT) -# define EFM32_DMA_XFERSIZE_SHIFT_HWORD (1 << EFM32_DMA_SOURCSEL_SHIFT) -# define EFM32_DMA_XFERSIZE_SHIFT_WORD (2 << EFM32_DMA_SOURCSEL_SHIFT) +#define EFM32_DMA_XFERSIZE_SHIFT (10) /* Bits 10-11: Transfer size */ +#define EFM32_DMA_XFERSIZE_MASK (3 << EFM32_DMA_XFERSIZE_SHIFT) +# define EFM32_DMA_XFERSIZE_BYTE (0 << EFM32_DMA_SOURCSEL_SHIFT) +# define EFM32_DMA_XFERSIZE_HWORD (1 << EFM32_DMA_SOURCSEL_SHIFT) +# define EFM32_DMA_XFERSIZE_WORD (2 << EFM32_DMA_SOURCSEL_SHIFT) -#define EFM32_DMA_SINGLE_MASK (1 << 12) /* Bit 12: Single or Buffer full request */ -# define EFM32_DMA_SINGLE (1 << 12) /* 1=Buffer full request */ -# define EFM32_DMA_BUFFER_FULL (0) /* 0=Buffer full request */ +#define EFM32_DMA_SINGLE_MASK (1 << 12) /* Bit 12: Single or Buffer full request */ +# define EFM32_DMA_SINGLE (1 << 12) /* 1=Buffer full request */ +# define EFM32_DMA_BUFFER_FULL (0) /* 0=Buffer full request */ + +#define EFM32_DMA_MEMINCR_MASK (1 << 13) /* Bit 13: Increment memory address */ +# define EFM32_DMA_MEMINCR (1 << 13) /* 1=Increment memory address */ +# define EFM32_DMA_NOINCR (0) /* 0=No memory address increment */ /************************************************************************************ * Public Types diff --git a/arch/arm/src/efm32/efm32_spi.c b/arch/arm/src/efm32/efm32_spi.c index 20e2634e95..20b5b7f5e0 100644 --- a/arch/arm/src/efm32/efm32_spi.c +++ b/arch/arm/src/efm32/efm32_spi.c @@ -46,9 +46,12 @@ #include #include #include +#include #include #include +#include +#include #include #include @@ -70,20 +73,31 @@ /* Configuration ********************************************************************/ /* SPI interrupts */ -#ifdef CONFIG_EFM32_SPI_DMA -# error DMA driven SPI not yet supported -#endif - #ifdef CONFIG_EFM32_SPI_INTERRUPTS # error Interrupt driven SPI not yet supported #endif +#ifndef CONFIG_EFM32_SPI_DMA_TIMEO_NSEC +# define CONFIG_EFM32_SPI_DMA_TIMEO_NSEC 500 +#endif + +#ifndef CONFIG_EFM32_SPI_DMA_MINSIZE +# define CONFIG_EFM32_SPI_DMA_MINSIZE 16 +#endif + /* Can't have both interrupt driven SPI and SPI DMA */ #if defined(CONFIG_EFM32_SPI_INTERRUPTS) && defined(CONFIG_EFM32_SPI_DMA) # error Cannot enable both interrupt mode and DMA mode for SPI #endif +/* DMA definitions ******************************************************************/ + +#define SPI_DMA8_CONFIG (EFM32_DMA_XFERSIZE_BYTE| EFM32_DMA_MEMINCR) +#define SPI_DMA8NULL_CONFIG (EFM32_DMA_XFERSIZE_BYTE | EFM32_DMA_NOINCR) +#define SPI_DMA16_CONFIG (EFM32_DMA_XFERSIZE_HWORD | EFM32_DMA_MEMINCR) +#define SPI_DMA16NULL_CONFIG (EFM32_DMA_XFERSIZE_HWORD | EFM32_DMA_NOINCR) + /* Debug ****************************************************************************/ /* Check if SPI debug is enabled */ @@ -118,11 +132,11 @@ struct efm32_spiconfig_s dma_config_t rxconfig; /* RX DMA configuration (excluding transfer width) */ dma_config_t txconfig; /* TX DMA configuration (excluding transfer width) */ #endif - void (*select)(FAR struct spi_dev_s *dev, enum spi_dev_e devid, + void (*select)(struct spi_dev_s *dev, enum spi_dev_e devid, bool selected); - uint8_t (*status)(FAR struct spi_dev_s *dev, enum spi_dev_e devid); + uint8_t (*status)(struct spi_dev_s *dev, enum spi_dev_e devid); #ifdef CONFIG_SPI_CMDDATA - int (*cmddata)(FAR struct spi_dev_s *dev, enum spi_dev_e devid, bool cmd); + int (*cmddata)(struct spi_dev_s *dev, enum spi_dev_e devid, bool cmd); #endif }; @@ -134,8 +148,9 @@ struct efm32_spidev_s const struct efm32_spiconfig_s *config; /* Constant SPI hardware configuration */ #ifdef CONFIG_EFM32_SPI_DMA - volatile int8_t rxresult; /* Result of the RX DMA */ - volatile int8_t txresult; /* Result of the TX DMA */ + WDOG_ID wdog; /* Timer to catch hung DMA */ + volatile uint8_t rxresult; /* Result of the RX DMA */ + volatile uint8_t txresult; /* Result of the TX DMA */ DMA_HANDLE rxdmach; /* RX DMA channel handle */ DMA_HANDLE txdmach; /* TX DMA channel handle */ sem_t rxdmasem; /* Wait for RX DMA to complete */ @@ -164,6 +179,25 @@ static void spi_putreg(const struct efm32_spiconfig_s *config, unsigned int regoffset, uint32_t regval); static bool spi_16bitmode(struct efm32_spidev_s *priv); +/* DMA support */ + +#ifdef CONFIG_EFM32_SPI_DMA +static void spi_dma_timeout(int argc, uint32_t arg1, ...); +static void spi_dmarxwait(struct efm32_spidev_s *priv); +static void spi_dmatxwait(struct efm32_spidev_s *priv); +static inline void spi_dmarxwakeup(struct efm32_spidev_s *priv); +static inline void spi_dmatxwakeup(struct efm32_spidev_s *priv); +static void spi_dmarxcallback(DMA_HANDLE handle, uint8_t status, void *arg); +static void spi_dmatxcallback(DMA_HANDLE handle, uint8_t status, void *arg); +static void spi_dmarxsetup(struct efm32_spidev_s *priv, + void *rxbuffer, void *rxdummy, size_t nwords); +static void spi_dmatxsetup(struct efm32_spidev_s *priv, + const void *txbuffer, const void *txdummy, + size_t nwords); +static inline void spi_dmarxstart(FAR struct efm32_spidev_s *priv); +static inline void spi_dmatxstart(FAR struct efm32_spidev_s *priv); +#endif + /* SPI methods */ #ifndef CONFIG_SPI_OWNBUS @@ -174,9 +208,9 @@ static void spi_select(struct spi_dev_s *dev, enum spi_dev_e devid, static uint32_t spi_setfrequency(struct spi_dev_s *dev, uint32_t frequency); static void spi_setmode(struct spi_dev_s *dev, enum spi_mode_e mode); static void spi_setbits(struct spi_dev_s *dev, int nbits); -static uint8_t spi_status(FAR struct spi_dev_s *dev, enum spi_dev_e devid); +static uint8_t spi_status(struct spi_dev_s *dev, enum spi_dev_e devid); #ifdef CONFIG_SPI_CMDDATA -static int spi_cmddata(FAR struct spi_dev_s *dev, enum spi_dev_e devid, +static int spi_cmddata(struct spi_dev_s *dev, enum spi_dev_e devid, bool cmd); #endif static uint16_t spi_send(struct spi_dev_s *dev, uint16_t wd); @@ -191,7 +225,7 @@ static void spi_recvblock(struct spi_dev_s *dev, void *rxbuffer, /* Initialization */ -static void spi_portinitialize(struct efm32_spidev_s *priv); +static int spi_portinitialize(struct efm32_spidev_s *priv); /**************************************************************************** * Private Data @@ -357,7 +391,6 @@ static void spi_putreg(const struct efm32_spiconfig_s *config, * True: 16-bit; false: 8-bit * ****************************************************************************/ -/* helper SPI function */ static bool spi_16bitmode(struct efm32_spidev_s *priv) { @@ -373,16 +406,340 @@ static bool spi_16bitmode(struct efm32_spidev_s *priv) #endif } +/**************************************************************************** + * Name: spi_dma_timeout + * + * Description: + * Invoked when a DMA timeout occurs + * + ****************************************************************************/ + +#ifdef CONFIG_EFM32_SPI_DMA +static void spi_dma_timeout(int argc, uint32_t arg1, ...) +{ + struct efm32_spidev_s *priv = (struct efm32_spidev_s *)((uintptr_t)arg1); + + /* Mark DMA timeout error and wakeup form RX and TX waiters */ + + DEBUGASSERT(priv->rxresult == EINPROGRESS || priv->txresult == EINPROGRESS); + if (priv->rxresult == EINPROGRESS) + { + priv->rxresult = ETIMEDOUT; + spi_dmarxwakeup(priv); + } + + if (priv->txresult == EINPROGRESS) + { + priv->txresult = ETIMEDOUT; + spi_dmatxwakeup(priv); + } +} +#endif + +/**************************************************************************** + * Name: spi_dmarxwait + * + * Description: + * Wait for RX DMA to complete. + * + ****************************************************************************/ + +#ifdef CONFIG_EFM32_SPI_DMA +static void spi_dmarxwait(struct efm32_spidev_s *priv) +{ + irqstate_t flags; + + /* Take the semaphore (perhaps waiting). */ + + flags = irqsave(); + while (sem_wait(&priv->rxdmasem) != 0) + { + /* The only case that an error should occur here is if the wait was awakened + * by a signal. + */ + + DEBUGASSERT(errno == EINTR); + } + + /* Cancel the timeout only if both the RX and TX transfers have completed */ + + DEBUGASSERT(priv->rxresult != EINPROGRESS); + if (priv->txresult != EINPROGRESS) + { + wd_cancel(priv->wdog); + } + + irqrestore(flags); +} +#endif + +/**************************************************************************** + * Name: spi_dmatxwait + * + * Description: + * Wait for DMA to complete. + * + ****************************************************************************/ + +#ifdef CONFIG_EFM32_SPI_DMA +static void spi_dmatxwait(struct efm32_spidev_s *priv) +{ + irqstate_t flags; + + /* Take the semaphore (perhaps waiting). */ + + flags = irqsave(); + while (sem_wait(&priv->txdmasem) != 0) + { + /* The only case that an error should occur here is if the wait was awakened + * by a signal. + */ + + DEBUGASSERT(errno == EINTR); + } + + /* Cancel the timeout only if both the RX and TX transfers have completed */ + + DEBUGASSERT(priv->txresult != EINPROGRESS); + if (priv->rxresult != EINPROGRESS) + { + wd_cancel(priv->wdog); + } + + irqrestore(flags); +} +#endif + +/**************************************************************************** + * Name: spi_dmarxwakeup + * + * Description: + * Signal that DMA is complete + * + ****************************************************************************/ + +#ifdef CONFIG_EFM32_SPI_DMA +static inline void spi_dmarxwakeup(struct efm32_spidev_s *priv) +{ + (void)sem_post(&priv->rxdmasem); +} +#endif + +/**************************************************************************** + * Name: spi_dmatxwakeup + * + * Description: + * Signal that DMA is complete + * + ****************************************************************************/ + +#ifdef CONFIG_EFM32_SPI_DMA +static inline void spi_dmatxwakeup(struct efm32_spidev_s *priv) +{ + (void)sem_post(&priv->txdmasem); +} +#endif + +/**************************************************************************** + * Name: spi_dmarxcallback + * + * Description: + * Called when the RX DMA completes + * + ****************************************************************************/ + +#ifdef CONFIG_EFM32_SPI_DMA +static void spi_dmarxcallback(DMA_HANDLE handle, uint8_t status, void *arg) +{ + struct efm32_spidev_s *priv = (struct efm32_spidev_s *)arg; + DEBUGASSERT(priv && status != EINPROGRESS); + + /* Wake-up the SPI driver */ + + priv->rxresult = status; + spi_dmarxwakeup(priv); +} +#endif + +/**************************************************************************** + * Name: spi_dmatxcallback + * + * Description: + * Called when the RX DMA completes + * + ****************************************************************************/ + +#ifdef CONFIG_EFM32_SPI_DMA +static void spi_dmatxcallback(DMA_HANDLE handle, uint8_t status, void *arg) +{ + struct efm32_spidev_s *priv = (struct efm32_spidev_s *)arg; + DEBUGASSERT(priv && status != EINPROGRESS); + + /* Wake-up the SPI driver */ + + priv->txresult = status; + spi_dmatxwakeup(priv); +} +#endif + +/**************************************************************************** + * Name: spi_dmarxsetup + * + * Description: + * Setup to perform RX DMA + * + ****************************************************************************/ + +#ifdef CONFIG_EFM32_SPI_DMA +static void spi_dmarxsetup(struct efm32_spidev_s *priv, void *rxbuffer, + void *rxdummy, size_t nwords) +{ + const struct efm32_spiconfig_s *config = priv->config; + dma_config_t dmaconfig = config->rxconfig; + size_t nbytes; + + /* 8- or 16-bit mode? */ + + if (spi_16bitmode(priv)) + { + /* 16-bit mode -- is there a buffer to receive data in? */ + + if (rxbuffer) + { + dmaconfig |= SPI_DMA16_CONFIG; + } + else + { + rxbuffer = rxdummy; + dmaconfig |= SPI_DMA16NULL_CONFIG; + } + + nbytes = nwords << 1; + } + else + { + /* 8-bit mode -- is there a buffer to receive data in? */ + + if (rxbuffer) + { + dmaconfig |= SPI_DMA8_CONFIG; + } + else + { + rxbuffer = rxdummy; + dmaconfig |= SPI_DMA8NULL_CONFIG; + } + + nbytes = nwords; + } + + /* Configure the RX DMA */ + + efm32_rxdmasetup(priv->rxdmach, config->base + EFM32_USART_RXDATA_OFFSET, + (uintptr_t)rxbuffer, nbytes, dmaconfig); +} +#endif + +/**************************************************************************** + * Name: spi_dmatxsetup + * + * Description: + * Setup to perform TX DMA + * + ****************************************************************************/ + +#ifdef CONFIG_EFM32_SPI_DMA +static void spi_dmatxsetup(struct efm32_spidev_s *priv, const void *txbuffer, + const void *txdummy, size_t nwords) +{ + const struct efm32_spiconfig_s *config = priv->config; + dma_config_t dmaconfig = config->txconfig; + size_t nbytes; + + /* 8- or 16-bit mode? */ + + if (spi_16bitmode(priv)) + { + /* 16-bit mode -- is there a buffer to receive data in? */ + + if (txbuffer) + { + dmaconfig |= SPI_DMA16_CONFIG; + } + else + { + txbuffer = txdummy; + dmaconfig |= SPI_DMA16NULL_CONFIG; + } + + nbytes = nwords << 1; + } + else + { + /* 8-bit mode -- is there a buffer to receive data in? */ + + if (txbuffer) + { + dmaconfig |= SPI_DMA8_CONFIG; + } + else + { + txbuffer = txdummy; + dmaconfig |= SPI_DMA8NULL_CONFIG; + } + + nbytes = nwords; + } + + /* Configure the RX DMA */ + + efm32_txdmasetup(priv->txdmach, config->base + EFM32_USART_TXDATA_OFFSET, + (uintptr_t)txbuffer, nbytes, dmaconfig); +} +#endif + +/**************************************************************************** + * Name: spi_dmarxstart + * + * Description: + * Start RX DMA + * + ****************************************************************************/ + +#ifdef CONFIG_EFM32_SPI_DMA +static void spi_dmarxstart(FAR struct efm32_spidev_s *priv) +{ + priv->rxresult = EINPROGRESS; + efm32_dmastart(priv->rxdmach, spi_dmarxcallback, priv); +} +#endif + +/**************************************************************************** + * Name: spi_dmatxstart + * + * Description: + * Start TX DMA + * + ****************************************************************************/ + +#ifdef CONFIG_EFM32_SPI_DMA +static inline void spi_dmatxstart(FAR struct efm32_spidev_s *priv) +{ + priv->txresult = EINPROGRESS; + efm32_dmastart(priv->txdmach, spi_dmatxcallback, priv); +} +#endif + /**************************************************************************** * Name: spi_lock * * Description: - * On SPI busses where there are multiple devices, it will be necessary to - * lock SPI to have exclusive access to the busses for a sequence of + * On SPI buses where there are multiple devices, it will be necessary to + * lock SPI to have exclusive access to the buses for a sequence of * transfers. The bus should be locked before the chip is selected. After * locking the SPI bus, the caller should then also call the setfrequency, * setbits, and setmode methods to make sure that the SPI is properly - * configured for the device. If the SPI buss is being shared, then it + * configured for the device. If the SPI bus is being shared, then it * may have been left in an incompatible state. * * Input Parameters: @@ -425,7 +782,7 @@ static int spi_lock(struct spi_dev_s *dev, bool lock) * Name: spi_select * * Description: - * Enable/disable the SPI chip select. + * Enable/disable the SPI chip select. * * Input Parameters: * dev - Device-specific state data @@ -743,7 +1100,7 @@ static void spi_setbits(struct spi_dev_s *dev, int nbits) * ****************************************************************************/ -static uint8_t spi_status(FAR struct spi_dev_s *dev, enum spi_dev_e devid) +static uint8_t spi_status(struct spi_dev_s *dev, enum spi_dev_e devid) { struct efm32_spidev_s *priv = (struct efm32_spidev_s *)dev; const struct efm32_spiconfig_s *config; @@ -781,7 +1138,7 @@ static uint8_t spi_status(FAR struct spi_dev_s *dev, enum spi_dev_e devid) ****************************************************************************/ #ifdef CONFIG_SPI_CMDDATA -static int spi_cmddata(FAR struct spi_dev_s *dev, enum spi_dev_e devid, +static int spi_cmddata(struct spi_dev_s *dev, enum spi_dev_e devid, bool cmd); { struct efm32_spidev_s *priv = (struct efm32_spidev_s *)dev; @@ -859,12 +1216,12 @@ static uint16_t spi_send(struct spi_dev_s *dev, uint16_t wd) * ****************************************************************************/ -#if !defined(CONFIG_EFM32_SPI_DMA) -static void spi_exchange(struct spi_dev_s *dev, const void *txbuffer, - void *rxbuffer, size_t nwords) -#else +#if defined(CONFIG_EFM32_SPI_DMA) && CONFIG_EFM32_SPI_DMA_MINSIZE > 0 static void spi_exchange_nodma(struct spi_dev_s *dev, const void *txbuffer, void *rxbuffer, size_t nwords) +#else +static void spi_exchange(struct spi_dev_s *dev, const void *txbuffer, + void *rxbuffer, size_t nwords) #endif { struct efm32_spidev_s *priv = (struct efm32_spidev_s *)dev; @@ -989,7 +1346,7 @@ static void spi_exchange_nodma(struct spi_dev_s *dev, const void *txbuffer, } } } - + DEBUGASSERT(unsent == 0); } @@ -1018,27 +1375,30 @@ static void spi_exchange(struct spi_dev_s *dev, const void *txbuffer, void *rxbuffer, size_t nwords) { struct efm32_spidev_s *priv = (struct efm32_spidev_s *)dev; - const struct efm32_spiconfig_s *config; + static uint16_t rxdummy = 0xffff; + static const uint16_t txdummy = 0xffff; + irqstate_t flags; + uint64_t ticks; + int ret; DEBUGASSERT(priv && priv->config); - config = priv->config; -#ifdef CONFIG_STM32_DMACAPABLE - if ((txbuffer && !stm32_dmacapable((uint32_t)txbuffer, nwords, priv->txccr)) || - (rxbuffer && !stm32_dmacapable((uint32_t)rxbuffer, nwords, priv->rxccr))) +#if CONFIG_EFM32_SPI_DMA_MINSIZE > 0 + if (nwords <= CONFIG_EFM32_SPI_DMA_MINSIZE) { - /* Unsupported memory region, fall back to non-DMA method. */ + /* Small transfer, fall back to non-DMA method. */ spi_exchange_nodma(dev, txbuffer, rxbuffer, nwords); } else #endif { - static uint16_t rxdummy = 0xffff; - static const uint16_t txdummy = 0xffff; - spivdbg("txbuffer=%p rxbuffer=%p nwords=%d\n", txbuffer, rxbuffer, nwords); + /* Pre-calculate the timerout value */ + + ticks = (CONFIG_EFM32_SPI_DMA_TIMEO_NSEC * nwords) / NSEC_PER_TICK; + /* Setup DMAs */ spi_dmarxsetup(priv, rxbuffer, &rxdummy, nwords); @@ -1046,13 +1406,25 @@ static void spi_exchange(struct spi_dev_s *dev, const void *txbuffer, /* Start the DMAs */ + flags = irqsave(); spi_dmarxstart(priv); spi_dmatxstart(priv); - /* Then wait for each to complete */ + /* Start a timer to catch hung DMA transfers. Timeout will be canceled + * when both RX and TX transfers complete. + */ + + ret = wd_start(priv->wdog, (int)ticks, spi_dma_timeout, 1, (uint32_t)priv); + if (ret < 0) + { + spidbg("ERROR: Failed to start timeout\n"); + } + + /* Then wait for each to complete. TX should complete first */ - spi_dmarxwait(priv); spi_dmatxwait(priv); + spi_dmarxwait(priv); + irqrestore(flags); } } #endif /* CONFIG_EFM32_SPI_DMA */ @@ -1115,7 +1487,7 @@ static void spi_recvblock(struct spi_dev_s *dev, void *rxbuffer, size_t nwords) * Name: spi_portinitialize * * Description: - * Initialize the selected SPI port in its default state + * Initialize the selected SPI port in its default state * (Master, 8-bit, mode 0, etc.) * * Input Parameter: @@ -1126,7 +1498,7 @@ static void spi_recvblock(struct spi_dev_s *dev, void *rxbuffer, size_t nwords) * ****************************************************************************/ -static void spi_portinitialize(struct efm32_spidev_s *priv) +static int spi_portinitialize(struct efm32_spidev_s *priv) { const struct efm32_spiconfig_s *config = priv->config; uint32_t regval; @@ -1135,11 +1507,12 @@ static void spi_portinitialize(struct efm32_spidev_s *priv) efm32_uart_reset(config->base); - /* Configure CR1. Default configuration: - * Mode 0: USART_CTRL_CLKPOL_IDLELOW - * USART_CTRL_CLKPHA_SAMPLELEADING - * 8-bit: USART_FRAME_DATABITS_EIGHT - * MSB tranmitted first: ~USART_CTRL_MSBF + /* NOTES: + * + * 1. USART GPIO pins were configured in efm32_lowsetup(). Chip select + * pins must be configured by board specific logic before + * efm32_spi_initialize() is called. + * 2. Clocking for the USART as also enabled in up_lowsetup(); */ /* Set bits for synchronous mode */ @@ -1179,15 +1552,57 @@ static void spi_portinitialize(struct efm32_spidev_s *priv) sem_init(&priv->exclsem, 0, 1); #endif - /* Initialize the SPI semaphores that is used to wait for DMA completion */ - #ifdef CONFIG_EFM32_SPI_DMA - spi_portinitialize_dma(priv); + /* Allocate two DMA channels... one for the RX and one for the TX side of + * the transfer. + */ + + priv->rxdmach = efm32_dmachannel(); + if (!priv->rxdmach) + { + spidbg("ERROR: Failed to allocate the RX DMA channel for SPI port: %d\n", port); + goto errout; + } + + priv->txdmach = efm32_dmachannel(); + if (!priv->txdmach) + { + spidbg("ERROR: Failed to allocate the TX DMA channel for SPI port: %d\n", port); + goto errout_with_rxdmach; + } + + /* Allocate a timer to catch hung DMA transfers */ + + priv->wdog = wd_create(); + if (!priv->wdog) + { + spidbg("ERROR: Failed to create a timer for SPI port: %d\n", port); + goto errout_with_txdmach; + } + + /* Initialized semaphores used to wait for DMA completion */ + + (void)sem_init(&priv->rxdmasem, 0, 0); + (void)sem_init(&priv->txdmasem, 0, 0); #endif /* Enable SPI */ spi_putreg(config, EFM32_USART_CMD_OFFSET, USART_CMD_RXEN | USART_CMD_TXEN); + return OK; + +#ifdef CONFIG_EFM32_SPI_DMA +errout_with_txdmach: + efm32_dmafree(priv->txdmach); + priv->txdmach = NULL; + +errout_with_rxdmach: + efm32_dmafree(priv->rxdmach); + priv->rxdmach = NULL; + +errout: + return -EBUSY; +#endif } /**************************************************************************** @@ -1213,6 +1628,7 @@ struct spi_dev_s *efm32_spi_initialize(int port) const struct efm32_spiconfig_s *config; struct efm32_spidev_s *priv; irqstate_t flags; + int ret; #ifdef CONFIG_EFM32_USART0_ISSPI if (port == 0) @@ -1254,44 +1670,17 @@ struct spi_dev_s *efm32_spi_initialize(int port) /* Initialize the state structure */ priv->spidev = &g_spiops; - priv->config = config; + priv->config = config; -#ifdef CONFIG_EFM32_SPI_DMA - /* Allocate two DMA channels... one for the RX and one for the TX side - * of the transfer. - */ + /* Initialize the SPI device */ - priv->rxdmach = efm3_dmachannel(); - if (!priv->rxdmach) - { - spidbg("ERROR: Failed to allocate the RX DMA channel for SPI port: %d\n", port); - return NULL; - } - - priv-txdmach = efm3_dmachannel(); - if (!priv->txdmach) - { - spidbg("ERROR: Failed to allocate the TX DMA channel for SPI port: %d\n", port); - efm32_dmafree(priv->rxdmach); - priv->rxdmach = NULL; - return NULL; - } - - /* Initialized semaphores used to wait for DMA completion */ - - sem_init(&priv->rxdmasem, 0, 0); - sem_init(&priv->txdmasem, 0, 0); -#endif - - /* NOTES: - * - * 1. USART GPIO pins were configured in efm32_lowsetup(). Chip - * select pins must be configured by board specific logic before - * efm32_spi_initialize() is called. - * 2. Clocking for the USART as also enabled in up_lowsetup(); - */ - - spi_portinitialize(priv); + ret = spi_portinitialize(priv); + if (ret < 0) + { + spidbg("ERROR: Failed to initialize SPI port %d\n", port); + irqrestore(flags); + return NULL; + } /* Now we are initialized */