]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
spi: Add driver for the RZ/V2H(P) RSPI IP
authorFabrizio Castro <fabrizio.castro.jz@renesas.com>
Fri, 4 Jul 2025 16:20:35 +0000 (17:20 +0100)
committerMark Brown <broonie@kernel.org>
Thu, 24 Jul 2025 13:00:23 +0000 (14:00 +0100)
The Renesas RZ/V2H(P) RSPI IP supports 4-wire and 3-wire
serial communications in both host role and target role.
It can use a DMA, but the I/O can also be driven by the
processor.

RX-only, TX-only, and RX-TX operations are available in
DMA mode, while in processor I/O mode it only RX-TX
operations are supported.

Add a driver to support 4-wire serial communications as
host role in processor I/O mode.

Signed-off-by: Fabrizio Castro <fabrizio.castro.jz@renesas.com>
Link: https://patch.msgid.link/20250704162036.468765-3-fabrizio.castro.jz@renesas.com
Signed-off-by: Mark Brown <broonie@kernel.org>
drivers/spi/Kconfig
drivers/spi/Makefile
drivers/spi/spi-rzv2h-rspi.c [new file with mode: 0644]

index c51da3fc3604977b05388687e5e64a58370186c4..937e441b9410b929728addf059e421db472b0f2c 100644 (file)
@@ -923,6 +923,14 @@ config SPI_RSPI
        help
          SPI driver for Renesas RSPI and QSPI blocks.
 
+config SPI_RZV2H_RSPI
+       tristate "Renesas RZ/V2H RSPI controller"
+       depends on ARCH_RENESAS || COMPILE_TEST
+       help
+         RSPI driver for the Renesas RZ/V2H Serial Peripheral Interface (RSPI).
+         RSPI supports both SPI host and SPI target roles. This option only
+         enables the SPI host role.
+
 config SPI_RZV2M_CSI
        tristate "Renesas RZ/V2M CSI controller"
        depends on ARCH_RENESAS || COMPILE_TEST
index 4ea89f6fc531625060255ecff237470927e1f041..c19d02653b8ae0c1f2ce9c6f7ee05fce79f678c5 100644 (file)
@@ -126,6 +126,7 @@ obj-$(CONFIG_MACH_REALTEK_RTL)              += spi-realtek-rtl.o
 obj-$(CONFIG_SPI_REALTEK_SNAND)                += spi-realtek-rtl-snand.o
 obj-$(CONFIG_SPI_RPCIF)                        += spi-rpc-if.o
 obj-$(CONFIG_SPI_RSPI)                 += spi-rspi.o
+obj-$(CONFIG_SPI_RZV2H_RSPI)           += spi-rzv2h-rspi.o
 obj-$(CONFIG_SPI_RZV2M_CSI)            += spi-rzv2m-csi.o
 obj-$(CONFIG_SPI_S3C64XX)              += spi-s3c64xx.o
 obj-$(CONFIG_SPI_SC18IS602)            += spi-sc18is602.o
