arch/imx9_lpspi: Use DMA safe buffers to do the DMA transfers

Using user allocated buffers for DMA transfers is not safe for two reasons:
- User space memory is virtual memory, DMA needs physical memory
- User memory buffer alignment cannot be guaranteed -> cache line ops
  are not safe
This commit is contained in:
Ville Juven 2024-04-18 15:46:31 +03:00 committed by Xiang Xiao
parent 3079caf2ce
commit 3b5b755d1e
2 changed files with 52 additions and 9 deletions

View File

@ -718,6 +718,13 @@ config IMX9_LPSPI_DMATHRESHOLD
by polling logic. But we need a threshold value to determine what by polling logic. But we need a threshold value to determine what
is small. is small.
config IMX9_LPSPI_DMA_BUFFER_SIZE
int "LPSPI DMA buffer size"
default 4096
depends on IMX9_LPSPI_DMA
---help---
Set the LPSPI driver DMA buffer size.
config IMX9_LPSPI1_DMA config IMX9_LPSPI1_DMA
bool "LPSPI1 DMA" bool "LPSPI1 DMA"
default n default n

View File

@ -67,6 +67,7 @@
#include "arm64_internal.h" #include "arm64_internal.h"
#include "imx9_ccm.h" #include "imx9_ccm.h"
#include "imx9_clockconfig.h" #include "imx9_clockconfig.h"
#include "imx9_dma_alloc.h"
#include "imx9_gpio.h" #include "imx9_gpio.h"
#include "imx9_iomuxc.h" #include "imx9_iomuxc.h"
#include "imx9_lpspi.h" #include "imx9_lpspi.h"
@ -131,6 +132,8 @@ struct imx9_lpspidev_s
DMACH_HANDLE txdma; /* DMA channel handle for TX transfers */ DMACH_HANDLE txdma; /* DMA channel handle for TX transfers */
sem_t rxsem; /* Wait for RX DMA to complete */ sem_t rxsem; /* Wait for RX DMA to complete */
sem_t txsem; /* Wait for TX DMA to complete */ sem_t txsem; /* Wait for TX DMA to complete */
void *txbuf; /* Driver DMA safe buffer for TX */
void *rxbuf; /* Driver DMA safe buffer for RX */
#endif #endif
}; };
@ -1305,13 +1308,13 @@ static void imx9_lpspi_exchange(struct spi_dev_s *dev,
const void *txbuffer, const void *txbuffer,
void *rxbuffer, size_t nwords) void *rxbuffer, size_t nwords)
{ {
struct imx9_lpspidev_s *priv = (struct imx9_lpspidev_s *)dev;
int ret; int ret;
size_t adjust; size_t adjust;
ssize_t nbytes; ssize_t nbytes;
static uint8_t rxdummy[4] aligned_data(4); static uint8_t rxdummy[4] aligned_data(4);
static const uint16_t txdummy = 0xffff; static const uint16_t txdummy = 0xffff;
uint32_t regval; uint32_t regval;
struct imx9_lpspidev_s *priv = (struct imx9_lpspidev_s *)dev;
DEBUGASSERT(priv != NULL); DEBUGASSERT(priv != NULL);
DEBUGASSERT(priv && priv->spibase); DEBUGASSERT(priv && priv->spibase);
@ -1338,6 +1341,17 @@ static void imx9_lpspi_exchange(struct spi_dev_s *dev,
return; return;
} }
/* Check if the transfer is too long */
if (nbytes > CONFIG_IMX9_LPSPI_DMA_BUFFER_SIZE)
{
/* Transfer is too long, revert to slow non-DMA method */
spiwarn("frame %lu too long, fall back to no DMA transfer\n", nbytes);
imx9_lpspi_exchange_nodma(dev, txbuffer, rxbuffer, nwords);
return;
}
/* Disable SPI when we are configuring it */ /* Disable SPI when we are configuring it */
imx9_lpspi_modifyreg32(priv, IMX9_LPSPI_CR_OFFSET, LPSPI_CR_MEN, 0); imx9_lpspi_modifyreg32(priv, IMX9_LPSPI_CR_OFFSET, LPSPI_CR_MEN, 0);
@ -1362,13 +1376,22 @@ static void imx9_lpspi_exchange(struct spi_dev_s *dev,
if (txbuffer) if (txbuffer)
{ {
up_clean_dcache((uintptr_t)txbuffer, (uintptr_t)txbuffer + nbytes); /* Move the user data to device internal buffer */
memcpy(priv->txbuf, txbuffer, nbytes);
/* And flush it to RAM */
up_clean_dcache((uintptr_t)priv->txbuf,
(uintptr_t)priv->txbuf + nbytes);
} }
if (rxbuffer) if (rxbuffer)
{ {
up_invalidate_dcache((uintptr_t)rxbuffer, /* Prepare the RX buffer for DMA */
(uintptr_t)rxbuffer + nbytes);
up_invalidate_dcache((uintptr_t)priv->rxbuf,
(uintptr_t)priv->rxbuf + nbytes);
} }
/* Set up the DMA */ /* Set up the DMA */
@ -1378,7 +1401,7 @@ static void imx9_lpspi_exchange(struct spi_dev_s *dev,
struct imx9_edma_xfrconfig_s config; struct imx9_edma_xfrconfig_s config;
config.saddr = priv->spibase + IMX9_LPSPI_RDR_OFFSET; config.saddr = priv->spibase + IMX9_LPSPI_RDR_OFFSET;
config.daddr = (uintptr_t) (rxbuffer ? rxbuffer : rxdummy); config.daddr = (uintptr_t) (rxbuffer ? priv->rxbuf : rxdummy);
config.soff = 0; config.soff = 0;
config.doff = rxbuffer ? adjust : 0; config.doff = rxbuffer ? adjust : 0;
config.iter = nbytes; config.iter = nbytes;
@ -1391,7 +1414,7 @@ static void imx9_lpspi_exchange(struct spi_dev_s *dev,
#endif #endif
imx9_dmach_xfrsetup(priv->rxdma, &config); imx9_dmach_xfrsetup(priv->rxdma, &config);
config.saddr = (uintptr_t) (txbuffer ? txbuffer : &txdummy); config.saddr = (uintptr_t) (txbuffer ? priv->txbuf : &txdummy);
config.daddr = priv->spibase + IMX9_LPSPI_TDR_OFFSET; config.daddr = priv->spibase + IMX9_LPSPI_TDR_OFFSET;
config.soff = txbuffer ? adjust : 0; config.soff = txbuffer ? adjust : 0;
config.doff = 0; config.doff = 0;
@ -1436,10 +1459,16 @@ static void imx9_lpspi_exchange(struct spi_dev_s *dev,
imx9_lpspi_putreg32(priv, IMX9_LPSPI_DER_OFFSET, 0); imx9_lpspi_putreg32(priv, IMX9_LPSPI_DER_OFFSET, 0);
if (rxbuffer) if (rxbuffer && ret >= 0)
{ {
up_invalidate_dcache((uintptr_t)rxbuffer, /* Flush the RX data to ram */
(uintptr_t)rxbuffer + nbytes);
up_invalidate_dcache((uintptr_t)priv->rxbuf,
(uintptr_t)priv->rxbuf + nbytes);
/* Copy data to user buffer */
memcpy(rxbuffer, priv->rxbuf, nbytes);
} }
} }
@ -2044,6 +2073,13 @@ struct spi_dev_s *imx9_lpspibus_initialize(int bus)
priv->rxdma = imx9_dmach_alloc(priv->rxch, 0); priv->rxdma = imx9_dmach_alloc(priv->rxch, 0);
DEBUGASSERT(priv->rxdma && priv->txdma); DEBUGASSERT(priv->rxdma && priv->txdma);
} }
if (priv->txbuf == NULL && priv->rxbuf == NULL)
{
priv->txbuf = imx9_dma_alloc(CONFIG_IMX9_LPSPI_DMA_BUFFER_SIZE);
priv->rxbuf = imx9_dma_alloc(CONFIG_IMX9_LPSPI_DMA_BUFFER_SIZE);
DEBUGASSERT(priv->txbuf && priv->rxbuf);
}
} }
else else
{ {