From fb0140774aff45b8dc326987cc1da89484ecb081 Mon Sep 17 00:00:00 2001 From: Cosmin Tanislav Date: Mon, 1 Dec 2025 15:42:17 +0200 Subject: [PATCH 01/11] spi: rzv2h-rspi: fix rzv2h_rspi_transfer_one() indentation Add the missing space to align to open pararenthesis. Signed-off-by: Cosmin Tanislav Link: https://patch.msgid.link/20251201134229.600817-2-cosmin-gabriel.tanislav.xa@renesas.com Signed-off-by: Mark Brown --- drivers/spi/spi-rzv2h-rspi.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/spi/spi-rzv2h-rspi.c b/drivers/spi/spi-rzv2h-rspi.c index 1db7e4e5d64e..8cffc9cb6b6b 100644 --- a/drivers/spi/spi-rzv2h-rspi.c +++ b/drivers/spi/spi-rzv2h-rspi.c @@ -225,8 +225,8 @@ static int rzv2h_rspi_receive(struct rzv2h_rspi_priv *rspi, void *rxbuf, } static int rzv2h_rspi_transfer_one(struct spi_controller *controller, - struct spi_device *spi, - struct spi_transfer *transfer) + struct spi_device *spi, + struct spi_transfer *transfer) { struct rzv2h_rspi_priv *rspi = spi_controller_get_devdata(controller); unsigned int words_to_transfer, i; From 9e4830b35dc0d522f45e1ec3ee5b1ff1648afe1b Mon Sep 17 00:00:00 2001 From: Cosmin Tanislav Date: Mon, 1 Dec 2025 15:42:18 +0200 Subject: [PATCH 02/11] spi: rzv2h-rspi: remove call to spi_finalize_current_transfer() A call to spi_finalize_current_transfer() is only needed when the SPI transfer is completed outside of the current context, when the .transfer_one() implementation returns > 0. Since the SPI transfer is completed in the current context, and we return 0 from .transfer_one(), the SPI core assumes that the transfer has completed and it does not wait for the completion variable that would be set by a call to spi_finalize_current_transfer(). Remove the call to spi_finalize_current_transfer(). Signed-off-by: Cosmin Tanislav Link: https://patch.msgid.link/20251201134229.600817-3-cosmin-gabriel.tanislav.xa@renesas.com Signed-off-by: Mark Brown --- drivers/spi/spi-rzv2h-rspi.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/spi/spi-rzv2h-rspi.c b/drivers/spi/spi-rzv2h-rspi.c index 8cffc9cb6b6b..beea4fb83d10 100644 --- a/drivers/spi/spi-rzv2h-rspi.c +++ b/drivers/spi/spi-rzv2h-rspi.c @@ -250,8 +250,6 @@ static int rzv2h_rspi_transfer_one(struct spi_controller *controller, if (ret) transfer->error = SPI_TRANS_FAIL_IO; - spi_finalize_current_transfer(controller); - return ret; } From 218917659df165cff72439480929e68a6e127b55 Mon Sep 17 00:00:00 2001 From: Cosmin Tanislav Date: Mon, 1 Dec 2025 15:42:19 +0200 Subject: [PATCH 03/11] spi: rzv2h-rspi: do not set SPI_TRANS_FAIL_IO Setting SPI_TRANS_FAIL_IO has no effect if the transfer completes in the current context, as it is only handled when .transfer_one() returns > 0, when the SPI core must wait for the SPI transfer to complete. Do not set SPI_TRANS_FAIL_IO as we either return an error or 0, since we do our own waiting. Signed-off-by: Cosmin Tanislav Link: https://patch.msgid.link/20251201134229.600817-4-cosmin-gabriel.tanislav.xa@renesas.com Signed-off-by: Mark Brown --- drivers/spi/spi-rzv2h-rspi.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/drivers/spi/spi-rzv2h-rspi.c b/drivers/spi/spi-rzv2h-rspi.c index beea4fb83d10..02424d4e722a 100644 --- a/drivers/spi/spi-rzv2h-rspi.c +++ b/drivers/spi/spi-rzv2h-rspi.c @@ -247,9 +247,6 @@ static int rzv2h_rspi_transfer_one(struct spi_controller *controller, rzv2h_rspi_clear_all_irqs(rspi); - if (ret) - transfer->error = SPI_TRANS_FAIL_IO; - return ret; } From b73ac782828f27c2217a17bd26aa8710769f032d Mon Sep 17 00:00:00 2001 From: Cosmin Tanislav Date: Mon, 1 Dec 2025 15:42:20 +0200 Subject: [PATCH 04/11] spi: rzv2h-rspi: use device-managed APIs Non-device-managed APIs were initially used here to avoid the buggy interaction between PM domains and device-managed actions. Commit f99508074e78 ("PM: domains: Detach on device_unbind_cleanup()") fixed the interaction between PM domains and device-managed actions. Simplify the code by using device-managed actions to unregister the SPI controller and to assert and release the resets. Signed-off-by: Cosmin Tanislav Link: https://patch.msgid.link/20251201134229.600817-5-cosmin-gabriel.tanislav.xa@renesas.com Signed-off-by: Mark Brown --- drivers/spi/spi-rzv2h-rspi.c | 58 ++++++++++++------------------------ 1 file changed, 19 insertions(+), 39 deletions(-) diff --git a/drivers/spi/spi-rzv2h-rspi.c b/drivers/spi/spi-rzv2h-rspi.c index 02424d4e722a..6163ada3ccbb 100644 --- a/drivers/spi/spi-rzv2h-rspi.c +++ b/drivers/spi/spi-rzv2h-rspi.c @@ -93,7 +93,6 @@ struct rzv2h_rspi_info { }; struct rzv2h_rspi_priv { - struct reset_control_bulk_data resets[RSPI_RESET_NUM]; struct spi_controller *controller; const struct rzv2h_rspi_info *info; void __iomem *base; @@ -533,6 +532,7 @@ static int rzv2h_rspi_probe(struct platform_device *pdev) struct spi_controller *controller; struct device *dev = &pdev->dev; struct rzv2h_rspi_priv *rspi; + struct reset_control *reset; struct clk_bulk_data *clks; int irq_rx, ret, i; long tclk_rate; @@ -568,28 +568,29 @@ static int rzv2h_rspi_probe(struct platform_device *pdev) if (!rspi->tclk) return dev_err_probe(dev, -EINVAL, "Failed to get tclk\n"); - rspi->resets[0].id = "presetn"; - rspi->resets[1].id = "tresetn"; - ret = devm_reset_control_bulk_get_optional_exclusive(dev, RSPI_RESET_NUM, - rspi->resets); - if (ret) - return dev_err_probe(dev, ret, "cannot get resets\n"); + reset = devm_reset_control_get_optional_exclusive_deasserted(&pdev->dev, + "presetn"); + if (IS_ERR(reset)) + return dev_err_probe(&pdev->dev, PTR_ERR(reset), + "cannot get presetn reset\n"); + + reset = devm_reset_control_get_optional_exclusive_deasserted(&pdev->dev, + "tresetn"); + if (IS_ERR(reset)) + return dev_err_probe(&pdev->dev, PTR_ERR(reset), + "cannot get tresetn reset\n"); irq_rx = platform_get_irq_byname(pdev, "rx"); if (irq_rx < 0) return dev_err_probe(dev, irq_rx, "cannot get IRQ 'rx'\n"); - ret = reset_control_bulk_deassert(RSPI_RESET_NUM, rspi->resets); - if (ret) - return dev_err_probe(dev, ret, "failed to deassert resets\n"); - init_waitqueue_head(&rspi->wait); ret = devm_request_irq(dev, irq_rx, rzv2h_rx_irq_handler, 0, dev_name(dev), rspi); if (ret) { dev_err(dev, "cannot request `rx` IRQ\n"); - goto quit_resets; + return ret; } controller->mode_bits = SPI_CPHA | SPI_CPOL | SPI_CS_HIGH | @@ -601,20 +602,16 @@ static int rzv2h_rspi_probe(struct platform_device *pdev) controller->transfer_one = rzv2h_rspi_transfer_one; tclk_rate = clk_round_rate(rspi->tclk, 0); - if (tclk_rate < 0) { - ret = tclk_rate; - goto quit_resets; - } + if (tclk_rate < 0) + return tclk_rate; controller->min_speed_hz = rzv2h_rspi_calc_bitrate(tclk_rate, RSPI_SPBR_SPR_MAX, RSPI_SPCMD_BRDV_MAX); tclk_rate = clk_round_rate(rspi->tclk, ULONG_MAX); - if (tclk_rate < 0) { - ret = tclk_rate; - goto quit_resets; - } + if (tclk_rate < 0) + return tclk_rate; controller->max_speed_hz = rzv2h_rspi_calc_bitrate(tclk_rate, RSPI_SPBR_SPR_MIN, @@ -622,29 +619,13 @@ static int rzv2h_rspi_probe(struct platform_device *pdev) device_set_node(&controller->dev, dev_fwnode(dev)); - ret = spi_register_controller(controller); - if (ret) { + ret = devm_spi_register_controller(dev, controller); + if (ret) dev_err(dev, "register controller failed\n"); - goto quit_resets; - } - - return 0; - -quit_resets: - reset_control_bulk_assert(RSPI_RESET_NUM, rspi->resets); return ret; } -static void rzv2h_rspi_remove(struct platform_device *pdev) -{ - struct rzv2h_rspi_priv *rspi = platform_get_drvdata(pdev); - - spi_unregister_controller(rspi->controller); - - reset_control_bulk_assert(RSPI_RESET_NUM, rspi->resets); -} - static const struct rzv2h_rspi_info rzv2h_info = { .find_tclk_rate = rzv2h_rspi_find_rate_fixed, .tclk_name = "tclk", @@ -669,7 +650,6 @@ MODULE_DEVICE_TABLE(of, rzv2h_rspi_match); static struct platform_driver rzv2h_rspi_drv = { .probe = rzv2h_rspi_probe, - .remove = rzv2h_rspi_remove, .driver = { .name = "rzv2h_rspi", .of_match_table = rzv2h_rspi_match, From 28b590bd4c6a051ec61cf286a46a8b14846e6fcf Mon Sep 17 00:00:00 2001 From: Cosmin Tanislav Date: Mon, 1 Dec 2025 15:42:21 +0200 Subject: [PATCH 05/11] spi: rzv2h-rspi: store RX interrupt in state In preparation for implementing DMA support, store the RX interrupt number in the private state, to allow disabling it during DMA. Signed-off-by: Cosmin Tanislav Link: https://patch.msgid.link/20251201134229.600817-6-cosmin-gabriel.tanislav.xa@renesas.com Signed-off-by: Mark Brown --- drivers/spi/spi-rzv2h-rspi.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/drivers/spi/spi-rzv2h-rspi.c b/drivers/spi/spi-rzv2h-rspi.c index 6163ada3ccbb..50fd7ddef58d 100644 --- a/drivers/spi/spi-rzv2h-rspi.c +++ b/drivers/spi/spi-rzv2h-rspi.c @@ -100,6 +100,7 @@ struct rzv2h_rspi_priv { struct clk *pclk; wait_queue_head_t wait; unsigned int bytes_per_word; + int irq_rx; u32 last_speed_hz; u32 freq; u16 status; @@ -534,8 +535,8 @@ static int rzv2h_rspi_probe(struct platform_device *pdev) struct rzv2h_rspi_priv *rspi; struct reset_control *reset; struct clk_bulk_data *clks; - int irq_rx, ret, i; long tclk_rate; + int ret, i; controller = devm_spi_alloc_host(dev, sizeof(*rspi)); if (!controller) @@ -580,13 +581,13 @@ static int rzv2h_rspi_probe(struct platform_device *pdev) return dev_err_probe(&pdev->dev, PTR_ERR(reset), "cannot get tresetn reset\n"); - irq_rx = platform_get_irq_byname(pdev, "rx"); - if (irq_rx < 0) - return dev_err_probe(dev, irq_rx, "cannot get IRQ 'rx'\n"); + rspi->irq_rx = platform_get_irq_byname(pdev, "rx"); + if (rspi->irq_rx < 0) + return dev_err_probe(dev, rspi->irq_rx, "cannot get IRQ 'rx'\n"); init_waitqueue_head(&rspi->wait); - ret = devm_request_irq(dev, irq_rx, rzv2h_rx_irq_handler, 0, + ret = devm_request_irq(dev, rspi->irq_rx, rzv2h_rx_irq_handler, 0, dev_name(dev), rspi); if (ret) { dev_err(dev, "cannot request `rx` IRQ\n"); From 6f9026b5a18acdf190d1622831b100aacfca0eb3 Mon Sep 17 00:00:00 2001 From: Cosmin Tanislav Date: Mon, 1 Dec 2025 15:42:22 +0200 Subject: [PATCH 06/11] spi: rzv2h-rspi: set MUST_RX/MUST_TX In preparation for implementing DMA support, set MUST_RX and MUST_TX flags on the controller so that we always receive non-NULL buffers. The PIO mode already handles this manually by checking if rx_buf/tx_buf are set on the transfer, and doing a dummy read/write if not. DMA will not be able to implement this special handling, and although the SPI controller advertises support for transmit-only or receive-only transfers via SPCR's register TXMD bitfield, it does not seem to work. Remove the special handling for PIO and let the SPI controller core handle it. Signed-off-by: Cosmin Tanislav Link: https://patch.msgid.link/20251201134229.600817-7-cosmin-gabriel.tanislav.xa@renesas.com Signed-off-by: Mark Brown --- drivers/spi/spi-rzv2h-rspi.c | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/drivers/spi/spi-rzv2h-rspi.c b/drivers/spi/spi-rzv2h-rspi.c index 50fd7ddef58d..f0bbbd21c763 100644 --- a/drivers/spi/spi-rzv2h-rspi.c +++ b/drivers/spi/spi-rzv2h-rspi.c @@ -113,11 +113,7 @@ struct rzv2h_rspi_priv { static inline void rzv2h_rspi_tx_##type(struct rzv2h_rspi_priv *rspi, \ const void *txbuf, \ unsigned int index) { \ - type buf = 0; \ - \ - if (txbuf) \ - buf = ((type *)txbuf)[index]; \ - \ + type buf = ((type *)txbuf)[index]; \ func(buf, rspi->base + RSPI_SPDR); \ } @@ -126,9 +122,7 @@ static inline void rzv2h_rspi_rx_##type(struct rzv2h_rspi_priv *rspi, \ void *rxbuf, \ unsigned int index) { \ type buf = func(rspi->base + RSPI_SPDR); \ - \ - if (rxbuf) \ - ((type *)rxbuf)[index] = buf; \ + ((type *)rxbuf)[index] = buf; \ } RZV2H_RSPI_TX(writel, u32) @@ -596,6 +590,7 @@ static int rzv2h_rspi_probe(struct platform_device *pdev) controller->mode_bits = SPI_CPHA | SPI_CPOL | SPI_CS_HIGH | SPI_LSB_FIRST | SPI_LOOP; + controller->flags = SPI_CONTROLLER_MUST_RX | SPI_CONTROLLER_MUST_TX; controller->bits_per_word_mask = SPI_BPW_RANGE_MASK(4, 32); controller->prepare_message = rzv2h_rspi_prepare_message; controller->unprepare_message = rzv2h_rspi_unprepare_message; From a886baaaa6e12a9b7d5a9687d11d3b895f1b87c9 Mon Sep 17 00:00:00 2001 From: Cosmin Tanislav Date: Mon, 1 Dec 2025 15:42:23 +0200 Subject: [PATCH 07/11] spi: rzv2h-rspi: set TX FIFO threshold to 0 In PIO mode we send data word-by-word, and wait for the received data to be available after each sent word, making no use of the TX interrupt. In DMA mode, we need to set the RX and TX FIFO thresholds to 0, as described in the User Manual. In preparation for implementing DMA support, set TX FIFO threshold to 0, as RX FIFO threshold is already 0. Signed-off-by: Cosmin Tanislav Link: https://patch.msgid.link/20251201134229.600817-8-cosmin-gabriel.tanislav.xa@renesas.com Signed-off-by: Mark Brown --- drivers/spi/spi-rzv2h-rspi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/spi/spi-rzv2h-rspi.c b/drivers/spi/spi-rzv2h-rspi.c index f0bbbd21c763..83bb0b7400b2 100644 --- a/drivers/spi/spi-rzv2h-rspi.c +++ b/drivers/spi/spi-rzv2h-rspi.c @@ -501,7 +501,7 @@ static int rzv2h_rspi_prepare_message(struct spi_controller *ctlr, writeb(0, rspi->base + RSPI_SSLP); /* Setup FIFO thresholds */ - conf16 = FIELD_PREP(RSPI_SPDCR2_TTRG, rspi->info->fifo_size - 1); + conf16 = FIELD_PREP(RSPI_SPDCR2_TTRG, 0); conf16 |= FIELD_PREP(RSPI_SPDCR2_RTRG, 0); writew(conf16, rspi->base + RSPI_SPDCR2); From d49eea07de5851e1b8941ad6b6179be7ec36a986 Mon Sep 17 00:00:00 2001 From: Cosmin Tanislav Date: Mon, 1 Dec 2025 15:42:24 +0200 Subject: [PATCH 08/11] spi: rzv2h-rspi: enable TX buffer empty interrupt In preparation for implementing DMA support, enable the transmit buffer empty interrupt, which is necessary for DMA to write more data to the FIFO. This does not affect the PIO mode as we do not even request the TX interrupt line. Signed-off-by: Cosmin Tanislav Link: https://patch.msgid.link/20251201134229.600817-9-cosmin-gabriel.tanislav.xa@renesas.com Signed-off-by: Mark Brown --- drivers/spi/spi-rzv2h-rspi.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/spi/spi-rzv2h-rspi.c b/drivers/spi/spi-rzv2h-rspi.c index 83bb0b7400b2..b31ef2f31f1b 100644 --- a/drivers/spi/spi-rzv2h-rspi.c +++ b/drivers/spi/spi-rzv2h-rspi.c @@ -37,6 +37,7 @@ /* Register SPCR */ #define RSPI_SPCR_BPEN BIT(31) #define RSPI_SPCR_MSTR BIT(30) +#define RSPI_SPCR_SPTIE BIT(20) #define RSPI_SPCR_SPRIE BIT(17) #define RSPI_SPCR_SCKASE BIT(12) #define RSPI_SPCR_SPE BIT(0) @@ -474,6 +475,9 @@ static int rzv2h_rspi_prepare_message(struct spi_controller *ctlr, /* SPI receive buffer full interrupt enable */ conf32 |= RSPI_SPCR_SPRIE; + /* SPI transmit buffer empty interrupt enable */ + conf32 |= RSPI_SPCR_SPTIE; + /* Bypass synchronization circuit */ conf32 |= FIELD_PREP(RSPI_SPCR_BPEN, rspi->use_pclk); From 1e5e10df8b9be71ca64435cbe7c96b189e5ee293 Mon Sep 17 00:00:00 2001 From: Cosmin Tanislav Date: Mon, 1 Dec 2025 15:42:25 +0200 Subject: [PATCH 09/11] spi: rzv2h-rspi: split out PIO transfer In preparation for implementing DMA support, split out the PIO transfer code into its own function. Signed-off-by: Cosmin Tanislav Link: https://patch.msgid.link/20251201134229.600817-10-cosmin-gabriel.tanislav.xa@renesas.com Signed-off-by: Mark Brown --- drivers/spi/spi-rzv2h-rspi.c | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/drivers/spi/spi-rzv2h-rspi.c b/drivers/spi/spi-rzv2h-rspi.c index b31ef2f31f1b..9f5bc047b485 100644 --- a/drivers/spi/spi-rzv2h-rspi.c +++ b/drivers/spi/spi-rzv2h-rspi.c @@ -219,17 +219,14 @@ static int rzv2h_rspi_receive(struct rzv2h_rspi_priv *rspi, void *rxbuf, return 0; } -static int rzv2h_rspi_transfer_one(struct spi_controller *controller, +static int rzv2h_rspi_transfer_pio(struct rzv2h_rspi_priv *rspi, struct spi_device *spi, - struct spi_transfer *transfer) + struct spi_transfer *transfer, + unsigned int words_to_transfer) { - struct rzv2h_rspi_priv *rspi = spi_controller_get_devdata(controller); - unsigned int words_to_transfer, i; + unsigned int i; int ret = 0; - transfer->effective_speed_hz = rspi->freq; - words_to_transfer = transfer->len / rspi->bytes_per_word; - for (i = 0; i < words_to_transfer; i++) { rzv2h_rspi_clear_all_irqs(rspi); @@ -240,6 +237,22 @@ static int rzv2h_rspi_transfer_one(struct spi_controller *controller, break; } + return ret; +} + +static int rzv2h_rspi_transfer_one(struct spi_controller *controller, + struct spi_device *spi, + struct spi_transfer *transfer) +{ + struct rzv2h_rspi_priv *rspi = spi_controller_get_devdata(controller); + unsigned int words_to_transfer; + int ret; + + transfer->effective_speed_hz = rspi->freq; + words_to_transfer = transfer->len / rspi->bytes_per_word; + + ret = rzv2h_rspi_transfer_pio(rspi, spi, transfer, words_to_transfer); + rzv2h_rspi_clear_all_irqs(rspi); return ret; From 163345e356722e98ba57cd120787d6e991da7b1d Mon Sep 17 00:00:00 2001 From: Cosmin Tanislav Date: Mon, 1 Dec 2025 15:42:26 +0200 Subject: [PATCH 10/11] spi: dt-bindings: renesas,rzv2h-rspi: document optional support for DMA The IP supports using DMA for reading and writing data from the FIFO, document it. Signed-off-by: Cosmin Tanislav Acked-by: Rob Herring (Arm) Link: https://patch.msgid.link/20251201134229.600817-11-cosmin-gabriel.tanislav.xa@renesas.com Signed-off-by: Mark Brown --- .../devicetree/bindings/spi/renesas,rzv2h-rspi.yaml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Documentation/devicetree/bindings/spi/renesas,rzv2h-rspi.yaml b/Documentation/devicetree/bindings/spi/renesas,rzv2h-rspi.yaml index 069557a587b5..a588b112e11e 100644 --- a/Documentation/devicetree/bindings/spi/renesas,rzv2h-rspi.yaml +++ b/Documentation/devicetree/bindings/spi/renesas,rzv2h-rspi.yaml @@ -57,6 +57,14 @@ properties: - const: presetn - const: tresetn + dmas: + maxItems: 2 + + dma-names: + items: + - const: rx + - const: tx + power-domains: maxItems: 1 From fa08b566860bca8ebf9300090b85174c34de7ca5 Mon Sep 17 00:00:00 2001 From: Cosmin Tanislav Date: Mon, 1 Dec 2025 15:42:27 +0200 Subject: [PATCH 11/11] spi: rzv2h-rspi: add support for DMA mode The DMA controller can be used to transfer data to and from the SPI controller without involving the CPU for each word of a SPI transfer. Add support for DMA mode. Signed-off-by: Cosmin Tanislav Link: https://patch.msgid.link/20251201134229.600817-12-cosmin-gabriel.tanislav.xa@renesas.com Signed-off-by: Mark Brown --- drivers/spi/spi-rzv2h-rspi.c | 169 ++++++++++++++++++++++++++++++++++- 1 file changed, 168 insertions(+), 1 deletion(-) diff --git a/drivers/spi/spi-rzv2h-rspi.c b/drivers/spi/spi-rzv2h-rspi.c index 9f5bc047b485..aae916882915 100644 --- a/drivers/spi/spi-rzv2h-rspi.c +++ b/drivers/spi/spi-rzv2h-rspi.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -21,6 +22,8 @@ #include #include +#include "internals.h" + /* Registers */ #define RSPI_SPDR 0x00 #define RSPI_SPCR 0x08 @@ -96,6 +99,7 @@ struct rzv2h_rspi_info { struct rzv2h_rspi_priv { struct spi_controller *controller; const struct rzv2h_rspi_info *info; + struct platform_device *pdev; void __iomem *base; struct clk *tclk; struct clk *pclk; @@ -108,6 +112,7 @@ struct rzv2h_rspi_priv { u8 spr; u8 brdv; bool use_pclk; + bool dma_callbacked; }; #define RZV2H_RSPI_TX(func, type) \ @@ -219,6 +224,20 @@ static int rzv2h_rspi_receive(struct rzv2h_rspi_priv *rspi, void *rxbuf, return 0; } +static bool rzv2h_rspi_can_dma(struct spi_controller *ctlr, struct spi_device *spi, + struct spi_transfer *xfer) +{ + struct rzv2h_rspi_priv *rspi = spi_controller_get_devdata(ctlr); + + if (ctlr->fallback) + return false; + + if (!ctlr->dma_tx || !ctlr->dma_rx) + return false; + + return xfer->len > rspi->info->fifo_size; +} + static int rzv2h_rspi_transfer_pio(struct rzv2h_rspi_priv *rspi, struct spi_device *spi, struct spi_transfer *transfer, @@ -240,21 +259,149 @@ static int rzv2h_rspi_transfer_pio(struct rzv2h_rspi_priv *rspi, return ret; } +static void rzv2h_rspi_dma_complete(void *arg) +{ + struct rzv2h_rspi_priv *rspi = arg; + + rspi->dma_callbacked = 1; + wake_up_interruptible(&rspi->wait); +} + +static struct dma_async_tx_descriptor * +rzv2h_rspi_setup_dma_channel(struct rzv2h_rspi_priv *rspi, + struct dma_chan *chan, struct sg_table *sg, + enum dma_slave_buswidth width, + enum dma_transfer_direction direction) +{ + struct dma_slave_config config = { + .dst_addr = rspi->pdev->resource->start + RSPI_SPDR, + .src_addr = rspi->pdev->resource->start + RSPI_SPDR, + .dst_addr_width = width, + .src_addr_width = width, + .direction = direction, + }; + struct dma_async_tx_descriptor *desc; + int ret; + + ret = dmaengine_slave_config(chan, &config); + if (ret) + return ERR_PTR(ret); + + desc = dmaengine_prep_slave_sg(chan, sg->sgl, sg->nents, direction, + DMA_PREP_INTERRUPT | DMA_CTRL_ACK); + if (!desc) + return ERR_PTR(-EAGAIN); + + if (direction == DMA_DEV_TO_MEM) { + desc->callback = rzv2h_rspi_dma_complete; + desc->callback_param = rspi; + } + + return desc; +} + +static enum dma_slave_buswidth +rzv2h_rspi_dma_width(struct rzv2h_rspi_priv *rspi) +{ + switch (rspi->bytes_per_word) { + case 4: + return DMA_SLAVE_BUSWIDTH_4_BYTES; + case 2: + return DMA_SLAVE_BUSWIDTH_2_BYTES; + case 1: + return DMA_SLAVE_BUSWIDTH_1_BYTE; + default: + return DMA_SLAVE_BUSWIDTH_UNDEFINED; + } +} + +static int rzv2h_rspi_transfer_dma(struct rzv2h_rspi_priv *rspi, + struct spi_device *spi, + struct spi_transfer *transfer, + unsigned int words_to_transfer) +{ + struct dma_async_tx_descriptor *tx_desc = NULL, *rx_desc = NULL; + enum dma_slave_buswidth width; + dma_cookie_t cookie; + int ret; + + width = rzv2h_rspi_dma_width(rspi); + if (width == DMA_SLAVE_BUSWIDTH_UNDEFINED) + return -EINVAL; + + rx_desc = rzv2h_rspi_setup_dma_channel(rspi, rspi->controller->dma_rx, + &transfer->rx_sg, width, + DMA_DEV_TO_MEM); + if (IS_ERR(rx_desc)) + return PTR_ERR(rx_desc); + + tx_desc = rzv2h_rspi_setup_dma_channel(rspi, rspi->controller->dma_tx, + &transfer->tx_sg, width, + DMA_MEM_TO_DEV); + if (IS_ERR(tx_desc)) + return PTR_ERR(tx_desc); + + cookie = dmaengine_submit(rx_desc); + if (dma_submit_error(cookie)) + return cookie; + + cookie = dmaengine_submit(tx_desc); + if (dma_submit_error(cookie)) { + dmaengine_terminate_sync(rspi->controller->dma_rx); + return cookie; + } + + /* + * DMA transfer does not need IRQs to be enabled. + * For PIO, we only use RX IRQ, so disable that. + */ + disable_irq(rspi->irq_rx); + + rspi->dma_callbacked = 0; + + dma_async_issue_pending(rspi->controller->dma_rx); + dma_async_issue_pending(rspi->controller->dma_tx); + rzv2h_rspi_clear_all_irqs(rspi); + + ret = wait_event_interruptible_timeout(rspi->wait, rspi->dma_callbacked, HZ); + if (ret) { + dmaengine_synchronize(rspi->controller->dma_tx); + dmaengine_synchronize(rspi->controller->dma_rx); + ret = 0; + } else { + dmaengine_terminate_sync(rspi->controller->dma_tx); + dmaengine_terminate_sync(rspi->controller->dma_rx); + ret = -ETIMEDOUT; + } + + enable_irq(rspi->irq_rx); + + return ret; +} + static int rzv2h_rspi_transfer_one(struct spi_controller *controller, struct spi_device *spi, struct spi_transfer *transfer) { struct rzv2h_rspi_priv *rspi = spi_controller_get_devdata(controller); + bool is_dma = spi_xfer_is_dma_mapped(controller, spi, transfer); unsigned int words_to_transfer; int ret; transfer->effective_speed_hz = rspi->freq; words_to_transfer = transfer->len / rspi->bytes_per_word; - ret = rzv2h_rspi_transfer_pio(rspi, spi, transfer, words_to_transfer); + if (is_dma) + ret = rzv2h_rspi_transfer_dma(rspi, spi, transfer, words_to_transfer); + else + ret = rzv2h_rspi_transfer_pio(rspi, spi, transfer, words_to_transfer); rzv2h_rspi_clear_all_irqs(rspi); + if (is_dma && ret == -EAGAIN) + /* Retry with PIO */ + transfer->error = SPI_TRANS_FAIL_NO_START; + return ret; } @@ -557,6 +704,7 @@ static int rzv2h_rspi_probe(struct platform_device *pdev) platform_set_drvdata(pdev, rspi); rspi->controller = controller; + rspi->pdev = pdev; rspi->info = device_get_match_data(dev); @@ -613,6 +761,7 @@ static int rzv2h_rspi_probe(struct platform_device *pdev) controller->unprepare_message = rzv2h_rspi_unprepare_message; controller->num_chipselect = 4; controller->transfer_one = rzv2h_rspi_transfer_one; + controller->can_dma = rzv2h_rspi_can_dma; tclk_rate = clk_round_rate(rspi->tclk, 0); if (tclk_rate < 0) @@ -630,6 +779,24 @@ static int rzv2h_rspi_probe(struct platform_device *pdev) RSPI_SPBR_SPR_MIN, RSPI_SPCMD_BRDV_MIN); + controller->dma_tx = devm_dma_request_chan(dev, "tx"); + if (IS_ERR(controller->dma_tx)) { + ret = dev_warn_probe(dev, PTR_ERR(controller->dma_tx), + "failed to request TX DMA channel\n"); + if (ret == -EPROBE_DEFER) + return ret; + controller->dma_tx = NULL; + } + + controller->dma_rx = devm_dma_request_chan(dev, "rx"); + if (IS_ERR(controller->dma_rx)) { + ret = dev_warn_probe(dev, PTR_ERR(controller->dma_rx), + "failed to request RX DMA channel\n"); + if (ret == -EPROBE_DEFER) + return ret; + controller->dma_rx = NULL; + } + device_set_node(&controller->dev, dev_fwnode(dev)); ret = devm_spi_register_controller(dev, controller);