* [PATCH v2 0/7] spi: loongson: Fixes, cleanup and IRQ support
@ 2026-11-13 5:42 Binbin Zhou
2026-11-13 5:42 ` [PATCH v2 1/7] spi: loongson: Fix incorrect SPI mode caching logic Binbin Zhou
` (6 more replies)
0 siblings, 7 replies; 8+ messages in thread
From: Binbin Zhou @ 2026-11-13 5:42 UTC (permalink / raw)
To: Binbin Zhou, Huacai Chen, Yinbo Zhu, Mark Brown
Cc: Huacai Chen, Xuerui Wang, loongarch, linux-spi, Binbin Zhou
Hi all:
This series fixes several long-standing bugs in the Loongson SPI
controller driver, cleans up register access, and adds interrupt-driven
transfers with a polling fallback.
Grouped as follows:
Bug fixes (suitable for stable):
1. Fix SPI mode caching so mode bits can be cleared.
2. Drop the redundant and wrong CS deassert in setup(); the SPI core
already drives the idle CS after ->setup().
3. Separate PM register state from runtime state so set_clk() cannot
corrupt the saved suspend state.
4. Fix clock retrieval on ACPI platforms: try clk, fall back to
"clock-frequency", fail probe if neither is valid, manage the
clock over suspend/resume.
5. Fix the clock divider calculation with an explicit table, avoid
the off-by-one/out-of-bounds issue, propagate errors, and expose
min/max speed to the SPI core.
Cleanup:
6. Use bitfield helpers and set/clear/update_bits() for register
access. No functional change intended.
New feature:
7. Add interrupt mode support.
On a Loongson-2K3000 board, SPI flash reads improved from about 60 kB/s
in polling mode to about 950 kB/s with IRQ mode.
Thanks.
Binbin
===========
V2:
- Patch count reduced from 8 to 7;
- Deferred the private-definition move and regmap conversion from v1;
instead, register access is cleaned up with bitfield helpers.
- Reworked the CS fix to drop the redundant and wrong deassert in
setup(), since the SPI core drives the idle CS after ->setup().
- Reordered the clock fixes so the ACPI/clk-rate fix comes before the
divider fix.
- Reworked IRQ support: fifo-depth/icnt handling, spinlock-protected
transfer state, handle_err() cleanup, bounded message splitting, PCI
IRQ vector allocation, platform optional IRQ with -EPROBE_DEFER, and
polling fallback.
Link to V1:
https://lore.kernel.org/all/cover.1793689461.git.zhoubinbin@loongson.cn/
Binbin Zhou (7):
spi: loongson: Fix incorrect SPI mode caching logic
spi: loongson: Drop redundant CS deassert in setup()
spi: loongson: Make suspend/resume registers PM-specific
spi: loongson: Fix clock rate retrieval on ACPI platforms
spi: loongson: Fix clock divider calculation with explicit table
spi: loongson: Use bitfield helpers for register access
spi: loongson: Add interrupt mode support
drivers/spi/spi-loongson-core.c | 519 ++++++++++++++++++++++++++------
drivers/spi/spi-loongson-pci.c | 28 +-
drivers/spi/spi-loongson-plat.c | 17 +-
drivers/spi/spi-loongson.h | 129 ++++++--
4 files changed, 550 insertions(+), 143 deletions(-)
base-commit: c0ccde965b4874afea9bd40f8cc2362ab9910711
--
2.52.0
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH v2 1/7] spi: loongson: Fix incorrect SPI mode caching logic
2026-11-13 5:42 [PATCH v2 0/7] spi: loongson: Fixes, cleanup and IRQ support Binbin Zhou
@ 2026-11-13 5:42 ` Binbin Zhou
2026-11-13 5:42 ` [PATCH v2 2/7] spi: loongson: Drop redundant CS deassert in setup() Binbin Zhou
` (5 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Binbin Zhou @ 2026-11-13 5:42 UTC (permalink / raw)
To: Binbin Zhou, Huacai Chen, Yinbo Zhu, Mark Brown
Cc: Huacai Chen, Xuerui Wang, loongarch, linux-spi, Binbin Zhou
The current driver caches the SPI mode using bitwise OR:
loongson_spi->mode |= spi->mode;
This is incorrect because once a mode bit (e.g., SPI_CPOL or SPI_CPHA)
is set, it can never be cleared. If a device switches its mode settings,
the cached state will not match the actual hardware configuration, causing
loongson_spi_update_state() to miscalculate when a reprogram is needed.
Fix this by directly assigning the new mode:
loongson_spi->mode = spi->mode;
Fixes: 6c7a864007b6 ("spi: loongson: add bus driver for the loongson spi controller")
Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn>
---
drivers/spi/spi-loongson-core.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/spi/spi-loongson-core.c b/drivers/spi/spi-loongson-core.c
index 6ba7c56b1bdd..e95a9af94772 100644
--- a/drivers/spi/spi-loongson-core.c
+++ b/drivers/spi/spi-loongson-core.c
@@ -72,7 +72,7 @@ static void loongson_spi_set_mode(struct loongson_spi *loongson_spi,
val |= LOONGSON_SPI_SPCR_CPHA;
loongson_spi_write_reg(loongson_spi, LOONGSON_SPI_SPCR_REG, val);
- loongson_spi->mode |= spi->mode;
+ loongson_spi->mode = spi->mode;
}
static int loongson_spi_update_state(struct loongson_spi *loongson_spi,
--
2.52.0
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH v2 2/7] spi: loongson: Drop redundant CS deassert in setup()
2026-11-13 5:42 [PATCH v2 0/7] spi: loongson: Fixes, cleanup and IRQ support Binbin Zhou
2026-11-13 5:42 ` [PATCH v2 1/7] spi: loongson: Fix incorrect SPI mode caching logic Binbin Zhou
@ 2026-11-13 5:42 ` Binbin Zhou
2026-11-13 5:42 ` [PATCH v2 3/7] spi: loongson: Make suspend/resume registers PM-specific Binbin Zhou
` (4 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Binbin Zhou @ 2026-11-13 5:42 UTC (permalink / raw)
To: Binbin Zhou, Huacai Chen, Yinbo Zhu, Mark Brown
Cc: Huacai Chen, Xuerui Wang, loongarch, linux-spi, Binbin Zhou
loongson_spi_setup() calls loongson_spi_set_cs(spi, true) to idle the
chip select, but set_cs() receives the physical pin level after the
core applied SPI_CS_HIGH, so true asserts an active-high device.
The core also calls spi_set_cs(spi, false, true) after ->setup()
returns, making the manual call both wrong and redundant.
Remove the manual call.
Fixes: 6c7a864007b6 ("spi: loongson: add bus driver for the loongson spi controller")
Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn>
---
drivers/spi/spi-loongson-core.c | 1 -
1 file changed, 1 deletion(-)
diff --git a/drivers/spi/spi-loongson-core.c b/drivers/spi/spi-loongson-core.c
index e95a9af94772..bd4289022519 100644
--- a/drivers/spi/spi-loongson-core.c
+++ b/drivers/spi/spi-loongson-core.c
@@ -99,7 +99,6 @@ static int loongson_spi_setup(struct spi_device *spi)
return -EINVAL;
loongson_spi->hz = 0;
- loongson_spi_set_cs(spi, true);
return 0;
}
--
2.52.0
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH v2 3/7] spi: loongson: Make suspend/resume registers PM-specific
2026-11-13 5:42 [PATCH v2 0/7] spi: loongson: Fixes, cleanup and IRQ support Binbin Zhou
2026-11-13 5:42 ` [PATCH v2 1/7] spi: loongson: Fix incorrect SPI mode caching logic Binbin Zhou
2026-11-13 5:42 ` [PATCH v2 2/7] spi: loongson: Drop redundant CS deassert in setup() Binbin Zhou
@ 2026-11-13 5:42 ` Binbin Zhou
2026-11-13 5:42 ` [PATCH v2 4/7] spi: loongson: Fix clock rate retrieval on ACPI platforms Binbin Zhou
` (3 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Binbin Zhou @ 2026-11-13 5:42 UTC (permalink / raw)
To: Binbin Zhou, Huacai Chen, Yinbo Zhu, Mark Brown
Cc: Huacai Chen, Xuerui Wang, loongarch, linux-spi, Binbin Zhou
The driver reuses spcr/sper/spsr/para for runtime setup, polling and PM
state. set_clk() overwrites spcr/sper during normal operation, which can
corrupt the saved state before suspend.
Move these caches into a dedicated struct loongson_spi_suspend_store
(lss) used only by the PM callbacks, and switch other code to local
variables.
Fixes: 6c7a864007b6 ("spi: loongson: add bus driver for the loongson spi controller")
Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn>
---
drivers/spi/spi-loongson-core.c | 80 ++++++++++++++++++---------------
drivers/spi/spi-loongson.h | 14 +++---
2 files changed, 52 insertions(+), 42 deletions(-)
diff --git a/drivers/spi/spi-loongson-core.c b/drivers/spi/spi-loongson-core.c
index bd4289022519..452c2c35564d 100644
--- a/drivers/spi/spi-loongson-core.c
+++ b/drivers/spi/spi-loongson-core.c
@@ -40,22 +40,23 @@ static void loongson_spi_set_cs(struct spi_device *spi, bool en)
static void loongson_spi_set_clk(struct loongson_spi *loongson_spi, unsigned int hz)
{
- unsigned char val;
- unsigned int div, div_tmp;
static const char rdiv[12] = {0, 1, 4, 2, 3, 5, 6, 7, 8, 9, 10, 11};
+ unsigned char val, spr, spre;
+ unsigned int div, div_tmp;
div = clamp_val(DIV_ROUND_UP_ULL(loongson_spi->clk_rate, hz), 2, 4096);
div_tmp = rdiv[fls(div - 1)];
- loongson_spi->spcr = (div_tmp & GENMASK(1, 0)) >> 0;
- loongson_spi->sper = (div_tmp & GENMASK(3, 2)) >> 2;
+
+ spr = (div_tmp & GENMASK(1, 0)) >> 0;
val = loongson_spi_read_reg(loongson_spi, LOONGSON_SPI_SPCR_REG);
val &= ~GENMASK(1, 0);
- loongson_spi_write_reg(loongson_spi, LOONGSON_SPI_SPCR_REG, val |
- loongson_spi->spcr);
+ loongson_spi_write_reg(loongson_spi, LOONGSON_SPI_SPCR_REG, val | spr);
+
+ spre = (div_tmp & GENMASK(3, 2)) >> 2;
val = loongson_spi_read_reg(loongson_spi, LOONGSON_SPI_SPER_REG);
val &= ~GENMASK(1, 0);
- loongson_spi_write_reg(loongson_spi, LOONGSON_SPI_SPER_REG, val |
- loongson_spi->sper);
+ loongson_spi_write_reg(loongson_spi, LOONGSON_SPI_SPER_REG, val | spre);
+
loongson_spi->hz = hz;
}
@@ -106,8 +107,9 @@ static int loongson_spi_setup(struct spi_device *spi)
static int loongson_spi_write_read_8bit(struct spi_device *spi, const u8 **tx_buf,
u8 **rx_buf, unsigned int num)
{
- int ret;
struct loongson_spi *loongson_spi = spi_controller_get_devdata(spi->controller);
+ unsigned char status;
+ int ret;
if (tx_buf && *tx_buf)
loongson_spi_write_reg(loongson_spi, LOONGSON_SPI_FIFO_REG, *((*tx_buf)++));
@@ -115,8 +117,8 @@ static int loongson_spi_write_read_8bit(struct spi_device *spi, const u8 **tx_bu
loongson_spi_write_reg(loongson_spi, LOONGSON_SPI_FIFO_REG, 0);
ret = readb_poll_timeout(loongson_spi->base + LOONGSON_SPI_SPSR_REG,
- loongson_spi->spsr, (loongson_spi->spsr &
- LOONGSON_SPI_SPSR_RFEMPTY) != LOONGSON_SPI_SPSR_RFEMPTY,
+ status,
+ !(status & LOONGSON_SPI_SPSR_RFEMPTY),
1, USEC_PER_MSEC);
if (rx_buf && *rx_buf)
@@ -147,10 +149,11 @@ static int loongson_spi_write_read(struct spi_device *spi, struct spi_transfer *
static int loongson_spi_prepare_message(struct spi_controller *ctlr, struct spi_message *m)
{
struct loongson_spi *loongson_spi = spi_controller_get_devdata(ctlr);
+ unsigned char para;
- loongson_spi->para = loongson_spi_read_reg(loongson_spi, LOONGSON_SPI_PARA_REG);
- loongson_spi_write_reg(loongson_spi, LOONGSON_SPI_PARA_REG, loongson_spi->para &
- ~LOONGSON_SPI_PARA_MEM_EN);
+ para = loongson_spi_read_reg(loongson_spi, LOONGSON_SPI_PARA_REG);
+ loongson_spi_write_reg(loongson_spi, LOONGSON_SPI_PARA_REG,
+ para & ~LOONGSON_SPI_PARA_MEM_EN);
return 0;
}
@@ -170,8 +173,12 @@ static int loongson_spi_transfer_one(struct spi_controller *ctrl, struct spi_dev
static int loongson_spi_unprepare_message(struct spi_controller *ctrl, struct spi_message *m)
{
struct loongson_spi *loongson_spi = spi_controller_get_devdata(ctrl);
+ unsigned char para;
- loongson_spi_write_reg(loongson_spi, LOONGSON_SPI_PARA_REG, loongson_spi->para);
+ /* Restore the hardware SPI Flash read path */
+ para = loongson_spi_read_reg(loongson_spi, LOONGSON_SPI_PARA_REG);
+ loongson_spi_write_reg(loongson_spi, LOONGSON_SPI_PARA_REG,
+ para | LOONGSON_SPI_PARA_MEM_EN);
return 0;
}
@@ -230,38 +237,37 @@ EXPORT_SYMBOL_NS_GPL(loongson_spi_init_controller, "SPI_LOONGSON_CORE");
static int loongson_spi_suspend(struct device *dev)
{
- struct loongson_spi *loongson_spi;
- struct spi_controller *controller;
-
- controller = dev_get_drvdata(dev);
- spi_controller_suspend(controller);
+ struct spi_controller *controller = dev_get_drvdata(dev);
+ struct loongson_spi *loongson_spi = spi_controller_get_devdata(controller);
+ int ret;
- loongson_spi = spi_controller_get_devdata(controller);
+ ret = spi_controller_suspend(controller);
+ if (ret)
+ return ret;
- loongson_spi->spcr = loongson_spi_read_reg(loongson_spi, LOONGSON_SPI_SPCR_REG);
- loongson_spi->sper = loongson_spi_read_reg(loongson_spi, LOONGSON_SPI_SPER_REG);
- loongson_spi->spsr = loongson_spi_read_reg(loongson_spi, LOONGSON_SPI_SPSR_REG);
- loongson_spi->para = loongson_spi_read_reg(loongson_spi, LOONGSON_SPI_PARA_REG);
- loongson_spi->sfcs = loongson_spi_read_reg(loongson_spi, LOONGSON_SPI_SFCS_REG);
- loongson_spi->timi = loongson_spi_read_reg(loongson_spi, LOONGSON_SPI_TIMI_REG);
+ loongson_spi->lss.spcr = loongson_spi_read_reg(loongson_spi, LOONGSON_SPI_SPCR_REG);
+ loongson_spi->lss.sper = loongson_spi_read_reg(loongson_spi, LOONGSON_SPI_SPER_REG);
+ loongson_spi->lss.spsr = loongson_spi_read_reg(loongson_spi, LOONGSON_SPI_SPSR_REG);
+ loongson_spi->lss.para = loongson_spi_read_reg(loongson_spi, LOONGSON_SPI_PARA_REG);
+ loongson_spi->lss.sfcs = loongson_spi_read_reg(loongson_spi, LOONGSON_SPI_SFCS_REG);
+ loongson_spi->lss.timi = loongson_spi_read_reg(loongson_spi, LOONGSON_SPI_TIMI_REG);
return 0;
}
static int loongson_spi_resume(struct device *dev)
{
- struct loongson_spi *loongson_spi;
- struct spi_controller *controller;
+ struct spi_controller *controller = dev_get_drvdata(dev);
+ struct loongson_spi *loongson_spi = spi_controller_get_devdata(controller);
- controller = dev_get_drvdata(dev);
- loongson_spi = spi_controller_get_devdata(controller);
+ loongson_spi_write_reg(loongson_spi, LOONGSON_SPI_SPCR_REG, loongson_spi->lss.spcr);
+ loongson_spi_write_reg(loongson_spi, LOONGSON_SPI_SPER_REG, loongson_spi->lss.sper);
+ loongson_spi_write_reg(loongson_spi, LOONGSON_SPI_PARA_REG, loongson_spi->lss.para);
+ loongson_spi_write_reg(loongson_spi, LOONGSON_SPI_SFCS_REG, loongson_spi->lss.sfcs);
+ loongson_spi_write_reg(loongson_spi, LOONGSON_SPI_TIMI_REG, loongson_spi->lss.timi);
- loongson_spi_write_reg(loongson_spi, LOONGSON_SPI_SPCR_REG, loongson_spi->spcr);
- loongson_spi_write_reg(loongson_spi, LOONGSON_SPI_SPER_REG, loongson_spi->sper);
- loongson_spi_write_reg(loongson_spi, LOONGSON_SPI_SPSR_REG, loongson_spi->spsr);
- loongson_spi_write_reg(loongson_spi, LOONGSON_SPI_PARA_REG, loongson_spi->para);
- loongson_spi_write_reg(loongson_spi, LOONGSON_SPI_SFCS_REG, loongson_spi->sfcs);
- loongson_spi_write_reg(loongson_spi, LOONGSON_SPI_TIMI_REG, loongson_spi->timi);
+ loongson_spi_write_reg(loongson_spi, LOONGSON_SPI_SPSR_REG,
+ LOONGSON_SPI_SPSR_SPIF | LOONGSON_SPI_SPSR_WCOL);
spi_controller_resume(controller);
diff --git a/drivers/spi/spi-loongson.h b/drivers/spi/spi-loongson.h
index 35f95b161842..27b3e8accfcd 100644
--- a/drivers/spi/spi-loongson.h
+++ b/drivers/spi/spi-loongson.h
@@ -29,19 +29,23 @@
struct device;
struct spi_controller;
-struct loongson_spi {
- struct spi_controller *controller;
- void __iomem *base;
- int cs_active;
- unsigned int hz;
+struct loongson_spi_suspend_store {
unsigned char spcr;
unsigned char sper;
unsigned char spsr;
unsigned char para;
unsigned char sfcs;
unsigned char timi;
+};
+
+struct loongson_spi {
+ struct spi_controller *controller;
+ void __iomem *base;
+ int cs_active;
+ unsigned int hz;
unsigned int mode;
u64 clk_rate;
+ struct loongson_spi_suspend_store lss;
};
int loongson_spi_init_controller(struct device *dev, void __iomem *reg);
--
2.52.0
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH v2 4/7] spi: loongson: Fix clock rate retrieval on ACPI platforms
2026-11-13 5:42 [PATCH v2 0/7] spi: loongson: Fixes, cleanup and IRQ support Binbin Zhou
` (2 preceding siblings ...)
2026-11-13 5:42 ` [PATCH v2 3/7] spi: loongson: Make suspend/resume registers PM-specific Binbin Zhou
@ 2026-11-13 5:42 ` Binbin Zhou
2026-11-13 5:42 ` [PATCH v2 5/7] spi: loongson: Fix clock divider calculation with explicit table Binbin Zhou
` (2 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Binbin Zhou @ 2026-11-13 5:42 UTC (permalink / raw)
To: Binbin Zhou, Huacai Chen, Yinbo Zhu, Mark Brown
Cc: Huacai Chen, Xuerui Wang, loongarch, linux-spi, Binbin Zhou
On ACPI platforms the clock framework may be unavailable or return 0,
leaving clk_rate zero and producing a bogus SPI divider.
Add loongson_spi_get_clk_rate(): try the clk framework first, then fall
back to the "clock-frequency" device property (supported by both ACPI
and DT). Fail probe if neither provides a valid rate. Also disable and
re-enable the clock in suspend/resume.
Fixes: 6c7a864007b6 ("spi: loongson: add bus driver for the loongson spi controller")
Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn>
---
drivers/spi/spi-loongson-core.c | 43 +++++++++++++++++++++++++++++----
drivers/spi/spi-loongson.h | 1 +
2 files changed, 39 insertions(+), 5 deletions(-)
diff --git a/drivers/spi/spi-loongson-core.c b/drivers/spi/spi-loongson-core.c
index 452c2c35564d..19e73a64e14a 100644
--- a/drivers/spi/spi-loongson-core.c
+++ b/drivers/spi/spi-loongson-core.c
@@ -199,11 +199,38 @@ static void loongson_spi_reginit(struct loongson_spi *loongson_spi_dev)
loongson_spi_write_reg(loongson_spi_dev, LOONGSON_SPI_SPCR_REG, val);
}
+static int loongson_spi_get_clk_rate(struct device *dev)
+{
+ struct spi_controller *controller = dev_get_drvdata(dev);
+ struct loongson_spi *loongson_spi = spi_controller_get_devdata(controller);
+ unsigned int freq, ret;
+
+ loongson_spi->clk = devm_clk_get_optional_enabled(dev, NULL);
+ if (IS_ERR(loongson_spi->clk))
+ return dev_err_probe(dev, PTR_ERR(loongson_spi->clk),
+ "Failed to get spi clock");
+
+ if (loongson_spi->clk) {
+ loongson_spi->clk_rate = clk_get_rate(loongson_spi->clk);
+ if (!loongson_spi->clk_rate)
+ return dev_err_probe(dev, -EINVAL, "Failed to get frequency");
+ return 0;
+ }
+
+ ret = device_property_read_u32(dev, "clock-frequency", &freq);
+ if (ret || !freq)
+ return dev_err_probe(dev, -EINVAL, "Failed to parse clock-frequency");
+
+ loongson_spi->clk_rate = freq;
+
+ return 0;
+}
+
int loongson_spi_init_controller(struct device *dev, void __iomem *regs)
{
struct spi_controller *controller;
struct loongson_spi *spi;
- struct clk *clk;
+ int ret;
controller = devm_spi_alloc_host(dev, sizeof(struct loongson_spi));
if (controller == NULL)
@@ -222,11 +249,10 @@ int loongson_spi_init_controller(struct device *dev, void __iomem *regs)
spi->base = regs;
spi->controller = controller;
- clk = devm_clk_get_optional(dev, NULL);
- if (IS_ERR(clk))
- return dev_err_probe(dev, PTR_ERR(clk), "unable to get clock\n");
+ ret = loongson_spi_get_clk_rate(dev);
+ if (ret)
+ return ret;
- spi->clk_rate = clk_get_rate(clk);
loongson_spi_reginit(spi);
spi->mode = 0;
@@ -252,6 +278,8 @@ static int loongson_spi_suspend(struct device *dev)
loongson_spi->lss.sfcs = loongson_spi_read_reg(loongson_spi, LOONGSON_SPI_SFCS_REG);
loongson_spi->lss.timi = loongson_spi_read_reg(loongson_spi, LOONGSON_SPI_TIMI_REG);
+ clk_disable_unprepare(loongson_spi->clk);
+
return 0;
}
@@ -259,6 +287,11 @@ static int loongson_spi_resume(struct device *dev)
{
struct spi_controller *controller = dev_get_drvdata(dev);
struct loongson_spi *loongson_spi = spi_controller_get_devdata(controller);
+ int ret;
+
+ ret = clk_prepare_enable(loongson_spi->clk);
+ if (ret)
+ return ret;
loongson_spi_write_reg(loongson_spi, LOONGSON_SPI_SPCR_REG, loongson_spi->lss.spcr);
loongson_spi_write_reg(loongson_spi, LOONGSON_SPI_SPER_REG, loongson_spi->lss.sper);
diff --git a/drivers/spi/spi-loongson.h b/drivers/spi/spi-loongson.h
index 27b3e8accfcd..6b52e23cc62b 100644
--- a/drivers/spi/spi-loongson.h
+++ b/drivers/spi/spi-loongson.h
@@ -41,6 +41,7 @@ struct loongson_spi_suspend_store {
struct loongson_spi {
struct spi_controller *controller;
void __iomem *base;
+ struct clk *clk;
int cs_active;
unsigned int hz;
unsigned int mode;
--
2.52.0
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH v2 5/7] spi: loongson: Fix clock divider calculation with explicit table
2026-11-13 5:42 [PATCH v2 0/7] spi: loongson: Fixes, cleanup and IRQ support Binbin Zhou
` (3 preceding siblings ...)
2026-11-13 5:42 ` [PATCH v2 4/7] spi: loongson: Fix clock rate retrieval on ACPI platforms Binbin Zhou
@ 2026-11-13 5:42 ` Binbin Zhou
2026-11-13 5:42 ` [PATCH v2 6/7] spi: loongson: Use bitfield helpers for register access Binbin Zhou
2026-11-13 5:42 ` [PATCH v2 7/7] spi: loongson: Add interrupt mode support Binbin Zhou
6 siblings, 0 replies; 8+ messages in thread
From: Binbin Zhou @ 2026-11-13 5:42 UTC (permalink / raw)
To: Binbin Zhou, Huacai Chen, Yinbo Zhu, Mark Brown
Cc: Huacai Chen, Xuerui Wang, loongarch, linux-spi, Binbin Zhou
loongson_spi_set_clk() previously converted the requested divider into
SPCR.SPR/SPER.SPRE using rdiv[] and fls(div - 1). The rdiv[] mapping did
not match the actual divider encoding, so the programmed divider was
usually one step larger than requested, and the maximum divider could
index past the end of rdiv[].
For example:
- Requested divider 2: fls(2 - 1) = 1, rdiv[1] = 1, which encodes
divider 4, so the output clock is half the requested rate.
- Requested divider 4: fls(4 - 1) = 2, rdiv[2] = 4, which encodes
divider 8, again halving the output clock.
- Requested divider 8: fls(8 - 1) = 3, rdiv[3] = 2, which encodes
divider 16.
- Requested divider 4096: div is clamped to 4096, fls(4095) = 12, and
rdiv[12] is out of bounds because rdiv[] only has 12 entries.
Replace this with loongson_spi_div_table[], which explicitly maps each
supported divider from 2 to 4096 to its SPR/SPRE encoding, and select
the first entry whose divider is greater than or equal to the requested
value. This ensures the actual SPI clock does not exceed the requested
rate. Also make loongson_spi_set_clk() return an error and propagate it
to the caller, and validate hz and clk_rate before touching the hardware.
Fixes: 6c7a864007b6 ("spi: loongson: add bus driver for the loongson spi controller")
Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn>
---
drivers/spi/spi-loongson-core.c | 68 ++++++++++++++++++++++++++-------
drivers/spi/spi-loongson.h | 20 ++++++++++
2 files changed, 75 insertions(+), 13 deletions(-)
diff --git a/drivers/spi/spi-loongson-core.c b/drivers/spi/spi-loongson-core.c
index 19e73a64e14a..5991ce7b5603 100644
--- a/drivers/spi/spi-loongson-core.c
+++ b/drivers/spi/spi-loongson-core.c
@@ -16,6 +16,21 @@
#include "spi-loongson.h"
+static const struct loongson_spi_div_table div_table[] = {
+ { 2, 0, 0 },
+ { 4, 1, 0 },
+ { 8, 0, 1 },
+ { 16, 2, 0 },
+ { 32, 3, 0 },
+ { 64, 1, 1 },
+ { 128, 2, 1 },
+ { 256, 3, 1 },
+ { 512, 0, 2 },
+ {1024, 1, 2 },
+ {2048, 2, 2 },
+ {4096, 3, 2 },
+};
+
static inline void loongson_spi_write_reg(struct loongson_spi *spi, unsigned char reg,
unsigned char data)
{
@@ -38,26 +53,40 @@ static void loongson_spi_set_cs(struct spi_device *spi, bool en)
loongson_spi_write_reg(loongson_spi, LOONGSON_SPI_SFCS_REG, val | cs);
}
-static void loongson_spi_set_clk(struct loongson_spi *loongson_spi, unsigned int hz)
+static int loongson_spi_set_clk(struct loongson_spi *loongson_spi, unsigned int hz)
{
- static const char rdiv[12] = {0, 1, 4, 2, 3, 5, 6, 7, 8, 9, 10, 11};
- unsigned char val, spr, spre;
- unsigned int div, div_tmp;
+ unsigned long div;
+ unsigned char val;
+ unsigned int i;
+
+ if (!hz || !loongson_spi->clk_rate)
+ return -EINVAL;
+
+ div = DIV_ROUND_UP_ULL(loongson_spi->clk_rate, hz);
+ if (div < LOONGSON_SPI_DIV_MIN)
+ div = LOONGSON_SPI_DIV_MIN;
- div = clamp_val(DIV_ROUND_UP_ULL(loongson_spi->clk_rate, hz), 2, 4096);
- div_tmp = rdiv[fls(div - 1)];
+ for (i = 0; i < ARRAY_SIZE(div_table); i++) {
+ if (div_table[i].div >= div)
+ break;
+ }
+
+ if (i == ARRAY_SIZE(div_table))
+ return -EINVAL;
- spr = (div_tmp & GENMASK(1, 0)) >> 0;
val = loongson_spi_read_reg(loongson_spi, LOONGSON_SPI_SPCR_REG);
val &= ~GENMASK(1, 0);
- loongson_spi_write_reg(loongson_spi, LOONGSON_SPI_SPCR_REG, val | spr);
+ loongson_spi_write_reg(loongson_spi, LOONGSON_SPI_SPCR_REG,
+ val | div_table[i].spr);
- spre = (div_tmp & GENMASK(3, 2)) >> 2;
val = loongson_spi_read_reg(loongson_spi, LOONGSON_SPI_SPER_REG);
val &= ~GENMASK(1, 0);
- loongson_spi_write_reg(loongson_spi, LOONGSON_SPI_SPER_REG, val | spre);
+ loongson_spi_write_reg(loongson_spi, LOONGSON_SPI_SPER_REG,
+ val | div_table[i].spre);
loongson_spi->hz = hz;
+
+ return 0;
}
static void loongson_spi_set_mode(struct loongson_spi *loongson_spi,
@@ -79,8 +108,13 @@ static void loongson_spi_set_mode(struct loongson_spi *loongson_spi,
static int loongson_spi_update_state(struct loongson_spi *loongson_spi,
struct spi_device *spi, struct spi_transfer *t)
{
- if (t && loongson_spi->hz != t->speed_hz)
- loongson_spi_set_clk(loongson_spi, t->speed_hz);
+ int ret;
+
+ if (t && loongson_spi->hz != t->speed_hz) {
+ ret = loongson_spi_set_clk(loongson_spi, t->speed_hz);
+ if (ret)
+ return ret;
+ }
if ((spi->mode ^ loongson_spi->mode) & SPI_MODE_X_MASK)
loongson_spi_set_mode(loongson_spi, spi);
@@ -162,8 +196,12 @@ static int loongson_spi_transfer_one(struct spi_controller *ctrl, struct spi_dev
struct spi_transfer *xfer)
{
struct loongson_spi *loongson_spi = spi_controller_get_devdata(spi->controller);
+ int ret;
+
+ ret = loongson_spi_update_state(loongson_spi, spi, xfer);
+ if (ret)
+ return ret;
- loongson_spi_update_state(loongson_spi, spi, xfer);
if (xfer->len)
return loongson_spi_write_read(spi, xfer);
@@ -253,6 +291,10 @@ int loongson_spi_init_controller(struct device *dev, void __iomem *regs)
if (ret)
return ret;
+ /* Expose controller capabilities to the SPI core */
+ controller->max_speed_hz = spi->clk_rate / LOONGSON_SPI_DIV_MIN;
+ controller->min_speed_hz = DIV_ROUND_UP_ULL(spi->clk_rate, LOONGSON_SPI_DIV_MAX);
+
loongson_spi_reginit(spi);
spi->mode = 0;
diff --git a/drivers/spi/spi-loongson.h b/drivers/spi/spi-loongson.h
index 6b52e23cc62b..983a39c9b0ae 100644
--- a/drivers/spi/spi-loongson.h
+++ b/drivers/spi/spi-loongson.h
@@ -26,9 +26,29 @@
#define LOONGSON_SPI_SPSR_WCOL BIT(6)
#define LOONGSON_SPI_SPSR_SPIF BIT(7)
+/* Hardware limits and divider masks */
+#define LOONGSON_SPI_DIV_MIN 2
+#define LOONGSON_SPI_DIV_MAX 4096
+
struct device;
struct spi_controller;
+/**
+ * struct loongson_spi_div_table - SPI clock divider encoding
+ * @div: resulting clock divider (SPI input clock / SPI output clock)
+ * @spr: value for the SPCR.SPR field
+ * @spre: value for the SPER.SPRE field
+ *
+ * One entry per supported divider from 2 to 4096. The (spr, spre) pair
+ * is programmed into the controller to produce @div; entries are sorted
+ * by @div so the first entry >= the requested divider can be selected.
+ */
+struct loongson_spi_div_table {
+ unsigned int div;
+ unsigned char spr;
+ unsigned char spre;
+};
+
struct loongson_spi_suspend_store {
unsigned char spcr;
unsigned char sper;
--
2.52.0
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH v2 6/7] spi: loongson: Use bitfield helpers for register access
2026-11-13 5:42 [PATCH v2 0/7] spi: loongson: Fixes, cleanup and IRQ support Binbin Zhou
` (4 preceding siblings ...)
2026-11-13 5:42 ` [PATCH v2 5/7] spi: loongson: Fix clock divider calculation with explicit table Binbin Zhou
@ 2026-11-13 5:42 ` Binbin Zhou
2026-11-13 5:42 ` [PATCH v2 7/7] spi: loongson: Add interrupt mode support Binbin Zhou
6 siblings, 0 replies; 8+ messages in thread
From: Binbin Zhou @ 2026-11-13 5:42 UTC (permalink / raw)
To: Binbin Zhou, Huacai Chen, Yinbo Zhu, Mark Brown
Cc: Huacai Chen, Xuerui Wang, loongarch, linux-spi, Binbin Zhou
Replace open-coded read/modify/write sequences with
loongson_spi_set_bits()/clear_bits()/update_bits(), define register
bits via GENMASK/BIT, and use FIELD_PREP where the mask is a
compile-time constant.
No functional change intended.
Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn>
---
drivers/spi/spi-loongson-core.c | 136 ++++++++++++++++++--------------
drivers/spi/spi-loongson.h | 67 +++++++++++-----
2 files changed, 122 insertions(+), 81 deletions(-)
diff --git a/drivers/spi/spi-loongson-core.c b/drivers/spi/spi-loongson-core.c
index 5991ce7b5603..74f47150cc6b 100644
--- a/drivers/spi/spi-loongson-core.c
+++ b/drivers/spi/spi-loongson-core.c
@@ -2,6 +2,8 @@
// Loongson SPI Support
// Copyright (C) 2023 Loongson Technology Corporation Limited
+#include <linux/bitfield.h>
+#include <linux/bits.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/err.h>
@@ -31,32 +33,54 @@ static const struct loongson_spi_div_table div_table[] = {
{4096, 3, 2 },
};
-static inline void loongson_spi_write_reg(struct loongson_spi *spi, unsigned char reg,
- unsigned char data)
+static void loongson_spi_write_reg(struct loongson_spi *spi, unsigned int reg,
+ unsigned char data)
{
writeb(data, spi->base + reg);
}
-static inline char loongson_spi_read_reg(struct loongson_spi *spi, unsigned char reg)
+static unsigned char loongson_spi_read_reg(struct loongson_spi *spi, unsigned int reg)
{
return readb(spi->base + reg);
}
+static void loongson_spi_set_bits(struct loongson_spi *spi, unsigned int reg,
+ unsigned char bits)
+{
+ writeb(readb(spi->base + reg) | bits, spi->base + reg);
+}
+
+static void loongson_spi_clear_bits(struct loongson_spi *spi, unsigned int reg,
+ unsigned char bits)
+{
+ writeb(readb(spi->base + reg) & ~bits, spi->base + reg);
+}
+
+static void loongson_spi_update_bits(struct loongson_spi *spi, unsigned int reg,
+ unsigned char mask, unsigned char val)
+{
+ unsigned char tmp = readb(spi->base + reg);
+
+ tmp &= ~mask;
+ tmp |= val & mask;
+ writeb(tmp, spi->base + reg);
+}
+
static void loongson_spi_set_cs(struct spi_device *spi, bool en)
{
- int cs;
- unsigned char mask = (BIT(4) | BIT(0)) << spi_get_chipselect(spi, 0);
- unsigned char val = en ? mask : (BIT(0) << spi_get_chipselect(spi, 0));
struct loongson_spi *loongson_spi = spi_controller_get_devdata(spi->controller);
+ unsigned int cs = spi_get_chipselect(spi, 0);
+ unsigned char mask, val;
+
+ mask = LOONGSON_SPI_SFCS_SW_EN(cs) | LOONGSON_SPI_SFCS_OUT(cs);
+ val = LOONGSON_SPI_SFCS_SW_EN(cs) | field_prep(LOONGSON_SPI_SFCS_OUT(cs), en);
- cs = loongson_spi_read_reg(loongson_spi, LOONGSON_SPI_SFCS_REG) & ~mask;
- loongson_spi_write_reg(loongson_spi, LOONGSON_SPI_SFCS_REG, val | cs);
+ loongson_spi_update_bits(loongson_spi, LOONGSON_SPI_SFCS_REG, mask, val);
}
static int loongson_spi_set_clk(struct loongson_spi *loongson_spi, unsigned int hz)
{
unsigned long div;
- unsigned char val;
unsigned int i;
if (!hz || !loongson_spi->clk_rate)
@@ -74,15 +98,11 @@ static int loongson_spi_set_clk(struct loongson_spi *loongson_spi, unsigned int
if (i == ARRAY_SIZE(div_table))
return -EINVAL;
- val = loongson_spi_read_reg(loongson_spi, LOONGSON_SPI_SPCR_REG);
- val &= ~GENMASK(1, 0);
- loongson_spi_write_reg(loongson_spi, LOONGSON_SPI_SPCR_REG,
- val | div_table[i].spr);
+ loongson_spi_update_bits(loongson_spi, LOONGSON_SPI_SPCR_REG,
+ LOONGSON_SPI_SPCR_SPR, div_table[i].spr);
- val = loongson_spi_read_reg(loongson_spi, LOONGSON_SPI_SPER_REG);
- val &= ~GENMASK(1, 0);
- loongson_spi_write_reg(loongson_spi, LOONGSON_SPI_SPER_REG,
- val | div_table[i].spre);
+ loongson_spi_update_bits(loongson_spi, LOONGSON_SPI_SPER_REG,
+ LOONGSON_SPI_SPER_SPRE, div_table[i].spre);
loongson_spi->hz = hz;
@@ -92,21 +112,23 @@ static int loongson_spi_set_clk(struct loongson_spi *loongson_spi, unsigned int
static void loongson_spi_set_mode(struct loongson_spi *loongson_spi,
struct spi_device *spi)
{
- unsigned char val;
+ unsigned char val = 0;
- val = loongson_spi_read_reg(loongson_spi, LOONGSON_SPI_SPCR_REG);
- val &= ~(LOONGSON_SPI_SPCR_CPOL | LOONGSON_SPI_SPCR_CPHA);
if (spi->mode & SPI_CPOL)
val |= LOONGSON_SPI_SPCR_CPOL;
+
if (spi->mode & SPI_CPHA)
val |= LOONGSON_SPI_SPCR_CPHA;
- loongson_spi_write_reg(loongson_spi, LOONGSON_SPI_SPCR_REG, val);
+ loongson_spi_update_bits(loongson_spi, LOONGSON_SPI_SPCR_REG,
+ LOONGSON_SPI_SPCR_CPOL | LOONGSON_SPI_SPCR_CPHA,
+ val);
+
loongson_spi->mode = spi->mode;
}
static int loongson_spi_update_state(struct loongson_spi *loongson_spi,
- struct spi_device *spi, struct spi_transfer *t)
+ struct spi_device *spi, struct spi_transfer *t)
{
int ret;
@@ -124,13 +146,12 @@ static int loongson_spi_update_state(struct loongson_spi *loongson_spi,
static int loongson_spi_setup(struct spi_device *spi)
{
- struct loongson_spi *loongson_spi;
+ struct loongson_spi *loongson_spi = spi_controller_get_devdata(spi->controller);
- loongson_spi = spi_controller_get_devdata(spi->controller);
- if (spi->bits_per_word % 8)
+ if (spi->bits_per_word % BITS_PER_BYTE)
return -EINVAL;
- if (spi_get_chipselect(spi, 0) >= spi->controller->num_chipselect)
+ if (spi_get_chipselect(spi, 0) >= LOONGSON_SPI_NUM_CS)
return -EINVAL;
loongson_spi->hz = 0;
@@ -138,41 +159,41 @@ static int loongson_spi_setup(struct spi_device *spi)
return 0;
}
-static int loongson_spi_write_read_8bit(struct spi_device *spi, const u8 **tx_buf,
- u8 **rx_buf, unsigned int num)
+static int loongson_spi_write_read_8bit(struct spi_device *spi,
+ const u8 **tx_buf, u8 **rx_buf)
{
struct loongson_spi *loongson_spi = spi_controller_get_devdata(spi->controller);
- unsigned char status;
+ unsigned char status, val = 0;
int ret;
if (tx_buf && *tx_buf)
- loongson_spi_write_reg(loongson_spi, LOONGSON_SPI_FIFO_REG, *((*tx_buf)++));
- else
- loongson_spi_write_reg(loongson_spi, LOONGSON_SPI_FIFO_REG, 0);
+ val = *((*tx_buf)++);
+
+ loongson_spi_write_reg(loongson_spi, LOONGSON_SPI_FIFO_REG, val);
ret = readb_poll_timeout(loongson_spi->base + LOONGSON_SPI_SPSR_REG,
- status,
- !(status & LOONGSON_SPI_SPSR_RFEMPTY),
+ status, !(status & LOONGSON_SPI_SPSR_RFEMPTY),
1, USEC_PER_MSEC);
+ if (ret)
+ return ret;
+
+ val = loongson_spi_read_reg(loongson_spi, LOONGSON_SPI_FIFO_REG);
if (rx_buf && *rx_buf)
- *(*rx_buf)++ = loongson_spi_read_reg(loongson_spi, LOONGSON_SPI_FIFO_REG);
- else
- loongson_spi_read_reg(loongson_spi, LOONGSON_SPI_FIFO_REG);
+ *(*rx_buf)++ = val;
return ret;
}
static int loongson_spi_write_read(struct spi_device *spi, struct spi_transfer *xfer)
{
- int ret;
- unsigned int count;
+ unsigned int count = xfer->len;
const u8 *tx = xfer->tx_buf;
u8 *rx = xfer->rx_buf;
+ int ret;
- count = xfer->len;
do {
- ret = loongson_spi_write_read_8bit(spi, &tx, &rx, count);
+ ret = loongson_spi_write_read_8bit(spi, &tx, &rx);
if (ret)
break;
} while (--count);
@@ -183,11 +204,9 @@ static int loongson_spi_write_read(struct spi_device *spi, struct spi_transfer *
static int loongson_spi_prepare_message(struct spi_controller *ctlr, struct spi_message *m)
{
struct loongson_spi *loongson_spi = spi_controller_get_devdata(ctlr);
- unsigned char para;
- para = loongson_spi_read_reg(loongson_spi, LOONGSON_SPI_PARA_REG);
- loongson_spi_write_reg(loongson_spi, LOONGSON_SPI_PARA_REG,
- para & ~LOONGSON_SPI_PARA_MEM_EN);
+ loongson_spi_clear_bits(loongson_spi,
+ LOONGSON_SPI_PARA_REG, LOONGSON_SPI_PARA_MEM_EN);
return 0;
}
@@ -211,30 +230,27 @@ static int loongson_spi_transfer_one(struct spi_controller *ctrl, struct spi_dev
static int loongson_spi_unprepare_message(struct spi_controller *ctrl, struct spi_message *m)
{
struct loongson_spi *loongson_spi = spi_controller_get_devdata(ctrl);
- unsigned char para;
/* Restore the hardware SPI Flash read path */
- para = loongson_spi_read_reg(loongson_spi, LOONGSON_SPI_PARA_REG);
- loongson_spi_write_reg(loongson_spi, LOONGSON_SPI_PARA_REG,
- para | LOONGSON_SPI_PARA_MEM_EN);
+ loongson_spi_set_bits(loongson_spi,
+ LOONGSON_SPI_PARA_REG, LOONGSON_SPI_PARA_MEM_EN);
return 0;
}
static void loongson_spi_reginit(struct loongson_spi *loongson_spi_dev)
{
- unsigned char val;
-
- val = loongson_spi_read_reg(loongson_spi_dev, LOONGSON_SPI_SPCR_REG);
- val &= ~LOONGSON_SPI_SPCR_SPE;
- loongson_spi_write_reg(loongson_spi_dev, LOONGSON_SPI_SPCR_REG, val);
+ /* Disable SPI */
+ loongson_spi_clear_bits(loongson_spi_dev,
+ LOONGSON_SPI_SPCR_REG, LOONGSON_SPI_SPCR_SPE);
- loongson_spi_write_reg(loongson_spi_dev, LOONGSON_SPI_SPSR_REG,
- (LOONGSON_SPI_SPSR_SPIF | LOONGSON_SPI_SPSR_WCOL));
+ /* Clear status flags (write 1 to clear) */
+ loongson_spi_write_reg(loongson_spi_dev,
+ LOONGSON_SPI_SPSR_REG, LOONGSON_SPI_SPSR_EVENTS);
- val = loongson_spi_read_reg(loongson_spi_dev, LOONGSON_SPI_SPCR_REG);
- val |= LOONGSON_SPI_SPCR_SPE;
- loongson_spi_write_reg(loongson_spi_dev, LOONGSON_SPI_SPCR_REG, val);
+ /* Enable SPI */
+ loongson_spi_set_bits(loongson_spi_dev,
+ LOONGSON_SPI_SPCR_REG, LOONGSON_SPI_SPCR_SPE);
}
static int loongson_spi_get_clk_rate(struct device *dev)
@@ -280,7 +296,7 @@ int loongson_spi_init_controller(struct device *dev, void __iomem *regs)
controller->transfer_one = loongson_spi_transfer_one;
controller->unprepare_message = loongson_spi_unprepare_message;
controller->set_cs = loongson_spi_set_cs;
- controller->num_chipselect = 4;
+ controller->num_chipselect = LOONGSON_SPI_NUM_CS;
dev_set_drvdata(dev, controller);
spi = spi_controller_get_devdata(controller);
diff --git a/drivers/spi/spi-loongson.h b/drivers/spi/spi-loongson.h
index 983a39c9b0ae..c46169e8c780 100644
--- a/drivers/spi/spi-loongson.h
+++ b/drivers/spi/spi-loongson.h
@@ -5,34 +5,59 @@
#ifndef __LINUX_SPI_LOONGSON_H
#define __LINUX_SPI_LOONGSON_H
-#include <linux/bits.h>
-#include <linux/pm.h>
-#include <linux/types.h>
+/* SPI controller register offsets */
+#define LOONGSON_SPI_SPCR_REG 0x00
+#define LOONGSON_SPI_SPSR_REG 0x01
+#define LOONGSON_SPI_FIFO_REG 0x02
+#define LOONGSON_SPI_SPER_REG 0x03
+#define LOONGSON_SPI_PARA_REG 0x04
+#define LOONGSON_SPI_SFCS_REG 0x05
+#define LOONGSON_SPI_TIMI_REG 0x06
-#define LOONGSON_SPI_SPCR_REG 0x00
-#define LOONGSON_SPI_SPSR_REG 0x01
-#define LOONGSON_SPI_FIFO_REG 0x02
-#define LOONGSON_SPI_SPER_REG 0x03
-#define LOONGSON_SPI_PARA_REG 0x04
-#define LOONGSON_SPI_SFCS_REG 0x05
-#define LOONGSON_SPI_TIMI_REG 0x06
+/* SPCR bits */
+#define LOONGSON_SPI_SPCR_SPR GENMASK(1, 0)
+#define LOONGSON_SPI_SPCR_CPHA BIT(2)
+#define LOONGSON_SPI_SPCR_CPOL BIT(3)
+#define LOONGSON_SPI_SPCR_SPE BIT(6)
+#define LOONGSON_SPI_SPCR_SPIE BIT(7)
-/* Bits definition for Loongson SPI register */
-#define LOONGSON_SPI_PARA_MEM_EN BIT(0)
-#define LOONGSON_SPI_SPCR_CPHA BIT(2)
-#define LOONGSON_SPI_SPCR_CPOL BIT(3)
-#define LOONGSON_SPI_SPCR_SPE BIT(6)
-#define LOONGSON_SPI_SPSR_RFEMPTY BIT(0)
-#define LOONGSON_SPI_SPSR_WCOL BIT(6)
-#define LOONGSON_SPI_SPSR_SPIF BIT(7)
+/* SPSR bits */
+#define LOONGSON_SPI_SPSR_RFEMPTY BIT(0)
+#define LOONGSON_SPI_SPSR_RFFULL BIT(1)
+#define LOONGSON_SPI_SPSR_WFEMPTY BIT(2)
+#define LOONGSON_SPI_SPSR_WFFULL BIT(3)
+#define LOONGSON_SPI_SPSR_WCOL BIT(6)
+#define LOONGSON_SPI_SPSR_SPIF BIT(7)
+
+/* SPSR event bits: transfer complete (SPIF) or write collision (WCOL) */
+#define LOONGSON_SPI_SPSR_EVENTS (LOONGSON_SPI_SPSR_SPIF | \
+ LOONGSON_SPI_SPSR_WCOL)
+
+/* SPER bits */
+#define LOONGSON_SPI_SPER_SPRE GENMASK(1, 0)
+
+/* SFC_PARAM bits */
+#define LOONGSON_SPI_PARA_MEM_EN BIT(0)
+
+/*
+ * SFC_SOFTCS layout:
+ * bit cs : per-CS software-control enable
+ * bit cs + 4 : per-CS output level
+ *
+ * The chip select index is a runtime value, so FIELD_PREP() (which
+ * requires a compile-time constant mask) cannot be used here. These
+ * helpers keep the shift hidden behind a field-like accessor.
+ */
+#define LOONGSON_SPI_SFCS_OUT_SHIFT 4
+#define LOONGSON_SPI_SFCS_SW_EN(cs) BIT(cs)
+#define LOONGSON_SPI_SFCS_OUT(cs) BIT((cs) + LOONGSON_SPI_SFCS_OUT_SHIFT)
+
+#define LOONGSON_SPI_NUM_CS 4
/* Hardware limits and divider masks */
#define LOONGSON_SPI_DIV_MIN 2
#define LOONGSON_SPI_DIV_MAX 4096
-struct device;
-struct spi_controller;
-
/**
* struct loongson_spi_div_table - SPI clock divider encoding
* @div: resulting clock divider (SPI input clock / SPI output clock)
--
2.52.0
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH v2 7/7] spi: loongson: Add interrupt mode support
2026-11-13 5:42 [PATCH v2 0/7] spi: loongson: Fixes, cleanup and IRQ support Binbin Zhou
` (5 preceding siblings ...)
2026-11-13 5:42 ` [PATCH v2 6/7] spi: loongson: Use bitfield helpers for register access Binbin Zhou
@ 2026-11-13 5:42 ` Binbin Zhou
6 siblings, 0 replies; 8+ messages in thread
From: Binbin Zhou @ 2026-11-13 5:42 UTC (permalink / raw)
To: Binbin Zhou, Huacai Chen, Yinbo Zhu, Mark Brown
Cc: Huacai Chen, Xuerui Wang, loongarch, linux-spi, Binbin Zhou
Add an interrupt-driven transfer path alongside polling. The IRQ
handler drains the RX FIFO, refills TX up to icnt+1 bytes and
finalizes the transfer when complete; if no IRQ is available the
driver falls back to polling.
Because a tail shorter than the interrupt period does not raise SPIF,
the period is chosen to divide the transfer length, so the last batch
always completes. Messages are also split into bounded chunks to keep
each transfer within the SPI core timeout, given the low per-byte
throughput of this controller.
Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn>
---
drivers/spi/spi-loongson-core.c | 241 ++++++++++++++++++++++++++++++--
drivers/spi/spi-loongson-pci.c | 28 ++--
drivers/spi/spi-loongson-plat.c | 17 ++-
drivers/spi/spi-loongson.h | 23 ++-
4 files changed, 285 insertions(+), 24 deletions(-)
diff --git a/drivers/spi/spi-loongson-core.c b/drivers/spi/spi-loongson-core.c
index 74f47150cc6b..8d9f85d58dba 100644
--- a/drivers/spi/spi-loongson-core.c
+++ b/drivers/spi/spi-loongson-core.c
@@ -1,5 +1,16 @@
// SPDX-License-Identifier: GPL-2.0+
// Loongson SPI Support
+//
+// - Host-only controller with up to 4 chip selects.
+// - Tx/Rx FIFO with 4-bit wide entries;
+// (1 on Loongson-2K0300, 4 on Loongson-2K3000).
+// - Supports booting the SoC from SPI flash.
+// - Both a polling mode and an interrupt mode are supported; in
+// interrupt mode the number of bytes per interrupt is chosen from
+// the FIFO depth (one byte per IRQ on a single-entry FIFO, up to
+// 4 bytes per IRQ on an 4-entry FIFO). The driver automatically
+// falls back to polling if no IRQ is available.
+//
// Copyright (C) 2023 Loongson Technology Corporation Limited
#include <linux/bitfield.h>
@@ -201,9 +212,178 @@ static int loongson_spi_write_read(struct spi_device *spi, struct spi_transfer *
return ret;
}
+/*
+ * SPIF fires every (icnt + 1) bytes, and a tail shorter than that
+ * does not raise SPIF, which stalls the transfer. Pick the largest
+ * (icnt + 1) that divides len, falling back to 1 byte per interrupt.
+ */
+static unsigned int loongson_spi_calc_icnt(struct loongson_spi *loongson_spi,
+ unsigned int len)
+{
+ unsigned int icnt_max;
+
+ icnt_max = min_t(unsigned int,
+ FIELD_MAX(LOONGSON_SPI_SPER_ICNT),
+ loongson_spi->fifo_depth - 1);
+
+ for (unsigned int icnt = icnt_max; icnt > 0; icnt--)
+ if (!(len % (icnt + 1)))
+ return icnt;
+ return 0;
+}
+
+static irqreturn_t loongson_spi_irq(int irq, void *dev_id)
+{
+ struct loongson_spi *loongson_spi = dev_id;
+ struct spi_controller *ctrl = loongson_spi->controller;
+ unsigned char status, data, limit;
+ bool done = false;
+
+ scoped_guard(spinlock_irqsave, &loongson_spi->lock) {
+ if (!loongson_spi->total)
+ return IRQ_HANDLED;
+
+ status = loongson_spi_read_reg(loongson_spi, LOONGSON_SPI_SPSR_REG);
+
+ if (!(status & LOONGSON_SPI_SPSR_EVENTS))
+ return IRQ_NONE;
+
+ if (status & LOONGSON_SPI_SPSR_WCOL)
+ loongson_spi_write_reg(loongson_spi,
+ LOONGSON_SPI_SPSR_REG, LOONGSON_SPI_SPSR_WCOL);
+
+ if (!(status & LOONGSON_SPI_SPSR_SPIF))
+ return IRQ_HANDLED;
+
+ loongson_spi_write_reg(loongson_spi,
+ LOONGSON_SPI_SPSR_REG, LOONGSON_SPI_SPSR_SPIF);
+
+ /* Drain the RX FIFO */
+ while (loongson_spi->count < loongson_spi->total) {
+ status = loongson_spi_read_reg(loongson_spi, LOONGSON_SPI_SPSR_REG);
+ if (status & LOONGSON_SPI_SPSR_RFEMPTY)
+ break;
+
+ data = loongson_spi_read_reg(loongson_spi, LOONGSON_SPI_FIFO_REG);
+ if (loongson_spi->rx_ptr)
+ loongson_spi->rx_ptr[loongson_spi->count] = (u8)data;
+
+ loongson_spi->count++;
+ }
+
+ /*
+ * Refill the TX FIFO only until in-flight bytes reach icnt + 1,
+ * so the next batch still forms a full interrupt period.
+ */
+ limit = loongson_spi->icnt + 1;
+ while (loongson_spi->sent < loongson_spi->total &&
+ (loongson_spi->sent - loongson_spi->count) < limit) {
+ status = loongson_spi_read_reg(loongson_spi, LOONGSON_SPI_SPSR_REG);
+ if (status & LOONGSON_SPI_SPSR_WFFULL)
+ break;
+
+ data = loongson_spi->tx_ptr ?
+ loongson_spi->tx_ptr[loongson_spi->sent] : 0;
+
+ loongson_spi_write_reg(loongson_spi, LOONGSON_SPI_FIFO_REG, data);
+ loongson_spi->sent++;
+ }
+
+ if (loongson_spi->count >= loongson_spi->total) {
+ loongson_spi_clear_bits(loongson_spi,
+ LOONGSON_SPI_SPCR_REG, LOONGSON_SPI_SPCR_SPIE);
+ done = true;
+ }
+ }
+
+ if (done)
+ spi_finalize_current_transfer(ctrl);
+
+ return IRQ_HANDLED;
+}
+
+static int loongson_spi_transfer_one_irq(struct spi_controller *host, struct spi_device *spi,
+ struct spi_transfer *xfer)
+{
+ struct loongson_spi *loongson_spi = spi_controller_get_devdata(host);
+ unsigned int prefill, n = 0;
+
+ guard(spinlock_irqsave)(&loongson_spi->lock);
+
+ loongson_spi->tx_ptr = xfer->tx_buf;
+ loongson_spi->rx_ptr = xfer->rx_buf;
+ loongson_spi->total = xfer->len;
+ loongson_spi->count = 0;
+ loongson_spi->sent = 0;
+
+ /* Calculate and configure icnt */
+ loongson_spi->icnt = loongson_spi_calc_icnt(loongson_spi, xfer->len);
+ loongson_spi_update_bits(loongson_spi, LOONGSON_SPI_SPER_REG,
+ LOONGSON_SPI_SPER_ICNT,
+ FIELD_PREP(LOONGSON_SPI_SPER_ICNT, loongson_spi->icnt));
+
+ /* Clear any stale status */
+ loongson_spi_write_reg(loongson_spi,
+ LOONGSON_SPI_SPSR_REG, LOONGSON_SPI_SPSR_EVENTS);
+
+ /* Prefill at most icnt + 1 bytes */
+ prefill = min_t(unsigned int, xfer->len, loongson_spi->icnt + 1);
+ for (unsigned int i = 0; i < prefill; i++) {
+ unsigned int data = 0, status;
+
+ status = loongson_spi_read_reg(loongson_spi, LOONGSON_SPI_SPSR_REG);
+ if (status & LOONGSON_SPI_SPSR_WFFULL)
+ break;
+
+ if (loongson_spi->tx_ptr)
+ data = loongson_spi->tx_ptr[i];
+ loongson_spi_write_reg(loongson_spi, LOONGSON_SPI_FIFO_REG, data);
+
+ n++;
+ }
+ loongson_spi->sent = n;
+
+ /* Enable the byte-completion interrupt */
+ loongson_spi_set_bits(loongson_spi,
+ LOONGSON_SPI_SPCR_REG, LOONGSON_SPI_SPCR_SPIE);
+
+ return 1;
+}
+
+static void loongson_spi_handle_err(struct spi_controller *host, struct spi_message *msg)
+{
+ struct loongson_spi *loongson_spi = spi_controller_get_devdata(host);
+
+ /*
+ * The SPI core has given up on this message (e.g. timeout) and
+ * handed the buffers back to the caller. Stop the byte
+ * interrupt, clear pending status, and mark the transfer dead so
+ * that a late IRQ cannot write into the now-invalid buffers.
+ */
+ loongson_spi_clear_bits(loongson_spi,
+ LOONGSON_SPI_SPCR_REG, LOONGSON_SPI_SPCR_SPIE);
+
+ loongson_spi_write_reg(loongson_spi,
+ LOONGSON_SPI_SPSR_REG, LOONGSON_SPI_SPSR_EVENTS);
+
+ scoped_guard(spinlock_irqsave, &loongson_spi->lock) {
+ loongson_spi->tx_ptr = NULL;
+ loongson_spi->rx_ptr = NULL;
+ loongson_spi->total = 0;
+ loongson_spi->count = 0;
+ loongson_spi->sent = 0;
+ loongson_spi->icnt = 0;
+ }
+}
+
static int loongson_spi_prepare_message(struct spi_controller *ctlr, struct spi_message *m)
{
struct loongson_spi *loongson_spi = spi_controller_get_devdata(ctlr);
+ int ret;
+
+ ret = spi_split_transfers_maxsize(ctlr, m, LOONGSON_SPI_MAX_XFER_SIZE);
+ if (ret)
+ return ret;
loongson_spi_clear_bits(loongson_spi,
LOONGSON_SPI_PARA_REG, LOONGSON_SPI_PARA_MEM_EN);
@@ -221,10 +401,13 @@ static int loongson_spi_transfer_one(struct spi_controller *ctrl, struct spi_dev
if (ret)
return ret;
- if (xfer->len)
- return loongson_spi_write_read(spi, xfer);
+ if (!xfer->len)
+ return 0;
+
+ if (loongson_spi->use_irq)
+ return loongson_spi_transfer_one_irq(ctrl, spi, xfer);
- return 0;
+ return loongson_spi_write_read(spi, xfer);
}
static int loongson_spi_unprepare_message(struct spi_controller *ctrl, struct spi_message *m)
@@ -240,15 +423,16 @@ static int loongson_spi_unprepare_message(struct spi_controller *ctrl, struct sp
static void loongson_spi_reginit(struct loongson_spi *loongson_spi_dev)
{
- /* Disable SPI */
+ /* Disable SPI and its interrupt */
loongson_spi_clear_bits(loongson_spi_dev,
- LOONGSON_SPI_SPCR_REG, LOONGSON_SPI_SPCR_SPE);
+ LOONGSON_SPI_SPCR_REG,
+ LOONGSON_SPI_SPCR_SPE | LOONGSON_SPI_SPCR_SPIE);
/* Clear status flags (write 1 to clear) */
loongson_spi_write_reg(loongson_spi_dev,
LOONGSON_SPI_SPSR_REG, LOONGSON_SPI_SPSR_EVENTS);
- /* Enable SPI */
+ /* Enable SPI (interrupt remains disabled until a transfer starts) */
loongson_spi_set_bits(loongson_spi_dev,
LOONGSON_SPI_SPCR_REG, LOONGSON_SPI_SPCR_SPE);
}
@@ -280,7 +464,45 @@ static int loongson_spi_get_clk_rate(struct device *dev)
return 0;
}
-int loongson_spi_init_controller(struct device *dev, void __iomem *regs)
+static void loongson_spi_get_fifo_depth(struct device *dev)
+{
+ struct spi_controller *controller = dev_get_drvdata(dev);
+ struct loongson_spi *loongson_spi = spi_controller_get_devdata(controller);
+ unsigned int depth;
+
+ if (device_property_read_u32(dev, "fifo-depth", &depth)) {
+ loongson_spi->fifo_depth = 1;
+ dev_warn(dev,
+ "fifo-depth not declared, defaulting to 1 (single-byte)");
+ return;
+ }
+
+ loongson_spi->fifo_depth = depth;
+}
+
+static void loongson_spi_irqinit(struct device *dev, int irq)
+{
+ struct spi_controller *controller = dev_get_drvdata(dev);
+ struct loongson_spi *loongson_spi = spi_controller_get_devdata(controller);
+ int ret;
+
+ if (irq <= 0) {
+ loongson_spi->use_irq = false;
+ return;
+ }
+
+ ret = devm_request_irq(dev, irq, loongson_spi_irq, 0, "loongson-spi", loongson_spi);
+ if (ret) {
+ loongson_spi->use_irq = false;
+ dev_warn(dev, "failed to request IRQ %d with %d, using polling", irq, ret);
+ return;
+ }
+
+ spin_lock_init(&loongson_spi->lock);
+ loongson_spi->use_irq = true;
+}
+
+int loongson_spi_init_controller(struct device *dev, void __iomem *regs, int irq)
{
struct spi_controller *controller;
struct loongson_spi *spi;
@@ -295,6 +517,7 @@ int loongson_spi_init_controller(struct device *dev, void __iomem *regs)
controller->prepare_message = loongson_spi_prepare_message;
controller->transfer_one = loongson_spi_transfer_one;
controller->unprepare_message = loongson_spi_unprepare_message;
+ controller->handle_err = loongson_spi_handle_err;
controller->set_cs = loongson_spi_set_cs;
controller->num_chipselect = LOONGSON_SPI_NUM_CS;
dev_set_drvdata(dev, controller);
@@ -312,8 +535,8 @@ int loongson_spi_init_controller(struct device *dev, void __iomem *regs)
controller->min_speed_hz = DIV_ROUND_UP_ULL(spi->clk_rate, LOONGSON_SPI_DIV_MAX);
loongson_spi_reginit(spi);
-
- spi->mode = 0;
+ loongson_spi_get_fifo_depth(dev);
+ loongson_spi_irqinit(dev, irq);
return devm_spi_register_controller(dev, controller);
}
diff --git a/drivers/spi/spi-loongson-pci.c b/drivers/spi/spi-loongson-pci.c
index ccc26efd88fe..5317603dc486 100644
--- a/drivers/spi/spi-loongson-pci.c
+++ b/drivers/spi/spi-loongson-pci.c
@@ -7,25 +7,33 @@
#include "spi-loongson.h"
static int loongson_spi_pci_register(struct pci_dev *pdev,
- const struct pci_device_id *ent)
+ const struct pci_device_id *ent)
{
- int ret;
- void __iomem *reg_base;
struct device *dev = &pdev->dev;
- int pci_bar = 0;
+ void __iomem *reg_base;
+ int ret, irq = 0;
ret = pcim_enable_device(pdev);
if (ret < 0)
- return dev_err_probe(dev, ret, "cannot enable pci device\n");
+ return dev_err_probe(dev, ret, "cannot enable pci device");
- reg_base = pcim_iomap_region(pdev, pci_bar, pci_name(pdev));
+ reg_base = pcim_iomap_region(pdev, 0, pci_name(pdev));
ret = PTR_ERR_OR_ZERO(reg_base);
if (ret)
- return dev_err_probe(dev, ret, "failed to request and remap memory\n");
-
- ret = loongson_spi_init_controller(dev, reg_base);
+ return dev_err_probe(dev, ret, "failed to request and remap memory");
+
+ ret = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_ALL_TYPES);
+ if (ret < 0) {
+ dev_warn(dev, "no IRQ available, will use PIO mode");
+ } else {
+ irq = pci_irq_vector(pdev, 0);
+ if (irq < 0)
+ dev_warn(dev, "failed to get IRQ vector, will use PIO mode");
+ }
+
+ ret = loongson_spi_init_controller(dev, reg_base, irq);
if (ret)
- return dev_err_probe(dev, ret, "failed to initialize controller\n");
+ return dev_err_probe(dev, ret, "failed to initialize controller");
return 0;
}
diff --git a/drivers/spi/spi-loongson-plat.c b/drivers/spi/spi-loongson-plat.c
index 6f8202264341..82d8505b6b66 100644
--- a/drivers/spi/spi-loongson-plat.c
+++ b/drivers/spi/spi-loongson-plat.c
@@ -9,17 +9,26 @@
static int loongson_spi_platform_probe(struct platform_device *pdev)
{
- int ret;
- void __iomem *reg_base;
struct device *dev = &pdev->dev;
+ void __iomem *reg_base;
+ int ret, irq = 0;
reg_base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(reg_base))
return PTR_ERR(reg_base);
- ret = loongson_spi_init_controller(dev, reg_base);
+ ret = platform_get_irq_optional(pdev, 0);
+ if (ret == -EPROBE_DEFER)
+ return -EPROBE_DEFER;
+
+ if (ret < 0)
+ dev_warn(dev, "no IRQ specified, will use PIO mode");
+ else
+ irq = ret;
+
+ ret = loongson_spi_init_controller(dev, reg_base, irq);
if (ret)
- return dev_err_probe(dev, ret, "failed to initialize controller\n");
+ return dev_err_probe(dev, ret, "failed to initialize controller");
return 0;
}
diff --git a/drivers/spi/spi-loongson.h b/drivers/spi/spi-loongson.h
index c46169e8c780..442f22ee89af 100644
--- a/drivers/spi/spi-loongson.h
+++ b/drivers/spi/spi-loongson.h
@@ -35,6 +35,7 @@
/* SPER bits */
#define LOONGSON_SPI_SPER_SPRE GENMASK(1, 0)
+#define LOONGSON_SPI_SPER_ICNT GENMASK(7, 6)
/* SFC_PARAM bits */
#define LOONGSON_SPI_PARA_MEM_EN BIT(0)
@@ -54,6 +55,16 @@
#define LOONGSON_SPI_NUM_CS 4
+/*
+ * This controller has no DMA and moves one byte per IRQ/polling
+ * iteration, so actual throughput is far below speed_hz while the
+ * SPI core scales its timeout from the theoretical speed_hz.
+ * Split messages into fixed-size chunks so each transfer completes
+ * well inside the core's per-transfer timeout. This is a software
+ * policy, not a hardware limit.
+ */
+#define LOONGSON_SPI_MAX_XFER_SIZE SZ_4K
+
/* Hardware limits and divider masks */
#define LOONGSON_SPI_DIV_MIN 2
#define LOONGSON_SPI_DIV_MAX 4096
@@ -92,8 +103,18 @@ struct loongson_spi {
unsigned int mode;
u64 clk_rate;
struct loongson_spi_suspend_store lss;
+
+ spinlock_t lock; /* protects tx_ptr, rx_ptr and transfer state */
+ bool use_irq;
+ const unsigned char *tx_ptr;
+ unsigned char *rx_ptr;
+ unsigned int count;
+ unsigned int sent;
+ unsigned int total;
+ unsigned int icnt;
+ unsigned int fifo_depth;
};
-int loongson_spi_init_controller(struct device *dev, void __iomem *reg);
+int loongson_spi_init_controller(struct device *dev, void __iomem *regs, int irq);
extern const struct dev_pm_ops loongson_spi_dev_pm_ops;
#endif /* __LINUX_SPI_LOONGSON_H */
--
2.52.0
^ permalink raw reply related [flat|nested] 8+ messages in thread
end of thread, other threads:[~2026-11-13 5:42 UTC | newest]
Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-13 5:42 [PATCH v2 0/7] spi: loongson: Fixes, cleanup and IRQ support Binbin Zhou
2026-11-13 5:42 ` [PATCH v2 1/7] spi: loongson: Fix incorrect SPI mode caching logic Binbin Zhou
2026-11-13 5:42 ` [PATCH v2 2/7] spi: loongson: Drop redundant CS deassert in setup() Binbin Zhou
2026-11-13 5:42 ` [PATCH v2 3/7] spi: loongson: Make suspend/resume registers PM-specific Binbin Zhou
2026-11-13 5:42 ` [PATCH v2 4/7] spi: loongson: Fix clock rate retrieval on ACPI platforms Binbin Zhou
2026-11-13 5:42 ` [PATCH v2 5/7] spi: loongson: Fix clock divider calculation with explicit table Binbin Zhou
2026-11-13 5:42 ` [PATCH v2 6/7] spi: loongson: Use bitfield helpers for register access Binbin Zhou
2026-11-13 5:42 ` [PATCH v2 7/7] spi: loongson: Add interrupt mode support Binbin Zhou
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.