[PATCH v2 11/11] spi: dw: Add support for StarFive JHB100 SoC SFC

Changhuang Liang <[email protected]> Mon, 3 Aug 2026 05:40:44 -0700
Newsgroups org.kernel.vger.linux-spi,org.kernel.vger.linux-devicetree,org.kernel.vger.linux-kernel
Message-ID <[email protected]>
Add support for the StarFive JHB100 SoC SPI Flash Controller (SFC),
which is based on the Synopsys DesignWare SSI version 2.00a but with
some customizations.

The JHB100 SFC controller has the following special features:
1. Separate registers for instruction and address (DW_SPI_JHB100_INST
   and DW_SPI_JHB100_ADDR) instead of using the common data register.
2. A filter interrupt mask register (DW_SPI_JHB100_FILTER_IMR),
   which is default masked to disable filter interrupts as they
   are not used.
3. Requires a system controller phandle "starfive,sfc-filter-syscon"
   to configure 3-byte/4-byte address mode switching per chip select.
4. Different Set CS and Enable Controller Timing.

A new quirk flag DW_SPI_QUIRK_JHB100 is introduced to handle these
differences in the enhanced SPI memory operation path. The controller
uses the HSSI initialization path (DW_HSSI_ID) and shares the same
interrupt masking logic. Limit the JHB100 SFC address to 3-byte or
4-byte length.

Additionally, the platform_suspend() and platform_resume() callbacks
are introduced to handle platform-private suspend/resume procedures.

Signed-off-by: Changhuang Liang <[email protected]>
---
 drivers/spi/spi-dw-core.c | 48 ++++++++++++++++++++------
 drivers/spi/spi-dw-mmio.c | 72 +++++++++++++++++++++++++++++++++++++++
 drivers/spi/spi-dw.h      | 18 ++++++++++
 3 files changed, 127 insertions(+), 11 deletions(-)

diff --git a/drivers/spi/spi-dw-core.c b/drivers/spi/spi-dw-core.c
index 0d900afa015d..ce6144a34785 100644
--- a/drivers/spi/spi-dw-core.c
+++ b/drivers/spi/spi-dw-core.c
@@ -591,6 +591,8 @@ static int dw_spi_adjust_mem_op_size(struct spi_mem *mem, struct spi_mem_op *op)
 static bool dw_spi_supports_enh_mem_op(struct spi_mem *mem,
 				       const struct spi_mem_op *op)
 {
+	struct dw_spi *dws = spi_controller_get_devdata(mem->spi->controller);
+
 	if (op->addr.nbytes != 0 && op->addr.buswidth != 1 &&
 	    op->addr.buswidth != op->data.buswidth)
 		return false;
@@ -614,6 +616,10 @@ static bool dw_spi_supports_enh_mem_op(struct spi_mem *mem,
 	    FIELD_MAX(DW_SPI_ENH_CTRLR0_WAIT_CYCLE_MASK))
 		return false;
 
+	if ((dws->quirk_flags & DW_SPI_QUIRK_JHB100) && op->addr.nbytes &&
+	    op->addr.nbytes != 3 && op->addr.nbytes != 4)
+		return false;
+
 	return spi_mem_default_supports_op(mem, op);
 }
 
@@ -906,16 +912,30 @@ static void dw_spi_init_enh_mem_buf(struct dw_spi *dws, const struct spi_mem_op
 	}
 }
 
