Skip to content

Commit

Permalink
arch/xtensa/esp32s2: Fix SPI DMA implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
lucasssvaz authored and xiaoxiang781216 committed Jun 14, 2023
1 parent b2ff151 commit 4bcf454
Show file tree
Hide file tree
Showing 2 changed files with 166 additions and 41 deletions.
198 changes: 157 additions & 41 deletions arch/xtensa/src/esp32s2/esp32s2_spi.c
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@

/* SPI DMA reset before exchange */

#define SPI_DMA_RESET_MASK (SPI_DMA_AFIFO_RST_M | SPI_RX_AFIFO_RST_M)
#define SPI_DMA_RESET_MASK (SPI_AHBM_RST | SPI_AHBM_FIFO_RST)

#endif

Expand All @@ -96,6 +96,13 @@

#define SPI_DEFAULT_MODE (SPIDEV_MODE0)

/* Helper for applying the mask for a given register field.
* Mask is determined by the macros suffixed with _V and _S from the
* peripheral register description.
*/

#define VALUE_MASK(_val, _field) ((_val & (_field##_V)) << (_field##_S))

/* SPI Maximum buffer size in bytes */

#define SPI_MAX_BUF_SIZE (64)
Expand Down Expand Up @@ -359,7 +366,7 @@ static struct esp32s2_spi_priv_s esp32s2_spi2_priv =
.cpuint = -ENOMEM,
.dma_channel = -1,
.dma_rxdesc = spi2_dma_rxdesc,
.dma_txdesc = spi2_dma_rxdesc,
.dma_txdesc = spi2_dma_txdesc,
#endif
.frequency = 0,
.actual = 0,
Expand Down Expand Up @@ -444,7 +451,7 @@ static struct esp32s2_spi_priv_s esp32s2_spi3_priv =
.cpuint = -ENOMEM,
.dma_channel = -1,
.dma_rxdesc = spi3_dma_rxdesc,
.dma_txdesc = spi3_dma_rxdesc,
.dma_txdesc = spi3_dma_txdesc,
#endif
.frequency = 0,
.actual = 0,
Expand Down Expand Up @@ -897,88 +904,193 @@ static int esp32s2_spi_hwfeatures(struct spi_dev_s *dev,
****************************************************************************/

#if defined(CONFIG_ESP32S2_SPI2_DMA) || defined(CONFIG_ESP32S2_SPI3_DMA)
/****************************************************************************
* Name: esp32s2_spi_dma_exchange
*
* Description:
* Exchange a block of data from SPI by DMA.
*
* Input Parameters:
* priv - SPI private state data
* txbuffer - A pointer to the buffer of data to be sent
* rxbuffer - A pointer to the buffer in which to receive data
* nwords - the length of data that 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.
*
****************************************************************************/

static void esp32s2_spi_dma_exchange(struct esp32s2_spi_priv_s *priv,
const void *txbuffer,
void *rxbuffer,
uint32_t nwords)
{
const struct esp32s2_dmadesc_s *dma_rxdesc = priv->dma_rxdesc;
const struct esp32s2_dmadesc_s *dma_txdesc = priv->dma_txdesc;
const uint32_t total = nwords * (priv->nbits / 8);
const int32_t channel = priv->dma_channel;
const uint32_t id = priv->config->id;
uint32_t bytes = total;
uint32_t n;
uint8_t *tp;
uint8_t *rp;
uint32_t n;
uint32_t regval;
struct esp32s2_dmadesc_s *dma_tx_desc;
struct esp32s2_dmadesc_s *dma_rx_desc;
#if defined(CONFIG_ESP32S2_SPIRAM) && defined(CONFIG_ESP32S2_SPI3_DMA)
uint8_t *alloctp = NULL;
uint8_t *allocrp = NULL;
#endif

/* Define these constants outside transfer loop to avoid wasting CPU time
* with register offset calculation.
*/

const uint32_t id = priv->config->id;
const uintptr_t spi_dma_in_link_reg = SPI_DMA_IN_LINK_REG(id);
const uintptr_t spi_dma_out_link_reg = SPI_DMA_OUT_LINK_REG(id);
const uintptr_t spi_slave_reg = SPI_SLAVE_REG(id);
const uintptr_t spi_dma_conf_reg = SPI_DMA_CONF_REG(id);
const uintptr_t spi_mosi_dlen_reg = SPI_MOSI_DLEN_REG(id);
const uintptr_t spi_miso_dlen_reg = SPI_MISO_DLEN_REG(id);
const uintptr_t spi_user_reg = SPI_USER_REG(id);
const uintptr_t spi_cmd_reg = SPI_CMD_REG(id);

DEBUGASSERT((txbuffer != NULL) || (rxbuffer != NULL));

spiinfo("nwords=%" PRIu32 "\n", nwords);

tp = (uint8_t *)txbuffer;
rp = (uint8_t *)rxbuffer;
#if defined(CONFIG_ESP32S2_SPIRAM) && defined(CONFIG_ESP32S2_SPI3_DMA)
if (esp32s2_ptr_extram(txbuffer) && (id == 3))
{
# ifdef CONFIG_MM_KERNEL_HEAP
alloctp = kmm_malloc(total);
# elif defined(CONFIG_XTENSA_IMEM_USE_SEPARATE_HEAP)
alloctp = xtensa_imm_malloc(total);
# endif

DEBUGASSERT(alloctp != NULL);
memcpy(alloctp, txbuffer, total);
tp = alloctp;
}
else
#endif
{
tp = (uint8_t *)txbuffer;
}

#if defined(CONFIG_ESP32S2_SPIRAM) && defined(CONFIG_ESP32S2_SPI3_DMA)
if (esp32s2_ptr_extram(rxbuffer) && (id == 3))
{
# ifdef CONFIG_MM_KERNEL_HEAP
allocrp = kmm_malloc(total);
# elif defined(CONFIG_XTENSA_IMEM_USE_SEPARATE_HEAP)
allocrp = xtensa_imm_malloc(total);
# endif

DEBUGASSERT(allocrp != NULL);
rp = allocrp;
}
else
#endif
{
rp = (uint8_t *)rxbuffer;
}

if (tp == NULL)
{
tp = rp;
}

esp32s2_spi_set_regbits(SPI_DMA_INT_CLR_REG(id),
SPI_IN_DONE_INT_CLR_M);
#ifdef CONFIG_ESP32S2_SPI2_DMA
if (id == 2)
{
dma_tx_desc = spi2_dma_txdesc;
dma_rx_desc = spi2_dma_rxdesc;
}
#endif

esp32s2_spi_set_regbits(SPI_DMA_INT_ENA_REG(id),
SPI_IN_DONE_INT_ENA_M);
#ifdef CONFIG_ESP32S2_SPI3_DMA
if (id == 3)
{
dma_tx_desc = spi3_dma_txdesc;
dma_rx_desc = spi3_dma_rxdesc;
}
#endif

esp32s2_spi_clr_regbits(spi_slave_reg, SPI_TRANS_DONE_M);
esp32s2_spi_set_regbits(spi_slave_reg, SPI_INT_EN_M);

while (bytes != 0)
{
/* Enable SPI DMA TX */
putreg32(0, spi_dma_in_link_reg);
putreg32(0, spi_dma_out_link_reg);

esp32s2_spi_set_regbits(SPI_DMA_CONF_REG(id),
SPI_DMA_TX_ENA_M);
esp32s2_spi_set_regbits(spi_slave_reg, SPI_SOFT_RESET_M);
esp32s2_spi_clr_regbits(spi_slave_reg, SPI_SOFT_RESET_M);

n = esp32s2_dma_setup(channel, true, dma_txdesc, SPI_DMA_DESC_NUM,
tp, bytes);
esp32s2_dma_enable(channel, true);
esp32s2_spi_set_regbits(spi_dma_conf_reg, SPI_DMA_RESET_MASK);
esp32s2_spi_clr_regbits(spi_dma_conf_reg, SPI_DMA_RESET_MASK);

putreg32((n * 8 - 1), SPI_MOSI_DLEN_REG(id));
esp32s2_spi_set_regbits(SPI_USER_REG(id),
SPI_USR_MOSI_M);
n = esp32s2_dma_init(dma_tx_desc, SPI_DMA_DESC_NUM, tp, bytes);

regval = VALUE_MASK((uintptr_t)dma_tx_desc, SPI_OUTLINK_ADDR);
regval |= SPI_OUTLINK_START_M;
putreg32(regval, spi_dma_out_link_reg);
putreg32((n * 8 - 1), spi_mosi_dlen_reg);
esp32s2_spi_set_regbits(spi_user_reg, SPI_USR_MOSI_M);

tp += n;

if (rp != NULL)
{
/* Enable SPI DMA RX */

esp32s2_spi_set_regbits(SPI_DMA_CONF_REG(id),
SPI_DMA_RX_ENA_M);

esp32s2_dma_setup(channel, false, dma_rxdesc, SPI_DMA_DESC_NUM,
rp, bytes);
esp32s2_dma_enable(channel, false);
esp32s2_dma_init(dma_rx_desc, SPI_DMA_DESC_NUM, rp, bytes);

esp32s2_spi_set_regbits(SPI_USER_REG(id),
SPI_USR_MISO_M);
regval = VALUE_MASK((uintptr_t)dma_rx_desc, SPI_INLINK_ADDR);
regval |= SPI_INLINK_START_M;
putreg32(regval, spi_dma_in_link_reg);
putreg32((n * 8 - 1), spi_miso_dlen_reg);
esp32s2_spi_set_regbits(spi_user_reg, SPI_USR_MISO_M);

rp += n;
}
else
{
esp32s2_spi_clr_regbits(SPI_USER_REG(id), SPI_USR_MISO_M);
esp32s2_spi_clr_regbits(spi_user_reg, SPI_USR_MISO_M);
}

/* Trigger start of user-defined transaction for master. */

esp32s2_spi_set_regbits(SPI_CMD_REG(id), SPI_USR_M);
esp32s2_spi_set_regbits(spi_cmd_reg, SPI_USR_M);

esp32s2_spi_sem_waitdone(priv);

bytes -= n;
}

esp32s2_spi_clr_regbits(SPI_DMA_INT_ENA_REG(id),
SPI_IN_DONE_INT_ENA_M);
esp32s2_spi_clr_regbits(spi_slave_reg, SPI_INT_EN_M);

#if defined(CONFIG_ESP32S2_SPIRAM) && defined(CONFIG_ESP32S2_SPI3_DMA)
if (allocrp)
{
memcpy(rxbuffer, allocrp, total);
# ifdef CONFIG_MM_KERNEL_HEAP
kmm_free(allocrp);
# elif defined(CONFIG_XTENSA_IMEM_USE_SEPARATE_HEAP)
xtensa_imm_free(allocrp);
# endif
}
#endif

#if defined(CONFIG_ESP32S2_SPIRAM) && defined(CONFIG_ESP32S2_SPI3_DMA)
if (alloctp)
{
# ifdef CONFIG_MM_KERNEL_HEAP
kmm_free(alloctp);
# elif defined(CONFIG_XTENSA_IMEM_USE_SEPARATE_HEAP)
xtensa_imm_free(alloctp);
# endif
}
#endif
}
#endif

Expand Down Expand Up @@ -1321,6 +1433,7 @@ void esp32s2_spi_dma_init(struct spi_dev_s *dev)
{
struct esp32s2_spi_priv_s *priv = (struct esp32s2_spi_priv_s *)dev;
const uint32_t id = priv->config->id;
uint32_t regval;

/* Enable DMA clock for the SPI peripheral */

Expand All @@ -1330,9 +1443,12 @@ void esp32s2_spi_dma_init(struct spi_dev_s *dev)

modifyreg32(SYSTEM_PERIP_RST_EN0_REG, priv->config->dma_rst_bit, 0);

/* Initialize DMA controller */
/* Enable DMA burst */

esp32s2_dma_init();
regval = SPI_OUT_DATA_BURST_EN_M |
SPI_INDSCR_BURST_EN_M |
SPI_OUTDSCR_BURST_EN_M;
putreg32(regval, SPI_DMA_CONF_REG(id));

/* Disable segment transaction mode for SPI Master */

Expand Down
9 changes: 9 additions & 0 deletions arch/xtensa/src/esp32s2/hardware/esp32s2_spi.h
Original file line number Diff line number Diff line change
Expand Up @@ -1015,6 +1015,15 @@
#define SPI_INT_RD_BUF_DONE_EN_V 0x00000001
#define SPI_INT_RD_BUF_DONE_EN_S 5

/* SPI_INT_EN : R/W ;bitpos:[11:5] ;default: 7'b001_0000 ; */

/* Description: Interrupt enable bits for the 7 sources */

#define SPI_INT_EN 0x0000007F
#define SPI_INT_EN_M ((SPI_INT_EN_V)<<(SPI_INT_EN_S))
#define SPI_INT_EN_V 0x7F
#define SPI_INT_EN_S 5

/* SPI_TRANS_DONE : R/W; bitpos: [4]; default: 0;
* The interrupt raw bit for the completion of any operation in both the
* master mode and the slave mode. Can not be changed by CONF_buf.
Expand Down

0 comments on commit 4bcf454

Please sign in to comment.