From de4c2875a5ff2c886df60f2086c6affca83f890a Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 29 May 2020 16:11:50 +0300 Subject: spi: dw: Set xfer effective_speed_hz Seeing DW APB SSI controller doesn't support setting the exactly requested SPI bus frequency, but only a rounded frequency determined by means of the odd-numbered half-worded reference clock divider, it would be good to tune the SPI core up and initialize the current transfer effective_speed_hz. By doing so the core will be able to execute the xfer-related delays with better accuracy. Signed-off-by: Serge Semin Cc: Georgy Vlasov Cc: Ramil Zaripov Cc: Alexey Malahov Cc: Thomas Bogendoerfer Cc: Arnd Bergmann Cc: Feng Tang Cc: Andy Shevchenko Cc: Rob Herring Cc: linux-mips@vger.kernel.org Cc: devicetree@vger.kernel.org Link: https://lore.kernel.org/r/20200529131205.31838-2-Sergey.Semin@baikalelectronics.ru Signed-off-by: Mark Brown --- drivers/spi/spi-dw.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers') diff --git a/drivers/spi/spi-dw.c b/drivers/spi/spi-dw.c index 1edb8cdd11ee..e6fc0bcf214d 100644 --- a/drivers/spi/spi-dw.c +++ b/drivers/spi/spi-dw.c @@ -352,6 +352,7 @@ static int dw_spi_transfer_one(struct spi_controller *master, spi_set_clk(dws, chip->clk_div); } + transfer->effective_speed_hz = dws->max_freq / chip->clk_div; dws->n_bytes = DIV_ROUND_UP(transfer->bits_per_word, BITS_PER_BYTE); cr0 = dws->update_cr0(master, spi, transfer); -- cgit v1.2.3 From f0410bbf7d0fb80149e3b17d11d31f5b5197873e Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 29 May 2020 16:11:51 +0300 Subject: spi: dw: Return any value retrieved from the dma_transfer callback DW APB SSI DMA-part of the driver may need to perform the requested SPI-transfer synchronously. In that case the dma_transfer() callback will return 0 as a marker of the SPI transfer being finished so the SPI core doesn't need to wait and may proceed with the SPI message trasnfers pumping procedure. This will be needed to fix the problem when DMA transactions are finished, but there is still data left in the SPI Tx/Rx FIFOs being sent/received. But for now make dma_transfer to return 1 as the normal dw_spi_transfer_one() method. Signed-off-by: Serge Semin Cc: Georgy Vlasov Cc: Ramil Zaripov Cc: Alexey Malahov Cc: Thomas Bogendoerfer Cc: Arnd Bergmann Cc: Andy Shevchenko Cc: Feng Tang Cc: Rob Herring Cc: linux-mips@vger.kernel.org Cc: devicetree@vger.kernel.org Link: https://lore.kernel.org/r/20200529131205.31838-3-Sergey.Semin@baikalelectronics.ru Signed-off-by: Mark Brown --- drivers/spi/spi-dw-mid.c | 2 +- drivers/spi/spi-dw.c | 7 ++----- 2 files changed, 3 insertions(+), 6 deletions(-) (limited to 'drivers') diff --git a/drivers/spi/spi-dw-mid.c b/drivers/spi/spi-dw-mid.c index b1710132b7b2..7ff1acaa55f8 100644 --- a/drivers/spi/spi-dw-mid.c +++ b/drivers/spi/spi-dw-mid.c @@ -288,7 +288,7 @@ static int mid_spi_dma_transfer(struct dw_spi *dws, struct spi_transfer *xfer) dma_async_issue_pending(dws->txchan); } - return 0; + return 1; } static void mid_spi_dma_stop(struct dw_spi *dws) diff --git a/drivers/spi/spi-dw.c b/drivers/spi/spi-dw.c index e6fc0bcf214d..73af58dbcd6a 100644 --- a/drivers/spi/spi-dw.c +++ b/drivers/spi/spi-dw.c @@ -389,11 +389,8 @@ static int dw_spi_transfer_one(struct spi_controller *master, spi_enable_chip(dws, 1); - if (dws->dma_mapped) { - ret = dws->dma_ops->dma_transfer(dws, transfer); - if (ret < 0) - return ret; - } + if (dws->dma_mapped) + return dws->dma_ops->dma_transfer(dws, transfer); return 1; } -- cgit v1.2.3 From bdbdf0f06337d3661b64c0288c291cb06624065e Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 29 May 2020 16:11:52 +0300 Subject: spi: dw: Locally wait for the DMA transfers completion In general each DMA-based SPI transfer can be split up into two stages: DMA data transmission/reception and SPI-bus transmission/reception. DMA asynchronous transactions completion can be tracked by means of the DMA async Tx-descriptor completion callback. But that callback being called indicates that the DMA transfer has been finished, it doesn't mean that SPI data transmission is also done. Moreover in fact it isn't for at least Tx-only SPI transfers. Upon DMA transfer completion some data is left in the Tx FIFO and being pushed out by the SPI controller. So in order to make sure that an SPI transfer is completely pushed to the SPI-bus, the driver has to wait for both DMA transaction and the SPI-bus transmission/reception are finished. Note if there is a way to asynchronously track the former event by means of the DMA async Tx callback, there isn't easy one for the later (IRQ-based solution won't work since SPI controller doesn't notify about Rx FIFO being empty). The DMA transfer completion callback isn't suitable to wait for the SPI controller activity finish either. The callback might (in case of DW DMAC it will) be called in the tasklet context. Waiting for the SPI controller to complete the transfer might take a considerable amount of time since SPI-bus might be pretty slow. In this case delaying the execution in the tasklet atomic context might cause significant system performance drop. So to speak the best option we've got to solve the problem is to consequently wait for both stages being finished in the locally implemented SPI transfer execution procedure even if it costs us of the local wait-function re-implementation. In this case we don't need to use the SPI-core transfer-wait functionality, but we'll make sure that all DMA and SPI-bus transactions are completely finished before the SPI-core transfer_one callback returns. In this commit we provide an implementation of the DMA-transfers completion wait functionality. The DW APB SSI DMA-specific SPI transfer_one function waits for both Tx and Rx DMA transfers being finished, and only then exits with zero returned signalling to the SPI core that the SPI transfer is finished. This implementation is fully equivalent to the currently used DMA-execution-SPI-core-wait algorithm. The SPI-bus transmission/reception wait methods will be added in the follow-up commits. Signed-off-by: Serge Semin Cc: Georgy Vlasov Cc: Ramil Zaripov Cc: Alexey Malahov Cc: Thomas Bogendoerfer Cc: Arnd Bergmann Cc: Feng Tang Cc: Andy Shevchenko Cc: Rob Herring Cc: linux-mips@vger.kernel.org Cc: devicetree@vger.kernel.org Link: https://lore.kernel.org/r/20200529131205.31838-4-Sergey.Semin@baikalelectronics.ru Signed-off-by: Mark Brown --- drivers/spi/spi-dw-mid.c | 44 ++++++++++++++++++++++++++++++++++++++++---- drivers/spi/spi-dw.h | 2 ++ 2 files changed, 42 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/spi/spi-dw-mid.c b/drivers/spi/spi-dw-mid.c index 7ff1acaa55f8..355b641c4483 100644 --- a/drivers/spi/spi-dw-mid.c +++ b/drivers/spi/spi-dw-mid.c @@ -11,9 +11,11 @@ #include "spi-dw.h" #ifdef CONFIG_SPI_DW_MID_DMA +#include #include #include #include +#include #include #include @@ -66,6 +68,8 @@ static int mid_spi_dma_init_mfld(struct device *dev, struct dw_spi *dws) dws->master->dma_rx = dws->rxchan; dws->master->dma_tx = dws->txchan; + init_completion(&dws->dma_completion); + return 0; free_rxchan: @@ -91,6 +95,8 @@ static int mid_spi_dma_init_generic(struct device *dev, struct dw_spi *dws) dws->master->dma_rx = dws->rxchan; dws->master->dma_tx = dws->txchan; + init_completion(&dws->dma_completion); + return 0; } @@ -121,7 +127,7 @@ static irqreturn_t dma_transfer(struct dw_spi *dws) dev_err(&dws->master->dev, "%s: FIFO overrun/underrun\n", __func__); dws->master->cur_msg->status = -EIO; - spi_finalize_current_transfer(dws->master); + complete(&dws->dma_completion); return IRQ_HANDLED; } @@ -142,6 +148,29 @@ static enum dma_slave_buswidth convert_dma_width(u8 n_bytes) { return DMA_SLAVE_BUSWIDTH_UNDEFINED; } +static int dw_spi_dma_wait(struct dw_spi *dws, struct spi_transfer *xfer) +{ + unsigned long long ms; + + ms = xfer->len * MSEC_PER_SEC * BITS_PER_BYTE; + do_div(ms, xfer->effective_speed_hz); + ms += ms + 200; + + if (ms > UINT_MAX) + ms = UINT_MAX; + + ms = wait_for_completion_timeout(&dws->dma_completion, + msecs_to_jiffies(ms)); + + if (ms == 0) { + dev_err(&dws->master->cur_msg->spi->dev, + "DMA transaction timed out\n"); + return -ETIMEDOUT; + } + + return 0; +} + /* * dws->dma_chan_busy is set before the dma transfer starts, callback for tx * channel will clear a corresponding bit. @@ -155,7 +184,7 @@ static void dw_spi_dma_tx_done(void *arg) return; dw_writel(dws, DW_SPI_DMACR, 0); - spi_finalize_current_transfer(dws->master); + complete(&dws->dma_completion); } static struct dma_async_tx_descriptor *dw_spi_dma_prepare_tx(struct dw_spi *dws, @@ -204,7 +233,7 @@ static void dw_spi_dma_rx_done(void *arg) return; dw_writel(dws, DW_SPI_DMACR, 0); - spi_finalize_current_transfer(dws->master); + complete(&dws->dma_completion); } static struct dma_async_tx_descriptor *dw_spi_dma_prepare_rx(struct dw_spi *dws, @@ -260,6 +289,8 @@ static int mid_spi_dma_setup(struct dw_spi *dws, struct spi_transfer *xfer) /* Set the interrupt mask */ spi_umask_intr(dws, imr); + reinit_completion(&dws->dma_completion); + dws->transfer_handler = dma_transfer; return 0; @@ -268,6 +299,7 @@ static int mid_spi_dma_setup(struct dw_spi *dws, struct spi_transfer *xfer) static int mid_spi_dma_transfer(struct dw_spi *dws, struct spi_transfer *xfer) { struct dma_async_tx_descriptor *txdesc, *rxdesc; + int ret; /* Prepare the TX dma transfer */ txdesc = dw_spi_dma_prepare_tx(dws, xfer); @@ -288,7 +320,11 @@ static int mid_spi_dma_transfer(struct dw_spi *dws, struct spi_transfer *xfer) dma_async_issue_pending(dws->txchan); } - return 1; + ret = dw_spi_dma_wait(dws, xfer); + if (ret) + return ret; + + return 0; } static void mid_spi_dma_stop(struct dw_spi *dws) diff --git a/drivers/spi/spi-dw.h b/drivers/spi/spi-dw.h index 79782e93eb12..9585d0c83a6d 100644 --- a/drivers/spi/spi-dw.h +++ b/drivers/spi/spi-dw.h @@ -2,6 +2,7 @@ #ifndef DW_SPI_HEADER_H #define DW_SPI_HEADER_H +#include #include #include #include @@ -145,6 +146,7 @@ struct dw_spi { unsigned long dma_chan_busy; dma_addr_t dma_addr; /* phy address of the Data register */ const struct dw_spi_dma_ops *dma_ops; + struct completion dma_completion; #ifdef CONFIG_DEBUG_FS struct dentry *debugfs; -- cgit v1.2.3 From 1ade2d8a72f9240825f6be050f0d49c840f7daeb Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 29 May 2020 16:11:53 +0300 Subject: spi: dw: Add SPI Tx-done wait method to DMA-based transfer Since DMA transfers are performed asynchronously with actual SPI bus transfers, then even if DMA transactions are finished it doesn't mean all data is actually pushed to the SPI bus. Some data might still be in the controller FIFO. This is specifically true for Tx-only transfers. In this case if the next SPI transfer is recharged while a tail of the previous one is still in FIFO, we'll loose that tail data. In order to fix that problem let's add the wait procedure of the Tx SPI transfer completion after the DMA transactions are finished. Fixes: 7063c0d942a1 ("spi/dw_spi: add DMA support") Co-developed-by: Georgy Vlasov Signed-off-by: Georgy Vlasov Signed-off-by: Serge Semin Cc: Ramil Zaripov Cc: Alexey Malahov Cc: Thomas Bogendoerfer Cc: Arnd Bergmann Cc: Andy Shevchenko Cc: Feng Tang Cc: Rob Herring Cc: linux-mips@vger.kernel.org Cc: devicetree@vger.kernel.org Link: https://lore.kernel.org/r/20200529131205.31838-5-Sergey.Semin@baikalelectronics.ru Signed-off-by: Mark Brown --- drivers/spi/spi-dw-mid.c | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) (limited to 'drivers') diff --git a/drivers/spi/spi-dw-mid.c b/drivers/spi/spi-dw-mid.c index 355b641c4483..846e3db91329 100644 --- a/drivers/spi/spi-dw-mid.c +++ b/drivers/spi/spi-dw-mid.c @@ -19,6 +19,7 @@ #include #include +#define WAIT_RETRIES 5 #define RX_BUSY 0 #define TX_BUSY 1 @@ -171,6 +172,33 @@ static int dw_spi_dma_wait(struct dw_spi *dws, struct spi_transfer *xfer) return 0; } +static inline bool dw_spi_dma_tx_busy(struct dw_spi *dws) +{ + return !(dw_readl(dws, DW_SPI_SR) & SR_TF_EMPT); +} + +static int dw_spi_dma_wait_tx_done(struct dw_spi *dws, + struct spi_transfer *xfer) +{ + int retry = WAIT_RETRIES; + struct spi_delay delay; + u32 nents; + + nents = dw_readl(dws, DW_SPI_TXFLR); + delay.unit = SPI_DELAY_UNIT_SCK; + delay.value = nents * dws->n_bytes * BITS_PER_BYTE; + + while (dw_spi_dma_tx_busy(dws) && retry--) + spi_delay_exec(&delay, xfer); + + if (retry < 0) { + dev_err(&dws->master->dev, "Tx hanged up\n"); + return -EIO; + } + + return 0; +} + /* * dws->dma_chan_busy is set before the dma transfer starts, callback for tx * channel will clear a corresponding bit. @@ -324,6 +352,12 @@ static int mid_spi_dma_transfer(struct dw_spi *dws, struct spi_transfer *xfer) if (ret) return ret; + if (txdesc && dws->master->cur_msg->status == -EINPROGRESS) { + ret = dw_spi_dma_wait_tx_done(dws, xfer); + if (ret) + return ret; + } + return 0; } -- cgit v1.2.3 From 33726eff3d98e643f7d7a0940f4024844b430c82 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 29 May 2020 16:11:54 +0300 Subject: spi: dw: Add SPI Rx-done wait method to DMA-based transfer Having any data left in the Rx FIFO after the DMA engine claimed it has finished all DMA transactions is an abnormal situation, since the DW SPI controller driver expects to have all the data being fetched and placed into the SPI Rx buffer at that moment. In case if that has happened we hopefully assume that the DMA engine may still be doing the data fetching, thus we give it sometime to finish. If after a short period of time the data is still left in the Rx FIFO, the driver will give up waiting and return an error indicating that the SPI controller/DMA engine must have hung up or failed at some point of doing their duties. Fixes: 7063c0d942a1 ("spi/dw_spi: add DMA support") Co-developed-by: Georgy Vlasov Signed-off-by: Georgy Vlasov Signed-off-by: Serge Semin Cc: Ramil Zaripov Cc: Alexey Malahov Cc: Thomas Bogendoerfer Cc: Arnd Bergmann Cc: Andy Shevchenko Cc: Feng Tang Cc: Rob Herring Cc: linux-mips@vger.kernel.org Cc: devicetree@vger.kernel.org Link: https://lore.kernel.org/r/20200529131205.31838-6-Sergey.Semin@baikalelectronics.ru Signed-off-by: Mark Brown --- drivers/spi/spi-dw-mid.c | 48 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/spi/spi-dw-mid.c b/drivers/spi/spi-dw-mid.c index 846e3db91329..abd6955ad1f7 100644 --- a/drivers/spi/spi-dw-mid.c +++ b/drivers/spi/spi-dw-mid.c @@ -248,6 +248,49 @@ static struct dma_async_tx_descriptor *dw_spi_dma_prepare_tx(struct dw_spi *dws, return txdesc; } +static inline bool dw_spi_dma_rx_busy(struct dw_spi *dws) +{ + return !!(dw_readl(dws, DW_SPI_SR) & SR_RF_NOT_EMPT); +} + +static int dw_spi_dma_wait_rx_done(struct dw_spi *dws) +{ + int retry = WAIT_RETRIES; + struct spi_delay delay; + unsigned long ns, us; + u32 nents; + + /* + * It's unlikely that DMA engine is still doing the data fetching, but + * if it's let's give it some reasonable time. The timeout calculation + * is based on the synchronous APB/SSI reference clock rate, on a + * number of data entries left in the Rx FIFO, times a number of clock + * periods normally needed for a single APB read/write transaction + * without PREADY signal utilized (which is true for the DW APB SSI + * controller). + */ + nents = dw_readl(dws, DW_SPI_RXFLR); + ns = 4U * NSEC_PER_SEC / dws->max_freq * nents; + if (ns <= NSEC_PER_USEC) { + delay.unit = SPI_DELAY_UNIT_NSECS; + delay.value = ns; + } else { + us = DIV_ROUND_UP(ns, NSEC_PER_USEC); + delay.unit = SPI_DELAY_UNIT_USECS; + delay.value = clamp_val(us, 0, USHRT_MAX); + } + + while (dw_spi_dma_rx_busy(dws) && retry--) + spi_delay_exec(&delay, NULL); + + if (retry < 0) { + dev_err(&dws->master->dev, "Rx hanged up\n"); + return -EIO; + } + + return 0; +} + /* * dws->dma_chan_busy is set before the dma transfer starts, callback for rx * channel will clear a corresponding bit. @@ -358,7 +401,10 @@ static int mid_spi_dma_transfer(struct dw_spi *dws, struct spi_transfer *xfer) return ret; } - return 0; + if (rxdesc && dws->master->cur_msg->status == -EINPROGRESS) + ret = dw_spi_dma_wait_rx_done(dws); + + return ret; } static void mid_spi_dma_stop(struct dw_spi *dws) -- cgit v1.2.3 From c534df9d6225314d1403e4330a22d68c35e0eb55 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 29 May 2020 16:11:55 +0300 Subject: spi: dw: Parameterize the DMA Rx/Tx burst length It isn't good to have numeric literals in the code especially if there are multiple of them and they are related. Let's replace the Tx and Rx burst level literals with the corresponding constants. Co-developed-by: Georgy Vlasov Co-developed-by: Ramil Zaripov Signed-off-by: Georgy Vlasov Signed-off-by: Ramil Zaripov Signed-off-by: Serge Semin Reviewed-by: Andy Shevchenko Cc: Alexey Malahov Cc: Thomas Bogendoerfer Cc: Arnd Bergmann Cc: Feng Tang Cc: Rob Herring Cc: linux-mips@vger.kernel.org Cc: devicetree@vger.kernel.org Link: https://lore.kernel.org/r/20200529131205.31838-7-Sergey.Semin@baikalelectronics.ru Signed-off-by: Mark Brown --- drivers/spi/spi-dw-mid.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/spi/spi-dw-mid.c b/drivers/spi/spi-dw-mid.c index abd6955ad1f7..189b517f77fc 100644 --- a/drivers/spi/spi-dw-mid.c +++ b/drivers/spi/spi-dw-mid.c @@ -21,7 +21,9 @@ #define WAIT_RETRIES 5 #define RX_BUSY 0 +#define RX_BURST_LEVEL 16 #define TX_BUSY 1 +#define TX_BURST_LEVEL 16 static bool mid_spi_dma_chan_filter(struct dma_chan *chan, void *param) { @@ -227,7 +229,7 @@ static struct dma_async_tx_descriptor *dw_spi_dma_prepare_tx(struct dw_spi *dws, memset(&txconf, 0, sizeof(txconf)); txconf.direction = DMA_MEM_TO_DEV; txconf.dst_addr = dws->dma_addr; - txconf.dst_maxburst = 16; + txconf.dst_maxburst = TX_BURST_LEVEL; txconf.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; txconf.dst_addr_width = convert_dma_width(dws->n_bytes); txconf.device_fc = false; @@ -319,7 +321,7 @@ static struct dma_async_tx_descriptor *dw_spi_dma_prepare_rx(struct dw_spi *dws, memset(&rxconf, 0, sizeof(rxconf)); rxconf.direction = DMA_DEV_TO_MEM; rxconf.src_addr = dws->dma_addr; - rxconf.src_maxburst = 16; + rxconf.src_maxburst = RX_BURST_LEVEL; rxconf.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; rxconf.src_addr_width = convert_dma_width(dws->n_bytes); rxconf.device_fc = false; @@ -344,8 +346,8 @@ static int mid_spi_dma_setup(struct dw_spi *dws, struct spi_transfer *xfer) { u16 imr = 0, dma_ctrl = 0; - dw_writel(dws, DW_SPI_DMARDLR, 0xf); - dw_writel(dws, DW_SPI_DMATDLR, 0x10); + dw_writel(dws, DW_SPI_DMARDLR, RX_BURST_LEVEL - 1); + dw_writel(dws, DW_SPI_DMATDLR, TX_BURST_LEVEL); if (xfer->tx_buf) { dma_ctrl |= SPI_DMA_TDMAE; -- cgit v1.2.3 From 0b2b66514fc9971b3a6002ba038d74f77705fd34 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 29 May 2020 16:11:56 +0300 Subject: spi: dw: Use DMA max burst to set the request thresholds Each channel of DMA controller may have a limited length of burst transaction (number of IO operations performed at ones in a single DMA client request). This parameter can be used to setup the most optimal DMA Tx/Rx data level values. In order to avoid the Tx buffer overrun we can set the DMA Tx level to be of FIFO depth minus the maximum burst transactions length. To prevent the Rx buffer underflow the DMA Rx level should be set to the maximum burst transactions length. This commit setups the DMA channels and the DW SPI DMA Tx/Rx levels in accordance with these rules. Signed-off-by: Serge Semin Reviewed-by: Andy Shevchenko Cc: Alexey Malahov Cc: Thomas Bogendoerfer Cc: Arnd Bergmann Cc: Feng Tang Cc: Rob Herring Cc: linux-mips@vger.kernel.org Cc: devicetree@vger.kernel.org Link: https://lore.kernel.org/r/20200529131205.31838-8-Sergey.Semin@baikalelectronics.ru Signed-off-by: Mark Brown --- drivers/spi/spi-dw-mid.c | 37 +++++++++++++++++++++++++++++++++---- drivers/spi/spi-dw.h | 2 ++ 2 files changed, 35 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/spi/spi-dw-mid.c b/drivers/spi/spi-dw-mid.c index 189b517f77fc..1cf9e3ffe07b 100644 --- a/drivers/spi/spi-dw-mid.c +++ b/drivers/spi/spi-dw-mid.c @@ -36,6 +36,31 @@ static bool mid_spi_dma_chan_filter(struct dma_chan *chan, void *param) return true; } +static void mid_spi_maxburst_init(struct dw_spi *dws) +{ + struct dma_slave_caps caps; + u32 max_burst, def_burst; + int ret; + + def_burst = dws->fifo_len / 2; + + ret = dma_get_slave_caps(dws->rxchan, &caps); + if (!ret && caps.max_burst) + max_burst = caps.max_burst; + else + max_burst = RX_BURST_LEVEL; + + dws->rxburst = min(max_burst, def_burst); + + ret = dma_get_slave_caps(dws->txchan, &caps); + if (!ret && caps.max_burst) + max_burst = caps.max_burst; + else + max_burst = TX_BURST_LEVEL; + + dws->txburst = min(max_burst, def_burst); +} + static int mid_spi_dma_init_mfld(struct device *dev, struct dw_spi *dws) { struct dw_dma_slave slave = { @@ -73,6 +98,8 @@ static int mid_spi_dma_init_mfld(struct device *dev, struct dw_spi *dws) init_completion(&dws->dma_completion); + mid_spi_maxburst_init(dws); + return 0; free_rxchan: @@ -100,6 +127,8 @@ static int mid_spi_dma_init_generic(struct device *dev, struct dw_spi *dws) init_completion(&dws->dma_completion); + mid_spi_maxburst_init(dws); + return 0; } @@ -229,7 +258,7 @@ static struct dma_async_tx_descriptor *dw_spi_dma_prepare_tx(struct dw_spi *dws, memset(&txconf, 0, sizeof(txconf)); txconf.direction = DMA_MEM_TO_DEV; txconf.dst_addr = dws->dma_addr; - txconf.dst_maxburst = TX_BURST_LEVEL; + txconf.dst_maxburst = dws->txburst; txconf.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; txconf.dst_addr_width = convert_dma_width(dws->n_bytes); txconf.device_fc = false; @@ -321,7 +350,7 @@ static struct dma_async_tx_descriptor *dw_spi_dma_prepare_rx(struct dw_spi *dws, memset(&rxconf, 0, sizeof(rxconf)); rxconf.direction = DMA_DEV_TO_MEM; rxconf.src_addr = dws->dma_addr; - rxconf.src_maxburst = RX_BURST_LEVEL; + rxconf.src_maxburst = dws->rxburst; rxconf.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; rxconf.src_addr_width = convert_dma_width(dws->n_bytes); rxconf.device_fc = false; @@ -346,8 +375,8 @@ static int mid_spi_dma_setup(struct dw_spi *dws, struct spi_transfer *xfer) { u16 imr = 0, dma_ctrl = 0; - dw_writel(dws, DW_SPI_DMARDLR, RX_BURST_LEVEL - 1); - dw_writel(dws, DW_SPI_DMATDLR, TX_BURST_LEVEL); + dw_writel(dws, DW_SPI_DMARDLR, dws->rxburst - 1); + dw_writel(dws, DW_SPI_DMATDLR, dws->fifo_len - dws->txburst); if (xfer->tx_buf) { dma_ctrl |= SPI_DMA_TDMAE; diff --git a/drivers/spi/spi-dw.h b/drivers/spi/spi-dw.h index 9585d0c83a6d..9247670fcdfb 100644 --- a/drivers/spi/spi-dw.h +++ b/drivers/spi/spi-dw.h @@ -142,7 +142,9 @@ struct dw_spi { /* DMA info */ struct dma_chan *txchan; + u32 txburst; struct dma_chan *rxchan; + u32 rxburst; unsigned long dma_chan_busy; dma_addr_t dma_addr; /* phy address of the Data register */ const struct dw_spi_dma_ops *dma_ops; -- cgit v1.2.3 From 46164fde6b7890e7a3982d54549947c8394c0192 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 29 May 2020 16:11:57 +0300 Subject: spi: dw: Fix Rx-only DMA transfers Tx-only DMA transfers are working perfectly fine since in this case the code just ignores the Rx FIFO overflow interrupts. But it turns out the SPI Rx-only transfers are broken since nothing pushing any data to the shift registers, so the Rx FIFO is left empty and the SPI core subsystems just returns a timeout error. Since DW DMAC driver doesn't support something like cyclic write operations of a single byte to a device register, the only way to support the Rx-only SPI transfers is to fake it by using a dummy Tx-buffer. This is what we intend to fix in this commit by setting the SPI_CONTROLLER_MUST_TX flag for DMA-capable platform. Signed-off-by: Serge Semin Reviewed-by: Andy Shevchenko Cc: Georgy Vlasov Cc: Ramil Zaripov Cc: Alexey Malahov Cc: Thomas Bogendoerfer Cc: Arnd Bergmann Cc: Feng Tang Cc: Rob Herring Cc: linux-mips@vger.kernel.org Cc: devicetree@vger.kernel.org Link: https://lore.kernel.org/r/20200529131205.31838-9-Sergey.Semin@baikalelectronics.ru Signed-off-by: Mark Brown --- drivers/spi/spi-dw.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers') diff --git a/drivers/spi/spi-dw.c b/drivers/spi/spi-dw.c index 73af58dbcd6a..1f1a3536b7cf 100644 --- a/drivers/spi/spi-dw.c +++ b/drivers/spi/spi-dw.c @@ -515,6 +515,7 @@ int dw_spi_add_host(struct device *dev, struct dw_spi *dws) dev_warn(dev, "DMA init failed\n"); } else { master->can_dma = dws->dma_ops->can_dma; + master->flags |= SPI_CONTROLLER_MUST_TX; } } -- cgit v1.2.3 From 77ccff803d27279ccc100dc906c6f456c8fa515c Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 29 May 2020 16:11:58 +0300 Subject: spi: dw: Add core suffix to the DW APB SSI core source file Generic DMA support is going to be part of the DW APB SSI core object. In order to preserve the kernel loadable module name as spi-dw.ko, let's add the "-core" suffix to the object with generic DW APB SSI code and build it into the target spi-dw.ko driver. Suggested-by: Andy Shevchenko Signed-off-by: Serge Semin Reviewed-by: Andy Shevchenko Cc: Georgy Vlasov Cc: Ramil Zaripov Cc: Alexey Malahov Cc: Thomas Bogendoerfer Cc: Feng Tang Cc: Rob Herring Cc: Arnd Bergmann Cc: linux-mips@vger.kernel.org Cc: devicetree@vger.kernel.org Link: https://lore.kernel.org/r/20200529131205.31838-10-Sergey.Semin@baikalelectronics.ru Signed-off-by: Mark Brown --- drivers/spi/Makefile | 1 + drivers/spi/spi-dw-core.c | 577 ++++++++++++++++++++++++++++++++++++++++++++++ drivers/spi/spi-dw.c | 577 ---------------------------------------------- 3 files changed, 578 insertions(+), 577 deletions(-) create mode 100644 drivers/spi/spi-dw-core.c delete mode 100644 drivers/spi/spi-dw.c (limited to 'drivers') diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index 889368f4ad53..840a5a1c655e 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -36,6 +36,7 @@ obj-$(CONFIG_SPI_COLDFIRE_QSPI) += spi-coldfire-qspi.o obj-$(CONFIG_SPI_DAVINCI) += spi-davinci.o obj-$(CONFIG_SPI_DLN2) += spi-dln2.o obj-$(CONFIG_SPI_DESIGNWARE) += spi-dw.o +spi-dw-y := spi-dw-core.o obj-$(CONFIG_SPI_DW_MMIO) += spi-dw-mmio.o obj-$(CONFIG_SPI_DW_PCI) += spi-dw-midpci.o spi-dw-midpci-objs := spi-dw-pci.o spi-dw-mid.o diff --git a/drivers/spi/spi-dw-core.c b/drivers/spi/spi-dw-core.c new file mode 100644 index 000000000000..1f1a3536b7cf --- /dev/null +++ b/drivers/spi/spi-dw-core.c @@ -0,0 +1,577 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Designware SPI core controller driver (refer pxa2xx_spi.c) + * + * Copyright (c) 2009, Intel Corporation. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "spi-dw.h" + +#ifdef CONFIG_DEBUG_FS +#include +#endif + +/* Slave spi_dev related */ +struct chip_data { + u8 tmode; /* TR/TO/RO/EEPROM */ + u8 type; /* SPI/SSP/MicroWire */ + + u16 clk_div; /* baud rate divider */ + u32 speed_hz; /* baud rate */ +}; + +#ifdef CONFIG_DEBUG_FS +#define SPI_REGS_BUFSIZE 1024 +static ssize_t dw_spi_show_regs(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct dw_spi *dws = file->private_data; + char *buf; + u32 len = 0; + ssize_t ret; + + buf = kzalloc(SPI_REGS_BUFSIZE, GFP_KERNEL); + if (!buf) + return 0; + + len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len, + "%s registers:\n", dev_name(&dws->master->dev)); + len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len, + "=================================\n"); + len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len, + "CTRLR0: \t0x%08x\n", dw_readl(dws, DW_SPI_CTRLR0)); + len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len, + "CTRLR1: \t0x%08x\n", dw_readl(dws, DW_SPI_CTRLR1)); + len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len, + "SSIENR: \t0x%08x\n", dw_readl(dws, DW_SPI_SSIENR)); + len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len, + "SER: \t\t0x%08x\n", dw_readl(dws, DW_SPI_SER)); + len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len, + "BAUDR: \t\t0x%08x\n", dw_readl(dws, DW_SPI_BAUDR)); + len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len, + "TXFTLR: \t0x%08x\n", dw_readl(dws, DW_SPI_TXFTLR)); + len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len, + "RXFTLR: \t0x%08x\n", dw_readl(dws, DW_SPI_RXFTLR)); + len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len, + "TXFLR: \t\t0x%08x\n", dw_readl(dws, DW_SPI_TXFLR)); + len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len, + "RXFLR: \t\t0x%08x\n", dw_readl(dws, DW_SPI_RXFLR)); + len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len, + "SR: \t\t0x%08x\n", dw_readl(dws, DW_SPI_SR)); + len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len, + "IMR: \t\t0x%08x\n", dw_readl(dws, DW_SPI_IMR)); + len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len, + "ISR: \t\t0x%08x\n", dw_readl(dws, DW_SPI_ISR)); + len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len, + "DMACR: \t\t0x%08x\n", dw_readl(dws, DW_SPI_DMACR)); + len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len, + "DMATDLR: \t0x%08x\n", dw_readl(dws, DW_SPI_DMATDLR)); + len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len, + "DMARDLR: \t0x%08x\n", dw_readl(dws, DW_SPI_DMARDLR)); + len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len, + "=================================\n"); + + ret = simple_read_from_buffer(user_buf, count, ppos, buf, len); + kfree(buf); + return ret; +} + +static const struct file_operations dw_spi_regs_ops = { + .owner = THIS_MODULE, + .open = simple_open, + .read = dw_spi_show_regs, + .llseek = default_llseek, +}; + +static int dw_spi_debugfs_init(struct dw_spi *dws) +{ + char name[32]; + + snprintf(name, 32, "dw_spi%d", dws->master->bus_num); + dws->debugfs = debugfs_create_dir(name, NULL); + if (!dws->debugfs) + return -ENOMEM; + + debugfs_create_file("registers", S_IFREG | S_IRUGO, + dws->debugfs, (void *)dws, &dw_spi_regs_ops); + return 0; +} + +static void dw_spi_debugfs_remove(struct dw_spi *dws) +{ + debugfs_remove_recursive(dws->debugfs); +} + +#else +static inline int dw_spi_debugfs_init(struct dw_spi *dws) +{ + return 0; +} + +static inline void dw_spi_debugfs_remove(struct dw_spi *dws) +{ +} +#endif /* CONFIG_DEBUG_FS */ + +void dw_spi_set_cs(struct spi_device *spi, bool enable) +{ + struct dw_spi *dws = spi_controller_get_devdata(spi->controller); + bool cs_high = !!(spi->mode & SPI_CS_HIGH); + + /* + * DW SPI controller demands any native CS being set in order to + * proceed with data transfer. So in order to activate the SPI + * communications we must set a corresponding bit in the Slave + * Enable register no matter whether the SPI core is configured to + * support active-high or active-low CS level. + */ + if (cs_high == enable) + dw_writel(dws, DW_SPI_SER, BIT(spi->chip_select)); + else if (dws->cs_override) + dw_writel(dws, DW_SPI_SER, 0); +} +EXPORT_SYMBOL_GPL(dw_spi_set_cs); + +/* Return the max entries we can fill into tx fifo */ +static inline u32 tx_max(struct dw_spi *dws) +{ + u32 tx_left, tx_room, rxtx_gap; + + tx_left = (dws->tx_end - dws->tx) / dws->n_bytes; + tx_room = dws->fifo_len - dw_readl(dws, DW_SPI_TXFLR); + + /* + * Another concern is about the tx/rx mismatch, we + * though to use (dws->fifo_len - rxflr - txflr) as + * one maximum value for tx, but it doesn't cover the + * data which is out of tx/rx fifo and inside the + * shift registers. So a control from sw point of + * view is taken. + */ + rxtx_gap = ((dws->rx_end - dws->rx) - (dws->tx_end - dws->tx)) + / dws->n_bytes; + + return min3(tx_left, tx_room, (u32) (dws->fifo_len - rxtx_gap)); +} + +/* Return the max entries we should read out of rx fifo */ +static inline u32 rx_max(struct dw_spi *dws) +{ + u32 rx_left = (dws->rx_end - dws->rx) / dws->n_bytes; + + return min_t(u32, rx_left, dw_readl(dws, DW_SPI_RXFLR)); +} + +static void dw_writer(struct dw_spi *dws) +{ + u32 max; + u16 txw = 0; + + spin_lock(&dws->buf_lock); + max = tx_max(dws); + while (max--) { + /* Set the tx word if the transfer's original "tx" is not null */ + if (dws->tx_end - dws->len) { + if (dws->n_bytes == 1) + txw = *(u8 *)(dws->tx); + else + txw = *(u16 *)(dws->tx); + } + dw_write_io_reg(dws, DW_SPI_DR, txw); + dws->tx += dws->n_bytes; + } + spin_unlock(&dws->buf_lock); +} + +static void dw_reader(struct dw_spi *dws) +{ + u32 max; + u16 rxw; + + spin_lock(&dws->buf_lock); + max = rx_max(dws); + while (max--) { + rxw = dw_read_io_reg(dws, DW_SPI_DR); + /* Care rx only if the transfer's original "rx" is not null */ + if (dws->rx_end - dws->len) { + if (dws->n_bytes == 1) + *(u8 *)(dws->rx) = rxw; + else + *(u16 *)(dws->rx) = rxw; + } + dws->rx += dws->n_bytes; + } + spin_unlock(&dws->buf_lock); +} + +static void int_error_stop(struct dw_spi *dws, const char *msg) +{ + spi_reset_chip(dws); + + dev_err(&dws->master->dev, "%s\n", msg); + dws->master->cur_msg->status = -EIO; + spi_finalize_current_transfer(dws->master); +} + +static irqreturn_t interrupt_transfer(struct dw_spi *dws) +{ + u16 irq_status = dw_readl(dws, DW_SPI_ISR); + + /* Error handling */ + if (irq_status & (SPI_INT_TXOI | SPI_INT_RXOI | SPI_INT_RXUI)) { + dw_readl(dws, DW_SPI_ICR); + int_error_stop(dws, "interrupt_transfer: fifo overrun/underrun"); + return IRQ_HANDLED; + } + + dw_reader(dws); + if (dws->rx_end == dws->rx) { + spi_mask_intr(dws, SPI_INT_TXEI); + spi_finalize_current_transfer(dws->master); + return IRQ_HANDLED; + } + if (irq_status & SPI_INT_TXEI) { + spi_mask_intr(dws, SPI_INT_TXEI); + dw_writer(dws); + /* Enable TX irq always, it will be disabled when RX finished */ + spi_umask_intr(dws, SPI_INT_TXEI); + } + + return IRQ_HANDLED; +} + +static irqreturn_t dw_spi_irq(int irq, void *dev_id) +{ + struct spi_controller *master = dev_id; + struct dw_spi *dws = spi_controller_get_devdata(master); + u16 irq_status = dw_readl(dws, DW_SPI_ISR) & 0x3f; + + if (!irq_status) + return IRQ_NONE; + + if (!master->cur_msg) { + spi_mask_intr(dws, SPI_INT_TXEI); + return IRQ_HANDLED; + } + + return dws->transfer_handler(dws); +} + +/* Configure CTRLR0 for DW_apb_ssi */ +u32 dw_spi_update_cr0(struct spi_controller *master, struct spi_device *spi, + struct spi_transfer *transfer) +{ + struct chip_data *chip = spi_get_ctldata(spi); + u32 cr0; + + /* Default SPI mode is SCPOL = 0, SCPH = 0 */ + cr0 = (transfer->bits_per_word - 1) + | (chip->type << SPI_FRF_OFFSET) + | ((((spi->mode & SPI_CPOL) ? 1 : 0) << SPI_SCOL_OFFSET) | + (((spi->mode & SPI_CPHA) ? 1 : 0) << SPI_SCPH_OFFSET) | + (((spi->mode & SPI_LOOP) ? 1 : 0) << SPI_SRL_OFFSET)) + | (chip->tmode << SPI_TMOD_OFFSET); + + return cr0; +} +EXPORT_SYMBOL_GPL(dw_spi_update_cr0); + +/* Configure CTRLR0 for DWC_ssi */ +u32 dw_spi_update_cr0_v1_01a(struct spi_controller *master, + struct spi_device *spi, + struct spi_transfer *transfer) +{ + struct chip_data *chip = spi_get_ctldata(spi); + u32 cr0; + + /* CTRLR0[ 4: 0] Data Frame Size */ + cr0 = (transfer->bits_per_word - 1); + + /* CTRLR0[ 7: 6] Frame Format */ + cr0 |= chip->type << DWC_SSI_CTRLR0_FRF_OFFSET; + + /* + * SPI mode (SCPOL|SCPH) + * CTRLR0[ 8] Serial Clock Phase + * CTRLR0[ 9] Serial Clock Polarity + */ + cr0 |= ((spi->mode & SPI_CPOL) ? 1 : 0) << DWC_SSI_CTRLR0_SCPOL_OFFSET; + cr0 |= ((spi->mode & SPI_CPHA) ? 1 : 0) << DWC_SSI_CTRLR0_SCPH_OFFSET; + + /* CTRLR0[11:10] Transfer Mode */ + cr0 |= chip->tmode << DWC_SSI_CTRLR0_TMOD_OFFSET; + + /* CTRLR0[13] Shift Register Loop */ + cr0 |= ((spi->mode & SPI_LOOP) ? 1 : 0) << DWC_SSI_CTRLR0_SRL_OFFSET; + + return cr0; +} +EXPORT_SYMBOL_GPL(dw_spi_update_cr0_v1_01a); + +static int dw_spi_transfer_one(struct spi_controller *master, + struct spi_device *spi, struct spi_transfer *transfer) +{ + struct dw_spi *dws = spi_controller_get_devdata(master); + struct chip_data *chip = spi_get_ctldata(spi); + unsigned long flags; + u8 imask = 0; + u16 txlevel = 0; + u32 cr0; + int ret; + + dws->dma_mapped = 0; + spin_lock_irqsave(&dws->buf_lock, flags); + dws->tx = (void *)transfer->tx_buf; + dws->tx_end = dws->tx + transfer->len; + dws->rx = transfer->rx_buf; + dws->rx_end = dws->rx + transfer->len; + dws->len = transfer->len; + spin_unlock_irqrestore(&dws->buf_lock, flags); + + /* Ensure dw->rx and dw->rx_end are visible */ + smp_mb(); + + spi_enable_chip(dws, 0); + + /* Handle per transfer options for bpw and speed */ + if (transfer->speed_hz != dws->current_freq) { + if (transfer->speed_hz != chip->speed_hz) { + /* clk_div doesn't support odd number */ + chip->clk_div = (DIV_ROUND_UP(dws->max_freq, transfer->speed_hz) + 1) & 0xfffe; + chip->speed_hz = transfer->speed_hz; + } + dws->current_freq = transfer->speed_hz; + spi_set_clk(dws, chip->clk_div); + } + + transfer->effective_speed_hz = dws->max_freq / chip->clk_div; + dws->n_bytes = DIV_ROUND_UP(transfer->bits_per_word, BITS_PER_BYTE); + + cr0 = dws->update_cr0(master, spi, transfer); + dw_writel(dws, DW_SPI_CTRLR0, cr0); + + /* Check if current transfer is a DMA transaction */ + if (master->can_dma && master->can_dma(master, spi, transfer)) + dws->dma_mapped = master->cur_msg_mapped; + + /* For poll mode just disable all interrupts */ + spi_mask_intr(dws, 0xff); + + /* + * Interrupt mode + * we only need set the TXEI IRQ, as TX/RX always happen syncronizely + */ + if (dws->dma_mapped) { + ret = dws->dma_ops->dma_setup(dws, transfer); + if (ret < 0) { + spi_enable_chip(dws, 1); + return ret; + } + } else { + txlevel = min_t(u16, dws->fifo_len / 2, dws->len / dws->n_bytes); + dw_writel(dws, DW_SPI_TXFTLR, txlevel); + + /* Set the interrupt mask */ + imask |= SPI_INT_TXEI | SPI_INT_TXOI | + SPI_INT_RXUI | SPI_INT_RXOI; + spi_umask_intr(dws, imask); + + dws->transfer_handler = interrupt_transfer; + } + + spi_enable_chip(dws, 1); + + if (dws->dma_mapped) + return dws->dma_ops->dma_transfer(dws, transfer); + + return 1; +} + +static void dw_spi_handle_err(struct spi_controller *master, + struct spi_message *msg) +{ + struct dw_spi *dws = spi_controller_get_devdata(master); + + if (dws->dma_mapped) + dws->dma_ops->dma_stop(dws); + + spi_reset_chip(dws); +} + +/* This may be called twice for each spi dev */ +static int dw_spi_setup(struct spi_device *spi) +{ + struct chip_data *chip; + + /* Only alloc on first setup */ + chip = spi_get_ctldata(spi); + if (!chip) { + chip = kzalloc(sizeof(struct chip_data), GFP_KERNEL); + if (!chip) + return -ENOMEM; + spi_set_ctldata(spi, chip); + } + + chip->tmode = SPI_TMOD_TR; + + return 0; +} + +static void dw_spi_cleanup(struct spi_device *spi) +{ + struct chip_data *chip = spi_get_ctldata(spi); + + kfree(chip); + spi_set_ctldata(spi, NULL); +} + +/* Restart the controller, disable all interrupts, clean rx fifo */ +static void spi_hw_init(struct device *dev, struct dw_spi *dws) +{ + spi_reset_chip(dws); + + /* + * Try to detect the FIFO depth if not set by interface driver, + * the depth could be from 2 to 256 from HW spec + */ + if (!dws->fifo_len) { + u32 fifo; + + for (fifo = 1; fifo < 256; fifo++) { + dw_writel(dws, DW_SPI_TXFTLR, fifo); + if (fifo != dw_readl(dws, DW_SPI_TXFTLR)) + break; + } + dw_writel(dws, DW_SPI_TXFTLR, 0); + + dws->fifo_len = (fifo == 1) ? 0 : fifo; + dev_dbg(dev, "Detected FIFO size: %u bytes\n", dws->fifo_len); + } + + /* enable HW fixup for explicit CS deselect for Amazon's alpine chip */ + if (dws->cs_override) + dw_writel(dws, DW_SPI_CS_OVERRIDE, 0xF); +} + +int dw_spi_add_host(struct device *dev, struct dw_spi *dws) +{ + struct spi_controller *master; + int ret; + + if (!dws) + return -EINVAL; + + master = spi_alloc_master(dev, 0); + if (!master) + return -ENOMEM; + + dws->master = master; + dws->type = SSI_MOTO_SPI; + dws->dma_addr = (dma_addr_t)(dws->paddr + DW_SPI_DR); + spin_lock_init(&dws->buf_lock); + + spi_controller_set_devdata(master, dws); + + ret = request_irq(dws->irq, dw_spi_irq, IRQF_SHARED, dev_name(dev), + master); + if (ret < 0) { + dev_err(dev, "can not get IRQ\n"); + goto err_free_master; + } + + master->use_gpio_descriptors = true; + master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_LOOP; + master->bits_per_word_mask = SPI_BPW_RANGE_MASK(4, 16); + master->bus_num = dws->bus_num; + master->num_chipselect = dws->num_cs; + master->setup = dw_spi_setup; + master->cleanup = dw_spi_cleanup; + master->set_cs = dw_spi_set_cs; + master->transfer_one = dw_spi_transfer_one; + master->handle_err = dw_spi_handle_err; + master->max_speed_hz = dws->max_freq; + master->dev.of_node = dev->of_node; + master->dev.fwnode = dev->fwnode; + master->flags = SPI_MASTER_GPIO_SS; + master->auto_runtime_pm = true; + + if (dws->set_cs) + master->set_cs = dws->set_cs; + + /* Basic HW init */ + spi_hw_init(dev, dws); + + if (dws->dma_ops && dws->dma_ops->dma_init) { + ret = dws->dma_ops->dma_init(dev, dws); + if (ret) { + dev_warn(dev, "DMA init failed\n"); + } else { + master->can_dma = dws->dma_ops->can_dma; + master->flags |= SPI_CONTROLLER_MUST_TX; + } + } + + ret = devm_spi_register_controller(dev, master); + if (ret) { + dev_err(&master->dev, "problem registering spi master\n"); + goto err_dma_exit; + } + + dw_spi_debugfs_init(dws); + return 0; + +err_dma_exit: + if (dws->dma_ops && dws->dma_ops->dma_exit) + dws->dma_ops->dma_exit(dws); + spi_enable_chip(dws, 0); + free_irq(dws->irq, master); +err_free_master: + spi_controller_put(master); + return ret; +} +EXPORT_SYMBOL_GPL(dw_spi_add_host); + +void dw_spi_remove_host(struct dw_spi *dws) +{ + dw_spi_debugfs_remove(dws); + + if (dws->dma_ops && dws->dma_ops->dma_exit) + dws->dma_ops->dma_exit(dws); + + spi_shutdown_chip(dws); + + free_irq(dws->irq, dws->master); +} +EXPORT_SYMBOL_GPL(dw_spi_remove_host); + +int dw_spi_suspend_host(struct dw_spi *dws) +{ + int ret; + + ret = spi_controller_suspend(dws->master); + if (ret) + return ret; + + spi_shutdown_chip(dws); + return 0; +} +EXPORT_SYMBOL_GPL(dw_spi_suspend_host); + +int dw_spi_resume_host(struct dw_spi *dws) +{ + spi_hw_init(&dws->master->dev, dws); + return spi_controller_resume(dws->master); +} +EXPORT_SYMBOL_GPL(dw_spi_resume_host); + +MODULE_AUTHOR("Feng Tang "); +MODULE_DESCRIPTION("Driver for DesignWare SPI controller core"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/spi/spi-dw.c b/drivers/spi/spi-dw.c deleted file mode 100644 index 1f1a3536b7cf..000000000000 --- a/drivers/spi/spi-dw.c +++ /dev/null @@ -1,577 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Designware SPI core controller driver (refer pxa2xx_spi.c) - * - * Copyright (c) 2009, Intel Corporation. - */ - -#include -#include -#include -#include -#include -#include -#include - -#include "spi-dw.h" - -#ifdef CONFIG_DEBUG_FS -#include -#endif - -/* Slave spi_dev related */ -struct chip_data { - u8 tmode; /* TR/TO/RO/EEPROM */ - u8 type; /* SPI/SSP/MicroWire */ - - u16 clk_div; /* baud rate divider */ - u32 speed_hz; /* baud rate */ -}; - -#ifdef CONFIG_DEBUG_FS -#define SPI_REGS_BUFSIZE 1024 -static ssize_t dw_spi_show_regs(struct file *file, char __user *user_buf, - size_t count, loff_t *ppos) -{ - struct dw_spi *dws = file->private_data; - char *buf; - u32 len = 0; - ssize_t ret; - - buf = kzalloc(SPI_REGS_BUFSIZE, GFP_KERNEL); - if (!buf) - return 0; - - len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len, - "%s registers:\n", dev_name(&dws->master->dev)); - len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len, - "=================================\n"); - len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len, - "CTRLR0: \t0x%08x\n", dw_readl(dws, DW_SPI_CTRLR0)); - len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len, - "CTRLR1: \t0x%08x\n", dw_readl(dws, DW_SPI_CTRLR1)); - len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len, - "SSIENR: \t0x%08x\n", dw_readl(dws, DW_SPI_SSIENR)); - len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len, - "SER: \t\t0x%08x\n", dw_readl(dws, DW_SPI_SER)); - len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len, - "BAUDR: \t\t0x%08x\n", dw_readl(dws, DW_SPI_BAUDR)); - len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len, - "TXFTLR: \t0x%08x\n", dw_readl(dws, DW_SPI_TXFTLR)); - len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len, - "RXFTLR: \t0x%08x\n", dw_readl(dws, DW_SPI_RXFTLR)); - len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len, - "TXFLR: \t\t0x%08x\n", dw_readl(dws, DW_SPI_TXFLR)); - len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len, - "RXFLR: \t\t0x%08x\n", dw_readl(dws, DW_SPI_RXFLR)); - len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len, - "SR: \t\t0x%08x\n", dw_readl(dws, DW_SPI_SR)); - len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len, - "IMR: \t\t0x%08x\n", dw_readl(dws, DW_SPI_IMR)); - len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len, - "ISR: \t\t0x%08x\n", dw_readl(dws, DW_SPI_ISR)); - len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len, - "DMACR: \t\t0x%08x\n", dw_readl(dws, DW_SPI_DMACR)); - len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len, - "DMATDLR: \t0x%08x\n", dw_readl(dws, DW_SPI_DMATDLR)); - len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len, - "DMARDLR: \t0x%08x\n", dw_readl(dws, DW_SPI_DMARDLR)); - len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len, - "=================================\n"); - - ret = simple_read_from_buffer(user_buf, count, ppos, buf, len); - kfree(buf); - return ret; -} - -static const struct file_operations dw_spi_regs_ops = { - .owner = THIS_MODULE, - .open = simple_open, - .read = dw_spi_show_regs, - .llseek = default_llseek, -}; - -static int dw_spi_debugfs_init(struct dw_spi *dws) -{ - char name[32]; - - snprintf(name, 32, "dw_spi%d", dws->master->bus_num); - dws->debugfs = debugfs_create_dir(name, NULL); - if (!dws->debugfs) - return -ENOMEM; - - debugfs_create_file("registers", S_IFREG | S_IRUGO, - dws->debugfs, (void *)dws, &dw_spi_regs_ops); - return 0; -} - -static void dw_spi_debugfs_remove(struct dw_spi *dws) -{ - debugfs_remove_recursive(dws->debugfs); -} - -#else -static inline int dw_spi_debugfs_init(struct dw_spi *dws) -{ - return 0; -} - -static inline void dw_spi_debugfs_remove(struct dw_spi *dws) -{ -} -#endif /* CONFIG_DEBUG_FS */ - -void dw_spi_set_cs(struct spi_device *spi, bool enable) -{ - struct dw_spi *dws = spi_controller_get_devdata(spi->controller); - bool cs_high = !!(spi->mode & SPI_CS_HIGH); - - /* - * DW SPI controller demands any native CS being set in order to - * proceed with data transfer. So in order to activate the SPI - * communications we must set a corresponding bit in the Slave - * Enable register no matter whether the SPI core is configured to - * support active-high or active-low CS level. - */ - if (cs_high == enable) - dw_writel(dws, DW_SPI_SER, BIT(spi->chip_select)); - else if (dws->cs_override) - dw_writel(dws, DW_SPI_SER, 0); -} -EXPORT_SYMBOL_GPL(dw_spi_set_cs); - -/* Return the max entries we can fill into tx fifo */ -static inline u32 tx_max(struct dw_spi *dws) -{ - u32 tx_left, tx_room, rxtx_gap; - - tx_left = (dws->tx_end - dws->tx) / dws->n_bytes; - tx_room = dws->fifo_len - dw_readl(dws, DW_SPI_TXFLR); - - /* - * Another concern is about the tx/rx mismatch, we - * though to use (dws->fifo_len - rxflr - txflr) as - * one maximum value for tx, but it doesn't cover the - * data which is out of tx/rx fifo and inside the - * shift registers. So a control from sw point of - * view is taken. - */ - rxtx_gap = ((dws->rx_end - dws->rx) - (dws->tx_end - dws->tx)) - / dws->n_bytes; - - return min3(tx_left, tx_room, (u32) (dws->fifo_len - rxtx_gap)); -} - -/* Return the max entries we should read out of rx fifo */ -static inline u32 rx_max(struct dw_spi *dws) -{ - u32 rx_left = (dws->rx_end - dws->rx) / dws->n_bytes; - - return min_t(u32, rx_left, dw_readl(dws, DW_SPI_RXFLR)); -} - -static void dw_writer(struct dw_spi *dws) -{ - u32 max; - u16 txw = 0; - - spin_lock(&dws->buf_lock); - max = tx_max(dws); - while (max--) { - /* Set the tx word if the transfer's original "tx" is not null */ - if (dws->tx_end - dws->len) { - if (dws->n_bytes == 1) - txw = *(u8 *)(dws->tx); - else - txw = *(u16 *)(dws->tx); - } - dw_write_io_reg(dws, DW_SPI_DR, txw); - dws->tx += dws->n_bytes; - } - spin_unlock(&dws->buf_lock); -} - -static void dw_reader(struct dw_spi *dws) -{ - u32 max; - u16 rxw; - - spin_lock(&dws->buf_lock); - max = rx_max(dws); - while (max--) { - rxw = dw_read_io_reg(dws, DW_SPI_DR); - /* Care rx only if the transfer's original "rx" is not null */ - if (dws->rx_end - dws->len) { - if (dws->n_bytes == 1) - *(u8 *)(dws->rx) = rxw; - else - *(u16 *)(dws->rx) = rxw; - } - dws->rx += dws->n_bytes; - } - spin_unlock(&dws->buf_lock); -} - -static void int_error_stop(struct dw_spi *dws, const char *msg) -{ - spi_reset_chip(dws); - - dev_err(&dws->master->dev, "%s\n", msg); - dws->master->cur_msg->status = -EIO; - spi_finalize_current_transfer(dws->master); -} - -static irqreturn_t interrupt_transfer(struct dw_spi *dws) -{ - u16 irq_status = dw_readl(dws, DW_SPI_ISR); - - /* Error handling */ - if (irq_status & (SPI_INT_TXOI | SPI_INT_RXOI | SPI_INT_RXUI)) { - dw_readl(dws, DW_SPI_ICR); - int_error_stop(dws, "interrupt_transfer: fifo overrun/underrun"); - return IRQ_HANDLED; - } - - dw_reader(dws); - if (dws->rx_end == dws->rx) { - spi_mask_intr(dws, SPI_INT_TXEI); - spi_finalize_current_transfer(dws->master); - return IRQ_HANDLED; - } - if (irq_status & SPI_INT_TXEI) { - spi_mask_intr(dws, SPI_INT_TXEI); - dw_writer(dws); - /* Enable TX irq always, it will be disabled when RX finished */ - spi_umask_intr(dws, SPI_INT_TXEI); - } - - return IRQ_HANDLED; -} - -static irqreturn_t dw_spi_irq(int irq, void *dev_id) -{ - struct spi_controller *master = dev_id; - struct dw_spi *dws = spi_controller_get_devdata(master); - u16 irq_status = dw_readl(dws, DW_SPI_ISR) & 0x3f; - - if (!irq_status) - return IRQ_NONE; - - if (!master->cur_msg) { - spi_mask_intr(dws, SPI_INT_TXEI); - return IRQ_HANDLED; - } - - return dws->transfer_handler(dws); -} - -/* Configure CTRLR0 for DW_apb_ssi */ -u32 dw_spi_update_cr0(struct spi_controller *master, struct spi_device *spi, - struct spi_transfer *transfer) -{ - struct chip_data *chip = spi_get_ctldata(spi); - u32 cr0; - - /* Default SPI mode is SCPOL = 0, SCPH = 0 */ - cr0 = (transfer->bits_per_word - 1) - | (chip->type << SPI_FRF_OFFSET) - | ((((spi->mode & SPI_CPOL) ? 1 : 0) << SPI_SCOL_OFFSET) | - (((spi->mode & SPI_CPHA) ? 1 : 0) << SPI_SCPH_OFFSET) | - (((spi->mode & SPI_LOOP) ? 1 : 0) << SPI_SRL_OFFSET)) - | (chip->tmode << SPI_TMOD_OFFSET); - - return cr0; -} -EXPORT_SYMBOL_GPL(dw_spi_update_cr0); - -/* Configure CTRLR0 for DWC_ssi */ -u32 dw_spi_update_cr0_v1_01a(struct spi_controller *master, - struct spi_device *spi, - struct spi_transfer *transfer) -{ - struct chip_data *chip = spi_get_ctldata(spi); - u32 cr0; - - /* CTRLR0[ 4: 0] Data Frame Size */ - cr0 = (transfer->bits_per_word - 1); - - /* CTRLR0[ 7: 6] Frame Format */ - cr0 |= chip->type << DWC_SSI_CTRLR0_FRF_OFFSET; - - /* - * SPI mode (SCPOL|SCPH) - * CTRLR0[ 8] Serial Clock Phase - * CTRLR0[ 9] Serial Clock Polarity - */ - cr0 |= ((spi->mode & SPI_CPOL) ? 1 : 0) << DWC_SSI_CTRLR0_SCPOL_OFFSET; - cr0 |= ((spi->mode & SPI_CPHA) ? 1 : 0) << DWC_SSI_CTRLR0_SCPH_OFFSET; - - /* CTRLR0[11:10] Transfer Mode */ - cr0 |= chip->tmode << DWC_SSI_CTRLR0_TMOD_OFFSET; - - /* CTRLR0[13] Shift Register Loop */ - cr0 |= ((spi->mode & SPI_LOOP) ? 1 : 0) << DWC_SSI_CTRLR0_SRL_OFFSET; - - return cr0; -} -EXPORT_SYMBOL_GPL(dw_spi_update_cr0_v1_01a); - -static int dw_spi_transfer_one(struct spi_controller *master, - struct spi_device *spi, struct spi_transfer *transfer) -{ - struct dw_spi *dws = spi_controller_get_devdata(master); - struct chip_data *chip = spi_get_ctldata(spi); - unsigned long flags; - u8 imask = 0; - u16 txlevel = 0; - u32 cr0; - int ret; - - dws->dma_mapped = 0; - spin_lock_irqsave(&dws->buf_lock, flags); - dws->tx = (void *)transfer->tx_buf; - dws->tx_end = dws->tx + transfer->len; - dws->rx = transfer->rx_buf; - dws->rx_end = dws->rx + transfer->len; - dws->len = transfer->len; - spin_unlock_irqrestore(&dws->buf_lock, flags); - - /* Ensure dw->rx and dw->rx_end are visible */ - smp_mb(); - - spi_enable_chip(dws, 0); - - /* Handle per transfer options for bpw and speed */ - if (transfer->speed_hz != dws->current_freq) { - if (transfer->speed_hz != chip->speed_hz) { - /* clk_div doesn't support odd number */ - chip->clk_div = (DIV_ROUND_UP(dws->max_freq, transfer->speed_hz) + 1) & 0xfffe; - chip->speed_hz = transfer->speed_hz; - } - dws->current_freq = transfer->speed_hz; - spi_set_clk(dws, chip->clk_div); - } - - transfer->effective_speed_hz = dws->max_freq / chip->clk_div; - dws->n_bytes = DIV_ROUND_UP(transfer->bits_per_word, BITS_PER_BYTE); - - cr0 = dws->update_cr0(master, spi, transfer); - dw_writel(dws, DW_SPI_CTRLR0, cr0); - - /* Check if current transfer is a DMA transaction */ - if (master->can_dma && master->can_dma(master, spi, transfer)) - dws->dma_mapped = master->cur_msg_mapped; - - /* For poll mode just disable all interrupts */ - spi_mask_intr(dws, 0xff); - - /* - * Interrupt mode - * we only need set the TXEI IRQ, as TX/RX always happen syncronizely - */ - if (dws->dma_mapped) { - ret = dws->dma_ops->dma_setup(dws, transfer); - if (ret < 0) { - spi_enable_chip(dws, 1); - return ret; - } - } else { - txlevel = min_t(u16, dws->fifo_len / 2, dws->len / dws->n_bytes); - dw_writel(dws, DW_SPI_TXFTLR, txlevel); - - /* Set the interrupt mask */ - imask |= SPI_INT_TXEI | SPI_INT_TXOI | - SPI_INT_RXUI | SPI_INT_RXOI; - spi_umask_intr(dws, imask); - - dws->transfer_handler = interrupt_transfer; - } - - spi_enable_chip(dws, 1); - - if (dws->dma_mapped) - return dws->dma_ops->dma_transfer(dws, transfer); - - return 1; -} - -static void dw_spi_handle_err(struct spi_controller *master, - struct spi_message *msg) -{ - struct dw_spi *dws = spi_controller_get_devdata(master); - - if (dws->dma_mapped) - dws->dma_ops->dma_stop(dws); - - spi_reset_chip(dws); -} - -/* This may be called twice for each spi dev */ -static int dw_spi_setup(struct spi_device *spi) -{ - struct chip_data *chip; - - /* Only alloc on first setup */ - chip = spi_get_ctldata(spi); - if (!chip) { - chip = kzalloc(sizeof(struct chip_data), GFP_KERNEL); - if (!chip) - return -ENOMEM; - spi_set_ctldata(spi, chip); - } - - chip->tmode = SPI_TMOD_TR; - - return 0; -} - -static void dw_spi_cleanup(struct spi_device *spi) -{ - struct chip_data *chip = spi_get_ctldata(spi); - - kfree(chip); - spi_set_ctldata(spi, NULL); -} - -/* Restart the controller, disable all interrupts, clean rx fifo */ -static void spi_hw_init(struct device *dev, struct dw_spi *dws) -{ - spi_reset_chip(dws); - - /* - * Try to detect the FIFO depth if not set by interface driver, - * the depth could be from 2 to 256 from HW spec - */ - if (!dws->fifo_len) { - u32 fifo; - - for (fifo = 1; fifo < 256; fifo++) { - dw_writel(dws, DW_SPI_TXFTLR, fifo); - if (fifo != dw_readl(dws, DW_SPI_TXFTLR)) - break; - } - dw_writel(dws, DW_SPI_TXFTLR, 0); - - dws->fifo_len = (fifo == 1) ? 0 : fifo; - dev_dbg(dev, "Detected FIFO size: %u bytes\n", dws->fifo_len); - } - - /* enable HW fixup for explicit CS deselect for Amazon's alpine chip */ - if (dws->cs_override) - dw_writel(dws, DW_SPI_CS_OVERRIDE, 0xF); -} - -int dw_spi_add_host(struct device *dev, struct dw_spi *dws) -{ - struct spi_controller *master; - int ret; - - if (!dws) - return -EINVAL; - - master = spi_alloc_master(dev, 0); - if (!master) - return -ENOMEM; - - dws->master = master; - dws->type = SSI_MOTO_SPI; - dws->dma_addr = (dma_addr_t)(dws->paddr + DW_SPI_DR); - spin_lock_init(&dws->buf_lock); - - spi_controller_set_devdata(master, dws); - - ret = request_irq(dws->irq, dw_spi_irq, IRQF_SHARED, dev_name(dev), - master); - if (ret < 0) { - dev_err(dev, "can not get IRQ\n"); - goto err_free_master; - } - - master->use_gpio_descriptors = true; - master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_LOOP; - master->bits_per_word_mask = SPI_BPW_RANGE_MASK(4, 16); - master->bus_num = dws->bus_num; - master->num_chipselect = dws->num_cs; - master->setup = dw_spi_setup; - master->cleanup = dw_spi_cleanup; - master->set_cs = dw_spi_set_cs; - master->transfer_one = dw_spi_transfer_one; - master->handle_err = dw_spi_handle_err; - master->max_speed_hz = dws->max_freq; - master->dev.of_node = dev->of_node; - master->dev.fwnode = dev->fwnode; - master->flags = SPI_MASTER_GPIO_SS; - master->auto_runtime_pm = true; - - if (dws->set_cs) - master->set_cs = dws->set_cs; - - /* Basic HW init */ - spi_hw_init(dev, dws); - - if (dws->dma_ops && dws->dma_ops->dma_init) { - ret = dws->dma_ops->dma_init(dev, dws); - if (ret) { - dev_warn(dev, "DMA init failed\n"); - } else { - master->can_dma = dws->dma_ops->can_dma; - master->flags |= SPI_CONTROLLER_MUST_TX; - } - } - - ret = devm_spi_register_controller(dev, master); - if (ret) { - dev_err(&master->dev, "problem registering spi master\n"); - goto err_dma_exit; - } - - dw_spi_debugfs_init(dws); - return 0; - -err_dma_exit: - if (dws->dma_ops && dws->dma_ops->dma_exit) - dws->dma_ops->dma_exit(dws); - spi_enable_chip(dws, 0); - free_irq(dws->irq, master); -err_free_master: - spi_controller_put(master); - return ret; -} -EXPORT_SYMBOL_GPL(dw_spi_add_host); - -void dw_spi_remove_host(struct dw_spi *dws) -{ - dw_spi_debugfs_remove(dws); - - if (dws->dma_ops && dws->dma_ops->dma_exit) - dws->dma_ops->dma_exit(dws); - - spi_shutdown_chip(dws); - - free_irq(dws->irq, dws->master); -} -EXPORT_SYMBOL_GPL(dw_spi_remove_host); - -int dw_spi_suspend_host(struct dw_spi *dws) -{ - int ret; - - ret = spi_controller_suspend(dws->master); - if (ret) - return ret; - - spi_shutdown_chip(dws); - return 0; -} -EXPORT_SYMBOL_GPL(dw_spi_suspend_host); - -int dw_spi_resume_host(struct dw_spi *dws) -{ - spi_hw_init(&dws->master->dev, dws); - return spi_controller_resume(dws->master); -} -EXPORT_SYMBOL_GPL(dw_spi_resume_host); - -MODULE_AUTHOR("Feng Tang "); -MODULE_DESCRIPTION("Driver for DesignWare SPI controller core"); -MODULE_LICENSE("GPL v2"); -- cgit v1.2.3 From 6c710c0cb6725bdbe647b958756685aed0295936 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 29 May 2020 16:11:59 +0300 Subject: spi: dw: Move Non-DMA code to the DW PCIe-SPI driver This is a preparation patch before adding the DW DMA support into the DW SPI MMIO driver. We need to unpin the Non-DMA-specific code from the intended to be generic DW APB SSI DMA code. This isn't that hard, since the most part of the spi-dw-mid.c driver in fact implements a generic DMA interface for the DW SPI controller driver. The only Intel MID specifics concern getting the max frequency from the MRST Clock Control Unit and fetching the DMA controller channels from corresponding PCIe DMA controller. Since first one is related with the SPI interface configuration we moved it' implementation into the DW PCIe-SPI driver module. After that former spi-dw-mid.c file can be just renamed to be the DW SPI DMA module optionally compiled in to the DW APB SSI core driver. Co-developed-by: Georgy Vlasov Co-developed-by: Ramil Zaripov Signed-off-by: Georgy Vlasov Signed-off-by: Ramil Zaripov Signed-off-by: Serge Semin Reviewed-by: Andy Shevchenko Cc: Alexey Malahov Cc: Thomas Bogendoerfer Cc: Arnd Bergmann Cc: Feng Tang Cc: Rob Herring Cc: linux-mips@vger.kernel.org Cc: devicetree@vger.kernel.org Link: https://lore.kernel.org/r/20200529131205.31838-11-Sergey.Semin@baikalelectronics.ru Signed-off-by: Mark Brown --- drivers/spi/Kconfig | 8 +- drivers/spi/Makefile | 4 +- drivers/spi/spi-dw-dma.c | 481 ++++++++++++++++++++++++++++++++++++++++++ drivers/spi/spi-dw-mid.c | 529 ----------------------------------------------- drivers/spi/spi-dw-pci.c | 50 ++++- drivers/spi/spi-dw.h | 14 +- 6 files changed, 545 insertions(+), 541 deletions(-) create mode 100644 drivers/spi/spi-dw-dma.c delete mode 100644 drivers/spi/spi-dw-mid.c (limited to 'drivers') diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index 099d6a75a371..73d508eb4871 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -226,14 +226,14 @@ config SPI_DESIGNWARE help general driver for SPI controller core from DesignWare +config SPI_DW_DMA + bool "DMA support for DW SPI controller" + depends on SPI_DESIGNWARE && DW_DMAC_PCI + config SPI_DW_PCI tristate "PCI interface driver for DW SPI core" depends on SPI_DESIGNWARE && PCI -config SPI_DW_MID_DMA - bool "DMA support for DW SPI controller on Intel MID platform" - depends on SPI_DW_PCI && DW_DMAC_PCI - config SPI_DW_MMIO tristate "Memory-mapped io interface driver for DW SPI core" depends on SPI_DESIGNWARE diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index 840a5a1c655e..d2e41d3d464a 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -37,9 +37,9 @@ obj-$(CONFIG_SPI_DAVINCI) += spi-davinci.o obj-$(CONFIG_SPI_DLN2) += spi-dln2.o obj-$(CONFIG_SPI_DESIGNWARE) += spi-dw.o spi-dw-y := spi-dw-core.o +spi-dw-$(CONFIG_SPI_DW_DMA) += spi-dw-dma.o obj-$(CONFIG_SPI_DW_MMIO) += spi-dw-mmio.o -obj-$(CONFIG_SPI_DW_PCI) += spi-dw-midpci.o -spi-dw-midpci-objs := spi-dw-pci.o spi-dw-mid.o +obj-$(CONFIG_SPI_DW_PCI) += spi-dw-pci.o obj-$(CONFIG_SPI_EFM32) += spi-efm32.o obj-$(CONFIG_SPI_EP93XX) += spi-ep93xx.o obj-$(CONFIG_SPI_FALCON) += spi-falcon.o diff --git a/drivers/spi/spi-dw-dma.c b/drivers/spi/spi-dw-dma.c new file mode 100644 index 000000000000..7ae31682b5de --- /dev/null +++ b/drivers/spi/spi-dw-dma.c @@ -0,0 +1,481 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Special handling for DW DMA core + * + * Copyright (c) 2009, 2014 Intel Corporation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "spi-dw.h" + +#define WAIT_RETRIES 5 +#define RX_BUSY 0 +#define RX_BURST_LEVEL 16 +#define TX_BUSY 1 +#define TX_BURST_LEVEL 16 + +static bool mid_spi_dma_chan_filter(struct dma_chan *chan, void *param) +{ + struct dw_dma_slave *s = param; + + if (s->dma_dev != chan->device->dev) + return false; + + chan->private = s; + return true; +} + +static void mid_spi_maxburst_init(struct dw_spi *dws) +{ + struct dma_slave_caps caps; + u32 max_burst, def_burst; + int ret; + + def_burst = dws->fifo_len / 2; + + ret = dma_get_slave_caps(dws->rxchan, &caps); + if (!ret && caps.max_burst) + max_burst = caps.max_burst; + else + max_burst = RX_BURST_LEVEL; + + dws->rxburst = min(max_burst, def_burst); + + ret = dma_get_slave_caps(dws->txchan, &caps); + if (!ret && caps.max_burst) + max_burst = caps.max_burst; + else + max_burst = TX_BURST_LEVEL; + + dws->txburst = min(max_burst, def_burst); +} + +static int mid_spi_dma_init_mfld(struct device *dev, struct dw_spi *dws) +{ + struct dw_dma_slave slave = { + .src_id = 0, + .dst_id = 0 + }; + struct pci_dev *dma_dev; + dma_cap_mask_t mask; + + /* + * Get pci device for DMA controller, currently it could only + * be the DMA controller of Medfield + */ + dma_dev = pci_get_device(PCI_VENDOR_ID_INTEL, 0x0827, NULL); + if (!dma_dev) + return -ENODEV; + + dma_cap_zero(mask); + dma_cap_set(DMA_SLAVE, mask); + + /* 1. Init rx channel */ + slave.dma_dev = &dma_dev->dev; + dws->rxchan = dma_request_channel(mask, mid_spi_dma_chan_filter, &slave); + if (!dws->rxchan) + goto err_exit; + + /* 2. Init tx channel */ + slave.dst_id = 1; + dws->txchan = dma_request_channel(mask, mid_spi_dma_chan_filter, &slave); + if (!dws->txchan) + goto free_rxchan; + + dws->master->dma_rx = dws->rxchan; + dws->master->dma_tx = dws->txchan; + + init_completion(&dws->dma_completion); + + mid_spi_maxburst_init(dws); + + return 0; + +free_rxchan: + dma_release_channel(dws->rxchan); + dws->rxchan = NULL; +err_exit: + return -EBUSY; +} + +static int mid_spi_dma_init_generic(struct device *dev, struct dw_spi *dws) +{ + dws->rxchan = dma_request_slave_channel(dev, "rx"); + if (!dws->rxchan) + return -ENODEV; + + dws->txchan = dma_request_slave_channel(dev, "tx"); + if (!dws->txchan) { + dma_release_channel(dws->rxchan); + dws->rxchan = NULL; + return -ENODEV; + } + + dws->master->dma_rx = dws->rxchan; + dws->master->dma_tx = dws->txchan; + + init_completion(&dws->dma_completion); + + mid_spi_maxburst_init(dws); + + return 0; +} + +static void mid_spi_dma_exit(struct dw_spi *dws) +{ + if (dws->txchan) { + dmaengine_terminate_sync(dws->txchan); + dma_release_channel(dws->txchan); + } + + if (dws->rxchan) { + dmaengine_terminate_sync(dws->rxchan); + dma_release_channel(dws->rxchan); + } + + dw_writel(dws, DW_SPI_DMACR, 0); +} + +static irqreturn_t dma_transfer(struct dw_spi *dws) +{ + u16 irq_status = dw_readl(dws, DW_SPI_ISR); + + if (!irq_status) + return IRQ_NONE; + + dw_readl(dws, DW_SPI_ICR); + spi_reset_chip(dws); + + dev_err(&dws->master->dev, "%s: FIFO overrun/underrun\n", __func__); + dws->master->cur_msg->status = -EIO; + complete(&dws->dma_completion); + return IRQ_HANDLED; +} + +static bool mid_spi_can_dma(struct spi_controller *master, + struct spi_device *spi, struct spi_transfer *xfer) +{ + struct dw_spi *dws = spi_controller_get_devdata(master); + + return xfer->len > dws->fifo_len; +} + +static enum dma_slave_buswidth convert_dma_width(u8 n_bytes) { + if (n_bytes == 1) + return DMA_SLAVE_BUSWIDTH_1_BYTE; + else if (n_bytes == 2) + return DMA_SLAVE_BUSWIDTH_2_BYTES; + + return DMA_SLAVE_BUSWIDTH_UNDEFINED; +} + +static int dw_spi_dma_wait(struct dw_spi *dws, struct spi_transfer *xfer) +{ + unsigned long long ms; + + ms = xfer->len * MSEC_PER_SEC * BITS_PER_BYTE; + do_div(ms, xfer->effective_speed_hz); + ms += ms + 200; + + if (ms > UINT_MAX) + ms = UINT_MAX; + + ms = wait_for_completion_timeout(&dws->dma_completion, + msecs_to_jiffies(ms)); + + if (ms == 0) { + dev_err(&dws->master->cur_msg->spi->dev, + "DMA transaction timed out\n"); + return -ETIMEDOUT; + } + + return 0; +} + +static inline bool dw_spi_dma_tx_busy(struct dw_spi *dws) +{ + return !(dw_readl(dws, DW_SPI_SR) & SR_TF_EMPT); +} + +static int dw_spi_dma_wait_tx_done(struct dw_spi *dws, + struct spi_transfer *xfer) +{ + int retry = WAIT_RETRIES; + struct spi_delay delay; + u32 nents; + + nents = dw_readl(dws, DW_SPI_TXFLR); + delay.unit = SPI_DELAY_UNIT_SCK; + delay.value = nents * dws->n_bytes * BITS_PER_BYTE; + + while (dw_spi_dma_tx_busy(dws) && retry--) + spi_delay_exec(&delay, xfer); + + if (retry < 0) { + dev_err(&dws->master->dev, "Tx hanged up\n"); + return -EIO; + } + + return 0; +} + +/* + * dws->dma_chan_busy is set before the dma transfer starts, callback for tx + * channel will clear a corresponding bit. + */ +static void dw_spi_dma_tx_done(void *arg) +{ + struct dw_spi *dws = arg; + + clear_bit(TX_BUSY, &dws->dma_chan_busy); + if (test_bit(RX_BUSY, &dws->dma_chan_busy)) + return; + + dw_writel(dws, DW_SPI_DMACR, 0); + complete(&dws->dma_completion); +} + +static struct dma_async_tx_descriptor *dw_spi_dma_prepare_tx(struct dw_spi *dws, + struct spi_transfer *xfer) +{ + struct dma_slave_config txconf; + struct dma_async_tx_descriptor *txdesc; + + if (!xfer->tx_buf) + return NULL; + + memset(&txconf, 0, sizeof(txconf)); + txconf.direction = DMA_MEM_TO_DEV; + txconf.dst_addr = dws->dma_addr; + txconf.dst_maxburst = dws->txburst; + txconf.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + txconf.dst_addr_width = convert_dma_width(dws->n_bytes); + txconf.device_fc = false; + + dmaengine_slave_config(dws->txchan, &txconf); + + txdesc = dmaengine_prep_slave_sg(dws->txchan, + xfer->tx_sg.sgl, + xfer->tx_sg.nents, + DMA_MEM_TO_DEV, + DMA_PREP_INTERRUPT | DMA_CTRL_ACK); + if (!txdesc) + return NULL; + + txdesc->callback = dw_spi_dma_tx_done; + txdesc->callback_param = dws; + + return txdesc; +} + +static inline bool dw_spi_dma_rx_busy(struct dw_spi *dws) +{ + return !!(dw_readl(dws, DW_SPI_SR) & SR_RF_NOT_EMPT); +} + +static int dw_spi_dma_wait_rx_done(struct dw_spi *dws) +{ + int retry = WAIT_RETRIES; + struct spi_delay delay; + unsigned long ns, us; + u32 nents; + + /* + * It's unlikely that DMA engine is still doing the data fetching, but + * if it's let's give it some reasonable time. The timeout calculation + * is based on the synchronous APB/SSI reference clock rate, on a + * number of data entries left in the Rx FIFO, times a number of clock + * periods normally needed for a single APB read/write transaction + * without PREADY signal utilized (which is true for the DW APB SSI + * controller). + */ + nents = dw_readl(dws, DW_SPI_RXFLR); + ns = 4U * NSEC_PER_SEC / dws->max_freq * nents; + if (ns <= NSEC_PER_USEC) { + delay.unit = SPI_DELAY_UNIT_NSECS; + delay.value = ns; + } else { + us = DIV_ROUND_UP(ns, NSEC_PER_USEC); + delay.unit = SPI_DELAY_UNIT_USECS; + delay.value = clamp_val(us, 0, USHRT_MAX); + } + + while (dw_spi_dma_rx_busy(dws) && retry--) + spi_delay_exec(&delay, NULL); + + if (retry < 0) { + dev_err(&dws->master->dev, "Rx hanged up\n"); + return -EIO; + } + + return 0; +} + +/* + * dws->dma_chan_busy is set before the dma transfer starts, callback for rx + * channel will clear a corresponding bit. + */ +static void dw_spi_dma_rx_done(void *arg) +{ + struct dw_spi *dws = arg; + + clear_bit(RX_BUSY, &dws->dma_chan_busy); + if (test_bit(TX_BUSY, &dws->dma_chan_busy)) + return; + + dw_writel(dws, DW_SPI_DMACR, 0); + complete(&dws->dma_completion); +} + +static struct dma_async_tx_descriptor *dw_spi_dma_prepare_rx(struct dw_spi *dws, + struct spi_transfer *xfer) +{ + struct dma_slave_config rxconf; + struct dma_async_tx_descriptor *rxdesc; + + if (!xfer->rx_buf) + return NULL; + + memset(&rxconf, 0, sizeof(rxconf)); + rxconf.direction = DMA_DEV_TO_MEM; + rxconf.src_addr = dws->dma_addr; + rxconf.src_maxburst = dws->rxburst; + rxconf.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + rxconf.src_addr_width = convert_dma_width(dws->n_bytes); + rxconf.device_fc = false; + + dmaengine_slave_config(dws->rxchan, &rxconf); + + rxdesc = dmaengine_prep_slave_sg(dws->rxchan, + xfer->rx_sg.sgl, + xfer->rx_sg.nents, + DMA_DEV_TO_MEM, + DMA_PREP_INTERRUPT | DMA_CTRL_ACK); + if (!rxdesc) + return NULL; + + rxdesc->callback = dw_spi_dma_rx_done; + rxdesc->callback_param = dws; + + return rxdesc; +} + +static int mid_spi_dma_setup(struct dw_spi *dws, struct spi_transfer *xfer) +{ + u16 imr = 0, dma_ctrl = 0; + + dw_writel(dws, DW_SPI_DMARDLR, dws->rxburst - 1); + dw_writel(dws, DW_SPI_DMATDLR, dws->fifo_len - dws->txburst); + + if (xfer->tx_buf) { + dma_ctrl |= SPI_DMA_TDMAE; + imr |= SPI_INT_TXOI; + } + if (xfer->rx_buf) { + dma_ctrl |= SPI_DMA_RDMAE; + imr |= SPI_INT_RXUI | SPI_INT_RXOI; + } + dw_writel(dws, DW_SPI_DMACR, dma_ctrl); + + /* Set the interrupt mask */ + spi_umask_intr(dws, imr); + + reinit_completion(&dws->dma_completion); + + dws->transfer_handler = dma_transfer; + + return 0; +} + +static int mid_spi_dma_transfer(struct dw_spi *dws, struct spi_transfer *xfer) +{ + struct dma_async_tx_descriptor *txdesc, *rxdesc; + int ret; + + /* Prepare the TX dma transfer */ + txdesc = dw_spi_dma_prepare_tx(dws, xfer); + + /* Prepare the RX dma transfer */ + rxdesc = dw_spi_dma_prepare_rx(dws, xfer); + + /* rx must be started before tx due to spi instinct */ + if (rxdesc) { + set_bit(RX_BUSY, &dws->dma_chan_busy); + dmaengine_submit(rxdesc); + dma_async_issue_pending(dws->rxchan); + } + + if (txdesc) { + set_bit(TX_BUSY, &dws->dma_chan_busy); + dmaengine_submit(txdesc); + dma_async_issue_pending(dws->txchan); + } + + ret = dw_spi_dma_wait(dws, xfer); + if (ret) + return ret; + + if (txdesc && dws->master->cur_msg->status == -EINPROGRESS) { + ret = dw_spi_dma_wait_tx_done(dws, xfer); + if (ret) + return ret; + } + + if (rxdesc && dws->master->cur_msg->status == -EINPROGRESS) + ret = dw_spi_dma_wait_rx_done(dws); + + return ret; +} + +static void mid_spi_dma_stop(struct dw_spi *dws) +{ + if (test_bit(TX_BUSY, &dws->dma_chan_busy)) { + dmaengine_terminate_sync(dws->txchan); + clear_bit(TX_BUSY, &dws->dma_chan_busy); + } + if (test_bit(RX_BUSY, &dws->dma_chan_busy)) { + dmaengine_terminate_sync(dws->rxchan); + clear_bit(RX_BUSY, &dws->dma_chan_busy); + } + + dw_writel(dws, DW_SPI_DMACR, 0); +} + +static const struct dw_spi_dma_ops mfld_dma_ops = { + .dma_init = mid_spi_dma_init_mfld, + .dma_exit = mid_spi_dma_exit, + .dma_setup = mid_spi_dma_setup, + .can_dma = mid_spi_can_dma, + .dma_transfer = mid_spi_dma_transfer, + .dma_stop = mid_spi_dma_stop, +}; + +void dw_spi_mid_setup_dma_mfld(struct dw_spi *dws) +{ + dws->dma_ops = &mfld_dma_ops; +} +EXPORT_SYMBOL_GPL(dw_spi_mid_setup_dma_mfld); + +static const struct dw_spi_dma_ops generic_dma_ops = { + .dma_init = mid_spi_dma_init_generic, + .dma_exit = mid_spi_dma_exit, + .dma_setup = mid_spi_dma_setup, + .can_dma = mid_spi_can_dma, + .dma_transfer = mid_spi_dma_transfer, + .dma_stop = mid_spi_dma_stop, +}; + +void dw_spi_mid_setup_dma_generic(struct dw_spi *dws) +{ + dws->dma_ops = &generic_dma_ops; +} +EXPORT_SYMBOL_GPL(dw_spi_mid_setup_dma_generic); diff --git a/drivers/spi/spi-dw-mid.c b/drivers/spi/spi-dw-mid.c deleted file mode 100644 index 1cf9e3ffe07b..000000000000 --- a/drivers/spi/spi-dw-mid.c +++ /dev/null @@ -1,529 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Special handling for DW core on Intel MID platform - * - * Copyright (c) 2009, 2014 Intel Corporation. - */ - -#include -#include - -#include "spi-dw.h" - -#ifdef CONFIG_SPI_DW_MID_DMA -#include -#include -#include -#include -#include -#include -#include - -#define WAIT_RETRIES 5 -#define RX_BUSY 0 -#define RX_BURST_LEVEL 16 -#define TX_BUSY 1 -#define TX_BURST_LEVEL 16 - -static bool mid_spi_dma_chan_filter(struct dma_chan *chan, void *param) -{ - struct dw_dma_slave *s = param; - - if (s->dma_dev != chan->device->dev) - return false; - - chan->private = s; - return true; -} - -static void mid_spi_maxburst_init(struct dw_spi *dws) -{ - struct dma_slave_caps caps; - u32 max_burst, def_burst; - int ret; - - def_burst = dws->fifo_len / 2; - - ret = dma_get_slave_caps(dws->rxchan, &caps); - if (!ret && caps.max_burst) - max_burst = caps.max_burst; - else - max_burst = RX_BURST_LEVEL; - - dws->rxburst = min(max_burst, def_burst); - - ret = dma_get_slave_caps(dws->txchan, &caps); - if (!ret && caps.max_burst) - max_burst = caps.max_burst; - else - max_burst = TX_BURST_LEVEL; - - dws->txburst = min(max_burst, def_burst); -} - -static int mid_spi_dma_init_mfld(struct device *dev, struct dw_spi *dws) -{ - struct dw_dma_slave slave = { - .src_id = 0, - .dst_id = 0 - }; - struct pci_dev *dma_dev; - dma_cap_mask_t mask; - - /* - * Get pci device for DMA controller, currently it could only - * be the DMA controller of Medfield - */ - dma_dev = pci_get_device(PCI_VENDOR_ID_INTEL, 0x0827, NULL); - if (!dma_dev) - return -ENODEV; - - dma_cap_zero(mask); - dma_cap_set(DMA_SLAVE, mask); - - /* 1. Init rx channel */ - slave.dma_dev = &dma_dev->dev; - dws->rxchan = dma_request_channel(mask, mid_spi_dma_chan_filter, &slave); - if (!dws->rxchan) - goto err_exit; - - /* 2. Init tx channel */ - slave.dst_id = 1; - dws->txchan = dma_request_channel(mask, mid_spi_dma_chan_filter, &slave); - if (!dws->txchan) - goto free_rxchan; - - dws->master->dma_rx = dws->rxchan; - dws->master->dma_tx = dws->txchan; - - init_completion(&dws->dma_completion); - - mid_spi_maxburst_init(dws); - - return 0; - -free_rxchan: - dma_release_channel(dws->rxchan); - dws->rxchan = NULL; -err_exit: - return -EBUSY; -} - -static int mid_spi_dma_init_generic(struct device *dev, struct dw_spi *dws) -{ - dws->rxchan = dma_request_slave_channel(dev, "rx"); - if (!dws->rxchan) - return -ENODEV; - - dws->txchan = dma_request_slave_channel(dev, "tx"); - if (!dws->txchan) { - dma_release_channel(dws->rxchan); - dws->rxchan = NULL; - return -ENODEV; - } - - dws->master->dma_rx = dws->rxchan; - dws->master->dma_tx = dws->txchan; - - init_completion(&dws->dma_completion); - - mid_spi_maxburst_init(dws); - - return 0; -} - -static void mid_spi_dma_exit(struct dw_spi *dws) -{ - if (dws->txchan) { - dmaengine_terminate_sync(dws->txchan); - dma_release_channel(dws->txchan); - } - - if (dws->rxchan) { - dmaengine_terminate_sync(dws->rxchan); - dma_release_channel(dws->rxchan); - } - - dw_writel(dws, DW_SPI_DMACR, 0); -} - -static irqreturn_t dma_transfer(struct dw_spi *dws) -{ - u16 irq_status = dw_readl(dws, DW_SPI_ISR); - - if (!irq_status) - return IRQ_NONE; - - dw_readl(dws, DW_SPI_ICR); - spi_reset_chip(dws); - - dev_err(&dws->master->dev, "%s: FIFO overrun/underrun\n", __func__); - dws->master->cur_msg->status = -EIO; - complete(&dws->dma_completion); - return IRQ_HANDLED; -} - -static bool mid_spi_can_dma(struct spi_controller *master, - struct spi_device *spi, struct spi_transfer *xfer) -{ - struct dw_spi *dws = spi_controller_get_devdata(master); - - return xfer->len > dws->fifo_len; -} - -static enum dma_slave_buswidth convert_dma_width(u8 n_bytes) { - if (n_bytes == 1) - return DMA_SLAVE_BUSWIDTH_1_BYTE; - else if (n_bytes == 2) - return DMA_SLAVE_BUSWIDTH_2_BYTES; - - return DMA_SLAVE_BUSWIDTH_UNDEFINED; -} - -static int dw_spi_dma_wait(struct dw_spi *dws, struct spi_transfer *xfer) -{ - unsigned long long ms; - - ms = xfer->len * MSEC_PER_SEC * BITS_PER_BYTE; - do_div(ms, xfer->effective_speed_hz); - ms += ms + 200; - - if (ms > UINT_MAX) - ms = UINT_MAX; - - ms = wait_for_completion_timeout(&dws->dma_completion, - msecs_to_jiffies(ms)); - - if (ms == 0) { - dev_err(&dws->master->cur_msg->spi->dev, - "DMA transaction timed out\n"); - return -ETIMEDOUT; - } - - return 0; -} - -static inline bool dw_spi_dma_tx_busy(struct dw_spi *dws) -{ - return !(dw_readl(dws, DW_SPI_SR) & SR_TF_EMPT); -} - -static int dw_spi_dma_wait_tx_done(struct dw_spi *dws, - struct spi_transfer *xfer) -{ - int retry = WAIT_RETRIES; - struct spi_delay delay; - u32 nents; - - nents = dw_readl(dws, DW_SPI_TXFLR); - delay.unit = SPI_DELAY_UNIT_SCK; - delay.value = nents * dws->n_bytes * BITS_PER_BYTE; - - while (dw_spi_dma_tx_busy(dws) && retry--) - spi_delay_exec(&delay, xfer); - - if (retry < 0) { - dev_err(&dws->master->dev, "Tx hanged up\n"); - return -EIO; - } - - return 0; -} - -/* - * dws->dma_chan_busy is set before the dma transfer starts, callback for tx - * channel will clear a corresponding bit. - */ -static void dw_spi_dma_tx_done(void *arg) -{ - struct dw_spi *dws = arg; - - clear_bit(TX_BUSY, &dws->dma_chan_busy); - if (test_bit(RX_BUSY, &dws->dma_chan_busy)) - return; - - dw_writel(dws, DW_SPI_DMACR, 0); - complete(&dws->dma_completion); -} - -static struct dma_async_tx_descriptor *dw_spi_dma_prepare_tx(struct dw_spi *dws, - struct spi_transfer *xfer) -{ - struct dma_slave_config txconf; - struct dma_async_tx_descriptor *txdesc; - - if (!xfer->tx_buf) - return NULL; - - memset(&txconf, 0, sizeof(txconf)); - txconf.direction = DMA_MEM_TO_DEV; - txconf.dst_addr = dws->dma_addr; - txconf.dst_maxburst = dws->txburst; - txconf.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; - txconf.dst_addr_width = convert_dma_width(dws->n_bytes); - txconf.device_fc = false; - - dmaengine_slave_config(dws->txchan, &txconf); - - txdesc = dmaengine_prep_slave_sg(dws->txchan, - xfer->tx_sg.sgl, - xfer->tx_sg.nents, - DMA_MEM_TO_DEV, - DMA_PREP_INTERRUPT | DMA_CTRL_ACK); - if (!txdesc) - return NULL; - - txdesc->callback = dw_spi_dma_tx_done; - txdesc->callback_param = dws; - - return txdesc; -} - -static inline bool dw_spi_dma_rx_busy(struct dw_spi *dws) -{ - return !!(dw_readl(dws, DW_SPI_SR) & SR_RF_NOT_EMPT); -} - -static int dw_spi_dma_wait_rx_done(struct dw_spi *dws) -{ - int retry = WAIT_RETRIES; - struct spi_delay delay; - unsigned long ns, us; - u32 nents; - - /* - * It's unlikely that DMA engine is still doing the data fetching, but - * if it's let's give it some reasonable time. The timeout calculation - * is based on the synchronous APB/SSI reference clock rate, on a - * number of data entries left in the Rx FIFO, times a number of clock - * periods normally needed for a single APB read/write transaction - * without PREADY signal utilized (which is true for the DW APB SSI - * controller). - */ - nents = dw_readl(dws, DW_SPI_RXFLR); - ns = 4U * NSEC_PER_SEC / dws->max_freq * nents; - if (ns <= NSEC_PER_USEC) { - delay.unit = SPI_DELAY_UNIT_NSECS; - delay.value = ns; - } else { - us = DIV_ROUND_UP(ns, NSEC_PER_USEC); - delay.unit = SPI_DELAY_UNIT_USECS; - delay.value = clamp_val(us, 0, USHRT_MAX); - } - - while (dw_spi_dma_rx_busy(dws) && retry--) - spi_delay_exec(&delay, NULL); - - if (retry < 0) { - dev_err(&dws->master->dev, "Rx hanged up\n"); - return -EIO; - } - - return 0; -} - -/* - * dws->dma_chan_busy is set before the dma transfer starts, callback for rx - * channel will clear a corresponding bit. - */ -static void dw_spi_dma_rx_done(void *arg) -{ - struct dw_spi *dws = arg; - - clear_bit(RX_BUSY, &dws->dma_chan_busy); - if (test_bit(TX_BUSY, &dws->dma_chan_busy)) - return; - - dw_writel(dws, DW_SPI_DMACR, 0); - complete(&dws->dma_completion); -} - -static struct dma_async_tx_descriptor *dw_spi_dma_prepare_rx(struct dw_spi *dws, - struct spi_transfer *xfer) -{ - struct dma_slave_config rxconf; - struct dma_async_tx_descriptor *rxdesc; - - if (!xfer->rx_buf) - return NULL; - - memset(&rxconf, 0, sizeof(rxconf)); - rxconf.direction = DMA_DEV_TO_MEM; - rxconf.src_addr = dws->dma_addr; - rxconf.src_maxburst = dws->rxburst; - rxconf.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; - rxconf.src_addr_width = convert_dma_width(dws->n_bytes); - rxconf.device_fc = false; - - dmaengine_slave_config(dws->rxchan, &rxconf); - - rxdesc = dmaengine_prep_slave_sg(dws->rxchan, - xfer->rx_sg.sgl, - xfer->rx_sg.nents, - DMA_DEV_TO_MEM, - DMA_PREP_INTERRUPT | DMA_CTRL_ACK); - if (!rxdesc) - return NULL; - - rxdesc->callback = dw_spi_dma_rx_done; - rxdesc->callback_param = dws; - - return rxdesc; -} - -static int mid_spi_dma_setup(struct dw_spi *dws, struct spi_transfer *xfer) -{ - u16 imr = 0, dma_ctrl = 0; - - dw_writel(dws, DW_SPI_DMARDLR, dws->rxburst - 1); - dw_writel(dws, DW_SPI_DMATDLR, dws->fifo_len - dws->txburst); - - if (xfer->tx_buf) { - dma_ctrl |= SPI_DMA_TDMAE; - imr |= SPI_INT_TXOI; - } - if (xfer->rx_buf) { - dma_ctrl |= SPI_DMA_RDMAE; - imr |= SPI_INT_RXUI | SPI_INT_RXOI; - } - dw_writel(dws, DW_SPI_DMACR, dma_ctrl); - - /* Set the interrupt mask */ - spi_umask_intr(dws, imr); - - reinit_completion(&dws->dma_completion); - - dws->transfer_handler = dma_transfer; - - return 0; -} - -static int mid_spi_dma_transfer(struct dw_spi *dws, struct spi_transfer *xfer) -{ - struct dma_async_tx_descriptor *txdesc, *rxdesc; - int ret; - - /* Prepare the TX dma transfer */ - txdesc = dw_spi_dma_prepare_tx(dws, xfer); - - /* Prepare the RX dma transfer */ - rxdesc = dw_spi_dma_prepare_rx(dws, xfer); - - /* rx must be started before tx due to spi instinct */ - if (rxdesc) { - set_bit(RX_BUSY, &dws->dma_chan_busy); - dmaengine_submit(rxdesc); - dma_async_issue_pending(dws->rxchan); - } - - if (txdesc) { - set_bit(TX_BUSY, &dws->dma_chan_busy); - dmaengine_submit(txdesc); - dma_async_issue_pending(dws->txchan); - } - - ret = dw_spi_dma_wait(dws, xfer); - if (ret) - return ret; - - if (txdesc && dws->master->cur_msg->status == -EINPROGRESS) { - ret = dw_spi_dma_wait_tx_done(dws, xfer); - if (ret) - return ret; - } - - if (rxdesc && dws->master->cur_msg->status == -EINPROGRESS) - ret = dw_spi_dma_wait_rx_done(dws); - - return ret; -} - -static void mid_spi_dma_stop(struct dw_spi *dws) -{ - if (test_bit(TX_BUSY, &dws->dma_chan_busy)) { - dmaengine_terminate_sync(dws->txchan); - clear_bit(TX_BUSY, &dws->dma_chan_busy); - } - if (test_bit(RX_BUSY, &dws->dma_chan_busy)) { - dmaengine_terminate_sync(dws->rxchan); - clear_bit(RX_BUSY, &dws->dma_chan_busy); - } - - dw_writel(dws, DW_SPI_DMACR, 0); -} - -static const struct dw_spi_dma_ops mfld_dma_ops = { - .dma_init = mid_spi_dma_init_mfld, - .dma_exit = mid_spi_dma_exit, - .dma_setup = mid_spi_dma_setup, - .can_dma = mid_spi_can_dma, - .dma_transfer = mid_spi_dma_transfer, - .dma_stop = mid_spi_dma_stop, -}; - -static void dw_spi_mid_setup_dma_mfld(struct dw_spi *dws) -{ - dws->dma_ops = &mfld_dma_ops; -} - -static const struct dw_spi_dma_ops generic_dma_ops = { - .dma_init = mid_spi_dma_init_generic, - .dma_exit = mid_spi_dma_exit, - .dma_setup = mid_spi_dma_setup, - .can_dma = mid_spi_can_dma, - .dma_transfer = mid_spi_dma_transfer, - .dma_stop = mid_spi_dma_stop, -}; - -static void dw_spi_mid_setup_dma_generic(struct dw_spi *dws) -{ - dws->dma_ops = &generic_dma_ops; -} -#else /* CONFIG_SPI_DW_MID_DMA */ -static inline void dw_spi_mid_setup_dma_mfld(struct dw_spi *dws) {} -static inline void dw_spi_mid_setup_dma_generic(struct dw_spi *dws) {} -#endif - -/* Some specific info for SPI0 controller on Intel MID */ - -/* HW info for MRST Clk Control Unit, 32b reg per controller */ -#define MRST_SPI_CLK_BASE 100000000 /* 100m */ -#define MRST_CLK_SPI_REG 0xff11d86c -#define CLK_SPI_BDIV_OFFSET 0 -#define CLK_SPI_BDIV_MASK 0x00000007 -#define CLK_SPI_CDIV_OFFSET 9 -#define CLK_SPI_CDIV_MASK 0x00000e00 -#define CLK_SPI_DISABLE_OFFSET 8 - -int dw_spi_mid_init_mfld(struct dw_spi *dws) -{ - void __iomem *clk_reg; - u32 clk_cdiv; - - clk_reg = ioremap(MRST_CLK_SPI_REG, 16); - if (!clk_reg) - return -ENOMEM; - - /* Get SPI controller operating freq info */ - clk_cdiv = readl(clk_reg + dws->bus_num * sizeof(u32)); - clk_cdiv &= CLK_SPI_CDIV_MASK; - clk_cdiv >>= CLK_SPI_CDIV_OFFSET; - dws->max_freq = MRST_SPI_CLK_BASE / (clk_cdiv + 1); - - iounmap(clk_reg); - - /* Register hook to configure CTRLR0 */ - dws->update_cr0 = dw_spi_update_cr0; - - dw_spi_mid_setup_dma_mfld(dws); - return 0; -} - -int dw_spi_mid_init_generic(struct dw_spi *dws) -{ - /* Register hook to configure CTRLR0 */ - dws->update_cr0 = dw_spi_update_cr0; - - dw_spi_mid_setup_dma_generic(dws); - return 0; -} diff --git a/drivers/spi/spi-dw-pci.c b/drivers/spi/spi-dw-pci.c index dde54a918b5d..c13707b8493e 100644 --- a/drivers/spi/spi-dw-pci.c +++ b/drivers/spi/spi-dw-pci.c @@ -15,6 +15,15 @@ #define DRIVER_NAME "dw_spi_pci" +/* HW info for MRST Clk Control Unit, 32b reg per controller */ +#define MRST_SPI_CLK_BASE 100000000 /* 100m */ +#define MRST_CLK_SPI_REG 0xff11d86c +#define CLK_SPI_BDIV_OFFSET 0 +#define CLK_SPI_BDIV_MASK 0x00000007 +#define CLK_SPI_CDIV_OFFSET 9 +#define CLK_SPI_CDIV_MASK 0x00000e00 +#define CLK_SPI_DISABLE_OFFSET 8 + struct spi_pci_desc { int (*setup)(struct dw_spi *); u16 num_cs; @@ -22,20 +31,55 @@ struct spi_pci_desc { u32 max_freq; }; +static int spi_mid_init(struct dw_spi *dws) +{ + void __iomem *clk_reg; + u32 clk_cdiv; + + clk_reg = ioremap(MRST_CLK_SPI_REG, 16); + if (!clk_reg) + return -ENOMEM; + + /* Get SPI controller operating freq info */ + clk_cdiv = readl(clk_reg + dws->bus_num * sizeof(u32)); + clk_cdiv &= CLK_SPI_CDIV_MASK; + clk_cdiv >>= CLK_SPI_CDIV_OFFSET; + dws->max_freq = MRST_SPI_CLK_BASE / (clk_cdiv + 1); + + iounmap(clk_reg); + + /* Register hook to configure CTRLR0 */ + dws->update_cr0 = dw_spi_update_cr0; + + dw_spi_mid_setup_dma_mfld(dws); + + return 0; +} + +static int spi_generic_init(struct dw_spi *dws) +{ + /* Register hook to configure CTRLR0 */ + dws->update_cr0 = dw_spi_update_cr0; + + dw_spi_mid_setup_dma_generic(dws); + + return 0; +} + static struct spi_pci_desc spi_pci_mid_desc_1 = { - .setup = dw_spi_mid_init_mfld, + .setup = spi_mid_init, .num_cs = 5, .bus_num = 0, }; static struct spi_pci_desc spi_pci_mid_desc_2 = { - .setup = dw_spi_mid_init_mfld, + .setup = spi_mid_init, .num_cs = 2, .bus_num = 1, }; static struct spi_pci_desc spi_pci_ehl_desc = { - .setup = dw_spi_mid_init_generic, + .setup = spi_generic_init, .num_cs = 2, .bus_num = -1, .max_freq = 100000000, diff --git a/drivers/spi/spi-dw.h b/drivers/spi/spi-dw.h index 9247670fcdfb..91608cf12636 100644 --- a/drivers/spi/spi-dw.h +++ b/drivers/spi/spi-dw.h @@ -257,8 +257,16 @@ extern u32 dw_spi_update_cr0_v1_01a(struct spi_controller *master, struct spi_device *spi, struct spi_transfer *transfer); -/* platform related setup */ -extern int dw_spi_mid_init_mfld(struct dw_spi *dws); -extern int dw_spi_mid_init_generic(struct dw_spi *dws); +#ifdef CONFIG_SPI_DW_DMA + +extern void dw_spi_mid_setup_dma_mfld(struct dw_spi *dws); +extern void dw_spi_mid_setup_dma_generic(struct dw_spi *dws); + +#else + +static inline void dw_spi_mid_setup_dma_mfld(struct dw_spi *dws) {} +static inline void dw_spi_mid_setup_dma_generic(struct dw_spi *dws) {} + +#endif /* !CONFIG_SPI_DW_DMA */ #endif /* DW_SPI_HEADER_H */ -- cgit v1.2.3 From 06cfadb8c51b05c6b91c2d43e0fe72b3d643dced Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 29 May 2020 16:12:00 +0300 Subject: spi: dw: Remove DW DMA code dependency from DW_DMAC_PCI Since there is a generic method available to initialize the DW SPI DMA interface on any DT and ACPI-based platforms, which in general can be designed with not only DW DMAC but with any DMA engine on board, we can freely remove the CONFIG_DW_DMAC_PCI config from dependency list of CONFIG_SPI_DW_DMA. Especially seeing that we don't use anything DW DMAC specific in the new driver. Co-developed-by: Georgy Vlasov Co-developed-by: Ramil Zaripov Signed-off-by: Georgy Vlasov Signed-off-by: Ramil Zaripov Signed-off-by: Serge Semin Reviewed-by: Andy Shevchenko Cc: Alexey Malahov Cc: Thomas Bogendoerfer Cc: Arnd Bergmann Cc: Feng Tang Cc: Rob Herring Cc: linux-mips@vger.kernel.org Cc: devicetree@vger.kernel.org Link: https://lore.kernel.org/r/20200529131205.31838-12-Sergey.Semin@baikalelectronics.ru Signed-off-by: Mark Brown --- drivers/spi/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index 73d508eb4871..a20995090ff9 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -228,7 +228,7 @@ config SPI_DESIGNWARE config SPI_DW_DMA bool "DMA support for DW SPI controller" - depends on SPI_DESIGNWARE && DW_DMAC_PCI + depends on SPI_DESIGNWARE config SPI_DW_PCI tristate "PCI interface driver for DW SPI core" -- cgit v1.2.3 From ecb3a67edfd353837dc23b538fb250d1dfd88e7b Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 29 May 2020 16:12:01 +0300 Subject: spi: dw: Add DW SPI DMA/PCI/MMIO dependency on the DW SPI core Seeing all of the DW SPI driver components like DW SPI DMA/PCI/MMIO depend on the DW SPI core code it's better to use the if-endif conditional kernel config statement to signify that common dependency. Co-developed-by: Georgy Vlasov Co-developed-by: Ramil Zaripov Signed-off-by: Georgy Vlasov Signed-off-by: Ramil Zaripov Signed-off-by: Serge Semin Reviewed-by: Andy Shevchenko Cc: Alexey Malahov Cc: Thomas Bogendoerfer Cc: Arnd Bergmann Cc: Feng Tang Cc: Rob Herring Cc: linux-mips@vger.kernel.org Cc: devicetree@vger.kernel.org Link: https://lore.kernel.org/r/20200529131205.31838-13-Sergey.Semin@baikalelectronics.ru Signed-off-by: Mark Brown --- drivers/spi/Kconfig | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index a20995090ff9..8f1f8fca79e3 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -226,17 +226,20 @@ config SPI_DESIGNWARE help general driver for SPI controller core from DesignWare +if SPI_DESIGNWARE + config SPI_DW_DMA bool "DMA support for DW SPI controller" - depends on SPI_DESIGNWARE config SPI_DW_PCI tristate "PCI interface driver for DW SPI core" - depends on SPI_DESIGNWARE && PCI + depends on PCI config SPI_DW_MMIO tristate "Memory-mapped io interface driver for DW SPI core" - depends on SPI_DESIGNWARE + depends on HAS_IOMEM + +endif config SPI_DLN2 tristate "Diolan DLN-2 USB SPI adapter" -- cgit v1.2.3 From 57784411728ff4d72ae051fdbba1e54fcb1f8d6f Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 29 May 2020 16:12:02 +0300 Subject: spi: dw: Cleanup generic DW DMA code namings Since from now the former Intel MID platform layer is used as a generic DW SPI DMA module, let's alter the internal methods naming to be DMA-related instead of having the "mid_" prefix. Co-developed-by: Georgy Vlasov Co-developed-by: Ramil Zaripov Signed-off-by: Georgy Vlasov Signed-off-by: Ramil Zaripov Signed-off-by: Serge Semin Reviewed-by: Andy Shevchenko Cc: Alexey Malahov Cc: Thomas Bogendoerfer Cc: Arnd Bergmann Cc: Feng Tang Cc: Rob Herring Cc: linux-mips@vger.kernel.org Cc: devicetree@vger.kernel.org Link: https://lore.kernel.org/r/20200529131205.31838-14-Sergey.Semin@baikalelectronics.ru Signed-off-by: Mark Brown --- drivers/spi/spi-dw-dma.c | 85 ++++++++++++++++++++++++------------------------ drivers/spi/spi-dw-pci.c | 4 +-- drivers/spi/spi-dw.h | 8 ++--- 3 files changed, 49 insertions(+), 48 deletions(-) (limited to 'drivers') diff --git a/drivers/spi/spi-dw-dma.c b/drivers/spi/spi-dw-dma.c index 7ae31682b5de..1b96cec6d8cd 100644 --- a/drivers/spi/spi-dw-dma.c +++ b/drivers/spi/spi-dw-dma.c @@ -23,7 +23,7 @@ #define TX_BUSY 1 #define TX_BURST_LEVEL 16 -static bool mid_spi_dma_chan_filter(struct dma_chan *chan, void *param) +static bool dw_spi_dma_chan_filter(struct dma_chan *chan, void *param) { struct dw_dma_slave *s = param; @@ -34,7 +34,7 @@ static bool mid_spi_dma_chan_filter(struct dma_chan *chan, void *param) return true; } -static void mid_spi_maxburst_init(struct dw_spi *dws) +static void dw_spi_dma_maxburst_init(struct dw_spi *dws) { struct dma_slave_caps caps; u32 max_burst, def_burst; @@ -59,7 +59,7 @@ static void mid_spi_maxburst_init(struct dw_spi *dws) dws->txburst = min(max_burst, def_burst); } -static int mid_spi_dma_init_mfld(struct device *dev, struct dw_spi *dws) +static int dw_spi_dma_init_mfld(struct device *dev, struct dw_spi *dws) { struct dw_dma_slave slave = { .src_id = 0, @@ -81,13 +81,13 @@ static int mid_spi_dma_init_mfld(struct device *dev, struct dw_spi *dws) /* 1. Init rx channel */ slave.dma_dev = &dma_dev->dev; - dws->rxchan = dma_request_channel(mask, mid_spi_dma_chan_filter, &slave); + dws->rxchan = dma_request_channel(mask, dw_spi_dma_chan_filter, &slave); if (!dws->rxchan) goto err_exit; /* 2. Init tx channel */ slave.dst_id = 1; - dws->txchan = dma_request_channel(mask, mid_spi_dma_chan_filter, &slave); + dws->txchan = dma_request_channel(mask, dw_spi_dma_chan_filter, &slave); if (!dws->txchan) goto free_rxchan; @@ -96,7 +96,7 @@ static int mid_spi_dma_init_mfld(struct device *dev, struct dw_spi *dws) init_completion(&dws->dma_completion); - mid_spi_maxburst_init(dws); + dw_spi_dma_maxburst_init(dws); return 0; @@ -107,7 +107,7 @@ err_exit: return -EBUSY; } -static int mid_spi_dma_init_generic(struct device *dev, struct dw_spi *dws) +static int dw_spi_dma_init_generic(struct device *dev, struct dw_spi *dws) { dws->rxchan = dma_request_slave_channel(dev, "rx"); if (!dws->rxchan) @@ -125,12 +125,12 @@ static int mid_spi_dma_init_generic(struct device *dev, struct dw_spi *dws) init_completion(&dws->dma_completion); - mid_spi_maxburst_init(dws); + dw_spi_dma_maxburst_init(dws); return 0; } -static void mid_spi_dma_exit(struct dw_spi *dws) +static void dw_spi_dma_exit(struct dw_spi *dws) { if (dws->txchan) { dmaengine_terminate_sync(dws->txchan); @@ -145,7 +145,7 @@ static void mid_spi_dma_exit(struct dw_spi *dws) dw_writel(dws, DW_SPI_DMACR, 0); } -static irqreturn_t dma_transfer(struct dw_spi *dws) +static irqreturn_t dw_spi_dma_transfer_handler(struct dw_spi *dws) { u16 irq_status = dw_readl(dws, DW_SPI_ISR); @@ -161,15 +161,16 @@ static irqreturn_t dma_transfer(struct dw_spi *dws) return IRQ_HANDLED; } -static bool mid_spi_can_dma(struct spi_controller *master, - struct spi_device *spi, struct spi_transfer *xfer) +static bool dw_spi_can_dma(struct spi_controller *master, + struct spi_device *spi, struct spi_transfer *xfer) { struct dw_spi *dws = spi_controller_get_devdata(master); return xfer->len > dws->fifo_len; } -static enum dma_slave_buswidth convert_dma_width(u8 n_bytes) { +static enum dma_slave_buswidth dw_spi_dma_convert_width(u8 n_bytes) +{ if (n_bytes == 1) return DMA_SLAVE_BUSWIDTH_1_BYTE; else if (n_bytes == 2) @@ -244,8 +245,8 @@ static void dw_spi_dma_tx_done(void *arg) complete(&dws->dma_completion); } -static struct dma_async_tx_descriptor *dw_spi_dma_prepare_tx(struct dw_spi *dws, - struct spi_transfer *xfer) +static struct dma_async_tx_descriptor * +dw_spi_dma_prepare_tx(struct dw_spi *dws, struct spi_transfer *xfer) { struct dma_slave_config txconf; struct dma_async_tx_descriptor *txdesc; @@ -258,7 +259,7 @@ static struct dma_async_tx_descriptor *dw_spi_dma_prepare_tx(struct dw_spi *dws, txconf.dst_addr = dws->dma_addr; txconf.dst_maxburst = dws->txburst; txconf.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; - txconf.dst_addr_width = convert_dma_width(dws->n_bytes); + txconf.dst_addr_width = dw_spi_dma_convert_width(dws->n_bytes); txconf.device_fc = false; dmaengine_slave_config(dws->txchan, &txconf); @@ -350,7 +351,7 @@ static struct dma_async_tx_descriptor *dw_spi_dma_prepare_rx(struct dw_spi *dws, rxconf.src_addr = dws->dma_addr; rxconf.src_maxburst = dws->rxburst; rxconf.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; - rxconf.src_addr_width = convert_dma_width(dws->n_bytes); + rxconf.src_addr_width = dw_spi_dma_convert_width(dws->n_bytes); rxconf.device_fc = false; dmaengine_slave_config(dws->rxchan, &rxconf); @@ -369,7 +370,7 @@ static struct dma_async_tx_descriptor *dw_spi_dma_prepare_rx(struct dw_spi *dws, return rxdesc; } -static int mid_spi_dma_setup(struct dw_spi *dws, struct spi_transfer *xfer) +static int dw_spi_dma_setup(struct dw_spi *dws, struct spi_transfer *xfer) { u16 imr = 0, dma_ctrl = 0; @@ -391,12 +392,12 @@ static int mid_spi_dma_setup(struct dw_spi *dws, struct spi_transfer *xfer) reinit_completion(&dws->dma_completion); - dws->transfer_handler = dma_transfer; + dws->transfer_handler = dw_spi_dma_transfer_handler; return 0; } -static int mid_spi_dma_transfer(struct dw_spi *dws, struct spi_transfer *xfer) +static int dw_spi_dma_transfer(struct dw_spi *dws, struct spi_transfer *xfer) { struct dma_async_tx_descriptor *txdesc, *rxdesc; int ret; @@ -436,7 +437,7 @@ static int mid_spi_dma_transfer(struct dw_spi *dws, struct spi_transfer *xfer) return ret; } -static void mid_spi_dma_stop(struct dw_spi *dws) +static void dw_spi_dma_stop(struct dw_spi *dws) { if (test_bit(TX_BUSY, &dws->dma_chan_busy)) { dmaengine_terminate_sync(dws->txchan); @@ -450,32 +451,32 @@ static void mid_spi_dma_stop(struct dw_spi *dws) dw_writel(dws, DW_SPI_DMACR, 0); } -static const struct dw_spi_dma_ops mfld_dma_ops = { - .dma_init = mid_spi_dma_init_mfld, - .dma_exit = mid_spi_dma_exit, - .dma_setup = mid_spi_dma_setup, - .can_dma = mid_spi_can_dma, - .dma_transfer = mid_spi_dma_transfer, - .dma_stop = mid_spi_dma_stop, +static const struct dw_spi_dma_ops dw_spi_dma_mfld_ops = { + .dma_init = dw_spi_dma_init_mfld, + .dma_exit = dw_spi_dma_exit, + .dma_setup = dw_spi_dma_setup, + .can_dma = dw_spi_can_dma, + .dma_transfer = dw_spi_dma_transfer, + .dma_stop = dw_spi_dma_stop, }; -void dw_spi_mid_setup_dma_mfld(struct dw_spi *dws) +void dw_spi_dma_setup_mfld(struct dw_spi *dws) { - dws->dma_ops = &mfld_dma_ops; + dws->dma_ops = &dw_spi_dma_mfld_ops; } -EXPORT_SYMBOL_GPL(dw_spi_mid_setup_dma_mfld); - -static const struct dw_spi_dma_ops generic_dma_ops = { - .dma_init = mid_spi_dma_init_generic, - .dma_exit = mid_spi_dma_exit, - .dma_setup = mid_spi_dma_setup, - .can_dma = mid_spi_can_dma, - .dma_transfer = mid_spi_dma_transfer, - .dma_stop = mid_spi_dma_stop, +EXPORT_SYMBOL_GPL(dw_spi_dma_setup_mfld); + +static const struct dw_spi_dma_ops dw_spi_dma_generic_ops = { + .dma_init = dw_spi_dma_init_generic, + .dma_exit = dw_spi_dma_exit, + .dma_setup = dw_spi_dma_setup, + .can_dma = dw_spi_can_dma, + .dma_transfer = dw_spi_dma_transfer, + .dma_stop = dw_spi_dma_stop, }; -void dw_spi_mid_setup_dma_generic(struct dw_spi *dws) +void dw_spi_dma_setup_generic(struct dw_spi *dws) { - dws->dma_ops = &generic_dma_ops; + dws->dma_ops = &dw_spi_dma_generic_ops; } -EXPORT_SYMBOL_GPL(dw_spi_mid_setup_dma_generic); +EXPORT_SYMBOL_GPL(dw_spi_dma_setup_generic); diff --git a/drivers/spi/spi-dw-pci.c b/drivers/spi/spi-dw-pci.c index c13707b8493e..2ea73809ca34 100644 --- a/drivers/spi/spi-dw-pci.c +++ b/drivers/spi/spi-dw-pci.c @@ -51,7 +51,7 @@ static int spi_mid_init(struct dw_spi *dws) /* Register hook to configure CTRLR0 */ dws->update_cr0 = dw_spi_update_cr0; - dw_spi_mid_setup_dma_mfld(dws); + dw_spi_dma_setup_mfld(dws); return 0; } @@ -61,7 +61,7 @@ static int spi_generic_init(struct dw_spi *dws) /* Register hook to configure CTRLR0 */ dws->update_cr0 = dw_spi_update_cr0; - dw_spi_mid_setup_dma_generic(dws); + dw_spi_dma_setup_generic(dws); return 0; } diff --git a/drivers/spi/spi-dw.h b/drivers/spi/spi-dw.h index 91608cf12636..0b2cd7994513 100644 --- a/drivers/spi/spi-dw.h +++ b/drivers/spi/spi-dw.h @@ -259,13 +259,13 @@ extern u32 dw_spi_update_cr0_v1_01a(struct spi_controller *master, #ifdef CONFIG_SPI_DW_DMA -extern void dw_spi_mid_setup_dma_mfld(struct dw_spi *dws); -extern void dw_spi_mid_setup_dma_generic(struct dw_spi *dws); +extern void dw_spi_dma_setup_mfld(struct dw_spi *dws); +extern void dw_spi_dma_setup_generic(struct dw_spi *dws); #else -static inline void dw_spi_mid_setup_dma_mfld(struct dw_spi *dws) {} -static inline void dw_spi_mid_setup_dma_generic(struct dw_spi *dws) {} +static inline void dw_spi_dma_setup_mfld(struct dw_spi *dws) {} +static inline void dw_spi_dma_setup_generic(struct dw_spi *dws) {} #endif /* !CONFIG_SPI_DW_DMA */ -- cgit v1.2.3 From 0fdad596d46b28d5c3e39d1897c5e3878b64d9a2 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 29 May 2020 16:12:03 +0300 Subject: spi: dw: Add DMA support to the DW SPI MMIO driver Since the common code in the spi-dw-dma.c driver is ready to be used by the MMIO driver and now provides a method to generically (on any DT or ACPI-based platforms) retrieve the Tx/Rx DMA channel handlers, we can use it and a set of the common DW SPI DMA callbacks to enable DMA at least for generic "snps,dw-apb-ssi" and "snps,dwc-ssi-1.01a" devices. Co-developed-by: Georgy Vlasov Co-developed-by: Ramil Zaripov Signed-off-by: Georgy Vlasov Signed-off-by: Ramil Zaripov Signed-off-by: Serge Semin Reviewed-by: Andy Shevchenko Cc: Alexey Malahov Cc: Thomas Bogendoerfer Cc: Arnd Bergmann Cc: Feng Tang Cc: Rob Herring Cc: linux-mips@vger.kernel.org Cc: devicetree@vger.kernel.org Link: https://lore.kernel.org/r/20200529131205.31838-15-Sergey.Semin@baikalelectronics.ru Signed-off-by: Mark Brown --- drivers/spi/spi-dw-mmio.c | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'drivers') diff --git a/drivers/spi/spi-dw-mmio.c b/drivers/spi/spi-dw-mmio.c index 0894b4c09496..e23d0c53a664 100644 --- a/drivers/spi/spi-dw-mmio.c +++ b/drivers/spi/spi-dw-mmio.c @@ -149,6 +149,8 @@ static int dw_spi_dw_apb_init(struct platform_device *pdev, /* Register hook to configure CTRLR0 */ dwsmmio->dws.update_cr0 = dw_spi_update_cr0; + dw_spi_dma_setup_generic(&dwsmmio->dws); + return 0; } @@ -158,6 +160,8 @@ static int dw_spi_dwc_ssi_init(struct platform_device *pdev, /* Register hook to configure CTRLR0 */ dwsmmio->dws.update_cr0 = dw_spi_update_cr0_v1_01a; + dw_spi_dma_setup_generic(&dwsmmio->dws); + return 0; } -- cgit v1.2.3 From 8378449d1f79add31be77e77fc7df9f639878e9c Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 29 May 2020 16:12:04 +0300 Subject: spi: dw: Use regset32 DebugFS method to create regdump file DebugFS kernel interface provides a dedicated method to create the registers dump file. Use it instead of creating a generic DebugFS file with manually written read callback function. Signed-off-by: Serge Semin Reviewed-by: Andy Shevchenko Cc: Georgy Vlasov Cc: Ramil Zaripov Cc: Alexey Malahov Cc: Thomas Bogendoerfer Cc: Arnd Bergmann Cc: Feng Tang Cc: Rob Herring Cc: linux-mips@vger.kernel.org Cc: devicetree@vger.kernel.org Link: https://lore.kernel.org/r/20200529131205.31838-16-Sergey.Semin@baikalelectronics.ru Signed-off-by: Mark Brown --- drivers/spi/spi-dw-core.c | 86 ++++++++++++++--------------------------------- drivers/spi/spi-dw.h | 2 ++ 2 files changed, 28 insertions(+), 60 deletions(-) (limited to 'drivers') diff --git a/drivers/spi/spi-dw-core.c b/drivers/spi/spi-dw-core.c index 1f1a3536b7cf..caeece6681b3 100644 --- a/drivers/spi/spi-dw-core.c +++ b/drivers/spi/spi-dw-core.c @@ -29,66 +29,29 @@ struct chip_data { }; #ifdef CONFIG_DEBUG_FS -#define SPI_REGS_BUFSIZE 1024 -static ssize_t dw_spi_show_regs(struct file *file, char __user *user_buf, - size_t count, loff_t *ppos) -{ - struct dw_spi *dws = file->private_data; - char *buf; - u32 len = 0; - ssize_t ret; - - buf = kzalloc(SPI_REGS_BUFSIZE, GFP_KERNEL); - if (!buf) - return 0; - - len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len, - "%s registers:\n", dev_name(&dws->master->dev)); - len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len, - "=================================\n"); - len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len, - "CTRLR0: \t0x%08x\n", dw_readl(dws, DW_SPI_CTRLR0)); - len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len, - "CTRLR1: \t0x%08x\n", dw_readl(dws, DW_SPI_CTRLR1)); - len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len, - "SSIENR: \t0x%08x\n", dw_readl(dws, DW_SPI_SSIENR)); - len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len, - "SER: \t\t0x%08x\n", dw_readl(dws, DW_SPI_SER)); - len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len, - "BAUDR: \t\t0x%08x\n", dw_readl(dws, DW_SPI_BAUDR)); - len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len, - "TXFTLR: \t0x%08x\n", dw_readl(dws, DW_SPI_TXFTLR)); - len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len, - "RXFTLR: \t0x%08x\n", dw_readl(dws, DW_SPI_RXFTLR)); - len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len, - "TXFLR: \t\t0x%08x\n", dw_readl(dws, DW_SPI_TXFLR)); - len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len, - "RXFLR: \t\t0x%08x\n", dw_readl(dws, DW_SPI_RXFLR)); - len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len, - "SR: \t\t0x%08x\n", dw_readl(dws, DW_SPI_SR)); - len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len, - "IMR: \t\t0x%08x\n", dw_readl(dws, DW_SPI_IMR)); - len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len, - "ISR: \t\t0x%08x\n", dw_readl(dws, DW_SPI_ISR)); - len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len, - "DMACR: \t\t0x%08x\n", dw_readl(dws, DW_SPI_DMACR)); - len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len, - "DMATDLR: \t0x%08x\n", dw_readl(dws, DW_SPI_DMATDLR)); - len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len, - "DMARDLR: \t0x%08x\n", dw_readl(dws, DW_SPI_DMARDLR)); - len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len, - "=================================\n"); - - ret = simple_read_from_buffer(user_buf, count, ppos, buf, len); - kfree(buf); - return ret; + +#define DW_SPI_DBGFS_REG(_name, _off) \ +{ \ + .name = _name, \ + .offset = _off, \ } -static const struct file_operations dw_spi_regs_ops = { - .owner = THIS_MODULE, - .open = simple_open, - .read = dw_spi_show_regs, - .llseek = default_llseek, +static const struct debugfs_reg32 dw_spi_dbgfs_regs[] = { + DW_SPI_DBGFS_REG("CTRLR0", DW_SPI_CTRLR0), + DW_SPI_DBGFS_REG("CTRLR1", DW_SPI_CTRLR1), + DW_SPI_DBGFS_REG("SSIENR", DW_SPI_SSIENR), + DW_SPI_DBGFS_REG("SER", DW_SPI_SER), + DW_SPI_DBGFS_REG("BAUDR", DW_SPI_BAUDR), + DW_SPI_DBGFS_REG("TXFTLR", DW_SPI_TXFTLR), + DW_SPI_DBGFS_REG("RXFTLR", DW_SPI_RXFTLR), + DW_SPI_DBGFS_REG("TXFLR", DW_SPI_TXFLR), + DW_SPI_DBGFS_REG("RXFLR", DW_SPI_RXFLR), + DW_SPI_DBGFS_REG("SR", DW_SPI_SR), + DW_SPI_DBGFS_REG("IMR", DW_SPI_IMR), + DW_SPI_DBGFS_REG("ISR", DW_SPI_ISR), + DW_SPI_DBGFS_REG("DMACR", DW_SPI_DMACR), + DW_SPI_DBGFS_REG("DMATDLR", DW_SPI_DMATDLR), + DW_SPI_DBGFS_REG("DMARDLR", DW_SPI_DMARDLR), }; static int dw_spi_debugfs_init(struct dw_spi *dws) @@ -100,8 +63,11 @@ static int dw_spi_debugfs_init(struct dw_spi *dws) if (!dws->debugfs) return -ENOMEM; - debugfs_create_file("registers", S_IFREG | S_IRUGO, - dws->debugfs, (void *)dws, &dw_spi_regs_ops); + dws->regset.regs = dw_spi_dbgfs_regs; + dws->regset.nregs = ARRAY_SIZE(dw_spi_dbgfs_regs); + dws->regset.base = dws->regs; + debugfs_create_regset32("registers", 0400, dws->debugfs, &dws->regset); + return 0; } diff --git a/drivers/spi/spi-dw.h b/drivers/spi/spi-dw.h index 0b2cd7994513..151ba316619e 100644 --- a/drivers/spi/spi-dw.h +++ b/drivers/spi/spi-dw.h @@ -3,6 +3,7 @@ #define DW_SPI_HEADER_H #include +#include #include #include #include @@ -152,6 +153,7 @@ struct dw_spi { #ifdef CONFIG_DEBUG_FS struct dentry *debugfs; + struct debugfs_regset32 regset; #endif }; -- cgit v1.2.3