[PATCH] spi: meson-spicc: add support for DMA
Neil Armstrong
narmstrong at baylibre.com
Tue Sep 13 07:03:03 PDT 2022
The Amlogic SPICC controller has an embedded DMA but suffers from some
drawbacks that makes it complex to use:
- the DMA can only load FIFO at each multiple of 8 bytes, thus only 64 bits
per words transfers would work as expected.
- the DMA loads the FIFO in little-endian, producing little-endian bus output,
and it doesn't seem this can be changed
Nevertheless it's always good to have a DMA accelerated SPI transfer mode.
Signed-off-by: Neil Armstrong <narmstrong at baylibre.com>
---
drivers/spi/spi-meson-spicc.c | 197 +++++++++++++++++++++++++++++++---
1 file changed, 181 insertions(+), 16 deletions(-)
diff --git a/drivers/spi/spi-meson-spicc.c b/drivers/spi/spi-meson-spicc.c
index 6974a1c947aa..5efd5396020d 100644
--- a/drivers/spi/spi-meson-spicc.c
+++ b/drivers/spi/spi-meson-spicc.c
@@ -11,6 +11,7 @@
#include <linux/clk.h>
#include <linux/clk-provider.h>
#include <linux/device.h>
+#include <linux/dma-mapping.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/module.h>
@@ -33,6 +34,11 @@
* - CS management is dumb, and goes UP between every burst, so is really a
* "Data Valid" signal than a Chip Select, GPIO link should be used instead
* to have a CS go down over the full transfer
+ * DMA support:
+ * - the DMA can only load FIFO at each multiple of 8 bytes, thus only 64 bits
+ * per words transfers would work as expected.
+ * - the DMA loads the FIFO in little-endian, producing little-endian bus output,
+ * and it doesn't seem this can be changed
*/
#define SPICC_MAX_BURST 128
@@ -128,6 +134,14 @@
#define SPICC_DWADDR 0x24 /* Write Address of DMA */
+#define SPICC_LD_CNTL0 0x28
+#define DMA_READ_COUNTER_EN BIT(4)
+#define DMA_WRITE_COUNTER_EN BIT(5)
+
+#define SPICC_LD_CNTL1 0x2c
+#define DMA_READ_COUNTER GENMASK(15, 0)
+#define DMA_WRITE_COUNTER GENMASK(31, 16)
+
#define SPICC_ENH_CTL0 0x38 /* Enhanced Feature */
#define SPICC_ENH_CLK_CS_DELAY_MASK GENMASK(15, 0)
#define SPICC_ENH_DATARATE_MASK GENMASK(23, 16)
@@ -158,6 +172,8 @@ struct meson_spicc_device {
struct clk *pclk;
struct clk_divider pow2_div;
struct clk *clk;
+ bool using_dma;
+ bool is_dma_mapped;
struct spi_message *message;
struct spi_transfer *xfer;
const struct meson_spicc_data *data;
@@ -171,6 +187,96 @@ struct meson_spicc_device {
#define pow2_clk_to_spicc(_div) container_of(_div, struct meson_spicc_device, pow2_div)
+static int meson_spicc_dma_map(struct meson_spicc_device *spicc,
+ struct spi_transfer *t)
+{
+ struct device *dev = spicc->master->dev.parent;
+
+ if (t->tx_buf) {
+ t->tx_dma = dma_map_single(dev, (void *)t->tx_buf, t->len,
+ DMA_TO_DEVICE);
+ if (dma_mapping_error(dev, t->tx_dma)) {
+ dev_err(dev, "tx_dma map failed\n");
+ return -ENOMEM;
+ }
+ }
+
+ if (t->rx_buf) {
+ t->rx_dma = dma_map_single(dev, t->rx_buf, t->len,
+ DMA_FROM_DEVICE);
+ if (dma_mapping_error(dev, t->rx_dma)) {
+ if (t->tx_buf)
+ dma_unmap_single(dev, t->tx_dma, t->len,
+ DMA_TO_DEVICE);
+ dev_err(dev, "rx_dma map failed\n");
+ return -ENOMEM;
+ }
+ }
+
+ return 0;
+}
+
+static void meson_spicc_dma_unmap(struct meson_spicc_device *spicc,
+ struct spi_transfer *t)
+{
+ struct device *dev = spicc->master->dev.parent;
+
+ if (t->tx_buf)
+ dma_unmap_single(dev, t->tx_dma, t->len, DMA_TO_DEVICE);
+ if (t->rx_buf)
+ dma_unmap_single(dev, t->rx_dma, t->len, DMA_FROM_DEVICE);
+}
+
+static void meson_spicc_setup_dma_burst(struct meson_spicc_device *spicc)
+{
+ unsigned int words;
+ unsigned int count_en = 0;
+ unsigned int txfifo_thres = 0;
+ unsigned int read_req = 1;
+ unsigned int rxfifo_thres = 31;
+ unsigned int write_req = 1;
+ unsigned int ld_ctr1 = 0;
+
+ words = min_t(unsigned int,
+ spicc->xfer_remain / spicc->bytes_per_word,
+ spicc->data->fifo_size);
+
+ writel_bits_relaxed(SPICC_BURSTLENGTH_MASK,
+ FIELD_PREP(SPICC_BURSTLENGTH_MASK,
+ words - 1),
+ spicc->base + SPICC_CONREG);
+
+ /* Setup Xfer variables */
+ spicc->xfer_remain -= words * spicc->bytes_per_word;
+
+ if (spicc->tx_buf) {
+ count_en |= DMA_WRITE_COUNTER_EN;
+ txfifo_thres = spicc->bytes_per_word - 1;
+ write_req = words - 1;
+ ld_ctr1 |= FIELD_PREP(DMA_WRITE_COUNTER, 1);
+ }
+
+ if (spicc->rx_buf) {
+ count_en |= DMA_READ_COUNTER_EN;
+ rxfifo_thres = words - 1;
+ read_req = words - 1;
+ ld_ctr1 |= FIELD_PREP(DMA_READ_COUNTER, 1);
+ }
+
+ /* Enable DMA write/read counter */
+ writel_relaxed(count_en, spicc->base + SPICC_LD_CNTL0);
+
+ /* Setup burst length */
+ writel_relaxed(ld_ctr1, spicc->base + SPICC_LD_CNTL1);
+
+ writel_relaxed(SPICC_DMA_ENABLE | SPICC_DMA_URGENT |
+ FIELD_PREP(SPICC_TXFIFO_THRESHOLD_MASK, txfifo_thres) |
+ FIELD_PREP(SPICC_READ_BURST_MASK, read_req) |
+ FIELD_PREP(SPICC_RXFIFO_THRESHOLD_MASK, rxfifo_thres) |
+ FIELD_PREP(SPICC_WRITE_BURST_MASK, write_req),
+ spicc->base + SPICC_DMAREG);
+}
+
static void meson_spicc_oen_enable(struct meson_spicc_device *spicc)
{
u32 conf;
@@ -273,25 +379,52 @@ static irqreturn_t meson_spicc_irq(int irq, void *data)
{
struct meson_spicc_device *spicc = (void *) data;
- writel_bits_relaxed(SPICC_TC, SPICC_TC, spicc->base + SPICC_STATREG);
+ /* Sometimes, TC gets triggered while the RX fifo isn't fully flushed */
+ if (spicc->using_dma) {
+ unsigned int rxfifo_count = FIELD_GET(SPICC_RXCNT_MASK,
+ readl_relaxed(spicc->base + SPICC_TESTREG));
+
+ if (rxfifo_count)
+ return IRQ_HANDLED;
+ }
+
+ writel_relaxed(SPICC_TC, spicc->base + SPICC_STATREG);
/* Empty RX FIFO */
- meson_spicc_rx(spicc);
+ if (!spicc->using_dma)
+ meson_spicc_rx(spicc);
if (!spicc->xfer_remain) {
/* Disable all IRQs */
writel(0, spicc->base + SPICC_INTREG);
+ if (spicc->using_dma) {
+ /* Disable DMA transfer */
+ spicc->using_dma = 0;
+ writel_relaxed(0, spicc->base + SPICC_DMAREG);
+ writel_relaxed(0, spicc->base + SPICC_LD_CNTL0);
+ writel_relaxed(0, spicc->base + SPICC_LD_CNTL1);
+
+ /* Unmap DMA if was locally remapped */
+ if (!spicc->is_dma_mapped)
+ meson_spicc_dma_unmap(spicc, spicc->xfer);
+ }
+
spi_finalize_current_transfer(spicc->master);
return IRQ_HANDLED;
}
- /* Setup burst */
- meson_spicc_setup_burst(spicc);
+ if (spicc->using_dma) {
+ /* Setup burst */
+ meson_spicc_setup_dma_burst(spicc);
+ } else {
+ /* Setup burst */
+ meson_spicc_setup_burst(spicc);
- /* Start burst */
- writel_bits_relaxed(SPICC_XCH, SPICC_XCH, spicc->base + SPICC_CONREG);
+ /* Start burst */
+ writel_bits_relaxed(SPICC_XCH, SPICC_XCH, spicc->base + SPICC_CONREG);
+ }
return IRQ_HANDLED;
}
@@ -387,6 +520,9 @@ static int meson_spicc_transfer_one(struct spi_master *master,
{
struct meson_spicc_device *spicc = spi_master_get_devdata(master);
+ if (xfer->bits_per_word % 8 || xfer->bits_per_word > 64)
+ return -EINVAL;
+
/* Store current transfer */
spicc->xfer = xfer;
@@ -407,11 +543,40 @@ static int meson_spicc_transfer_one(struct spi_master *master,
meson_spicc_reset_fifo(spicc);
- /* Setup burst */
- meson_spicc_setup_burst(spicc);
-
- /* Start burst */
- writel_bits_relaxed(SPICC_XCH, SPICC_XCH, spicc->base + SPICC_CONREG);
+ /* By default, disable DMA transfer */
+ spicc->using_dma = 0;
+ writel_relaxed(0, spicc->base + SPICC_DMAREG);
+ writel_relaxed(0, spicc->base + SPICC_LD_CNTL0);
+ writel_relaxed(0, spicc->base + SPICC_LD_CNTL1);
+
+ /* DMA transfer can only operate 64bits words in Little-Endian */
+ if (xfer->bits_per_word == 64 &&
+ (spi->mode & SPI_LSB_FIRST) == SPI_LSB_FIRST &&
+ (spicc->is_dma_mapped || !meson_spicc_dma_map(spicc, xfer))) {
+ spicc->using_dma = 1;
+
+ /* Write DMA addresses */
+ writel_relaxed(xfer->tx_dma, spicc->base + SPICC_DRADDR);
+ writel_relaxed(xfer->rx_dma, spicc->base + SPICC_DWADDR);
+
+ /* Setup DMA period */
+ writel_relaxed(xfer->speed_hz >> 25,
+ spicc->base + SPICC_PERIODREG);
+
+ /* Setup burst */
+ meson_spicc_setup_dma_burst(spicc);
+
+ /* Enable DMA transfers */
+ writel_bits_relaxed(SPICC_SMC, SPICC_SMC,
+ spicc->base + SPICC_CONREG);
+ } else if(xfer->bits_per_word < 64 && !(spi->mode & SPI_LSB_FIRST)) {
+ /* Setup burst */
+ meson_spicc_setup_burst(spicc);
+
+ /* Start burst */
+ writel_bits_relaxed(SPICC_XCH, SPICC_XCH, spicc->base + SPICC_CONREG);
+ } else
+ return -EINVAL;
/* Enable interrupts */
writel_relaxed(SPICC_TC_EN, spicc->base + SPICC_INTREG);
@@ -471,6 +636,9 @@ static int meson_spicc_prepare_message(struct spi_master *master,
writel_bits_relaxed(SPICC_LBC_W1, 0, spicc->base + SPICC_TESTREG);
+ /* Store current message */
+ spicc->is_dma_mapped = message->is_dma_mapped;
+
return 0;
}
@@ -802,11 +970,8 @@ static int meson_spicc_probe(struct platform_device *pdev)
master->num_chipselect = 4;
master->dev.of_node = pdev->dev.of_node;
- master->mode_bits = SPI_CPHA | SPI_CPOL | SPI_CS_HIGH;
- master->bits_per_word_mask = SPI_BPW_MASK(32) |
- SPI_BPW_MASK(24) |
- SPI_BPW_MASK(16) |
- SPI_BPW_MASK(8);
+ /* SPI_LSB_FIRST is for DMA mode only */
+ master->mode_bits = SPI_CPHA | SPI_CPOL | SPI_CS_HIGH | SPI_LSB_FIRST;
master->flags = (SPI_MASTER_MUST_RX | SPI_MASTER_MUST_TX);
master->min_speed_hz = spicc->data->min_speed_hz;
master->max_speed_hz = spicc->data->max_speed_hz;
--
2.25.1
More information about the linux-amlogic
mailing list