From dc72e9e8cd19848d9ab286feef44d3d87b9f974a Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Thu, 13 Aug 2026 10:00:21 +0200 Subject: [PATCH 1/5] spi: ma35d1-qspi: Remove redundant reset operation The bus width is always set before every operation, no need to reset it manually at the end of each transfer. Signed-off-by: Miquel Raynal Link: https://patch.msgid.link/20260813-perso-ma35d1-upstream-qspi-v1-1-b217b9870eb1@bootlin.com Signed-off-by: Mark Brown --- drivers/spi/spi-ma35d1-qspi.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/spi/spi-ma35d1-qspi.c b/drivers/spi/spi-ma35d1-qspi.c index 541d5d72484c..929546dbe38f 100644 --- a/drivers/spi/spi-ma35d1-qspi.c +++ b/drivers/spi/spi-ma35d1-qspi.c @@ -502,7 +502,6 @@ static int nuvoton_qspi_mem_exec_op(struct spi_mem *mem, } out_deassert_cs: - nuvoton_qspi_set_bus_width(qspi, 1, SPI_MEM_DATA_IN); nuvoton_qspi_mem_set_cs(spi, false); return ret; @@ -556,7 +555,6 @@ static int nuvoton_qspi_transfer_one(struct spi_controller *ctlr, nuvoton_qspi_set_bus_width(qspi, buswidth, dir); ret = nuvoton_qspi_txrx(qspi, xfer->tx_buf, xfer->rx_buf, xfer->len); - nuvoton_qspi_set_bus_width(qspi, 1, SPI_MEM_DATA_IN); return ret; } From b5671d865ed99e5c09c03081706a1dab754a09dd Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Thu, 13 Aug 2026 10:00:22 +0200 Subject: [PATCH 2/5] spi: ma35d1-qspi: Move speed setting to bus configuration The speed setting is wrongly placed inside the "setup transfer" helper, since the bus configuration may require the speed to be correct. Indeed, DTR mode (not yet available) divides by 2 the bus clock when enabled. As a result, to remain at a constant clock speed (and improve the data rate), we must double the bus clock when enabling DTR. In order to prepare for this change, move all the bus configuration required for each step of the operation inside a unique helper called nuvoton_qspi_configure_bus(). Signed-off-by: Miquel Raynal Link: https://patch.msgid.link/20260813-perso-ma35d1-upstream-qspi-v1-2-b217b9870eb1@bootlin.com Signed-off-by: Mark Brown --- drivers/spi/spi-ma35d1-qspi.c | 69 ++++++++++++++++++++++------------- 1 file changed, 44 insertions(+), 25 deletions(-) diff --git a/drivers/spi/spi-ma35d1-qspi.c b/drivers/spi/spi-ma35d1-qspi.c index 929546dbe38f..b4fd0ab4bd31 100644 --- a/drivers/spi/spi-ma35d1-qspi.c +++ b/drivers/spi/spi-ma35d1-qspi.c @@ -131,11 +131,15 @@ static int nuvoton_qspi_reset_fifo(struct nuvoton_qspi *qspi) 1, NUVOTON_QSPI_TIMEOUT_US); } -static int nuvoton_qspi_set_speed(struct nuvoton_qspi *qspi, u32 speed_hz) +static int nuvoton_qspi_set_speed(struct spi_device *spi, u32 speed_hz) { + struct nuvoton_qspi *qspi = spi_controller_get_devdata(spi->controller); unsigned long clk_rate; u32 div; + if (!speed_hz) + speed_hz = spi->max_speed_hz; + if (!speed_hz) return -EINVAL; @@ -174,24 +178,16 @@ static int nuvoton_qspi_set_bits_per_word(struct nuvoton_qspi *qspi, u8 bpw) return 0; } -static int nuvoton_qspi_setup_transfer(struct spi_device *spi, - u32 speed_hz, u8 bpw) +static int nuvoton_qspi_setup_transfer(struct spi_device *spi, u8 bpw) { struct nuvoton_qspi *qspi = spi_controller_get_devdata(spi->controller); u32 mode = spi->mode & SPI_MODE_X_MASK; u32 ctl = 0; int ret; - if (!speed_hz) - speed_hz = spi->max_speed_hz; - if (!bpw) bpw = NUVOTON_QSPI_DEFAULT_BPW; - ret = nuvoton_qspi_set_speed(qspi, speed_hz); - if (ret) - return ret; - ret = nuvoton_qspi_set_bits_per_word(qspi, bpw); if (ret) return ret; @@ -216,11 +212,18 @@ static int nuvoton_qspi_setup_transfer(struct spi_device *spi, return 0; } -static void nuvoton_qspi_set_bus_width(struct nuvoton_qspi *qspi, - unsigned int buswidth, - enum spi_mem_data_dir dir) +static int nuvoton_qspi_configure_bus(struct spi_device *spi, + unsigned int buswidth, + enum spi_mem_data_dir dir, + u32 speed_hz) { + struct nuvoton_qspi *qspi = spi_controller_get_devdata(spi->controller); u32 ctl = 0; + int ret; + + ret = nuvoton_qspi_set_speed(spi, speed_hz); + if (ret) + return ret; if (buswidth == 4) ctl |= NUVOTON_QSPI_CTL_QUADIOEN_MASK; @@ -234,6 +237,8 @@ static void nuvoton_qspi_set_bus_width(struct nuvoton_qspi *qspi, NUVOTON_QSPI_CTL_QUADIOEN_MASK | NUVOTON_QSPI_CTL_DUALIOEN_MASK | NUVOTON_QSPI_CTL_DATDIR_MASK, ctl); + + return 0; } static u32 nuvoton_qspi_tx_byte(const void *txbuf, unsigned int idx) @@ -459,14 +464,17 @@ static int nuvoton_qspi_mem_exec_op(struct spi_mem *mem, int ret; int i; - ret = nuvoton_qspi_setup_transfer(spi, op->max_freq, - NUVOTON_QSPI_DEFAULT_BPW); + ret = nuvoton_qspi_setup_transfer(spi, NUVOTON_QSPI_DEFAULT_BPW); if (ret) return ret; nuvoton_qspi_mem_set_cs(spi, true); - nuvoton_qspi_set_bus_width(qspi, op->cmd.buswidth, SPI_MEM_DATA_OUT); + ret = nuvoton_qspi_configure_bus(spi, op->cmd.buswidth, SPI_MEM_DATA_OUT, + op->max_freq); + if (ret) + goto out_deassert_cs; + ret = nuvoton_qspi_txrx(qspi, &opcode, NULL, 1); if (ret) goto out_deassert_cs; @@ -475,24 +483,33 @@ static int nuvoton_qspi_mem_exec_op(struct spi_mem *mem, for (i = 0; i < op->addr.nbytes; i++) addr[i] = op->addr.val >> (8 * (op->addr.nbytes - i - 1)); - nuvoton_qspi_set_bus_width(qspi, op->addr.buswidth, - SPI_MEM_DATA_OUT); + ret = nuvoton_qspi_configure_bus(spi, op->addr.buswidth, SPI_MEM_DATA_OUT, + op->max_freq); + if (ret) + goto out_deassert_cs; + ret = nuvoton_qspi_txrx(qspi, addr, NULL, op->addr.nbytes); if (ret) goto out_deassert_cs; } if (op->dummy.nbytes) { - nuvoton_qspi_set_bus_width(qspi, op->dummy.buswidth, - SPI_MEM_DATA_OUT); + ret = nuvoton_qspi_configure_bus(spi, op->dummy.buswidth, SPI_MEM_DATA_OUT, + op->max_freq); + if (ret) + goto out_deassert_cs; + ret = nuvoton_qspi_txrx(qspi, NULL, NULL, op->dummy.nbytes); if (ret) goto out_deassert_cs; } if (op->data.nbytes) { - nuvoton_qspi_set_bus_width(qspi, op->data.buswidth, - op->data.dir); + ret = nuvoton_qspi_configure_bus(spi, op->data.buswidth, op->data.dir, + op->max_freq); + if (ret) + goto out_deassert_cs; + ret = nuvoton_qspi_txrx(qspi, op->data.dir == SPI_MEM_DATA_OUT ? op->data.buf.out : NULL, @@ -528,8 +545,7 @@ static int nuvoton_qspi_transfer_one(struct spi_controller *ctlr, unsigned int buswidth = 1; int ret; - ret = nuvoton_qspi_setup_transfer(spi, xfer->speed_hz, - xfer->bits_per_word); + ret = nuvoton_qspi_setup_transfer(spi, xfer->bits_per_word); if (ret) return ret; @@ -552,7 +568,10 @@ static int nuvoton_qspi_transfer_one(struct spi_controller *ctlr, buswidth = 2; } - nuvoton_qspi_set_bus_width(qspi, buswidth, dir); + ret = nuvoton_qspi_configure_bus(spi, buswidth, dir, xfer->speed_hz); + if (ret) + return ret; + ret = nuvoton_qspi_txrx(qspi, xfer->tx_buf, xfer->rx_buf, xfer->len); From 99542d244f534d4f3af6a76f18c99188458f8d12 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Thu, 13 Aug 2026 10:00:23 +0200 Subject: [PATCH 3/5] spi: ma35d1-qspi: Allow several command bytes The controller is capable of sending several bytes for the command, it does not even know this is a command. Just mimic the address steps here to allow double byte commands, which may be needed for DTR support. Signed-off-by: Miquel Raynal Link: https://patch.msgid.link/20260813-perso-ma35d1-upstream-qspi-v1-3-b217b9870eb1@bootlin.com Signed-off-by: Mark Brown --- drivers/spi/spi-ma35d1-qspi.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/drivers/spi/spi-ma35d1-qspi.c b/drivers/spi/spi-ma35d1-qspi.c index b4fd0ab4bd31..027a9433f2b1 100644 --- a/drivers/spi/spi-ma35d1-qspi.c +++ b/drivers/spi/spi-ma35d1-qspi.c @@ -395,9 +395,6 @@ static bool nuvoton_qspi_mem_supports_op(struct spi_mem *mem, op->dummy.buswidth > 4 || op->data.buswidth > 4) return false; - if (op->cmd.nbytes != 1) - return false; - if (op->addr.nbytes > 4) return false; @@ -459,8 +456,7 @@ static int nuvoton_qspi_mem_exec_op(struct spi_mem *mem, { struct spi_device *spi = mem->spi; struct nuvoton_qspi *qspi = spi_controller_get_devdata(spi->controller); - u8 opcode = op->cmd.opcode; - u8 addr[4]; + u8 cmd[2], addr[4]; int ret; int i; @@ -470,12 +466,15 @@ static int nuvoton_qspi_mem_exec_op(struct spi_mem *mem, nuvoton_qspi_mem_set_cs(spi, true); + for (i = 0; i < op->cmd.nbytes; i++) + cmd[i] = op->cmd.opcode >> (8 * (op->cmd.nbytes - i - 1)); + ret = nuvoton_qspi_configure_bus(spi, op->cmd.buswidth, SPI_MEM_DATA_OUT, op->max_freq); if (ret) goto out_deassert_cs; - ret = nuvoton_qspi_txrx(qspi, &opcode, NULL, 1); + ret = nuvoton_qspi_txrx(qspi, cmd, NULL, op->cmd.nbytes); if (ret) goto out_deassert_cs; From 15e9362f6190410557b3d54dae878b061aeab73c Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Thu, 13 Aug 2026 10:00:24 +0200 Subject: [PATCH 4/5] spi: ma35d1-qspi: Add DTR support The controller has DTR support, a bit must be set for it. The behaviour is interesting though, as the speed won't improve when enabled. This is because there seems to be an internal divisor (/2) which keeps the rate equal when DTR is enabled. As a result, this commit also doubles the target bus speed, which in practice does not happen. This way, there is a real gain: Before: $ flash_speed /dev/mtd0 -dc10 eraseblock write speed is 1000 KiB/s [...] eraseblock read speed is 1199 KiB/s [...] After: $ flash_speed /dev/mtd0 -dc10 eraseblock write speed is 985 KiB/s [...] eraseblock read speed is 1540 KiB/s [...] Signed-off-by: Miquel Raynal Link: https://patch.msgid.link/20260813-perso-ma35d1-upstream-qspi-v1-4-b217b9870eb1@bootlin.com Signed-off-by: Mark Brown --- drivers/spi/spi-ma35d1-qspi.c | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/drivers/spi/spi-ma35d1-qspi.c b/drivers/spi/spi-ma35d1-qspi.c index 027a9433f2b1..9965a11573d8 100644 --- a/drivers/spi/spi-ma35d1-qspi.c +++ b/drivers/spi/spi-ma35d1-qspi.c @@ -31,6 +31,7 @@ #define NUVOTON_QSPI_RX_OFFSET 0x30 /* Data Receive Register, RO */ /* QSPI Control Register bit masks */ +#define NUVOTON_QSPI_CTL_DTREN_MASK BIT(23) /* DTR I/O Mode Enable */ #define NUVOTON_QSPI_CTL_QUADIOEN_MASK BIT(22) /* Quad I/O Mode Enable */ #define NUVOTON_QSPI_CTL_DUALIOEN_MASK BIT(21) /* Dual I/O Mode Enable */ #define NUVOTON_QSPI_CTL_DATDIR_MASK BIT(20) /* Data Port Direction Control */ @@ -131,7 +132,7 @@ static int nuvoton_qspi_reset_fifo(struct nuvoton_qspi *qspi) 1, NUVOTON_QSPI_TIMEOUT_US); } -static int nuvoton_qspi_set_speed(struct spi_device *spi, u32 speed_hz) +static int nuvoton_qspi_set_speed(struct spi_device *spi, u32 speed_hz, bool dtr) { struct nuvoton_qspi *qspi = spi_controller_get_devdata(spi->controller); unsigned long clk_rate; @@ -143,6 +144,10 @@ static int nuvoton_qspi_set_speed(struct spi_device *spi, u32 speed_hz) if (!speed_hz) return -EINVAL; + /* Experimentally, when enabling DTR the frequency is cut in half */ + if (dtr) + speed_hz *= 2; + if (qspi->speed_hz == speed_hz) return 0; @@ -215,16 +220,19 @@ static int nuvoton_qspi_setup_transfer(struct spi_device *spi, u8 bpw) static int nuvoton_qspi_configure_bus(struct spi_device *spi, unsigned int buswidth, enum spi_mem_data_dir dir, - u32 speed_hz) + u32 speed_hz, bool dtr) { struct nuvoton_qspi *qspi = spi_controller_get_devdata(spi->controller); u32 ctl = 0; int ret; - ret = nuvoton_qspi_set_speed(spi, speed_hz); + ret = nuvoton_qspi_set_speed(spi, speed_hz, dtr); if (ret) return ret; + if (dtr) + ctl |= NUVOTON_QSPI_CTL_DTREN_MASK; + if (buswidth == 4) ctl |= NUVOTON_QSPI_CTL_QUADIOEN_MASK; else if (buswidth == 2) @@ -234,6 +242,7 @@ static int nuvoton_qspi_configure_bus(struct spi_device *spi, ctl |= NUVOTON_QSPI_CTL_DATDIR_MASK; nuvoton_qspi_update_bits(qspi, NUVOTON_QSPI_CTL_OFFSET, + NUVOTON_QSPI_CTL_DTREN_MASK | NUVOTON_QSPI_CTL_QUADIOEN_MASK | NUVOTON_QSPI_CTL_DUALIOEN_MASK | NUVOTON_QSPI_CTL_DATDIR_MASK, ctl); @@ -470,7 +479,7 @@ static int nuvoton_qspi_mem_exec_op(struct spi_mem *mem, cmd[i] = op->cmd.opcode >> (8 * (op->cmd.nbytes - i - 1)); ret = nuvoton_qspi_configure_bus(spi, op->cmd.buswidth, SPI_MEM_DATA_OUT, - op->max_freq); + op->max_freq, op->cmd.dtr); if (ret) goto out_deassert_cs; @@ -483,7 +492,7 @@ static int nuvoton_qspi_mem_exec_op(struct spi_mem *mem, addr[i] = op->addr.val >> (8 * (op->addr.nbytes - i - 1)); ret = nuvoton_qspi_configure_bus(spi, op->addr.buswidth, SPI_MEM_DATA_OUT, - op->max_freq); + op->max_freq, op->addr.dtr); if (ret) goto out_deassert_cs; @@ -494,7 +503,7 @@ static int nuvoton_qspi_mem_exec_op(struct spi_mem *mem, if (op->dummy.nbytes) { ret = nuvoton_qspi_configure_bus(spi, op->dummy.buswidth, SPI_MEM_DATA_OUT, - op->max_freq); + op->max_freq, op->dummy.dtr); if (ret) goto out_deassert_cs; @@ -505,7 +514,7 @@ static int nuvoton_qspi_mem_exec_op(struct spi_mem *mem, if (op->data.nbytes) { ret = nuvoton_qspi_configure_bus(spi, op->data.buswidth, op->data.dir, - op->max_freq); + op->max_freq, op->data.dtr); if (ret) goto out_deassert_cs; @@ -531,6 +540,7 @@ static const struct spi_controller_mem_ops nuvoton_qspi_mem_ops = { static const struct spi_controller_mem_caps nuvoton_qspi_mem_caps = { .per_op_freq = true, + .dtr = true, }; static int nuvoton_qspi_transfer_one(struct spi_controller *ctlr, @@ -567,7 +577,8 @@ static int nuvoton_qspi_transfer_one(struct spi_controller *ctlr, buswidth = 2; } - ret = nuvoton_qspi_configure_bus(spi, buswidth, dir, xfer->speed_hz); + ret = nuvoton_qspi_configure_bus(spi, buswidth, dir, xfer->speed_hz, + xfer->dtr_mode); if (ret) return ret; From 0730bb35229b3cabd4d84371bb45694b63c3d3f3 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Thu, 13 Aug 2026 10:00:25 +0200 Subject: [PATCH 5/5] spi: ma35d1-qspi: Use the existing update helper Read modify writes are already covered by a local helper, so use it. No functional change. Signed-off-by: Miquel Raynal Link: https://patch.msgid.link/20260813-perso-ma35d1-upstream-qspi-v1-5-b217b9870eb1@bootlin.com Signed-off-by: Mark Brown --- drivers/spi/spi-ma35d1-qspi.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/drivers/spi/spi-ma35d1-qspi.c b/drivers/spi/spi-ma35d1-qspi.c index 9965a11573d8..7f938d0c6f1f 100644 --- a/drivers/spi/spi-ma35d1-qspi.c +++ b/drivers/spi/spi-ma35d1-qspi.c @@ -115,10 +115,11 @@ static int nuvoton_qspi_reset_fifo(struct nuvoton_qspi *qspi) { u32 val; - val = nuvoton_qspi_read(qspi, NUVOTON_QSPI_FIFOCTL_OFFSET); - val |= NUVOTON_QSPI_FIFOCTL_TXRST_MASK | - NUVOTON_QSPI_FIFOCTL_RXRST_MASK; - nuvoton_qspi_write(qspi, val, NUVOTON_QSPI_FIFOCTL_OFFSET); + nuvoton_qspi_update_bits(qspi, NUVOTON_QSPI_FIFOCTL_OFFSET, + NUVOTON_QSPI_FIFOCTL_TXRST_MASK | + NUVOTON_QSPI_FIFOCTL_RXRST_MASK, + NUVOTON_QSPI_FIFOCTL_TXRST_MASK | + NUVOTON_QSPI_FIFOCTL_RXRST_MASK); /* * Give the controller a short time to latch the FIFO reset request @@ -356,9 +357,9 @@ static int nuvoton_qspi_hw_init(struct nuvoton_qspi *qspi) NUVOTON_QSPI_CTL_LSB_MASK, NUVOTON_QSPI_CTL_TXNEG_MASK); - val = nuvoton_qspi_read(qspi, NUVOTON_QSPI_CTL_OFFSET); - nuvoton_qspi_write(qspi, val | NUVOTON_QSPI_CTL_SPIEN_MASK, - NUVOTON_QSPI_CTL_OFFSET); + nuvoton_qspi_update_bits(qspi, NUVOTON_QSPI_CTL_OFFSET, + NUVOTON_QSPI_CTL_SPIEN_MASK, + NUVOTON_QSPI_CTL_SPIEN_MASK); ret = readl_poll_timeout(qspi->regs + NUVOTON_QSPI_STATUS_OFFSET, val, (val & NUVOTON_QSPI_STATUS_SPIENSTS_MASK),