arm/rp2040: RP2040 SPI DMA transfer support

This commit is contained in:
Yuichi Nakamura 2021-03-09 00:11:12 +09:00 committed by Alan Carvalho de Assis
parent b69df289bd
commit 938b1daf02
4 changed files with 340 additions and 28 deletions

View File

@ -90,6 +90,22 @@ config RP2040_SPI0
config RP2040_SPI1 config RP2040_SPI1
bool "SPI1" bool "SPI1"
config RP2040_SPI_DMA
bool "SPI DMA"
default y
depends on RP2040_DMAC
---help---
Use DMA to improve SPI transfer performance.
config RP2040_SPI_DMATHRESHOLD
int "SPI DMA threshold"
default 4
depends on RP2040_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. That value is provided by RP2040_SPI_DMATHRESHOLD.
config RP2040_SPI_DRIVER config RP2040_SPI_DRIVER
bool "SPI character driver" bool "SPI character driver"
default y default y

View File

@ -46,6 +46,10 @@
#include "rp2040_spi.h" #include "rp2040_spi.h"
#include "hardware/rp2040_spi.h" #include "hardware/rp2040_spi.h"
#ifdef CONFIG_RP2040_SPI_DMA
#include "rp2040_dmac.h"
#endif
#ifdef CONFIG_RP2040_SPI #ifdef CONFIG_RP2040_SPI
/**************************************************************************** /****************************************************************************
@ -81,6 +85,14 @@ struct rp2040_spidev_s
uint8_t mode; /* Mode 0,1,2,3 */ uint8_t mode; /* Mode 0,1,2,3 */
uint8_t port; /* Port number */ uint8_t port; /* Port number */
int initialized; /* Initialized flag */ int initialized; /* Initialized flag */
#ifdef CONFIG_RP2040_SPI_DMA
bool dmaenable; /* Use DMA or not */
DMA_HANDLE rxdmach; /* RX DMA channel handle */
DMA_HANDLE txdmach; /* TX DMA channel handle */
sem_t dmasem; /* Wait for DMA to complete */
dma_config_t rxconfig; /* RX DMA configuration */
dma_config_t txconfig; /* TX DMA configuration */
#endif
}; };
/**************************************************************************** /****************************************************************************
@ -94,6 +106,27 @@ static inline uint32_t spi_getreg(FAR struct rp2040_spidev_s *priv,
static inline void spi_putreg(FAR struct rp2040_spidev_s *priv, static inline void spi_putreg(FAR struct rp2040_spidev_s *priv,
uint8_t offset, uint32_t value); uint8_t offset, uint32_t value);
/* DMA support */
#ifdef CONFIG_RP2040_SPI_DMA
static void __unused spi_dmaexchange(FAR struct spi_dev_s *dev,
FAR const void *txbuffer,
FAR void *rxbuffer, size_t nwords);
static void spi_dmatxcallback(DMA_HANDLE handle, uint8_t status, void *data);
static void spi_dmarxcallback(DMA_HANDLE handle, uint8_t status, void *data);
static void spi_dmatxsetup(FAR struct rp2040_spidev_s *priv,
FAR const void *txbuffer, size_t nwords);
static void spi_dmarxsetup(FAR struct rp2040_spidev_s *priv,
FAR const void *rxbuffer, size_t nwords);
static void spi_dmatrxwait(FAR struct rp2040_spidev_s *priv);
#ifndef CONFIG_SPI_EXCHANGE
static void spi_dmasndblock(FAR struct spi_dev_s *dev,
FAR const void *buffer, size_t nwords);
static void spi_dmarecvblock(FAR struct spi_dev_s *dev,
FAR const void *buffer, size_t nwords);
#endif
#endif
/* SPI methods */ /* SPI methods */
static int spi_lock(FAR struct spi_dev_s *dev, bool lock); static int spi_lock(FAR struct spi_dev_s *dev, bool lock);
@ -207,6 +240,13 @@ static struct rp2040_spidev_s g_spi1dev =
}; };
#endif #endif
#ifdef CONFIG_RP2040_SPI_DMA
/* Dummy data if no data transfer needed */
uint32_t g_spitxdmadummy = 0xffffffff;
uint32_t g_spirxdmadummy = 0;
#endif
/**************************************************************************** /****************************************************************************
* Public Data * Public Data
****************************************************************************/ ****************************************************************************/
@ -452,6 +492,18 @@ static void spi_setbits(FAR struct spi_dev_s *dev, int nbits)
*/ */
priv->nbits = nbits; priv->nbits = nbits;
#ifdef CONFIG_RP2040_SPI_DMA
if (priv->nbits > 8)
{
priv->txconfig.size = RP2040_DMA_SIZE_HALFWORD;
priv->rxconfig.size = RP2040_DMA_SIZE_HALFWORD;
}
else
{
priv->txconfig.size = RP2040_DMA_SIZE_BYTE;
priv->rxconfig.size = RP2040_DMA_SIZE_BYTE;
}
#endif
} }
} }
@ -628,7 +680,24 @@ static void spi_do_exchange(FAR struct spi_dev_s *dev,
static void spi_exchange(FAR struct spi_dev_s *dev, FAR const void *txbuffer, static void spi_exchange(FAR struct spi_dev_s *dev, FAR const void *txbuffer,
FAR void *rxbuffer, size_t nwords) FAR void *rxbuffer, size_t nwords)
{ {
spi_do_exchange(dev, txbuffer, rxbuffer, nwords); #ifdef CONFIG_RP2040_SPI_DMA
FAR struct rp2040_spidev_s *priv = (FAR struct rp2040_spidev_s *)dev;
#ifdef CONFIG_RP2040_SPI_DMATHRESHOLD
size_t dmath = CONFIG_RP2040_SPI_DMATHRESHOLD;
#else
size_t dmath = 0;
#endif
if (priv->dmaenable && dmath < nwords)
{
spi_dmaexchange(dev, txbuffer, rxbuffer, nwords);
}
else
#endif
{
spi_do_exchange(dev, txbuffer, rxbuffer, nwords);
}
} }
/**************************************************************************** /****************************************************************************
@ -708,18 +777,30 @@ FAR struct spi_dev_s *rp2040_spibus_initialize(int port)
FAR struct rp2040_spidev_s *priv; FAR struct rp2040_spidev_s *priv;
uint32_t regval; uint32_t regval;
int i; int i;
#ifdef CONFIG_RP2040_SPI_DMA
dma_config_t txconf;
dma_config_t rxconf;
#endif
switch (port) switch (port)
{ {
#ifdef CONFIG_RP2040_SPI0 #ifdef CONFIG_RP2040_SPI0
case 0: case 0:
priv = &g_spi0dev; priv = &g_spi0dev;
#ifdef CONFIG_RP2040_SPI_DMA
txconf.dreq = RP2040_DMA_DREQ_SPI0_TX;
rxconf.dreq = RP2040_DMA_DREQ_SPI0_RX;
#endif
break; break;
#endif #endif
#ifdef CONFIG_RP2040_SPI1 #ifdef CONFIG_RP2040_SPI1
case 1: case 1:
priv = &g_spi1dev; priv = &g_spi1dev;
#ifdef CONFIG_RP2040_SPI_DMA
txconf.dreq = RP2040_DMA_DREQ_SPI1_TX;
rxconf.dreq = RP2040_DMA_DREQ_SPI1_RX;
#endif
break; break;
#endif #endif
@ -738,6 +819,25 @@ FAR struct spi_dev_s *rp2040_spibus_initialize(int port)
priv->spibasefreq = BOARD_PERI_FREQ; priv->spibasefreq = BOARD_PERI_FREQ;
/* DMA settings */
#ifdef CONFIG_RP2040_SPI_DMA
nxsem_init(&priv->dmasem, 0, 0);
nxsem_set_protocol(&priv->dmasem, SEM_PRIO_NONE);
priv->txdmach = rp2040_dmachannel();
txconf.size = RP2040_DMA_SIZE_BYTE;
txconf.noincr = false;
priv->txconfig = txconf;
priv->rxdmach = rp2040_dmachannel();
rxconf.size = RP2040_DMA_SIZE_BYTE;
rxconf.noincr = false;
priv->rxconfig = rxconf;
priv->dmaenable = true;
#endif
/* Configure 8-bit SPI mode */ /* Configure 8-bit SPI mode */
spi_putreg(priv, RP2040_SPI_SSPCR0_OFFSET, spi_putreg(priv, RP2040_SPI_SSPCR0_OFFSET,
@ -825,4 +925,224 @@ void spi_flush(FAR struct spi_dev_s *dev)
while (spi_getreg(priv, RP2040_SPI_SSPSR_OFFSET) & RP2040_SPI_SSPSR_RNE); while (spi_getreg(priv, RP2040_SPI_SSPSR_OFFSET) & RP2040_SPI_SSPSR_RNE);
} }
#ifdef CONFIG_RP2040_SPI_DMA
/****************************************************************************
* Name: spi_dmaexchange
*
* Description:
* Exchange a block of data from SPI using DMA
*
****************************************************************************/
static void spi_dmaexchange(FAR struct spi_dev_s *dev,
FAR const void *txbuffer,
FAR void *rxbuffer, size_t nwords)
{
FAR struct rp2040_spidev_s *priv = (FAR struct rp2040_spidev_s *)dev;
DEBUGASSERT(priv && priv->spibase);
/* Setup DMAs */
spi_dmatxsetup(priv, txbuffer, nwords);
spi_dmarxsetup(priv, rxbuffer, nwords);
/* Start the DMAs */
rp2040_dmastart(priv->rxdmach, spi_dmarxcallback, priv);
rp2040_dmastart(priv->txdmach, spi_dmatxcallback, priv);
/* Then wait for each to complete */
spi_dmatrxwait(priv);
}
#ifndef CONFIG_SPI_EXCHANGE
/****************************************************************************
* Name: spi_dmasndblock
*
* Description:
* Send a block of data on SPI using DMA
*
****************************************************************************/
static void spi_dmasndblock(FAR struct spi_dev_s *dev,
FAR const void *buffer, size_t nwords)
{
spi_dmaexchange(dev, buffer, NULL, nwords);
}
/****************************************************************************
* Name: spi_dmarecvblock
*
* Description:
* Receive a block of data on SPI using DMA
*
****************************************************************************/
static void spi_dmarecvblock(FAR struct spi_dev_s *dev,
FAR const void *buffer, size_t nwords)
{
spi_dmaexchange(dev, NULL, buffer, nwords);
}
#endif
/****************************************************************************
* Name: spi_dmatxcallback
*
* Description:
* Called when the TX DMA completes
*
****************************************************************************/
static void spi_dmatxcallback(DMA_HANDLE handle, uint8_t status, void *data)
{
FAR struct rp2040_spidev_s *priv = (FAR struct rp2040_spidev_s *)data;
/* Wake-up the SPI driver */
if (status != 0)
{
spierr("dma error\n");
}
nxsem_post(&priv->dmasem);
}
/****************************************************************************
* Name: spi_dmarxcallback
*
* Description:
* Called when the RX DMA completes
*
****************************************************************************/
static void spi_dmarxcallback(DMA_HANDLE handle, uint8_t status, void *data)
{
FAR struct rp2040_spidev_s *priv = (FAR struct rp2040_spidev_s *)data;
/* Wake-up the SPI driver */
if (status != 0)
{
spierr("dma error\n");
}
nxsem_post(&priv->dmasem);
}
/****************************************************************************
* Name: spi_dmatxsetup
*
* Description:
* Setup to perform TX DMA
*
****************************************************************************/
static void spi_dmatxsetup(FAR struct rp2040_spidev_s *priv,
FAR const void *txbuffer, size_t nwords)
{
uint32_t dst;
uint32_t val;
val = spi_getreg(priv, RP2040_SPI_SSPDMACR_OFFSET);
val |= RP2040_SPI_SSPDMACR_TXDMAE;
spi_putreg(priv, RP2040_SPI_SSPDMACR_OFFSET, val);
dst = priv->spibase + RP2040_SPI_SSPDR_OFFSET;
if (txbuffer == NULL)
{
/* No source data buffer. Point to our dummy buffer and leave
* the txconfig so that no address increment is performed.
*/
txbuffer = (FAR const void *)&g_spitxdmadummy;
priv->txconfig.noincr = true;
}
else
{
/* Source data is available. Use normal TX memory incrementing. */
priv->txconfig.noincr = false;
}
rp2040_txdmasetup(priv->txdmach, (uintptr_t)dst, (uintptr_t)txbuffer,
nwords, priv->txconfig);
}
/****************************************************************************
* Name: spi_dmarxsetup
*
* Description:
* Setup to perform RX DMA
*
****************************************************************************/
static void spi_dmarxsetup(FAR struct rp2040_spidev_s *priv,
FAR const void *rxbuffer, size_t nwords)
{
uint32_t src;
uint32_t val;
val = spi_getreg(priv, RP2040_SPI_SSPDMACR_OFFSET);
val |= RP2040_SPI_SSPDMACR_RXDMAE;
spi_putreg(priv, RP2040_SPI_SSPDMACR_OFFSET, val);
src = priv->spibase + RP2040_SPI_SSPDR_OFFSET;
if (rxbuffer == NULL)
{
/* No sink data buffer. Point to our dummy buffer and leave
* the rxconfig so that no address increment is performed.
*/
rxbuffer = (FAR const void *)&g_spirxdmadummy;
priv->rxconfig.noincr = true;
}
else
{
/* Receive buffer is available. Use normal RX memory incrementing. */
priv->rxconfig.noincr = false;
}
rp2040_rxdmasetup(priv->rxdmach, (uintptr_t)src, (uintptr_t)rxbuffer,
nwords, priv->rxconfig);
}
/****************************************************************************
* Name: spi_dmatrxwait
*
* Description:
* Wait for TX RX DMA to complete.
*
****************************************************************************/
static void spi_dmatrxwait(FAR struct rp2040_spidev_s *priv)
{
uint32_t val;
if (nxsem_wait_uninterruptible(&priv->dmasem) != OK)
{
spierr("dma error\n");
}
if (nxsem_wait_uninterruptible(&priv->dmasem) != OK)
{
spierr("dma error\n");
}
rp2040_dmastop(priv->txdmach);
rp2040_dmastop(priv->rxdmach);
val = spi_getreg(priv, RP2040_SPI_SSPDMACR_OFFSET);
val &= ~(RP2040_SPI_SSPDMACR_RXDMAE | RP2040_SPI_SSPDMACR_TXDMAE);
spi_putreg(priv, RP2040_SPI_SSPDMACR_OFFSET, val);
}
#endif
#endif #endif

View File

@ -28,7 +28,7 @@
#include <nuttx/config.h> #include <nuttx/config.h>
#include <nuttx/spi/spi.h> #include <nuttx/spi/spi.h>
#include "hardware/rp2040_spi.h" #include "hardware/rp2040_spi.h"
#ifdef CONFIG_RP2040_DMAC #ifdef CONFIG_RP2040_SPI_DMA
#include "rp2040_dmac.h" #include "rp2040_dmac.h"
#endif #endif
@ -58,9 +58,6 @@
* SPI driver to the SPI MMC/SD driver). * SPI driver to the SPI MMC/SD driver).
*/ */
#define RP2040_SPI_DMAC_CHTYPE_TX (0)
#define RP2040_SPI_DMAC_CHTYPE_RX (1)
/**************************************************************************** /****************************************************************************
* Public Types * Public Types
****************************************************************************/ ****************************************************************************/
@ -100,28 +97,6 @@ extern "C"
FAR struct spi_dev_s *rp2040_spibus_initialize(int port); FAR struct spi_dev_s *rp2040_spibus_initialize(int port);
/****************************************************************************
* Name: rp2040_spi_dmaconfig
*
* Description:
* Enable DMA configuration.
*
* Input Parameter:
* port - Port number
* chtype - Channel type(TX or RX)
* handle - DMA channel handle
* conf - DMA configuration
*
* Returned Value:
* None
*
****************************************************************************/
#ifdef CONFIG_RP2040_DMAC
void rp2040_spi_dmaconfig(int port, int chtype, DMA_HANDLE handle,
FAR dma_config_t *conf);
#endif
/**************************************************************************** /****************************************************************************
* Name: rp2040_spiXselect, rp2040_spiXstatus, and rp2040_spiXcmddata * Name: rp2040_spiXselect, rp2040_spiXstatus, and rp2040_spiXcmddata
* *

View File

@ -11,7 +11,8 @@ Currently only the following devices are suppored.
- UART (console port) - UART (console port)
- GPIO 0 (UART0 TX) and GPIO 1 (UART0 RX) are used for the console. - GPIO 0 (UART0 TX) and GPIO 1 (UART0 RX) are used for the console.
- I2C - I2C
- SPI (DMA transfer is not supported yet) - SPI
- DMAC
- Flash ROM Boot - Flash ROM Boot
- SRAM Boot - SRAM Boot
- If Pico SDK is available, nuttx.uf2 file which can be used in - If Pico SDK is available, nuttx.uf2 file which can be used in