kinetis:SPI use eDMA

Kinetis:SPI only allocate DMA once
This commit is contained in:
David Sidrane 2021-06-25 04:19:34 -07:00 committed by Xiang Xiao
parent 78bf264af0
commit e5a1b2e797
2 changed files with 506 additions and 13 deletions

View File

@ -330,6 +330,11 @@ config KINETIS_HAVE_DMA
bool
default n
config KINETIS_SPI
bool
default n
select SPI
config KINETIS_HAVE_I2C1
bool
default n
@ -536,14 +541,14 @@ config KINETIS_FLEXCAN1
config KINETIS_SPI0
bool "SPI0"
default n
select SPI
select KINETIS_SPI
---help---
Support SPI0
config KINETIS_SPI1
bool "SPI1"
default n
select SPI
select KINETIS_SPI
depends on KINETIS_HAVE_SPI1
---help---
Support SPI1
@ -551,7 +556,7 @@ config KINETIS_SPI1
config KINETIS_SPI2
bool "SPI2"
default n
select SPI
select KINETIS_SPI
depends on KINETIS_HAVE_SPI2
---help---
Support SPI2
@ -752,7 +757,7 @@ config KINETIS_FTFL
Support FLASH
config KINETIS_EDMA
bool "DMA"
bool "eDMA"
default n
depends on KINETIS_HAVE_DMA
select ARCH_DMA
@ -1204,6 +1209,48 @@ config KINETIS_EDMA_EDBG
endmenu # eDMA Global Configuration
menu "Kinetis SPI Configuration"
depends on KINETIS_SPI && KINETIS_EDMA
config KINETIS_SPI_DMA
bool "SPI DMA"
depends on KINETIS_EDMA
default n
---help---
Use DMA to improve SPI transfer performance.
config KINETIS_SPI_DMATHRESHOLD
int "SPI DMA threshold"
default 4
depends on KINETIS_SPI_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 KINETIS_SPI0_DMA
bool "SPI0 DMA"
default n
depends on KINETIS_SPI0 && KINETIS_SPI_DMA
---help---
Use DMA to improve SPI0 transfer performance.
config KINETIS_SPI1_DMA
bool "SPI1 DMA"
default n
depends on KINETIS_SPI1 && KINETIS_SPI_DMA
---help---
Use DMA to improve SPI1 transfer performance.
config KINETIS_SPI2_DMA
bool "SPI2 DMA"
default n
depends on KINETIS_SPI2 && KINETIS_SPI_DMA
---help---
Use DMA to improve SPI2 transfer performance.
endmenu # Kinetis SPI Configuration
if KINETIS_USBHS && USBHOST
menu "USB host controller driver (HCD) options"

View File