-static void dw_spi_enh_write_cmd_addr(struct dw_spi *dws, const struct spi_mem_op *op)
+static void dw_spi_enh_write_cmd_addr(struct dw_spi *dws, const struct spi_mem_op *op,
+				      struct spi_mem *mem)
 {
-	/* Send cmd as 32 bit value */
-	dw_write_io_reg(dws, DW_SPI_DR, op->cmd.opcode);
-	if (op->addr.nbytes) {
-		dw_write_io_reg(dws, DW_SPI_DR, lower_32_bits(op->addr.val));
-		if (op->addr.nbytes > 4) {
-			/* address more than 32bit */
-			dw_write_io_reg(dws, DW_SPI_DR, upper_32_bits(op->addr.val));
+	if (dws->quirk_flags & DW_SPI_QUIRK_JHB100) {
+		dw_write_io_reg(dws, DW_SPI_JHB100_INST, op->cmd.opcode);
+		if (op->addr.nbytes)
+			dw_write_io_reg(dws, DW_SPI_JHB100_ADDR, op->addr.val);
+
+		dw_spi_set_cs(mem->spi, false);
+		dw_spi_enable_chip(dws, 1);
+	} else {
+		dw_spi_enable_chip(dws, 1);
+
+		/* Send cmd as 32 bit value */
+		dw_write_io_reg(dws, DW_SPI_DR, op->cmd.opcode);
+		if (op->addr.nbytes) {
+			dw_write_io_reg(dws, DW_SPI_DR, lower_32_bits(op->addr.val));
+			if (op->addr.nbytes > 4) {
+				/* address more than 32bit */
+				dw_write_io_reg(dws, DW_SPI_DR, upper_32_bits(op->addr.val));
+			}
 		}
+
+		dw_spi_set_cs(mem->spi, false);
 	}
 }
 
@@ -981,10 +1001,16 @@ static int dw_spi_exec_enh_mem_op(struct spi_mem *mem, const struct spi_mem_op *
 
 	dw_spi_mask_intr(dws, 0xff);
 	reinit_completion(&ctlr->xfer_completion);
-	dw_spi_enable_chip(dws, 1);
 
-	dw_spi_enh_write_cmd_addr(dws, op);
-	dw_spi_set_cs(mem->spi, false);
+	if (op->addr.nbytes && dws->set_addr_nbyte) {
+		ret = dws->set_addr_nbyte(mem->spi, op->addr.nbytes);
+		if (ret) {
+			dw_spi_enable_chip(dws, 1);
+			return ret;
+		}
+	}
+
+	dw_spi_enh_write_cmd_addr(dws, op, mem);
 
 	/*
 	 * FIXME: The exact reason for this delay is not fully understood,
diff --git a/drivers/spi/spi-dw-mmio.c b/drivers/spi/spi-dw-mmio.c
index 603e81a92c57..8cdb0351605b 100644
--- a/drivers/spi/spi-dw-mmio.c
+++ b/drivers/spi/spi-dw-mmio.c
@@ -31,6 +31,8 @@ struct dw_spi_mmio {
 	struct clk     *pclk;
 	void           *priv;
 	struct reset_control *rstc;
+	void (*platform_suspend)(struct dw_spi_mmio *dwsmmio);
+	int (*platform_resume)(struct dw_spi_mmio *dwsmmio);
 };
 
 #define MSCC_CPU_SYSTEM_CTRL_GENERAL_CTRL	0x24
@@ -48,6 +50,8 @@ struct dw_spi_mmio {
 #define SPARX5_FORCE_ENA			0xa4
 #define SPARX5_FORCE_VAL			0xa8
 
+#define JHB100_ADDRMODE_CS			0x00
+
 struct dw_spi_mscc {
 	struct regmap       *syscon;
 	void __iomem        *spi_mst; /* Not sparx5 */
@@ -310,6 +314,58 @@ static int dw_spi_elba_init(struct platform_device *pdev,
 	return 0;
 }
 
+static int dw_spi_jhb100_set_addr_nbyte(struct spi_device *spi, u8 nbyte)
+{
+	struct dw_spi *dws = spi_controller_get_devdata(spi->controller);
+	struct dw_spi_mmio *dwsmmio = container_of(dws, struct dw_spi_mmio, dws);
+	struct regmap *syscon = dwsmmio->priv;
+
+	if (nbyte == 3) {
+		regmap_update_bits(syscon, JHB100_ADDRMODE_CS,
+				   BIT(spi_get_chipselect(spi, 0)),
+				   0);
+	} else if (nbyte == 4) {
+		regmap_update_bits(syscon, JHB100_ADDRMODE_CS,
+				   BIT(spi_get_chipselect(spi, 0)),
+				   BIT(spi_get_chipselect(spi, 0)));
+	} else {
+		dev_err(&spi->dev, "Unsupported address nbyte %d\n", nbyte);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int dw_spi_jhb100_resume(struct dw_spi_mmio *dwsmmio)
+{
+	dw_spi_jhb100_mask_intr(&dwsmmio->dws, 0xff);
+
+	return 0;
+}
+
+static int dw_spi_jhb100_init(struct platform_device *pdev,
+			      struct dw_spi_mmio *dwsmmio)
+{
+	struct regmap *syscon;
+
+	syscon = syscon_regmap_lookup_by_phandle(dev_of_node(&pdev->dev),
+						 "starfive,sfc-filter-syscon");
+	if (IS_ERR(syscon))
+		return dev_err_probe(&pdev->dev, PTR_ERR(syscon),
+				     "syscon regmap lookup failed\n");
+
+	dwsmmio->priv = syscon;
+	dwsmmio->platform_resume = dw_spi_jhb100_resume;
+
+	dwsmmio->dws.set_addr_nbyte = dw_spi_jhb100_set_addr_nbyte;
+	dwsmmio->dws.ip = DW_HSSI_ID;
+	dwsmmio->dws.quirk_flags = DW_SPI_QUIRK_JHB100;
+
+	dw_spi_jhb100_mask_intr(&dwsmmio->dws, 0xff);
+
+	return 0;
+}
+
 static int dw_spi_mmio_probe(struct platform_device *pdev)
 {
 	int (*init_func)(struct platform_device *pdev,
@@ -399,6 +455,9 @@ static int dw_spi_mmio_suspend(struct device *dev)
 	if (ret)
 		return ret;
 
+	if (dwsmmio->platform_suspend)
+		dwsmmio->platform_suspend(dwsmmio);
+
 	reset_control_assert(dwsmmio->rstc);
 
 	clk_disable_unprepare(dwsmmio->pclk);
@@ -410,12 +469,24 @@ static int dw_spi_mmio_suspend(struct device *dev)
 static int dw_spi_mmio_resume(struct device *dev)
 {
 	struct dw_spi_mmio *dwsmmio = dev_get_drvdata(dev);
+	int ret;
 
 	clk_prepare_enable(dwsmmio->clk);
 	clk_prepare_enable(dwsmmio->pclk);
 
 	reset_control_deassert(dwsmmio->rstc);
 
+	if (dwsmmio->platform_resume) {
+		ret = dwsmmio->platform_resume(dwsmmio);
+		if (ret) {
+			reset_control_assert(dwsmmio->rstc);
+
+			clk_disable_unprepare(dwsmmio->pclk);
+			clk_disable_unprepare(dwsmmio->clk);
+			return ret;
+		}
+	}
+
 	return dw_spi_resume_controller(&dwsmmio->dws);
 }
 
@@ -447,6 +518,7 @@ static const struct of_device_id dw_spi_mmio_of_match[] = {
 	{ .compatible = "microchip,sparx5-spi", dw_spi_mscc_sparx5_init},
 	{ .compatible = "canaan,k210-spi", dw_spi_canaan_k210_init},
 	{ .compatible = "amd,pensando-elba-spi", .data = dw_spi_elba_init},
+	{ .compatible = "starfive,jhb100-sfc", .data = dw_spi_jhb100_init},
 	{ /* end of table */}
 };
 MODULE_DEVICE_TABLE(of, dw_spi_mmio_of_match);
diff --git a/drivers/spi/spi-dw.h b/drivers/spi/spi-dw.h
index 2dae81c15423..2fce5bde75f0 100644
--- a/drivers/spi/spi-dw.h
+++ b/drivers/spi/spi-dw.h
@@ -67,6 +67,11 @@
 #define DW_SPI_SPI_CTRLR0		0xf4
 #define DW_SPI_CS_OVERRIDE		0xf4
 
+/* Register offsets (StarFive JHB100 DWC SSI IP-cores) */
+#define DW_SPI_JHB100_INST		0x1000
+#define DW_SPI_JHB100_ADDR		0x1004
+#define DW_SPI_JHB100_FILTER_IMR	0x1008
+
 /* Bit fields in CTRLR0 (DWC APB SSI) */
 #define DW_PSSI_CTRLR0_DFS_MASK			GENMASK(3, 0)
 #define DW_PSSI_CTRLR0_DFS32_MASK		GENMASK(20, 16)
@@ -199,6 +204,7 @@ struct dw_spi {
 	u32			num_cs;		/* chip select lines */
 	u16			bus_num;
 	void (*set_cs)(struct spi_device *spi, bool enable);
+	int (*set_addr_nbyte)(struct spi_device *spi, u8 nbyte);
 
 	/* Current message transfer state info */
 	void			*tx;
@@ -228,6 +234,9 @@ struct dw_spi {
 	const struct dw_spi_dma_ops *dma_ops;
 	struct completion	dma_completion;
 
+#define DW_SPI_QUIRK_JHB100		BIT(0)
+	u32			quirk_flags;
+
 #ifdef CONFIG_DEBUG_FS
 	struct dentry *debugfs;
 	struct debugfs_regset32 regset;
@@ -296,6 +305,15 @@ static inline void dw_spi_umask_intr(struct dw_spi *dws, u32 mask)
 	dw_writel(dws, DW_SPI_IMR, new_mask);
 }
 
+/* Disable JHB100 SPI filter IRQ bits */
+static inline void dw_spi_jhb100_mask_intr(struct dw_spi *dws, u32 mask)
+{
+	u32 new_mask;
+
+	new_mask = dw_readl(dws, DW_SPI_JHB100_FILTER_IMR) & ~mask;
+	dw_writel(dws, DW_SPI_JHB100_FILTER_IMR, new_mask);
+}
+
 /*
  * This disables the SPI controller, interrupts, clears the interrupts status
  * and CS, then re-enables the controller back. Transmit and receive FIFO
-- 
2.25.1