imxrt:SPI add DMA support

This commit is contained in:
David Sidrane 2022-07-21 05:40:31 -07:00 committed by Xiang Xiao
parent fa58381e58
commit 55aaba53fc
2 changed files with 445 additions and 20 deletions

View File

@ -1809,6 +1809,55 @@ config IMXRT_EDMA_EDBG
endmenu # eDMA Global Configuration
menu "LPSPI Configuration"
depends on IMXRT_LPSPI
config IMXRT_LPSPI_DMA
bool "LPSPI DMA"
depends on IMXRT_EDMA
default n
---help---
Use DMA to improve LPSPI transfer performance.
config IMXRT_LPSPI_DMATHRESHOLD
int "LPSPI DMA threshold"
default 4
depends on IMXRT_LPSPI_DMA
---help---
When SPI DMA is enabled, small DMA transfers will still be performed
by polling logic. But we need a threshold value to determine what
is small.
config IMXRT_LPSPI1_DMA
bool "LPSPI1 DMA"
default n
depends on IMXRT_LPSPI1 && IMXRT_LPSPI_DMA
---help---
Use DMA to improve LPSPI1 transfer performance.
config IMXRT_LPSPI2_DMA
bool "LPSPI2 DMA"
default n
depends on IMXRT_LPSPI2 && IMXRT_LPSPI_DMA
---help---
Use DMA to improve LPSPI2 transfer performance.
config IMXRT_LPSPI3_DMA
bool "LPSPI3 DMA"
default n
depends on IMXRT_LPSPI3 && IMXRT_LPSPI_DMA
---help---
Use DMA to improve LPSPI3 transfer performance.
config IMXRT_LPSPI4_DMA
bool "LPSPI4 DMA"
default n
depends on IMXRT_LPSPI4 && IMXRT_LPSPI_DMA
---help---
Use DMA to improve SPI4 transfer performance.
endmenu # LPSPI Configuration
if PM
config IMXRT_PM_SERIAL_ACTIVITY

View File