diff --git a/drivers/spi/spi-rzv2h-rspi.c b/drivers/spi/spi-rzv2h-rspi.c
new file mode 100644 (file)
index 0000000..dcc431b
--- /dev/null
@@ -0,0 +1,466 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Renesas RZ/V2H Renesas Serial Peripheral Interface (RSPI)
+ *
+ * Copyright (C) 2025 Renesas Electronics Corporation
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <linux/bits.h>
+#include <linux/clk.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/limits.h>
+#include <linux/log2.h>
+#include <linux/math.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/reset.h>
+#include <linux/spi/spi.h>
+#include <linux/wait.h>
+
+/* Registers */
+#define RSPI_SPDR              0x00
+#define RSPI_SPCR              0x08
+#define RSPI_SSLP              0x10
+#define RSPI_SPBR              0x11
+#define RSPI_SPSCR             0x13
+#define RSPI_SPCMD             0x14
+#define RSPI_SPDCR2            0x44
+#define RSPI_SPSR              0x52
+#define RSPI_SPSRC             0x6a
+#define RSPI_SPFCR             0x6c
+
+/* Register SPCR */
+#define RSPI_SPCR_MSTR         BIT(30)
+#define RSPI_SPCR_SPRIE                BIT(17)
+#define RSPI_SPCR_SCKASE       BIT(12)
+#define RSPI_SPCR_SPE          BIT(0)
+
+/* Register SPBR */
+#define RSPI_SPBR_SPR_MIN      0
+#define RSPI_SPBR_SPR_MAX      255
+
+/* Register SPCMD */
+#define RSPI_SPCMD_SSLA                GENMASK(25, 24)
+#define RSPI_SPCMD_SPB         GENMASK(20, 16)
+#define RSPI_SPCMD_LSBF                BIT(12)
+#define RSPI_SPCMD_SSLKP       BIT(7)
+#define RSPI_SPCMD_BRDV                GENMASK(3, 2)
+#define RSPI_SPCMD_CPOL                BIT(1)
+#define RSPI_SPCMD_CPHA                BIT(0)
+
+#define RSPI_SPCMD_BRDV_MIN    0
+#define RSPI_SPCMD_BRDV_MAX    3
+
+/* Register SPDCR2 */
+#define RSPI_SPDCR2_TTRG       GENMASK(11, 8)
+#define RSPI_SPDCR2_RTRG       GENMASK(3, 0)
+#define RSPI_FIFO_SIZE         16
+
+/* Register SPSR */
+#define RSPI_SPSR_SPRF         BIT(15)
+
+/* Register RSPI_SPSRC */
+#define RSPI_SPSRC_CLEAR       0xfd80
+
+#define RSPI_RESET_NUM         2
+#define RSPI_CLK_NUM           3
+
+struct rzv2h_rspi_priv {
+       struct reset_control_bulk_data resets[RSPI_RESET_NUM];
+       struct spi_controller *controller;
+       void __iomem *base;
+       struct clk *tclk;
+       wait_queue_head_t wait;
+       unsigned int bytes_per_word;
+       u32 freq;
+       u16 status;
+};
+
+#define RZV2H_RSPI_TX(func, type)                                      \
+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];                           \
+                                                                       \
+       func(buf, rspi->base + RSPI_SPDR);                              \
+}
+
+#define RZV2H_RSPI_RX(func, type)                                      \
+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;                           \
+}
+
+RZV2H_RSPI_TX(writel, u32)
+RZV2H_RSPI_TX(writew, u16)
+RZV2H_RSPI_TX(writeb, u8)
+RZV2H_RSPI_RX(readl, u32)
+RZV2H_RSPI_RX(readw, u16)
+RZV2H_RSPI_RX(readl, u8)
+
+static void rzv2h_rspi_reg_rmw(const struct rzv2h_rspi_priv *rspi,
+                               int reg_offs, u32 bit_mask, u32 value)
+{
+       u32 tmp;
+
+       value <<= __ffs(bit_mask);
+       tmp = (readl(rspi->base + reg_offs) & ~bit_mask) | value;
+       writel(tmp, rspi->base + reg_offs);
+}
+
+static inline void rzv2h_rspi_spe_disable(const struct rzv2h_rspi_priv *rspi)
+{
+       rzv2h_rspi_reg_rmw(rspi, RSPI_SPCR, RSPI_SPCR_SPE, 0);
+}
+
+static inline void rzv2h_rspi_spe_enable(const struct rzv2h_rspi_priv *rspi)
+{
+       rzv2h_rspi_reg_rmw(rspi, RSPI_SPCR, RSPI_SPCR_SPE, 1);
+}
+
+static inline void rzv2h_rspi_clear_fifos(const struct rzv2h_rspi_priv *rspi)
+{
+       writeb(1, rspi->base + RSPI_SPFCR);
+}
+
+static inline void rzv2h_rspi_clear_all_irqs(struct rzv2h_rspi_priv *rspi)
+{
+       writew(RSPI_SPSRC_CLEAR, rspi->base + RSPI_SPSRC);
+       rspi->status = 0;
+}
+
+static irqreturn_t rzv2h_rx_irq_handler(int irq, void *data)
+{
+       struct rzv2h_rspi_priv *rspi = data;
+
+       rspi->status = readw(rspi->base + RSPI_SPSR);
+       wake_up(&rspi->wait);
+
+       return IRQ_HANDLED;
+}
+
+static inline int rzv2h_rspi_wait_for_interrupt(struct rzv2h_rspi_priv *rspi,
+                                               u32 wait_mask)
+{
+       return wait_event_timeout(rspi->wait, (rspi->status & wait_mask),
+                                 HZ) == 0 ? -ETIMEDOUT : 0;
+}
+
+static void rzv2h_rspi_send(struct rzv2h_rspi_priv *rspi, const void *txbuf,
+                           unsigned int index)
+{
+       switch (rspi->bytes_per_word) {
+       case 4:
+               rzv2h_rspi_tx_u32(rspi, txbuf, index);
+               break;
+       case 2:
+               rzv2h_rspi_tx_u16(rspi, txbuf, index);
+               break;
+       default:
+               rzv2h_rspi_tx_u8(rspi, txbuf, index);
+       }
+}
+
+static int rzv2h_rspi_receive(struct rzv2h_rspi_priv *rspi, void *rxbuf,
+                             unsigned int index)
+{
+       int ret;
+
+       ret = rzv2h_rspi_wait_for_interrupt(rspi, RSPI_SPSR_SPRF);
+       if (ret)
+               return ret;
+
+       switch (rspi->bytes_per_word) {
+       case 4:
+               rzv2h_rspi_rx_u32(rspi, rxbuf, index);
+               break;
+       case 2:
+               rzv2h_rspi_rx_u16(rspi, rxbuf, index);
+               break;
+       default:
+               rzv2h_rspi_rx_u8(rspi, rxbuf, index);
+       }
+
+       return 0;
+}
+
+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, 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);
+
+               rzv2h_rspi_send(rspi, transfer->tx_buf, i);
+
+               ret = rzv2h_rspi_receive(rspi, transfer->rx_buf, i);
+               if (ret)
+                       break;
+       }
+
+       rzv2h_rspi_clear_all_irqs(rspi);
+
+       if (ret)
+               transfer->error = SPI_TRANS_FAIL_IO;
+
+       spi_finalize_current_transfer(controller);
+
+       return ret;
+}
+
+static inline u32 rzv2h_rspi_calc_bitrate(unsigned long tclk_rate, u8 spr,
+                                         u8 brdv)
+{
+       return DIV_ROUND_UP(tclk_rate, (2 * (spr + 1) * (1 << brdv)));
+}
+
+static u32 rzv2h_rspi_setup_clock(struct rzv2h_rspi_priv *rspi, u32 hz)
+{
+       unsigned long tclk_rate;
+       int spr;
+       u8 brdv;
+
+       /*
+        * From the manual:
+        * Bit rate = f(RSPI_n_TCLK)/(2*(n+1)*2^(N))
+        *
+        * Where:
+        * * RSPI_n_TCLK is fixed to 200MHz on V2H
+        * * n = SPR - is RSPI_SPBR.SPR (from 0 to 255)
+        * * N = BRDV - is RSPI_SPCMD.BRDV (from 0 to 3)
+        */
+       tclk_rate = clk_get_rate(rspi->tclk);
+       for (brdv = RSPI_SPCMD_BRDV_MIN; brdv <= RSPI_SPCMD_BRDV_MAX; brdv++) {
+               spr = DIV_ROUND_UP(tclk_rate, hz * (1 << (brdv + 1)));
+               spr--;
+               if (spr >= RSPI_SPBR_SPR_MIN && spr <= RSPI_SPBR_SPR_MAX)
+                       goto clock_found;
+       }
+
+       return 0;
+
+clock_found:
+       rzv2h_rspi_reg_rmw(rspi, RSPI_SPCMD, RSPI_SPCMD_BRDV, brdv);
+       writeb(spr, rspi->base + RSPI_SPBR);
+
+       return rzv2h_rspi_calc_bitrate(tclk_rate, spr, brdv);
+}
+
+static int rzv2h_rspi_prepare_message(struct spi_controller *ctlr,
+                                     struct spi_message *message)
+{
+       struct rzv2h_rspi_priv *rspi = spi_controller_get_devdata(ctlr);
+       const struct spi_device *spi = message->spi;
+       struct spi_transfer *xfer;
+       u32 speed_hz = U32_MAX;
+       u8 bits_per_word;
+       u32 conf32;
+       u16 conf16;
+
+       /* Make sure SPCR.SPE is 0 before amending the configuration */
+       rzv2h_rspi_spe_disable(rspi);
+
+       /* Configure the device to work in "host" mode */
+       conf32 = RSPI_SPCR_MSTR;
+
+       /* Auto-stop function */
+       conf32 |= RSPI_SPCR_SCKASE;
+
+       /* SPI receive buffer full interrupt enable */
+       conf32 |= RSPI_SPCR_SPRIE;
+
+       writel(conf32, rspi->base + RSPI_SPCR);
+
+       /* Use SPCMD0 only */
+       writeb(0x0, rspi->base + RSPI_SPSCR);
+
+       /* Setup mode */
+       conf32 = FIELD_PREP(RSPI_SPCMD_CPOL, !!(spi->mode & SPI_CPOL));
+       conf32 |= FIELD_PREP(RSPI_SPCMD_CPHA, !!(spi->mode & SPI_CPHA));
+       conf32 |= FIELD_PREP(RSPI_SPCMD_LSBF, !!(spi->mode & SPI_LSB_FIRST));
+       conf32 |= FIELD_PREP(RSPI_SPCMD_SSLKP, 1);
+       conf32 |= FIELD_PREP(RSPI_SPCMD_SSLA, spi_get_chipselect(spi, 0));
+       writel(conf32, rspi->base + RSPI_SPCMD);
+       if (spi->mode & SPI_CS_HIGH)
+               writeb(BIT(spi_get_chipselect(spi, 0)), rspi->base + RSPI_SSLP);
+       else
+               writeb(0, rspi->base + RSPI_SSLP);
+
+       /* Setup FIFO thresholds */
+       conf16 = FIELD_PREP(RSPI_SPDCR2_TTRG, RSPI_FIFO_SIZE - 1);
+       conf16 |= FIELD_PREP(RSPI_SPDCR2_RTRG, 0);
+       writew(conf16, rspi->base + RSPI_SPDCR2);
+
+       rzv2h_rspi_clear_fifos(rspi);
+
+       list_for_each_entry(xfer, &message->transfers, transfer_list) {
+               if (!xfer->speed_hz)
+                       continue;
+
+               speed_hz = min(xfer->speed_hz, speed_hz);
+               bits_per_word = xfer->bits_per_word;
+       }
+
+       if (speed_hz == U32_MAX)
+               return -EINVAL;
+
+       rspi->bytes_per_word = roundup_pow_of_two(BITS_TO_BYTES(bits_per_word));
+       rzv2h_rspi_reg_rmw(rspi, RSPI_SPCMD, RSPI_SPCMD_SPB, bits_per_word - 1);
+
+       rspi->freq = rzv2h_rspi_setup_clock(rspi, speed_hz);
+       if (!rspi->freq)
+               return -EINVAL;
+
+       rzv2h_rspi_spe_enable(rspi);
+
+       return 0;
+}
+
+static int rzv2h_rspi_unprepare_message(struct spi_controller *ctlr,
+                                       struct spi_message *message)
+{
+       struct rzv2h_rspi_priv *rspi = spi_controller_get_devdata(ctlr);
+
+       rzv2h_rspi_spe_disable(rspi);
+
+       return 0;
+}
+
+static int rzv2h_rspi_probe(struct platform_device *pdev)
+{
+       struct spi_controller *controller;
+       struct device *dev = &pdev->dev;
+       struct rzv2h_rspi_priv *rspi;
+       struct clk_bulk_data *clks;
+       unsigned long tclk_rate;
+       int irq_rx, ret, i;
+
+       controller = devm_spi_alloc_host(dev, sizeof(*rspi));
+       if (!controller)
+               return -ENOMEM;
+
+       rspi = spi_controller_get_devdata(controller);
+       platform_set_drvdata(pdev, rspi);
+
+       rspi->controller = controller;
+
+       rspi->base = devm_platform_ioremap_resource(pdev, 0);
+       if (IS_ERR(rspi->base))
+               return PTR_ERR(rspi->base);
+
+       ret = devm_clk_bulk_get_all_enabled(dev, &clks);
+       if (ret != RSPI_CLK_NUM)
+               return dev_err_probe(dev, ret >= 0 ? -EINVAL : ret,
+                                    "cannot get clocks\n");
+       for (i = 0; i < RSPI_CLK_NUM; i++) {
+               if (!strcmp(clks[i].id, "tclk")) {
+                       rspi->tclk = clks[i].clk;
+                       break;
+               }
+       }
+
+       if (!rspi->tclk)
+               return dev_err_probe(dev, -EINVAL, "Failed to get tclk\n");
+
+       tclk_rate = clk_get_rate(rspi->tclk);
+
+       rspi->resets[0].id = "presetn";
+       rspi->resets[1].id = "tresetn";
+       ret = devm_reset_control_bulk_get_exclusive(dev, RSPI_RESET_NUM,
+                                                   rspi->resets);
+       if (ret)
+               return dev_err_probe(dev, ret, "cannot get resets\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;
+       }
+
+       controller->mode_bits = SPI_CPHA | SPI_CPOL | SPI_CS_HIGH |
+                               SPI_LSB_FIRST;
+       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;
+       controller->num_chipselect = 4;
+       controller->transfer_one = rzv2h_rspi_transfer_one;
+       controller->min_speed_hz = rzv2h_rspi_calc_bitrate(tclk_rate,
+                                                          RSPI_SPBR_SPR_MAX,
+                                                          RSPI_SPCMD_BRDV_MAX);
+       controller->max_speed_hz = rzv2h_rspi_calc_bitrate(tclk_rate,
+                                                          RSPI_SPBR_SPR_MIN,
+                                                          RSPI_SPCMD_BRDV_MIN);
+
+       device_set_node(&controller->dev, dev_fwnode(dev));
+
+       ret = spi_register_controller(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 of_device_id rzv2h_rspi_match[] = {
+       { .compatible = "renesas,r9a09g057-rspi" },
+       { /* sentinel */ }
+};
+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,
+       },
+};
+module_platform_driver(rzv2h_rspi_drv);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Fabrizio Castro <fabrizio.castro.jz@renesas.com>");
+MODULE_DESCRIPTION("Renesas RZ/V2H(P) Serial Peripheral Interface Driver");