@ -67,7 +67,9 @@
#include "arm_arch.h"
#include "kinetis.h"
#include "kinetis_edma.h"
#include "kinetis_spi.h"
#include "hardware/kinetis_dmamux.h"
#include "hardware/kinetis_memorymap.h"
#include "hardware/kinetis_sim.h"
#include "hardware/kinetis_dspi.h"
@ -83,6 +85,10 @@
#define KINETIS_SPI_CLK_MAX (BOARD_BUS_FREQ / 2)
#define KINETIS_SPI_CLK_INIT 400000
#define SPI_SR_CLEAR (SPI_SR_TCF | SPI_SR_EOQF | SPI_SR_TFUF | \
SPI_SR_TFFF | SPI_SR_RFOF | SPI_SR_RFDF | \
SPI_SR_TXRXS)
/****************************************************************************
* Private Types
****************************************************************************/
@ -97,6 +103,16 @@ struct kinetis_spidev_s
uint8_t nbits; /* Width of word in bits (8 to 16) */
uint8_t mode; /* Mode 0,1,2,3 */
uint8_t ctarsel; /* Which CTAR */
#ifdef CONFIG_KINETIS_SPI_DMA
volatile uint32_t rxresult; /* Result of the RX DMA */
volatile uint32_t txresult; /* Result of the TX DMA */
const uint8_t rxch; /* The RX DMA channel number */
const uint8_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
};
/****************************************************************************
@ -132,6 +148,21 @@ static inline void spi_wait_status(FAR struct kinetis_spidev_s *priv,
static uint16_t spi_send_data(FAR struct kinetis_spidev_s *priv,
uint16_t wd, bool last);
/* DMA support */
#ifdef CONFIG_KINETIS_SPI_DMA
static int spi_dmarxwait(FAR struct kinetis_spidev_s *priv);
static int spi_dmatxwait(FAR struct kinetis_spidev_s *priv);
static inline void spi_dmarxwakeup(FAR struct kinetis_spidev_s *priv);
static inline void spi_dmatxwakeup(FAR struct kinetis_spidev_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 kinetis_spidev_s *priv);
static inline void spi_dmatxstart(FAR struct kinetis_spidev_s *priv);
#endif
/* SPI methods */
static int spi_lock(FAR struct spi_dev_s *dev, bool lock);
@ -197,6 +228,15 @@ static struct kinetis_spidev_s g_spi0dev =
},
.spibase = KINETIS_SPI0_BASE,
.ctarsel = KINETIS_SPI_CTAR0_OFFSET,
#ifdef CONFIG_KINETIS_SPI_DMA
# ifdef CONFIG_KINETIS_SPI0_DMA
.rxch = KINETIS_DMA_REQUEST_SRC_SPI0_RX,
.txch = KINETIS_DMA_REQUEST_SRC_SPI0_TX,
# else
.rxch = 0,
.txch = 0,
# endif
#endif
};
#endif
@ -237,6 +277,15 @@ static struct kinetis_spidev_s g_spi1dev =
},
.spibase = KINETIS_SPI1_BASE,
.ctarsel = KINETIS_SPI_CTAR0_OFFSET,
#ifdef CONFIG_KINETIS_SPI_DMA
# ifdef CONFIG_KINETIS_SPI1_DMA
.rxch = KINETIS_DMA_REQUEST_SRC_SPI1_RX,
.txch = KINETIS_DMA_REQUEST_SRC_SPI1_TX,
# else
.rxch = 0,
.txch = 0,
# endif
#endif
};
#endif
@ -277,6 +326,15 @@ static struct kinetis_spidev_s g_spi2dev =
},
.spibase = KINETIS_SPI2_BASE,
.ctarsel = KINETIS_SPI_CTAR0_OFFSET,
#ifdef CONFIG_KINETIS_SPI_DMA
# ifdef CONFIG_KINETIS_SPI2_DMA
.rxch = KINETIS_DMA_REQUEST_SRC_FTM3_CH6__SPI2_RX,
.txch = KINETIS_DMA_REQUEST_SRC_FTM3_CH7__SPI2_TX,
# else
.rxch = 0,
.txch = 0,
# endif
#endif
};
#endif
@ -284,6 +342,32 @@ static struct kinetis_spidev_s g_spi2dev =
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: spi_modifyreg
*
* Description:
* Atomic modification of the 32-bit contents of the SPI register at offset
*
* Input Parameters:
* priv - private SPI device structure
* offset - offset to the register of interest
* clearbits - bits to clear
* clearbits - bits to set
*
* Returned Value:
* None.
*
****************************************************************************/
#ifdef CONFIG_KINETIS_SPI_DMA
static inline void spi_modifyreg(FAR struct kinetis_spidev_s *priv,
uint8_t offset, uint32_t clearbits,
uint32_t setbits)
{
modifyreg32(priv->spibase + offset, clearbits, setbits);
}
#endif
/****************************************************************************
* Name: spi_getreg
*
@ -946,7 +1030,7 @@ static uint32_t spi_send(FAR struct spi_dev_s *dev, uint32_t wd)
}
/****************************************************************************
* Name: spi_exchange
* Name: spi_exchange (no DMA). aka spi_exchange_nodma
*
* Description:
* Exchange a block of data on SPI without using DMA
@ -966,8 +1050,15 @@ static uint32_t spi_send(FAR struct spi_dev_s *dev, uint32_t wd)
*
****************************************************************************/
#if !defined(CONFIG_STM32_SPI_DMA) || defined(CONFIG_STM32_SPI_DMATHRESHOLD)
# if !defined(CONFIG_KINETIS_SPI_DMA)
static void spi_exchange(FAR struct spi_dev_s *dev, FAR const void *txbuffer,
FAR void *rxbuffer, size_t nwords)
# else
static void spi_exchange_nodma(FAR struct spi_dev_s *dev,
FAR const void *txbuffer,
FAR void *rxbuffer, size_t nwords)
# endif
{
FAR struct kinetis_spidev_s *priv = (FAR struct kinetis_spidev_s *)dev;
uint8_t *brxptr = (uint8_t *)rxbuffer;
@ -1039,7 +1130,161 @@ static void spi_exchange(FAR struct spi_dev_s *dev, FAR const void *txbuffer,
}
}
}
#endif /* !defined(CONFIG_STM32_SPI_DMA) || defined(CONFIG_STM32_SPI_DMATHRESHOLD) */
/****************************************************************************
* 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 exchaned 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_KINETIS_SPI_DMA
static void spi_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] __attribute__((aligned(4)));
static const uint16_t txdummy = 0xffff;
FAR struct kinetis_spidev_s *priv = (FAR struct kinetis_spidev_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 << 1 : nwords;
/* Invalid DMA channels fall back to non-DMA method. */
if (priv->rxdma == NULL || priv->txdma == NULL
#ifdef CONFIG_KINETIS_SPI_DMATHRESHOLD
/* If this is a small SPI transfer, then let spi_exchange_nodma()
* do the work.
*/
|| nbytes <= CONFIG_KINETIS_SPI_DMATHRESHOLD
#endif
)
{
spi_exchange_nodma(dev, txbuffer, rxbuffer, nwords);
return;
}
/* Halt the SPI */
spi_run(priv, false);
/* Flush FIFOs */
spi_modifyreg(priv, KINETIS_SPI_MCR_OFFSET,
SPI_MCR_CLR_RXF | SPI_MCR_CLR_TXF,
SPI_MCR_CLR_RXF | SPI_MCR_CLR_TXF);
/* Clear all status bits */
spi_write_status(priv, SPI_SR_CLEAR);
/* disable DMA */
spi_modifyreg(priv, KINETIS_SPI_RSER_OFFSET,
SPI_RSER_RFDF_RE | SPI_RSER_TFFF_RE |
SPI_RSER_RFDF_DIRS | SPI_RSER_TFFF_DIRS,
0);
/* Set up the DMA */
adjust = (priv->nbits > 8) ? 2 : 1;
struct kinetis_edma_xfrconfig_s config;
config.saddr = priv->spibase + KINETIS_SPI_POPR_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.ttype = EDMA_PERIPH2MEM;
config.nbytes = adjust;
#ifdef CONFIG_KINETIS_EDMA_ELINK
config.linkch = NULL;
#endif
kinetis_dmach_xfrsetup(priv->rxdma, &config);
config.saddr = (uint32_t) (txbuffer ? txbuffer : &txdummy);
config.daddr = priv->spibase + KINETIS_SPI_PUSHR_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.ttype = EDMA_MEM2PERIPH;
config.nbytes = adjust;
#ifdef CONFIG_KINETIS_EDMA_ELINK
config.linkch = NULL;
#endif
kinetis_dmach_xfrsetup(priv->txdma, &config);
spi_modifyreg(priv, KINETIS_SPI_RSER_OFFSET, 0 ,
SPI_RSER_RFDF_RE | SPI_RSER_TFFF_RE |
SPI_RSER_RFDF_DIRS | SPI_RSER_TFFF_DIRS);
/* Start the DMAs */
spi_dmarxstart(priv);
spi_run(priv, true);
spi_putreg(priv, KINETIS_SPI_TCR_OFFSET, 0);
spi_write_control(priv, SPI_PUSHR_CTAS_CTAR0);
spi_dmatxstart(priv);
/* Then wait for each to complete */
ret = spi_dmarxwait(priv);
if (ret < 0)
{
ret = spi_dmatxwait(priv);
}
/* Reset any status */
spi_write_status(priv, spi_getreg(priv, KINETIS_SPI_SR_OFFSET));
/* Halt SPI */
spi_run(priv, false);
/* Disable DMA */
spi_modifyreg(priv, KINETIS_SPI_RSER_OFFSET,
SPI_RSER_RFDF_RE | SPI_RSER_TFFF_RE |
SPI_RSER_RFDF_DIRS | SPI_RSER_TFFF_DIRS,
0);
}
#endif /* CONFIG_KINETIS_SPI_DMA */
/****************************************************************************
* Name: spi_sndblock
*
@ -1099,6 +1344,174 @@ static void spi_recvblock(FAR struct spi_dev_s *dev, FAR void *rxbuffer,
}
#endif
/****************************************************************************
* Name: spi_dmarxwait
*
* Description:
* Wait for DMA to complete.
*
****************************************************************************/
#ifdef CONFIG_KINETIS_SPI_DMA
static int spi_dmarxwait(FAR struct kinetis_spidev_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_KINETIS_SPI_DMA
static int spi_dmatxwait(FAR struct kinetis_spidev_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_KINETIS_SPI_DMA
static inline void spi_dmarxwakeup(FAR struct kinetis_spidev_s *priv)
{
nxsem_post(&priv->rxsem);
}
#endif
/****************************************************************************
* Name: spi_dmatxwakeup
*
* Description:
* Signal that DMA is complete
*
****************************************************************************/
#ifdef CONFIG_KINETIS_SPI_DMA
static inline void spi_dmatxwakeup(FAR struct kinetis_spidev_s *priv)
{
nxsem_post(&priv->txsem);
}
#endif
/****************************************************************************
* Name: spi_dmarxcallback
*
* Description:
* Called when the RX DMA completes
*
****************************************************************************/
#ifdef CONFIG_KINETIS_SPI_DMA
static void spi_dmarxcallback(DMACH_HANDLE handle, void *arg, bool done,
int result)
{
FAR struct kinetis_spidev_s *priv = (FAR struct kinetis_spidev_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_KINETIS_SPI_DMA
static void spi_dmatxcallback(DMACH_HANDLE handle, void *arg, bool done,
int result)
{
FAR struct kinetis_spidev_s *priv = (FAR struct kinetis_spidev_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_KINETIS_SPI_DMA
static inline void spi_dmarxstart(FAR struct kinetis_spidev_s *priv)
{
priv->rxresult = 0;
kinetis_dmach_start(priv->rxdma, spi_dmarxcallback, priv);
}
#endif
/****************************************************************************
* Name: spi_dmatxstart
*
* Description:
* Start TX DMA
*
****************************************************************************/
#ifdef CONFIG_KINETIS_SPI_DMA
static inline void spi_dmatxstart(FAR struct kinetis_spidev_s *priv)
{
priv->txresult = 0;
kinetis_dmach_start(priv->txdma, spi_dmatxcallback, priv);
}
#endif
/****************************************************************************
* Public Functions
****************************************************************************/
@ -1251,6 +1664,39 @@ FAR struct spi_dev_s *kinetis_spibus_initialize(int port)
/* Initialize the SPI semaphore that enforces mutually exclusive access */
nxsem_init(&priv->exclsem, 0, 1);
#ifdef CONFIG_KINETIS_SPI_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 = kinetis_dmach_alloc(priv->txch | DMAMUX_CHCFG_ENBL,
0);
priv->rxdma = kinetis_dmach_alloc(priv->rxch | DMAMUX_CHCFG_ENBL,
0);
DEBUGASSERT(priv->rxdma && priv->txdma);
spi_modifyreg(priv, KINETIS_SPI_MCR_OFFSET,
0,
SPI_MCR_DIS_RXF | SPI_MCR_DIS_TXF
);
}
}
else
{
priv->rxdma = NULL;
priv->txdma = NULL;
}
#endif
return &priv->spidev;
}