@ -74,6 +74,9 @@
#include "hardware/imxrt_ccm.h"
#include "imxrt_periphclks.h"
#include "hardware/imxrt_dmamux.h"
#include "imxrt_edma.h"
#if defined(CONFIG_IMXRT_LPSPI1) || defined(CONFIG_IMXRT_LPSPI2) || \
defined(CONFIG_IMXRT_LPSPI3) || defined(CONFIG_IMXRT_LPSPI4)
@ -89,16 +92,15 @@
# error "Interrupt driven SPI not yet supported"
#endif
#if defined(CONFIG_IMXRT_LPSPI_DMA)
# error "DMA mode is not yet supported"
#endif
/* Can't have both interrupt driven SPI and SPI DMA */
#if defined(CONFIG_IMXRT_LPSPI_INTERRUPTS) && defined(CONFIG_IMXRT_LPSPI_DMA)
# error "Cannot enable both interrupt mode and DMA mode for SPI"
#endif
#define SPI_SR_CLEAR (LPSPI_SR_WCF | LPSPI_SR_FCF | LPSPI_SR_TCF | \
LPSPI_SR_TEF | LPSPI_SR_REF | LPSPI_SR_DMF)
/****************************************************************************
* Private Types
****************************************************************************/
@ -115,6 +117,16 @@ struct imxrt_lpspidev_s
uint32_t actual; /* Actual clock frequency */
int8_t nbits; /* Width of word in bits */
uint8_t mode; /* Mode 0,1,2,3 */
#ifdef CONFIG_IMXRT_LPSPI_DMA
volatile uint32_t rxresult; /* Result of the RX DMA */
volatile uint32_t txresult; /* Result of the TX DMA */
const uint16_t rxch; /* The RX DMA channel number */
const uint16_t txch; /* The TX DMA channel number */
DMACH_HANDLE rxdma; /* DMA channel handle for RX transfers */
DMACH_HANDLE txdma; /* DMA channel handle for TX transfers */
sem_t rxsem; /* Wait for RX DMA to complete */
sem_t txsem; /* Wait for TX DMA to complete */
#endif
};
enum imxrt_delay_e
@ -149,6 +161,21 @@ static inline void imxrt_lpspi_master_set_delay_scaler(
uint32_t scaler,
enum imxrt_delay_e type);
/* DMA support */
#ifdef CONFIG_IMXRT_LPSPI_DMA
static int spi_dmarxwait(FAR struct imxrt_lpspidev_s *priv);
static int spi_dmatxwait(FAR struct imxrt_lpspidev_s *priv);
static inline void spi_dmarxwakeup(FAR struct imxrt_lpspidev_s *priv);
static inline void spi_dmatxwakeup(FAR struct imxrt_lpspidev_s *priv);
static void spi_dmarxcallback(DMACH_HANDLE handle, void *arg,
bool done, int result);
static void spi_dmatxcallback(DMACH_HANDLE handle, void *arg,
bool done, int result);
static inline void spi_dmarxstart(FAR struct imxrt_lpspidev_s *priv);
static inline void spi_dmatxstart(FAR struct imxrt_lpspidev_s *priv);
#endif
/* SPI methods */
static int imxrt_lpspi_lock(struct spi_dev_s *dev, bool lock);
@ -221,9 +248,9 @@ static struct imxrt_lpspidev_s g_lpspi1dev =
#ifdef CONFIG_IMXRT_LPSPI_INTERRUPTS
.spiirq = IMXRT_IRQ_LPSPI1,
#endif
#ifdef CONFIG_IMXRT_LPSPI_DMA
.rxch = DMAMAP_LPSPI1_RX,
.txch = DMAMAP_LPSPI1_TX,
#ifdef CONFIG_IMXRT_LPSPI1_DMA
.rxch = IMXRT_DMACHAN_LPSPI1_RX,
.txch = IMXRT_DMACHAN_LPSPI1_TX,
#endif
};
#endif
@ -267,9 +294,9 @@ static struct imxrt_lpspidev_s g_lpspi2dev =
#ifdef CONFIG_IMXRT_LPSPI_INTERRUPTS
.spiirq = IMXRT_IRQ_LPSPI2,
#endif
#ifdef CONFIG_IMXRT_LPSPI_DMA
.rxch = DMAMAP_LPSPI2_RX,
.txch = DMAMAP_LPSPI2_TX,
#ifdef CONFIG_IMXRT_LPSPI2_DMA
.rxch = IMXRT_DMACHAN_LPSPI2_RX,
.txch = IMXRT_DMACHAN_LPSPI2_TX,
#endif
};
#endif
@ -313,9 +340,9 @@ static struct imxrt_lpspidev_s g_lpspi3dev =
#ifdef CONFIG_IMXRT_LPSPI_INTERRUPTS
.spiirq = IMXRT_IRQ_LPSPI3,
#endif
#ifdef CONFIG_IMXRT_LPSPI_DMA
.rxch = DMAMAP_LPSPI3_RX,
.txch = DMAMAP_LPSPI3_TX,
#ifdef CONFIG_IMXRT_LPSPI3_DMA
.rxch = IMXRT_DMACHAN_LPSPI3_RX,
.txch = IMXRT_DMACHAN_LPSPI3_TX,
#endif
};
#endif
@ -359,9 +386,9 @@ static struct imxrt_lpspidev_s g_lpspi4dev =
#ifdef CONFIG_IMXRT_LPSPI_INTERRUPTS
.spiirq = IMXRT_IRQ_LPSPI4,
#endif
#ifdef CONFIG_IMXRT_LPSPI_DMA
.rxch = DMAMAP_LPSPI4_RX,
.txch = DMAMAP_LPSPI4_TX,
#ifdef CONFIG_IMXRT_LPSPI4_DMA
.rxch = IMXRT_DMACHAN_LPSPI4_RX,
.txch = IMXRT_DMACHAN_LPSPI4_TX,
#endif
};
#endif
@ -596,7 +623,7 @@ imxrt_lpspi_9to16bitmode(struct imxrt_lpspidev_s *priv)
}
/****************************************************************************
* Name: imxrt_lpspi_modifyreg
* Name: imxrt_lpspi_modifyreg32
*
* Description:
* Clear and set bits in register
@ -1269,7 +1296,6 @@ static uint32_t imxrt_lpspi_send(struct spi_dev_s *dev, uint32_t wd)
*
****************************************************************************/
#if !defined(CONFIG_IMXRT_LPSPI_DMA) || defined(CONFIG_IMXRT_DMACAPABLE)
#if !defined(CONFIG_IMXRT_LPSPI_DMA)
static void imxrt_lpspi_exchange(struct spi_dev_s *dev,
const void *txbuffer,
@ -1355,7 +1381,159 @@ static void imxrt_lpspi_exchange_nodma(struct spi_dev_s *dev,
}
}
}
#endif /* !CONFIG_IMXRT_LPSPI_DMA || CONFIG_IMXRT_DMACAPABLE */
/****************************************************************************
* Name: spi_exchange (with DMA capability)
*
* Description:
* Exchange a block of data on SPI using DMA
*
* Input Parameters:
* dev - Device-specific state data
* txbuffer - A pointer to the buffer of data to be sent
* rxbuffer - A pointer to a buffer in which to receive data
* nwords - the length of data to be exchanged in units of words.
* The wordsize is determined by the number of bits-per-word
* selected for the SPI interface. If nbits <= 8, the data is
* packed into uint8_t's; if nbits > 8, the data is packed into
* uint16_t's
*
* Returned Value:
* None
*
****************************************************************************/
#ifdef CONFIG_IMXRT_LPSPI_DMA
static void imxrt_lpspi_exchange(FAR struct spi_dev_s * dev,
FAR const void * txbuffer,
FAR void * rxbuffer, size_t nwords)
{
int ret;
size_t adjust;
ssize_t nbytes;
static uint8_t rxdummy[4] aligned_data(4);
static const uint16_t txdummy = 0xffff;
uint32_t regval;
FAR struct imxrt_lpspidev_s *priv = (FAR struct imxrt_lpspidev_s *)dev;
DEBUGASSERT(priv != NULL);
DEBUGASSERT(priv && priv->spibase);
spiinfo("txbuffer=%p rxbuffer=%p nwords=%d\n", txbuffer, rxbuffer, nwords);
/* Convert the number of word to a number of bytes */
nbytes = (priv->nbits > 8) ? nwords << 2 : nwords;
/* Invalid DMA channels fall back to non-DMA method. */
if (priv->rxdma == NULL || priv->txdma == NULL
#ifdef CONFIG_IMXRT_LPSPI_DMATHRESHOLD
/* If this is a small SPI transfer, then let
* imxrt_lpspi_exchange_nodma() do the work.
*/
|| nbytes <= CONFIG_IMXRT_LPSPI_DMATHRESHOLD
#endif
)
{
imxrt_lpspi_exchange_nodma(dev, txbuffer, rxbuffer, nwords);
return;
}
/* ERR050456 workaround: Reset FIFOs using CR[RST] bit */
regval = imxrt_lpspi_getreg32(priv, IMXRT_LPSPI_CFGR1_OFFSET);
imxrt_lpspi_modifyreg32(priv, IMXRT_LPSPI_CR_OFFSET,
LPSPI_CR_RTF | LPSPI_CR_RRF,
LPSPI_CR_RTF | LPSPI_CR_RRF);
imxrt_lpspi_putreg32(priv, IMXRT_LPSPI_CFGR1_OFFSET, regval);
/* Clear all status bits */
imxrt_lpspi_putreg32(priv, IMXRT_LPSPI_SR_OFFSET, SPI_SR_CLEAR);
/* disable DMA */
imxrt_lpspi_putreg32(priv, IMXRT_LPSPI_DER_OFFSET, 0);
if (txbuffer)
{
up_clean_dcache((uintptr_t)txbuffer, (uintptr_t)txbuffer + nbytes);
}
if (rxbuffer)
{
up_invalidate_dcache((uintptr_t)rxbuffer,
(uintptr_t)rxbuffer + nbytes);
}
/* Set up the DMA */
adjust = (priv->nbits > 8) ? 2 : 1;
struct imxrt_edma_xfrconfig_s config;
config.saddr = priv->spibase + IMXRT_LPSPI_RDR_OFFSET;
config.daddr = (uint32_t) (rxbuffer ? rxbuffer : rxdummy);
config.soff = 0;
config.doff = rxbuffer ? adjust : 0;
config.iter = nbytes;
config.flags = EDMA_CONFIG_LINKTYPE_LINKNONE;
config.ssize = adjust == 1 ? EDMA_8BIT : EDMA_16BIT;
config.dsize = adjust == 1 ? EDMA_8BIT : EDMA_16BIT;
config.nbytes = adjust;
#ifdef CONFIG_KINETIS_EDMA_ELINK
config.linkch = NULL;
#endif
imxrt_dmach_xfrsetup(priv->rxdma, &config);
config.saddr = (uint32_t) (txbuffer ? txbuffer : &txdummy);
config.daddr = priv->spibase + IMXRT_LPSPI_TDR_OFFSET;
config.soff = txbuffer ? adjust : 0;
config.doff = 0;
config.iter = nbytes;
config.flags = EDMA_CONFIG_LINKTYPE_LINKNONE;
config.ssize = adjust == 1 ? EDMA_8BIT : EDMA_16BIT;
config.dsize = adjust == 1 ? EDMA_8BIT : EDMA_16BIT;
config.nbytes = adjust;
#ifdef CONFIG_KINETIS_EDMA_ELINK
config.linkch = NULL;
#endif
imxrt_dmach_xfrsetup(priv->txdma, &config);
/* Start the DMAs */
spi_dmarxstart(priv);
spi_dmatxstart(priv);
/* Invoke SPI DMA */
imxrt_lpspi_modifyreg32(priv, IMXRT_LPSPI_DER_OFFSET,
0, LPSPI_DER_TDDE | LPSPI_DER_RDDE);
/* Then wait for each to complete */
ret = spi_dmarxwait(priv);
if (ret < 0)
{
ret = spi_dmatxwait(priv);
}
/* Reset any status */
imxrt_lpspi_putreg32(priv, IMXRT_LPSPI_SR_OFFSET,
imxrt_lpspi_getreg32(priv,
IMXRT_LPSPI_SR_OFFSET));
/* Disable DMA */
imxrt_lpspi_putreg32(priv, IMXRT_LPSPI_DER_OFFSET, 0);
}
#endif /* CONFIG_IMXRT_SPI_DMA */
/****************************************************************************
* Name: imxrt_lpspi_sndblock
@ -1542,6 +1720,174 @@ static void imxrt_lpspi_bus_initialize(struct imxrt_lpspidev_s *priv)
imxrt_lpspi_modifyreg32(priv, IMXRT_LPSPI_CR_OFFSET, 0, LPSPI_CR_MEN);
}
/****************************************************************************
* Name: spi_dmarxwait
*
* Description:
* Wait for DMA to complete.
*
****************************************************************************/
#ifdef CONFIG_IMXRT_LPSPI_DMA
static int spi_dmarxwait(FAR struct imxrt_lpspidev_s *priv)
{
int ret;
/* Take the semaphore (perhaps waiting). If the result is zero, then the
* DMA must not really have completed.
*/
do
{
ret = nxsem_wait_uninterruptible(&priv->rxsem);
/* The only expected error is ECANCELED which would occur if the
* calling thread were canceled.
*/
DEBUGASSERT(ret == OK || ret == -ECANCELED);
}
while (priv->rxresult == 0 && ret == OK);
return ret;
}
#endif
/****************************************************************************
* Name: spi_dmatxwait
*
* Description:
* Wait for DMA to complete.
*
****************************************************************************/
#ifdef CONFIG_IMXRT_LPSPI_DMA
static int spi_dmatxwait(FAR struct imxrt_lpspidev_s *priv)
{
int ret;
/* Take the semaphore (perhaps waiting). If the result is zero, then the
* DMA must not really have completed.
*/
do
{
ret = nxsem_wait_uninterruptible(&priv->txsem);
/* The only expected error is ECANCELED which would occur if the
* calling thread were canceled.
*/
DEBUGASSERT(ret == OK || ret == -ECANCELED);
}
while (priv->txresult == 0 && ret == OK);
return ret;
}
#endif
/****************************************************************************
* Name: spi_dmarxwakeup
*
* Description:
* Signal that DMA is complete
*
****************************************************************************/
#ifdef CONFIG_IMXRT_LPSPI_DMA
static inline void spi_dmarxwakeup(FAR struct imxrt_lpspidev_s *priv)
{
nxsem_post(&priv->rxsem);
}
#endif
/****************************************************************************
* Name: spi_dmatxwakeup
*
* Description:
* Signal that DMA is complete
*
****************************************************************************/
#ifdef CONFIG_IMXRT_LPSPI_DMA
static inline void spi_dmatxwakeup(FAR struct imxrt_lpspidev_s *priv)
{
nxsem_post(&priv->txsem);
}
#endif
/****************************************************************************
* Name: spi_dmarxcallback
*
* Description:
* Called when the RX DMA completes
*
****************************************************************************/
#ifdef CONFIG_IMXRT_LPSPI_DMA
static void spi_dmarxcallback(DMACH_HANDLE handle, void *arg, bool done,
int result)
{
FAR struct imxrt_lpspidev_s *priv = (FAR struct imxrt_lpspidev_s *)arg;
priv->rxresult = result | 0x80000000; /* assure non-zero */
spi_dmarxwakeup(priv);
}
#endif
/****************************************************************************
* Name: spi_dmatxcallback
*
* Description:
* Called when the RX DMA completes
*
****************************************************************************/
#ifdef CONFIG_IMXRT_LPSPI_DMA
static void spi_dmatxcallback(DMACH_HANDLE handle, void *arg, bool done,
int result)
{
FAR struct imxrt_lpspidev_s *priv = (FAR struct imxrt_lpspidev_s *)arg;
/* Wake-up the SPI driver */
priv->txresult = result | 0x80000000; /* assure non-zero */
spi_dmatxwakeup(priv);
}
#endif
/****************************************************************************
* Name: spi_dmarxstart
*
* Description:
* Start RX DMA
*
****************************************************************************/
#ifdef CONFIG_IMXRT_LPSPI_DMA
static inline void spi_dmarxstart(FAR struct imxrt_lpspidev_s *priv)
{
priv->rxresult = 0;
imxrt_dmach_start(priv->rxdma, spi_dmarxcallback, priv);
}
#endif
/****************************************************************************
* Name: spi_dmatxstart
*
* Description:
* Start TX DMA
*
****************************************************************************/
#ifdef CONFIG_IMXRT_LPSPI_DMA
static inline void spi_dmatxstart(FAR struct imxrt_lpspidev_s *priv)
{
priv->txresult = 0;
imxrt_dmach_start(priv->txdma, spi_dmatxcallback, priv);
}
#endif
/****************************************************************************
* Public Functions
****************************************************************************/
@ -1694,9 +2040,39 @@ struct spi_dev_s *imxrt_lpspibus_initialize(int bus)
spierr("ERROR: Unsupported SPI bus: %d\n", bus);
}
#ifdef CONFIG_IMXRT_LPSPI_DMA
/* Initialize the SPI semaphores that is used to wait for DMA completion.
* This semaphore is used for signaling and, hence, should not have
* priority inheritance enabled.
*/
if (priv->rxch && priv->txch)
{
if (priv->txdma == NULL && priv->rxdma == NULL)
{
nxsem_init(&priv->rxsem, 0, 0);
nxsem_init(&priv->txsem, 0, 0);
nxsem_set_protocol(&priv->rxsem, SEM_PRIO_NONE);
nxsem_set_protocol(&priv->txsem, SEM_PRIO_NONE);
priv->txdma = imxrt_dmach_alloc(priv->txch | DMAMUX_CHCFG_ENBL,
0);
priv->rxdma = imxrt_dmach_alloc(priv->rxch | DMAMUX_CHCFG_ENBL,
0);
DEBUGASSERT(priv->rxdma && priv->txdma);
}
}
else
{
priv->rxdma = NULL;
priv->txdma = NULL;
}
#endif
leave_critical_section(flags);
return (struct spi_dev_s *)priv;
}
#endif /* CONFIG_IMXRT_LPSPI1 */
#endif /* CONFIG_IMXRT_LPSPI1 || CONFIG_IMXRT_LPSPI2 || CONFIG_IMXRT_LPSPI3 || CONFIG_IMXRT_LPSPI4 */