From 06f2005b9421292b6b6a0d611aa617f503ded0ff Mon Sep 17 00:00:00 2001 From: Tomer Maimon Date: Tue, 9 Jun 2026 10:14:58 +0300 Subject: [PATCH 001/132] spi: npcm-fiu: support dual and quad UMA writes The manual page-program path used the UMA command byte for the first byte of each continuation chunk. That forced command-lane semantics onto chunked writes and blocked dual/quad payload writes. Switch continuation chunks to data-only UMA transactions so WDBPCK controls the payload bus width. Signed-off-by: Tomer Maimon Link: https://patch.msgid.link/20260609071458.2692482-1-tmaimon77@gmail.com Signed-off-by: Mark Brown --- drivers/spi/spi-npcm-fiu.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/drivers/spi/spi-npcm-fiu.c b/drivers/spi/spi-npcm-fiu.c index 4b825044038b..96dfef4d9067 100644 --- a/drivers/spi/spi-npcm-fiu.c +++ b/drivers/spi/spi-npcm-fiu.c @@ -393,7 +393,7 @@ static int npcm_fiu_uma_write(struct spi_mem *mem, { struct npcm_fiu_spi *fiu = spi_controller_get_devdata(mem->spi->controller); - u32 uma_cfg = BIT(10); + u32 uma_cfg = cmd ? BIT(10) : 0; u32 data_reg[4] = {0}; u32 val; u32 i; @@ -403,8 +403,11 @@ static int npcm_fiu_uma_write(struct spi_mem *mem, (spi_get_chipselect(mem->spi, 0) << NPCM_FIU_UMA_CTS_DEV_NUM_SHIFT)); - regmap_update_bits(fiu->regmap, NPCM_FIU_UMA_CMD, - NPCM_FIU_UMA_CMD_CMD, cmd); + if (cmd) + regmap_update_bits(fiu->regmap, NPCM_FIU_UMA_CMD, + NPCM_FIU_UMA_CMD_CMD, cmd); + else + uma_cfg |= ilog2(op->data.buswidth) << NPCM_FIU_UMA_CFG_WDBPCK_SHIFT; if (data_size) { memcpy(data_reg, data, data_size); @@ -464,8 +467,7 @@ static int npcm_fiu_manualwrite(struct spi_mem *mem, /* Starting the data writing loop in multiples of 8 */ for (idx = 0; idx < num_data_chunks; ++idx) { - ret = npcm_fiu_uma_write(mem, op, data[0], false, - &data[1], CHUNK_SIZE - 1); + ret = npcm_fiu_uma_write(mem, op, 0, false, &data[0], CHUNK_SIZE); if (ret) return ret; @@ -474,8 +476,7 @@ static int npcm_fiu_manualwrite(struct spi_mem *mem, /* Handling chunk remains */ if (remain_data > 0) { - ret = npcm_fiu_uma_write(mem, op, data[0], false, - &data[1], remain_data - 1); + ret = npcm_fiu_uma_write(mem, op, 0, false, &data[0], remain_data); if (ret) return ret; } From cb8c374a632b8bfeb8aa2a4977eeeb293f3566a5 Mon Sep 17 00:00:00 2001 From: Ninad Naik Date: Thu, 18 Jun 2026 23:31:49 +0530 Subject: [PATCH 002/132] spi: dt-bindings: octeon: Convert to DT schema Convert octeon-3010 to DT schema Signed-off-by: Ninad Naik Reviewed-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260618180149.475658-1-ninadnaik07@gmail.com Signed-off-by: Mark Brown --- .../bindings/spi/cavium,octeon-3010-spi.yaml | 61 +++++++++++++++++++ .../devicetree/bindings/spi/spi-octeon.txt | 33 ---------- 2 files changed, 61 insertions(+), 33 deletions(-) create mode 100644 Documentation/devicetree/bindings/spi/cavium,octeon-3010-spi.yaml delete mode 100644 Documentation/devicetree/bindings/spi/spi-octeon.txt diff --git a/Documentation/devicetree/bindings/spi/cavium,octeon-3010-spi.yaml b/Documentation/devicetree/bindings/spi/cavium,octeon-3010-spi.yaml new file mode 100644 index 000000000000..f0b708e1ccbb --- /dev/null +++ b/Documentation/devicetree/bindings/spi/cavium,octeon-3010-spi.yaml @@ -0,0 +1,61 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/spi/cavium,octeon-3010-spi.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Cavium, Inc. OCTEON SoC SPI master controller + +description: + The Cavium OCTEON SPI controller is an SPI master controller found in + OCTEON SoCs. + +maintainers: + - Rob Herring + +allOf: + - $ref: spi-controller.yaml# + +properties: + compatible: + const: cavium,octeon-3010-spi + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + +required: + - compatible + - reg + - interrupts + +unevaluatedProperties: false + +examples: + - | + soc { + #address-cells = <2>; + #size-cells = <2>; + + spi@1070000001000 { + compatible = "cavium,octeon-3010-spi"; + reg = <0x10700 0x00001000 0x0 0x100>; + interrupts = <0 58>; + #address-cells = <1>; + #size-cells = <0>; + + eeprom@0 { + compatible = "st,m95256", "atmel,at25"; + reg = <0>; + spi-max-frequency = <5000000>; + spi-cpha; + spi-cpol; + pagesize = <64>; + size = <32768>; + address-width = <16>; + }; + }; + }; +... diff --git a/Documentation/devicetree/bindings/spi/spi-octeon.txt b/Documentation/devicetree/bindings/spi/spi-octeon.txt deleted file mode 100644 index 431add192342..000000000000 --- a/Documentation/devicetree/bindings/spi/spi-octeon.txt +++ /dev/null @@ -1,33 +0,0 @@ -Cavium, Inc. OCTEON SOC SPI master controller. - -Required properties: -- compatible : "cavium,octeon-3010-spi" -- reg : The register base for the controller. -- interrupts : One interrupt, used by the controller. -- #address-cells : <1>, as required by generic SPI binding. -- #size-cells : <0>, also as required by generic SPI binding. - -Child nodes as per the generic SPI binding. - -Example: - - spi@1070000001000 { - compatible = "cavium,octeon-3010-spi"; - reg = <0x10700 0x00001000 0x0 0x100>; - interrupts = <0 58>; - #address-cells = <1>; - #size-cells = <0>; - - eeprom@0 { - compatible = "st,m95256", "atmel,at25"; - reg = <0>; - spi-max-frequency = <5000000>; - spi-cpha; - spi-cpol; - - pagesize = <64>; - size = <32768>; - address-width = <16>; - }; - }; - From 5504e50ffbcf97b3b6ed3740532497922c96a1f1 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Tue, 16 Jun 2026 23:11:29 +0800 Subject: [PATCH 003/132] spi: qpic-snand: free OOB buffer with ECC context qcom_spi_ecc_init_ctx_pipelined() allocates qspi->oob_buf as part of the pipelined ECC context setup. The buffer is then used by the raw and ECC page I/O paths through the NAND ECC context. The matching cleanup callback only frees the qpic_ecc structure, and the init error path also skips the OOB buffer after it has been allocated. Free qspi->oob_buf on both paths and clear the pointer so the ECC context teardown owns all resources allocated during init. Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260616151129.76060-1-pengpeng@iscas.ac.cn Signed-off-by: Mark Brown --- drivers/spi/spi-qpic-snand.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/spi/spi-qpic-snand.c b/drivers/spi/spi-qpic-snand.c index 66f2d1b78ade..6cc53586b8a8 100644 --- a/drivers/spi/spi-qpic-snand.c +++ b/drivers/spi/spi-qpic-snand.c @@ -394,14 +394,19 @@ static int qcom_spi_ecc_init_ctx_pipelined(struct nand_device *nand) return 0; err_free_ecc_cfg: + kfree(snandc->qspi->oob_buf); + snandc->qspi->oob_buf = NULL; kfree(ecc_cfg); return ret; } static void qcom_spi_ecc_cleanup_ctx_pipelined(struct nand_device *nand) { + struct qcom_nand_controller *snandc = nand_to_qcom_snand(nand); struct qpic_ecc *ecc_cfg = nand_to_ecc_ctx(nand); + kfree(snandc->qspi->oob_buf); + snandc->qspi->oob_buf = NULL; kfree(ecc_cfg); } @@ -1645,4 +1650,3 @@ module_platform_driver(qcom_spi_driver); MODULE_DESCRIPTION("SPI driver for QPIC QSPI cores"); MODULE_AUTHOR("Md Sadre Alam "); MODULE_LICENSE("GPL"); - From 7a0d22e632a6f44a40bbe357e081e7a8e806a079 Mon Sep 17 00:00:00 2001 From: Patrice Chotard Date: Thu, 18 Jun 2026 08:46:35 +0200 Subject: [PATCH 004/132] spi: dt-bindings: st,stm32-qspi: Add power-domains property STM32 QSPI may be in a power domain. Allow a single 'power-domains' entry for STM32 QSPI. Signed-off-by: Patrice Chotard Acked-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260618-add_power_domain_for_qpsi-v1-1-4d7e57bcfb9a@foss.st.com Signed-off-by: Mark Brown --- Documentation/devicetree/bindings/spi/st,stm32-qspi.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Documentation/devicetree/bindings/spi/st,stm32-qspi.yaml b/Documentation/devicetree/bindings/spi/st,stm32-qspi.yaml index 3f1a27efff80..ee57739b73b8 100644 --- a/Documentation/devicetree/bindings/spi/st,stm32-qspi.yaml +++ b/Documentation/devicetree/bindings/spi/st,stm32-qspi.yaml @@ -50,6 +50,9 @@ properties: minItems: 1 maxItems: 2 + power-domains: + maxItems: 1 + required: - compatible - reg From bbbc1ef5b35341c5122418a14f8924688a4bb954 Mon Sep 17 00:00:00 2001 From: Jiawen Liu <1298662399@qq.com> Date: Thu, 18 Jun 2026 20:45:16 +0400 Subject: [PATCH 005/132] spi: bcm63xx: return error from failed controller suspend spi_controller_suspend() can fail if the SPI core cannot stop the controller. bcm63xx_spi_suspend() ignored that error, disabled the controller clock, and returned success. Return the suspend error before tearing down the clock. Signed-off-by: Jiawen Liu <1298662399@qq.com> Link: https://patch.msgid.link/tencent_0BD7D4091B90EC17A8B2BA5EBA8803725905@qq.com Signed-off-by: Mark Brown --- drivers/spi/spi-bcm63xx.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/spi/spi-bcm63xx.c b/drivers/spi/spi-bcm63xx.c index 43d7b54e3ae8..8cef725aab26 100644 --- a/drivers/spi/spi-bcm63xx.c +++ b/drivers/spi/spi-bcm63xx.c @@ -632,8 +632,11 @@ static int bcm63xx_spi_suspend(struct device *dev) { struct spi_controller *host = dev_get_drvdata(dev); struct bcm63xx_spi *bs = spi_controller_get_devdata(host); + int ret; - spi_controller_suspend(host); + ret = spi_controller_suspend(host); + if (ret) + return ret; clk_disable_unprepare(bs->clk); From 3b5414d76a0381db77b9694537254af7acfa187a Mon Sep 17 00:00:00 2001 From: Jiawen Liu <1298662399@qq.com> Date: Thu, 18 Jun 2026 20:45:17 +0400 Subject: [PATCH 006/132] spi: bcm63xx-hsspi: return error from failed controller suspend spi_controller_suspend() can fail if the SPI core cannot stop the controller. bcm63xx_hsspi_suspend() ignored that error, disabled the PLL and core clocks, and returned success. Return the suspend error before disabling the clocks. Signed-off-by: Jiawen Liu <1298662399@qq.com> Acked-by: William Zhang Link: https://patch.msgid.link/tencent_B5A06807924A77C8690730EBF7A052AABE05@qq.com Signed-off-by: Mark Brown --- drivers/spi/spi-bcm63xx-hsspi.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/spi/spi-bcm63xx-hsspi.c b/drivers/spi/spi-bcm63xx-hsspi.c index 58012e1b5ae7..37b3534d0b0a 100644 --- a/drivers/spi/spi-bcm63xx-hsspi.c +++ b/drivers/spi/spi-bcm63xx-hsspi.c @@ -889,8 +889,12 @@ static int bcm63xx_hsspi_suspend(struct device *dev) { struct spi_controller *host = dev_get_drvdata(dev); struct bcm63xx_hsspi *bs = spi_controller_get_devdata(host); + int ret; + + ret = spi_controller_suspend(host); + if (ret) + return ret; - spi_controller_suspend(host); clk_disable_unprepare(bs->pll_clk); clk_disable_unprepare(bs->clk); From 44d09ef0ba690ac46d17eb05c12a47a5dd75e231 Mon Sep 17 00:00:00 2001 From: Jiawen Liu <1298662399@qq.com> Date: Thu, 18 Jun 2026 20:45:18 +0400 Subject: [PATCH 007/132] spi: bcm-qspi: return error from failed controller suspend spi_controller_suspend() can fail if the SPI core cannot stop the controller. bcm_qspi_suspend() ignored that error, disabled the controller clock, uninitialized the hardware, and returned success. Return the suspend error before tearing down the clock and hardware state. Signed-off-by: Jiawen Liu <1298662399@qq.com> Reviewed-by: Kamal Dasu Link: https://patch.msgid.link/tencent_21BF5F9512F56D45FD9018BAF14ED2805808@qq.com Signed-off-by: Mark Brown --- drivers/spi/spi-bcm-qspi.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/spi/spi-bcm-qspi.c b/drivers/spi/spi-bcm-qspi.c index dcade7b99cef..ae1ffe13fa46 100644 --- a/drivers/spi/spi-bcm-qspi.c +++ b/drivers/spi/spi-bcm-qspi.c @@ -1692,13 +1692,17 @@ EXPORT_SYMBOL_GPL(bcm_qspi_remove); static int __maybe_unused bcm_qspi_suspend(struct device *dev) { struct bcm_qspi *qspi = dev_get_drvdata(dev); + int ret; /* store the override strap value */ if (!bcm_qspi_bspi_ver_three(qspi)) qspi->s3_strap_override_ctrl = bcm_qspi_read(qspi, BSPI, BSPI_STRAP_OVERRIDE_CTRL); - spi_controller_suspend(qspi->host); + ret = spi_controller_suspend(qspi->host); + if (ret) + return ret; + clk_disable_unprepare(qspi->clk); bcm_qspi_hw_uninit(qspi); From f4b14e67baffd2c879765275bda998a183594e8b Mon Sep 17 00:00:00 2001 From: Jiawen Liu <1298662399@qq.com> Date: Sat, 20 Jun 2026 12:39:28 +0400 Subject: [PATCH 008/132] spi: atcspi200: return error from failed controller suspend spi_controller_suspend() can fail when the SPI core cannot stop the controller. atcspi_suspend() ignored that error and disabled the controller clock anyway. Return the error before disabling the clock. Signed-off-by: Jiawen Liu <1298662399@qq.com> Link: https://patch.msgid.link/tencent_306FA547FD68D10EE4B2AE9C132060F12F06@qq.com Signed-off-by: Mark Brown --- drivers/spi/spi-atcspi200.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/spi/spi-atcspi200.c b/drivers/spi/spi-atcspi200.c index 6d4b6aeb3f5b..e0fd101a62bc 100644 --- a/drivers/spi/spi-atcspi200.c +++ b/drivers/spi/spi-atcspi200.c @@ -599,8 +599,11 @@ static int atcspi_suspend(struct device *dev) { struct spi_controller *host = dev_get_drvdata(dev); struct atcspi_dev *spi = spi_controller_get_devdata(host); + int ret; - spi_controller_suspend(host); + ret = spi_controller_suspend(host); + if (ret) + return ret; clk_disable_unprepare(spi->clk); From faa878d4805ca375738294bc52eae310eb287b16 Mon Sep 17 00:00:00 2001 From: Jiawen Liu <1298662399@qq.com> Date: Sat, 20 Jun 2026 12:39:29 +0400 Subject: [PATCH 009/132] spi: bcmbca-hsspi: return error from failed controller suspend spi_controller_suspend() can fail if pending transfers cannot stop. bcmbca_hsspi_suspend() ignored the error and still disabled the PLL and core clocks. Return the error before disabling the clocks. Signed-off-by: Jiawen Liu <1298662399@qq.com> Link: https://patch.msgid.link/tencent_54F5634545908FBA724E758054BF03953808@qq.com Signed-off-by: Mark Brown --- drivers/spi/spi-bcmbca-hsspi.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/spi/spi-bcmbca-hsspi.c b/drivers/spi/spi-bcmbca-hsspi.c index 09c1472ae4fa..af88ce04948b 100644 --- a/drivers/spi/spi-bcmbca-hsspi.c +++ b/drivers/spi/spi-bcmbca-hsspi.c @@ -568,8 +568,12 @@ static int bcmbca_hsspi_suspend(struct device *dev) { struct spi_controller *host = dev_get_drvdata(dev); struct bcmbca_hsspi *bs = spi_controller_get_devdata(host); + int ret; + + ret = spi_controller_suspend(host); + if (ret) + return ret; - spi_controller_suspend(host); clk_disable_unprepare(bs->pll_clk); clk_disable_unprepare(bs->clk); From 2543355f3da56a297e3803ccb5d29f4dce5f18f1 Mon Sep 17 00:00:00 2001 From: Jiawen Liu <1298662399@qq.com> Date: Sat, 20 Jun 2026 12:39:30 +0400 Subject: [PATCH 010/132] spi: fsl-dspi: clean up after failed suspend and resume dspi_suspend() disabled the IRQ before spi_controller_suspend(), but ignored a suspend failure and kept tearing the device down. Restore the IRQ and return the error if suspend fails. dspi_resume() also left the clock prepared if controller resume or hardware init failed. Route those failures through clock cleanup. Signed-off-by: Jiawen Liu <1298662399@qq.com> Link: https://patch.msgid.link/tencent_427FA55E3D59112524886E9C931CA0F92F06@qq.com Signed-off-by: Mark Brown --- drivers/spi/spi-fsl-dspi.c | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/drivers/spi/spi-fsl-dspi.c b/drivers/spi/spi-fsl-dspi.c index 019d05cdefe6..c2d283876ef8 100644 --- a/drivers/spi/spi-fsl-dspi.c +++ b/drivers/spi/spi-fsl-dspi.c @@ -1464,10 +1464,18 @@ static int dspi_init(struct fsl_dspi *dspi) static int dspi_suspend(struct device *dev) { struct fsl_dspi *dspi = dev_get_drvdata(dev); + int ret; if (dspi->irq) disable_irq(dspi->irq); - spi_controller_suspend(dspi->ctlr); + + ret = spi_controller_suspend(dspi->ctlr); + if (ret) { + if (dspi->irq) + enable_irq(dspi->irq); + return ret; + } + clk_disable_unprepare(dspi->clk); pinctrl_pm_select_sleep_state(dev); @@ -1485,12 +1493,15 @@ static int dspi_resume(struct device *dev) ret = clk_prepare_enable(dspi->clk); if (ret) return ret; - spi_controller_resume(dspi->ctlr); + + ret = spi_controller_resume(dspi->ctlr); + if (ret) + goto disable_clk; ret = dspi_init(dspi); if (ret) { dev_err(dev, "failed to initialize dspi during resume\n"); - return ret; + goto disable_clk; } dspi_set_mtf(dspi); @@ -1499,6 +1510,10 @@ static int dspi_resume(struct device *dev) enable_irq(dspi->irq); return 0; + +disable_clk: + clk_disable_unprepare(dspi->clk); + return ret; } #endif /* CONFIG_PM_SLEEP */ From 5d14285d60ba95561609a293064654a0c7d7d928 Mon Sep 17 00:00:00 2001 From: Jiawen Liu <1298662399@qq.com> Date: Sat, 20 Jun 2026 12:39:31 +0400 Subject: [PATCH 011/132] spi: nxp-fspi: disable runtime PM on probe failures nxp_fspi_probe() enables runtime PM and autosuspend before several operations that can fail. Some failure paths returned directly before the devm cleanup action was installed, leaving runtime PM enabled. Route those failures through a common runtime PM cleanup path. Use pm_runtime_resume_and_get() for the initial clock enable. Signed-off-by: Jiawen Liu <1298662399@qq.com> Link: https://patch.msgid.link/tencent_8FC0B8DFAF4AE67AEBA20548045D53A77707@qq.com Signed-off-by: Mark Brown --- drivers/spi/spi-nxp-fspi.c | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/drivers/spi/spi-nxp-fspi.c b/drivers/spi/spi-nxp-fspi.c index 1e36ae084dd8..d94a2a7b98d4 100644 --- a/drivers/spi/spi-nxp-fspi.c +++ b/drivers/spi/spi-nxp-fspi.c @@ -1350,9 +1350,11 @@ static int nxp_fspi_probe(struct platform_device *pdev) pm_runtime_use_autosuspend(dev); /* enable clock */ - ret = pm_runtime_get_sync(f->dev); - if (ret < 0) - return dev_err_probe(dev, ret, "Failed to enable clock"); + ret = pm_runtime_resume_and_get(f->dev); + if (ret < 0) { + ret = dev_err_probe(dev, ret, "Failed to enable clock"); + goto err_disable_pm; + } /* Clear potential interrupts */ reg = fspi_readl(f, f->iobase + FSPI_INTR); @@ -1362,18 +1364,24 @@ static int nxp_fspi_probe(struct platform_device *pdev) nxp_fspi_default_setup(f); ret = pm_runtime_put_sync(dev); - if (ret < 0) - return dev_err_probe(dev, ret, "Failed to disable clock"); + if (ret < 0) { + ret = dev_err_probe(dev, ret, "Failed to disable clock"); + goto err_disable_pm; + } init_completion(&f->c); ret = devm_request_irq(dev, irq, nxp_fspi_irq_handler, 0, pdev->name, f); - if (ret) - return dev_err_probe(dev, ret, "Failed to request irq\n"); + if (ret) { + ret = dev_err_probe(dev, ret, "Failed to request irq\n"); + goto err_disable_pm; + } ret = devm_mutex_init(dev, &f->lock); - if (ret) - return dev_err_probe(dev, ret, "Failed to initialize lock\n"); + if (ret) { + ret = dev_err_probe(dev, ret, "Failed to initialize lock\n"); + goto err_disable_pm; + } ctlr->bus_num = -1; ctlr->num_chipselect = NXP_FSPI_MAX_CHIPSELECT; @@ -1389,6 +1397,11 @@ static int nxp_fspi_probe(struct platform_device *pdev) return ret; return devm_spi_register_controller(&pdev->dev, ctlr); + +err_disable_pm: + pm_runtime_dont_use_autosuspend(dev); + pm_runtime_disable(dev); + return ret; } static int nxp_fspi_runtime_suspend(struct device *dev) From 59841dc5f6ae71645eaf8b0b86bd58c7ee58a776 Mon Sep 17 00:00:00 2001 From: Praveen Talari Date: Thu, 18 Jun 2026 14:36:39 +0530 Subject: [PATCH 012/132] spi: dt-bindings: describe SA8255p Add DT bindings for the QUP GENI SPI controller on sa8255p platform. SA8255p platform abstracts resources such as clocks, interconnect and GPIO pins configuration in Firmware. SCMI power and perf protocols are utilized to request resource configurations. SA8255p platform does not require the Serial Engine (SE) common properties as the SE firmware is loaded and managed by the TrustZone (TZ) secure environment. Co-developed-by: Nikunj Kela Signed-off-by: Nikunj Kela Reviewed-by: Krzysztof Kozlowski Signed-off-by: Praveen Talari Tested-by: Mattijs Korpershoek Link: https://patch.msgid.link/20260618-enable-spi-on-sa8255p-v4-1-f5b5067e7e1e@oss.qualcomm.com Signed-off-by: Mark Brown --- .../bindings/spi/qcom,sa8255p-geni-spi.yaml | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 Documentation/devicetree/bindings/spi/qcom,sa8255p-geni-spi.yaml diff --git a/Documentation/devicetree/bindings/spi/qcom,sa8255p-geni-spi.yaml b/Documentation/devicetree/bindings/spi/qcom,sa8255p-geni-spi.yaml new file mode 100644 index 000000000000..d9347d780ca4 --- /dev/null +++ b/Documentation/devicetree/bindings/spi/qcom,sa8255p-geni-spi.yaml @@ -0,0 +1,64 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/spi/qcom,sa8255p-geni-spi.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: GENI based Qualcomm Universal Peripheral (QUP) Serial Peripheral Interface (SPI) + +maintainers: + - Praveen Talari + +properties: + compatible: + const: qcom,sa8255p-geni-spi + + reg: + maxItems: 1 + + dmas: + maxItems: 2 + + dma-names: + items: + - const: tx + - const: rx + + interrupts: + maxItems: 1 + + power-domains: + maxItems: 2 + + power-domain-names: + items: + - const: power + - const: perf + +required: + - compatible + - reg + - interrupts + - power-domains + - power-domain-names + +allOf: + - $ref: /schemas/spi/spi-controller.yaml# + +unevaluatedProperties: false + +examples: + - | + #include + #include + + spi@888000 { + compatible = "qcom,sa8255p-geni-spi"; + reg = <0x888000 0x4000>; + interrupts = ; + dmas = <&gpi_dma0 0 0 QCOM_GPI_SPI>, + <&gpi_dma0 1 0 QCOM_GPI_SPI>; + dma-names = "tx", "rx"; + power-domains = <&scmi0_pd 0>, <&scmi0_dvfs 0>; + power-domain-names = "power", "perf"; + }; From 647277240b3623808c583d3d38e74d47180f60de Mon Sep 17 00:00:00 2001 From: Praveen Talari Date: Thu, 18 Jun 2026 14:36:40 +0530 Subject: [PATCH 013/132] spi: qcom-geni: Use geni_se_resources_init() for resource initialization Replace resources initialization such as clocks, ICC path and OPP with the common geni_se_resources_init() function to avoid code duplication across all drivers. The geni_se_resources_init() function handles all these resources internally, reducing code duplication and ensuring consistent resource management across GENI SE drivers. Reviewed-by: Konrad Dybcio Signed-off-by: Praveen Talari Tested-by: Mattijs Korpershoek Link: https://patch.msgid.link/20260618-enable-spi-on-sa8255p-v4-2-f5b5067e7e1e@oss.qualcomm.com Signed-off-by: Mark Brown --- drivers/spi/spi-geni-qcom.c | 26 +------------------------- 1 file changed, 1 insertion(+), 25 deletions(-) diff --git a/drivers/spi/spi-geni-qcom.c b/drivers/spi/spi-geni-qcom.c index 26e723cfea61..09feb6577efc 100644 --- a/drivers/spi/spi-geni-qcom.c +++ b/drivers/spi/spi-geni-qcom.c @@ -1045,7 +1045,6 @@ static int spi_geni_probe(struct platform_device *pdev) struct spi_controller *spi; struct spi_geni_master *mas; void __iomem *base; - struct clk *clk; struct device *dev = &pdev->dev; irq = platform_get_irq(pdev, 0); @@ -1060,10 +1059,6 @@ static int spi_geni_probe(struct platform_device *pdev) if (IS_ERR(base)) return PTR_ERR(base); - clk = devm_clk_get(dev, "se"); - if (IS_ERR(clk)) - return PTR_ERR(clk); - if (device_property_read_bool(dev, "spi-slave")) spi = devm_spi_alloc_target(dev, sizeof(*mas)); else @@ -1079,17 +1074,10 @@ static int spi_geni_probe(struct platform_device *pdev) mas->se.dev = dev; mas->se.wrapper = dev_get_drvdata(dev->parent); mas->se.base = base; - mas->se.clk = clk; - ret = devm_pm_opp_set_clkname(&pdev->dev, "se"); + ret = geni_se_resources_init(&mas->se); if (ret) return ret; - /* OPP table is optional */ - ret = devm_pm_opp_of_add_table(&pdev->dev); - if (ret && ret != -ENODEV) { - dev_err(&pdev->dev, "invalid OPP table in device tree\n"); - return ret; - } spi->bus_num = -1; spi->mode_bits = SPI_CPOL | SPI_CPHA | SPI_LOOP | SPI_CS_HIGH; @@ -1115,24 +1103,12 @@ static int spi_geni_probe(struct platform_device *pdev) if (spi->target) spi->target_abort = spi_geni_target_abort; - ret = geni_icc_get(&mas->se, NULL); - if (ret) - return ret; - pm_runtime_use_autosuspend(&pdev->dev); pm_runtime_set_autosuspend_delay(&pdev->dev, 250); ret = devm_pm_runtime_enable(dev); if (ret) return ret; - /* Set the bus quota to a reasonable value for register access */ - mas->se.icc_paths[GENI_TO_CORE].avg_bw = Bps_to_icc(CORE_2X_50_MHZ); - mas->se.icc_paths[CPU_TO_GENI].avg_bw = GENI_DEFAULT_BW; - - ret = geni_icc_set_bw(&mas->se); - if (ret) - return ret; - ret = spi_geni_init(mas); if (ret) return ret; From 059e41aab4c860287bbef20c8ea11cff5731883d Mon Sep 17 00:00:00 2001 From: Praveen Talari Date: Thu, 18 Jun 2026 14:36:41 +0530 Subject: [PATCH 014/132] spi: qcom-geni: Use resources helper APIs in runtime PM functions To manage GENI serial engine resources during runtime power management, drivers currently need to call functions for ICC, clock, and SE resource operations in both suspend and resume paths, resulting in code duplication across drivers. The new geni_se_resources_activate() and geni_se_resources_deactivate() helper APIs addresses this issue by providing a streamlined method to enable or disable all resources based, thereby eliminating redundancy across drivers. Reviewed-by: Konrad Dybcio Signed-off-by: Praveen Talari Tested-by: Mattijs Korpershoek Link: https://patch.msgid.link/20260618-enable-spi-on-sa8255p-v4-3-f5b5067e7e1e@oss.qualcomm.com Signed-off-by: Mark Brown --- drivers/spi/spi-geni-qcom.c | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/drivers/spi/spi-geni-qcom.c b/drivers/spi/spi-geni-qcom.c index 09feb6577efc..025165c4b952 100644 --- a/drivers/spi/spi-geni-qcom.c +++ b/drivers/spi/spi-geni-qcom.c @@ -1130,16 +1130,8 @@ static int __maybe_unused spi_geni_runtime_suspend(struct device *dev) { struct spi_controller *spi = dev_get_drvdata(dev); struct spi_geni_master *mas = spi_controller_get_devdata(spi); - int ret; - /* Drop the performance state vote */ - dev_pm_opp_set_rate(dev, 0); - - ret = geni_se_resources_off(&mas->se); - if (ret) - return ret; - - return geni_icc_disable(&mas->se); + return geni_se_resources_deactivate(&mas->se); } static int __maybe_unused spi_geni_runtime_resume(struct device *dev) @@ -1148,15 +1140,14 @@ static int __maybe_unused spi_geni_runtime_resume(struct device *dev) struct spi_geni_master *mas = spi_controller_get_devdata(spi); int ret; - ret = geni_icc_enable(&mas->se); + ret = geni_se_resources_activate(&mas->se); if (ret) return ret; - ret = geni_se_resources_on(&mas->se); - if (ret) - return ret; + if (mas->se.has_opp) + return dev_pm_opp_set_rate(mas->dev, mas->cur_sclk_hz); - return dev_pm_opp_set_rate(mas->dev, mas->cur_sclk_hz); + return 0; } static int __maybe_unused spi_geni_suspend(struct device *dev) From b3e1d0948ca94a16a81ea8cc26c6246e6cc44943 Mon Sep 17 00:00:00 2001 From: Praveen Talari Date: Thu, 18 Jun 2026 14:36:42 +0530 Subject: [PATCH 015/132] spi: qcom-geni: Enable SPI on SA8255p Qualcomm platforms The Qualcomm automotive SA8255p SoC relies on firmware to configure platform resources, including clocks, interconnects and TLMM. The driver requests resources operations over SCMI using power and performance protocols. The SCMI power protocol enables or disables resources like clocks, interconnect paths, and TLMM (GPIOs) using runtime PM framework APIs, such as resume/suspend, to control power states(on/off). The SCMI performance protocol manages SPI frequency, with each frequency rate represented by a performance level. The driver uses geni_se_set_perf_opp() API to request the desired frequency rate. As part of geni_se_set_perf_opp(), the OPP for the requested frequency is obtained using dev_pm_opp_find_freq_floor() and the performance level is set using dev_pm_opp_set_opp(). Signed-off-by: Praveen Talari Tested-by: Mattijs Korpershoek Reviewed-by: Konrad Dybcio Link: https://patch.msgid.link/20260618-enable-spi-on-sa8255p-v4-4-f5b5067e7e1e@oss.qualcomm.com Signed-off-by: Mark Brown --- drivers/spi/spi-geni-qcom.c | 49 +++++++++++++++++++++++++++++-------- 1 file changed, 39 insertions(+), 10 deletions(-) diff --git a/drivers/spi/spi-geni-qcom.c b/drivers/spi/spi-geni-qcom.c index 025165c4b952..88ac0833351c 100644 --- a/drivers/spi/spi-geni-qcom.c +++ b/drivers/spi/spi-geni-qcom.c @@ -78,6 +78,13 @@ #define GSI_CPHA BIT(4) #define GSI_CPOL BIT(5) +struct geni_spi_desc { + int (*resources_init)(struct geni_se *se); + int (*set_rate)(struct geni_se *se, unsigned long clk_freq); + int (*power_on)(struct geni_se *se); + int (*power_off)(struct geni_se *se); +}; + struct spi_geni_master { struct geni_se se; struct device *dev; @@ -105,6 +112,7 @@ struct spi_geni_master { struct dma_chan *tx; struct dma_chan *rx; int cur_xfer_mode; + const struct geni_spi_desc *dev_data; }; static void spi_slv_setup(struct spi_geni_master *mas) @@ -305,11 +313,12 @@ static void spi_setup_word_len(struct spi_geni_master *mas, u16 mode, writel(word_len, se->base + SE_SPI_WORD_LEN); } -static int geni_spi_set_clock_and_bw(struct spi_geni_master *mas, - unsigned long clk_hz) +static int geni_spi_set_clock_and_bw(struct geni_se *se, + unsigned long clk_hz) { + struct spi_controller *spi = dev_get_drvdata(se->dev); + struct spi_geni_master *mas = spi_controller_get_devdata(spi); u32 clk_sel, m_clk_cfg, idx, div; - struct geni_se *se = &mas->se; int ret; if (clk_hz == mas->cur_speed_hz) @@ -825,7 +834,7 @@ static int setup_se_xfer(struct spi_transfer *xfer, } /* Speed and bits per word can be overridden per transfer */ - ret = geni_spi_set_clock_and_bw(mas, xfer->speed_hz); + ret = mas->dev_data->set_rate(&mas->se, xfer->speed_hz); if (ret) return ret; @@ -1075,7 +1084,11 @@ static int spi_geni_probe(struct platform_device *pdev) mas->se.wrapper = dev_get_drvdata(dev->parent); mas->se.base = base; - ret = geni_se_resources_init(&mas->se); + mas->dev_data = device_get_match_data(&pdev->dev); + if (!mas->dev_data) + return -EINVAL; + + ret = mas->dev_data->resources_init(&mas->se); if (ret) return ret; @@ -1131,7 +1144,8 @@ static int __maybe_unused spi_geni_runtime_suspend(struct device *dev) struct spi_controller *spi = dev_get_drvdata(dev); struct spi_geni_master *mas = spi_controller_get_devdata(spi); - return geni_se_resources_deactivate(&mas->se); + return mas->dev_data->power_off ? + mas->dev_data->power_off(&mas->se) : 0; } static int __maybe_unused spi_geni_runtime_resume(struct device *dev) @@ -1140,9 +1154,11 @@ static int __maybe_unused spi_geni_runtime_resume(struct device *dev) struct spi_geni_master *mas = spi_controller_get_devdata(spi); int ret; - ret = geni_se_resources_activate(&mas->se); - if (ret) - return ret; + if (mas->dev_data->power_on) { + ret = mas->dev_data->power_on(&mas->se); + if (ret) + return ret; + } if (mas->se.has_opp) return dev_pm_opp_set_rate(mas->dev, mas->cur_sclk_hz); @@ -1188,8 +1204,21 @@ static const struct dev_pm_ops spi_geni_pm_ops = { SET_SYSTEM_SLEEP_PM_OPS(spi_geni_suspend, spi_geni_resume) }; +static const struct geni_spi_desc geni_spi = { + .resources_init = geni_se_resources_init, + .set_rate = geni_spi_set_clock_and_bw, + .power_on = geni_se_resources_activate, + .power_off = geni_se_resources_deactivate, +}; + +static const struct geni_spi_desc sa8255p_geni_spi = { + .resources_init = geni_se_domain_attach, + .set_rate = geni_se_set_perf_opp, +}; + static const struct of_device_id spi_geni_dt_match[] = { - { .compatible = "qcom,geni-spi" }, + { .compatible = "qcom,geni-spi", .data = &geni_spi }, + { .compatible = "qcom,sa8255p-geni-spi", .data = &sa8255p_geni_spi }, {} }; MODULE_DEVICE_TABLE(of, spi_geni_dt_match); From d8e9ea989acb54508477e4a8c9d9eaf8217e0081 Mon Sep 17 00:00:00 2001 From: Praveen Talari Date: Fri, 10 Jul 2026 15:42:44 +0530 Subject: [PATCH 016/132] spi: qcom-geni: Fix missing error check on pm_runtime_get_sync() spi_geni_init() calls pm_runtime_get_sync() to power up the device before accessing hardware registers, but never checks the return value. If the runtime resume fails, the function silently proceeds to read and write hardware registers on a device that may not be powered up, leading to register access faults. Fix this by replacing pm_runtime_get_sync() with the PM_RUNTIME_ACQUIRE_IF_ENABLED() macro and checking the result via PM_RUNTIME_ACQUIRE_ERR(), propagating any error back to the caller immediately before any hardware access occurs. Since the macro handles its own cleanup on failure, the out_pm label and the corresponding pm_runtime_put() call are no longer needed. Replace all goto out_pm paths with direct return ret statements and remove the label entirely. Fixes: 561de45f72bd ("spi: spi-geni-qcom: Add SPI driver support for GENI based QUP") Reviewed-by: Rafael J. Wysocki (Intel) Reviewed-by: Konrad Dybcio Signed-off-by: Praveen Talari Link: https://patch.msgid.link/20260710-fix_sticky_-einval_after_pm_runtime_api_failure-v4-2-be81d6c15043@oss.qualcomm.com Signed-off-by: Mark Brown --- drivers/spi/spi-geni-qcom.c | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/drivers/spi/spi-geni-qcom.c b/drivers/spi/spi-geni-qcom.c index 88ac0833351c..2914d781dbf5 100644 --- a/drivers/spi/spi-geni-qcom.c +++ b/drivers/spi/spi-geni-qcom.c @@ -622,25 +622,30 @@ static int spi_geni_init(struct spi_geni_master *mas) u32 spi_tx_cfg, fifo_disable; int ret = -ENXIO; - pm_runtime_get_sync(mas->dev); + PM_RUNTIME_ACQUIRE_IF_ENABLED(mas->dev, pm); + ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (ret < 0) { + dev_err(mas->dev, "Failed to resume and get %d\n", ret); + return ret; + } proto = geni_se_read_proto(se); if (spi->target) { if (proto != GENI_SE_SPI_SLAVE) { dev_err(mas->dev, "Invalid proto %d\n", proto); - goto out_pm; + return ret; } spi_slv_setup(mas); } else if (proto == GENI_SE_INVALID_PROTO) { ret = geni_load_se_firmware(se, GENI_SE_SPI); if (ret) { dev_err(mas->dev, "spi master firmware load failed ret: %d\n", ret); - goto out_pm; + return ret; } } else if (proto != GENI_SE_SPI) { dev_err(mas->dev, "Invalid proto %d\n", proto); - goto out_pm; + return ret; } mas->tx_fifo_depth = geni_se_get_tx_fifo_depth(se); @@ -673,7 +678,7 @@ static int spi_geni_init(struct spi_geni_master *mas) dev_dbg(mas->dev, "Using GPI DMA mode for SPI\n"); break; } else if (ret == -EPROBE_DEFER) { - goto out_pm; + return ret; } /* * in case of failure to get gpi dma channel, we can still do the @@ -702,8 +707,6 @@ static int spi_geni_init(struct spi_geni_master *mas) writel(spi_tx_cfg, se->base + SE_SPI_TRANS_CFG); } -out_pm: - pm_runtime_put(mas->dev); return ret; } From 51f761e4071f14001cc5bf220807847260d9cc93 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Sun, 5 Jul 2026 19:27:38 +0200 Subject: [PATCH 017/132] spi: atmel-quadspi: Simplify probe() with local 'dev' variable Instead of multiple '&pdev->dev' dereferences, just use a local 'dev' variable which makes multiple function calls shorter thus easier to follow. Signed-off-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260705172737.120095-4-krzysztof.kozlowski@oss.qualcomm.com Signed-off-by: Mark Brown --- drivers/spi/atmel-quadspi.c | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/drivers/spi/atmel-quadspi.c b/drivers/spi/atmel-quadspi.c index aaf7f4c46b22..8d9c77f5de43 100644 --- a/drivers/spi/atmel-quadspi.c +++ b/drivers/spi/atmel-quadspi.c @@ -1347,20 +1347,21 @@ static const struct atmel_qspi_ops atmel_qspi_sama7g5_ops = { static int atmel_qspi_probe(struct platform_device *pdev) { + struct device *dev = &pdev->dev; struct spi_controller *ctrl; struct atmel_qspi *aq; struct resource *res; int irq, err = 0; - ctrl = devm_spi_alloc_host(&pdev->dev, sizeof(*aq)); + ctrl = devm_spi_alloc_host(dev, sizeof(*aq)); if (!ctrl) return -ENOMEM; aq = spi_controller_get_devdata(ctrl); - aq->caps = of_device_get_match_data(&pdev->dev); + aq->caps = of_device_get_match_data(dev); if (!aq->caps) { - dev_err(&pdev->dev, "Could not retrieve QSPI caps\n"); + dev_err(dev, "Could not retrieve QSPI caps\n"); return -EINVAL; } @@ -1387,31 +1388,31 @@ static int atmel_qspi_probe(struct platform_device *pdev) /* Map the registers */ aq->regs = devm_platform_ioremap_resource_byname(pdev, "qspi_base"); if (IS_ERR(aq->regs)) - return dev_err_probe(&pdev->dev, PTR_ERR(aq->regs), + return dev_err_probe(dev, PTR_ERR(aq->regs), "missing registers\n"); /* Map the AHB memory */ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "qspi_mmap"); - aq->mem = devm_ioremap_resource(&pdev->dev, res); + aq->mem = devm_ioremap_resource(dev, res); if (IS_ERR(aq->mem)) - return dev_err_probe(&pdev->dev, PTR_ERR(aq->mem), + return dev_err_probe(dev, PTR_ERR(aq->mem), "missing AHB memory\n"); aq->mmap_size = resource_size(res); aq->mmap_phys_base = (dma_addr_t)res->start; /* Get the peripheral clock */ - aq->pclk = devm_clk_get_enabled(&pdev->dev, "pclk"); + aq->pclk = devm_clk_get_enabled(dev, "pclk"); if (IS_ERR(aq->pclk)) - aq->pclk = devm_clk_get_enabled(&pdev->dev, NULL); + aq->pclk = devm_clk_get_enabled(dev, NULL); if (IS_ERR(aq->pclk)) - return dev_err_probe(&pdev->dev, PTR_ERR(aq->pclk), + return dev_err_probe(dev, PTR_ERR(aq->pclk), "missing peripheral clock\n"); if (aq->caps->has_qspick) { /* Get the QSPI system clock */ - aq->qspick = devm_clk_get_enabled(&pdev->dev, "qspick"); + aq->qspick = devm_clk_get_enabled(dev, "qspick"); if (IS_ERR(aq->qspick)) { dev_err(&pdev->dev, "missing system clock\n"); err = PTR_ERR(aq->qspick); @@ -1420,7 +1421,7 @@ static int atmel_qspi_probe(struct platform_device *pdev) } else if (aq->caps->has_gclk) { /* Get the QSPI generic clock */ - aq->gclk = devm_clk_get(&pdev->dev, "gclk"); + aq->gclk = devm_clk_get(dev, "gclk"); if (IS_ERR(aq->gclk)) { dev_err(&pdev->dev, "missing Generic clock\n"); err = PTR_ERR(aq->gclk); @@ -1439,15 +1440,15 @@ static int atmel_qspi_probe(struct platform_device *pdev) if (irq < 0) return irq; - err = devm_request_irq(&pdev->dev, irq, atmel_qspi_interrupt, - 0, dev_name(&pdev->dev), aq); + err = devm_request_irq(dev, irq, atmel_qspi_interrupt, + 0, dev_name(dev), aq); if (err) return err; - pm_runtime_set_autosuspend_delay(&pdev->dev, 500); - pm_runtime_use_autosuspend(&pdev->dev); - devm_pm_runtime_set_active_enabled(&pdev->dev); - devm_pm_runtime_get_noresume(&pdev->dev); + pm_runtime_set_autosuspend_delay(dev, 500); + pm_runtime_use_autosuspend(dev); + devm_pm_runtime_set_active_enabled(dev); + devm_pm_runtime_get_noresume(dev); err = atmel_qspi_init(aq); if (err) @@ -1457,7 +1458,7 @@ static int atmel_qspi_probe(struct platform_device *pdev) if (err) return err; - pm_runtime_put_autosuspend(&pdev->dev); + pm_runtime_put_autosuspend(dev); return 0; } From 3ae07d8f30758ce7ebfe731459a06c94481fa742 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Sun, 5 Jul 2026 19:27:39 +0200 Subject: [PATCH 018/132] spi: atmel-quadspi: Simplify with dev_err_probe() Use dev_err_probe() to make error code handling simpler and handle deferred probe nicely (avoid spamming logs). Signed-off-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260705172737.120095-5-krzysztof.kozlowski@oss.qualcomm.com Signed-off-by: Mark Brown --- drivers/spi/atmel-quadspi.c | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/drivers/spi/atmel-quadspi.c b/drivers/spi/atmel-quadspi.c index 8d9c77f5de43..e3a4697fabce 100644 --- a/drivers/spi/atmel-quadspi.c +++ b/drivers/spi/atmel-quadspi.c @@ -1413,20 +1413,15 @@ static int atmel_qspi_probe(struct platform_device *pdev) if (aq->caps->has_qspick) { /* Get the QSPI system clock */ aq->qspick = devm_clk_get_enabled(dev, "qspick"); - if (IS_ERR(aq->qspick)) { - dev_err(&pdev->dev, "missing system clock\n"); - err = PTR_ERR(aq->qspick); - return err; - } - + if (IS_ERR(aq->qspick)) + return dev_err_probe(dev, PTR_ERR(aq->qspick), + "missing system clock\n"); } else if (aq->caps->has_gclk) { /* Get the QSPI generic clock */ aq->gclk = devm_clk_get(dev, "gclk"); - if (IS_ERR(aq->gclk)) { - dev_err(&pdev->dev, "missing Generic clock\n"); - err = PTR_ERR(aq->gclk); - return err; - } + if (IS_ERR(aq->gclk)) + return dev_err_probe(dev, PTR_ERR(aq->gclk), + "missing Generic clock\n"); } if (aq->caps->has_dma) { From 3e106385e3e68b02c9ebf1919c484e7e9079c4ed Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Sun, 5 Jul 2026 19:27:40 +0200 Subject: [PATCH 019/132] spi: altera: Simplify with dev_err_probe() Use dev_err_probe() to make error code handling simpler and handle deferred probe nicely (avoid spamming logs). Signed-off-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260705172737.120095-6-krzysztof.kozlowski@oss.qualcomm.com Signed-off-by: Mark Brown --- drivers/spi/spi-altera-platform.c | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/drivers/spi/spi-altera-platform.c b/drivers/spi/spi-altera-platform.c index 3de7df73f216..4fe69ce9da16 100644 --- a/drivers/spi/spi-altera-platform.c +++ b/drivers/spi/spi-altera-platform.c @@ -77,10 +77,8 @@ static int altera_spi_probe(struct platform_device *pdev) struct resource *regoff; hw->regmap = dev_get_regmap(pdev->dev.parent, NULL); - if (!hw->regmap) { - dev_err(&pdev->dev, "get regmap failed\n"); - return -ENODEV; - } + if (!hw->regmap) + return dev_err_probe(&pdev->dev, -ENODEV, "get regmap failed\n"); regoff = platform_get_resource(pdev, IORESOURCE_REG, 0); if (regoff) @@ -94,10 +92,9 @@ static int altera_spi_probe(struct platform_device *pdev) hw->regmap = devm_regmap_init_mmio(&pdev->dev, res, &spi_altera_config); - if (IS_ERR(hw->regmap)) { - dev_err(&pdev->dev, "regmap mmio init failed\n"); - return PTR_ERR(hw->regmap); - } + if (IS_ERR(hw->regmap)) + return dev_err_probe(&pdev->dev, PTR_ERR(hw->regmap), + "regmap mmio init failed\n"); } altera_spi_init_host(host); From 9c4a2dced735ed5ed4a6ec21f1743df82977a282 Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Tue, 14 Jul 2026 17:42:15 +0100 Subject: [PATCH 020/132] spi: bcm2835: make read-only const array pinctrl_compats static Don't populate the read-only const array pinctrl_compats on the stack at run time, instead make it static const char * const Signed-off-by: Colin Ian King Link: https://patch.msgid.link/20260714164215.183809-1-colin.i.king@gmail.com Signed-off-by: Mark Brown --- drivers/spi/spi-bcm2835.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/spi/spi-bcm2835.c b/drivers/spi/spi-bcm2835.c index ff94482fefcc..8f8715809c7c 100644 --- a/drivers/spi/spi-bcm2835.c +++ b/drivers/spi/spi-bcm2835.c @@ -1226,7 +1226,7 @@ static int bcm2835_spi_setup(struct spi_device *spi) struct bcm2835_spi *bs = spi_controller_get_devdata(ctlr); struct bcm2835_spidev *target = spi_get_ctldata(spi); struct gpiod_lookup_table *lookup __free(kfree) = NULL; - const char *pinctrl_compats[] = { + static const char * const pinctrl_compats[] = { "brcm,bcm2835-gpio", "brcm,bcm2711-gpio", "brcm,bcm7211-gpio", From 4bc5a274edab3f9afd11ec500eb526de7e7d91ae Mon Sep 17 00:00:00 2001 From: Rustam Adilov Date: Sat, 11 Jul 2026 13:34:11 +0500 Subject: [PATCH 021/132] spi: realtek-rtl: change to __raw reads and writes To make the spi driver operable with SWAP_IO_SPACE config enabled, replace all instances of readl/writel with their __raw variants. Otherwise readl/writel will do a byte swap which this driver does not intend to do. Tested-by: Carlo Szelinsky Signed-off-by: Rustam Adilov Link: https://patch.msgid.link/20260711083411.45836-1-adilov@disroot.org Signed-off-by: Mark Brown --- drivers/spi/spi-realtek-rtl.c | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/drivers/spi/spi-realtek-rtl.c b/drivers/spi/spi-realtek-rtl.c index 73065c8934dc..e1c40ff2e49d 100644 --- a/drivers/spi/spi-realtek-rtl.c +++ b/drivers/spi/spi-realtek-rtl.c @@ -35,58 +35,58 @@ static void rt_set_cs(struct spi_device *spi, bool active) u32 value; /* CS0 bit is active low */ - value = readl(REG(RTL_SPI_SFCSR)); + value = __raw_readl(REG(RTL_SPI_SFCSR)); if (active) value |= RTL_SPI_SFCSR_CSB0; else value &= ~RTL_SPI_SFCSR_CSB0; - writel(value, REG(RTL_SPI_SFCSR)); + __raw_writel(value, REG(RTL_SPI_SFCSR)); } static void set_size(struct rtspi *rtspi, int size) { u32 value; - value = readl(REG(RTL_SPI_SFCSR)); + value = __raw_readl(REG(RTL_SPI_SFCSR)); value &= RTL_SPI_SFCSR_LEN_MASK; if (size == 4) value |= RTL_SPI_SFCSR_LEN4; else if (size == 1) value |= RTL_SPI_SFCSR_LEN1; - writel(value, REG(RTL_SPI_SFCSR)); + __raw_writel(value, REG(RTL_SPI_SFCSR)); } static inline void wait_ready(struct rtspi *rtspi) { - while (!(readl(REG(RTL_SPI_SFCSR)) & RTL_SPI_SFCSR_RDY)) + while (!(__raw_readl(REG(RTL_SPI_SFCSR)) & RTL_SPI_SFCSR_RDY)) cpu_relax(); } static void send4(struct rtspi *rtspi, const u32 *buf) { wait_ready(rtspi); set_size(rtspi, 4); - writel(*buf, REG(RTL_SPI_SFDR)); + __raw_writel(*buf, REG(RTL_SPI_SFDR)); } static void send1(struct rtspi *rtspi, const u8 *buf) { wait_ready(rtspi); set_size(rtspi, 1); - writel(buf[0] << 24, REG(RTL_SPI_SFDR)); + __raw_writel(buf[0] << 24, REG(RTL_SPI_SFDR)); } static void rcv4(struct rtspi *rtspi, u32 *buf) { wait_ready(rtspi); set_size(rtspi, 4); - *buf = readl(REG(RTL_SPI_SFDR)); + *buf = __raw_readl(REG(RTL_SPI_SFDR)); } static void rcv1(struct rtspi *rtspi, u8 *buf) { wait_ready(rtspi); set_size(rtspi, 1); - *buf = readl(REG(RTL_SPI_SFDR)) >> 24; + *buf = __raw_readl(REG(RTL_SPI_SFDR)) >> 24; } static int transfer_one(struct spi_controller *ctrl, struct spi_device *spi, @@ -134,16 +134,16 @@ static void init_hw(struct rtspi *rtspi) u32 value; /* Turn on big-endian byte ordering */ - value = readl(REG(RTL_SPI_SFCR)); + value = __raw_readl(REG(RTL_SPI_SFCR)); value |= RTL_SPI_SFCR_RBO | RTL_SPI_SFCR_WBO; - writel(value, REG(RTL_SPI_SFCR)); + __raw_writel(value, REG(RTL_SPI_SFCR)); - value = readl(REG(RTL_SPI_SFCSR)); + value = __raw_readl(REG(RTL_SPI_SFCSR)); /* Permanently disable CS1, since it's never used */ value |= RTL_SPI_SFCSR_CSB1; /* Select CS0 for use */ value &= RTL_SPI_SFCSR_CS; - writel(value, REG(RTL_SPI_SFCSR)); + __raw_writel(value, REG(RTL_SPI_SFCSR)); } static int realtek_rtl_spi_probe(struct platform_device *pdev) From 2c1c13da3a3a639d2ac7221e1a5e57945cbc7235 Mon Sep 17 00:00:00 2001 From: Praveen Talari Date: Thu, 16 Jul 2026 13:55:45 +0530 Subject: [PATCH 022/132] spi: geni-qcom: Fix sticky ret causing wrong return value on invalid proto spi_geni_init() reuses 'ret' after it has already been set by the runtime PM acquire check earlier in the function. When an invalid protocol is later detected, the function returns this stale 'ret' value instead of a proper error code, so it can end up returning 0 (or some other non-error value) even though the protocol check failed. Fix this by returning -EINVAL directly on both invalid-proto paths. Fixes: d8e9ea989acb ("spi: qcom-geni: Fix missing error check on pm_runtime_get_sync()") Reported-by: kernel test robot Reported-by: Dan Carpenter Closes: https://lore.kernel.org/r/202607122241.qzP3QAXF-lkp@intel.com/ Signed-off-by: Praveen Talari Reviewed-by: Konrad Dybcio Acked-by: Mukesh Kumar Savaliya Link: https://patch.msgid.link/20260716-fix_return_error_code-v1-1-3295003aacd5@oss.qualcomm.com Signed-off-by: Mark Brown --- drivers/spi/spi-geni-qcom.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/spi/spi-geni-qcom.c b/drivers/spi/spi-geni-qcom.c index 2914d781dbf5..d262206ed663 100644 --- a/drivers/spi/spi-geni-qcom.c +++ b/drivers/spi/spi-geni-qcom.c @@ -634,7 +634,7 @@ static int spi_geni_init(struct spi_geni_master *mas) if (spi->target) { if (proto != GENI_SE_SPI_SLAVE) { dev_err(mas->dev, "Invalid proto %d\n", proto); - return ret; + return -EINVAL; } spi_slv_setup(mas); } else if (proto == GENI_SE_INVALID_PROTO) { @@ -645,7 +645,7 @@ static int spi_geni_init(struct spi_geni_master *mas) } } else if (proto != GENI_SE_SPI) { dev_err(mas->dev, "Invalid proto %d\n", proto); - return ret; + return -EINVAL; } mas->tx_fifo_depth = geni_se_get_tx_fifo_depth(se); From 61c2eaa653c2a0d83780d64a6006291c41f99c43 Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Thu, 16 Jul 2026 16:14:11 -0700 Subject: [PATCH 023/132] spi: orion: use devm_clk_get_optional_enabled for axi clock Replace the open-coded optional axi clock get/prepare/enable and the manual cleanup in probe/remove/runtime_resume with the managed helper devm_clk_get_optional_enabled(). This removes the now-unused out_rel_axi_clk error path and simplifies the clock lifecycle. Assisted-by: opencode:hy3-free Signed-off-by: Rosen Penev Link: https://patch.msgid.link/20260716231411.1737001-1-rosenp@gmail.com Signed-off-by: Mark Brown --- drivers/spi/spi-orion.c | 25 +++++++------------------ 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/drivers/spi/spi-orion.c b/drivers/spi/spi-orion.c index 265708a94984..e5e05512a1fd 100644 --- a/drivers/spi/spi-orion.c +++ b/drivers/spi/spi-orion.c @@ -691,12 +691,9 @@ static int orion_spi_probe(struct platform_device *pdev) return PTR_ERR(spi->clk); /* The following clock is only used by some SoCs */ - spi->axi_clk = devm_clk_get(&pdev->dev, "axi"); - if (PTR_ERR(spi->axi_clk) == -EPROBE_DEFER) - return -EPROBE_DEFER; - - if (!IS_ERR(spi->axi_clk)) - clk_prepare_enable(spi->axi_clk); + spi->axi_clk = devm_clk_get_optional_enabled(&pdev->dev, "axi"); + if (IS_ERR(spi->axi_clk)) + return PTR_ERR(spi->axi_clk); tclk_hz = clk_get_rate(spi->clk); @@ -719,10 +716,8 @@ static int orion_spi_probe(struct platform_device *pdev) host->min_speed_hz = DIV_ROUND_UP(tclk_hz, devdata->max_divisor); spi->base = devm_platform_get_and_ioremap_resource(pdev, 0, &r); - if (IS_ERR(spi->base)) { - status = PTR_ERR(spi->base); - goto out_rel_axi_clk; - } + if (IS_ERR(spi->base)) + return PTR_ERR(spi->base); for_each_available_child_of_node(pdev->dev.of_node, np) { struct orion_direct_acc *dir_acc; @@ -757,9 +752,8 @@ static int orion_spi_probe(struct platform_device *pdev) dir_acc = &spi->child[cs].direct_access; dir_acc->vaddr = devm_ioremap(&pdev->dev, r->start, PAGE_SIZE); if (!dir_acc->vaddr) { - status = -ENOMEM; of_node_put(np); - goto out_rel_axi_clk; + return -ENOMEM; } dir_acc->size = PAGE_SIZE; @@ -789,8 +783,6 @@ static int orion_spi_probe(struct platform_device *pdev) pm_runtime_put_noidle(&pdev->dev); pm_runtime_set_suspended(&pdev->dev); pm_runtime_dont_use_autosuspend(&pdev->dev); -out_rel_axi_clk: - clk_disable_unprepare(spi->axi_clk); return status; } @@ -799,12 +791,10 @@ static int orion_spi_probe(struct platform_device *pdev) static void orion_spi_remove(struct platform_device *pdev) { struct spi_controller *host = platform_get_drvdata(pdev); - struct orion_spi *spi = spi_controller_get_devdata(host); spi_unregister_controller(host); pm_runtime_get_sync(&pdev->dev); - clk_disable_unprepare(spi->axi_clk); pm_runtime_disable(&pdev->dev); pm_runtime_put_noidle(&pdev->dev); @@ -830,8 +820,7 @@ static int orion_spi_runtime_resume(struct device *dev) struct spi_controller *host = dev_get_drvdata(dev); struct orion_spi *spi = spi_controller_get_devdata(host); - if (!IS_ERR(spi->axi_clk)) - clk_prepare_enable(spi->axi_clk); + clk_prepare_enable(spi->axi_clk); return clk_prepare_enable(spi->clk); } #endif From f718ea6cb3f3e86b3a2a1faa15124975c6fbc9b4 Mon Sep 17 00:00:00 2001 From: Robert Marko Date: Thu, 9 Jul 2026 13:19:07 +0200 Subject: [PATCH 024/132] spi: dt-bindings: Document LAN969x QSPI LAN969x has 2 QSPI instances, so document their compatible. Signed-off-by: Robert Marko Link: https://patch.msgid.link/20260709112006.390742-2-robert.marko@sartura.hr Signed-off-by: Mark Brown --- Documentation/devicetree/bindings/spi/atmel,quadspi.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/spi/atmel,quadspi.yaml b/Documentation/devicetree/bindings/spi/atmel,quadspi.yaml index 30ab42c95c08..36c698ced99b 100644 --- a/Documentation/devicetree/bindings/spi/atmel,quadspi.yaml +++ b/Documentation/devicetree/bindings/spi/atmel,quadspi.yaml @@ -22,6 +22,7 @@ properties: - microchip,sama7d65-ospi - microchip,sama7g5-qspi - microchip,sama7g5-ospi + - microchip,lan9691-qspi reg: items: From cbbd5a945e85e7835ac5792d855d85c20e40d1e3 Mon Sep 17 00:00:00 2001 From: Robert Marko Date: Thu, 9 Jul 2026 13:19:08 +0200 Subject: [PATCH 025/132] spi: atmel-quadspi: add controller init callback Allow controller variants to provide a custom initialization callback through their capability data. This prepares the driver for variants which require a different hardware initialization sequence without adding SoC checks to the common path. Signed-off-by: Robert Marko Link: https://patch.msgid.link/20260709112006.390742-3-robert.marko@sartura.hr Signed-off-by: Mark Brown --- drivers/spi/atmel-quadspi.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/spi/atmel-quadspi.c b/drivers/spi/atmel-quadspi.c index aaf7f4c46b22..62ea84d234d0 100644 --- a/drivers/spi/atmel-quadspi.c +++ b/drivers/spi/atmel-quadspi.c @@ -256,8 +256,11 @@ static const struct atmel_qspi_pcal pcal[ATMEL_QSPI_PCAL_ARRAY_SIZE] = { {200000000, 7}, }; +struct atmel_qspi; + struct atmel_qspi_caps { u32 max_speed_hz; + int (*init)(struct atmel_qspi *aq); bool has_qspick; bool has_gclk; bool has_ricr; @@ -1156,6 +1159,9 @@ static int atmel_qspi_sama7g5_setup(struct spi_device *spi) /* The controller can communicate with a single peripheral device (target). */ aq->target_max_speed_hz = spi->max_speed_hz; + if (aq->caps->init) + return aq->caps->init(aq); + return atmel_qspi_sama7g5_init(aq); } @@ -1570,6 +1576,9 @@ static int __maybe_unused atmel_qspi_resume(struct device *dev) return ret; } + if (aq->caps->init) + return aq->caps->init(aq); + if (aq->caps->has_gclk) return atmel_qspi_sama7g5_init(aq); From b881f1d8d1dfbc4480776d3b16f80261a5a68cbc Mon Sep 17 00:00:00 2001 From: Robert Marko Date: Thu, 9 Jul 2026 13:19:09 +0200 Subject: [PATCH 026/132] spi: atmel-quadspi: use init callback for gclk variants Assign the existing SAMA7G5 initialization routine to every generic clock variant and dispatch initialization exclusively through the capability callback. This keeps hardware capabilities separate from initialization selection and lets variants override the sequence explicitly. Signed-off-by: Robert Marko Link: https://patch.msgid.link/20260709112006.390742-4-robert.marko@sartura.hr Signed-off-by: Mark Brown --- drivers/spi/atmel-quadspi.c | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/drivers/spi/atmel-quadspi.c b/drivers/spi/atmel-quadspi.c index 62ea84d234d0..d1aec14e3978 100644 --- a/drivers/spi/atmel-quadspi.c +++ b/drivers/spi/atmel-quadspi.c @@ -1159,10 +1159,7 @@ static int atmel_qspi_sama7g5_setup(struct spi_device *spi) /* The controller can communicate with a single peripheral device (target). */ aq->target_max_speed_hz = spi->max_speed_hz; - if (aq->caps->init) - return aq->caps->init(aq); - - return atmel_qspi_sama7g5_init(aq); + return aq->caps->init(aq); } static int atmel_qspi_setup(struct spi_device *spi) @@ -1576,11 +1573,8 @@ static int __maybe_unused atmel_qspi_resume(struct device *dev) return ret; } - if (aq->caps->init) - return aq->caps->init(aq); - if (aq->caps->has_gclk) - return atmel_qspi_sama7g5_init(aq); + return aq->caps->init(aq); ret = pm_runtime_force_resume(dev); if (ret < 0) @@ -1638,6 +1632,7 @@ static const struct atmel_qspi_caps atmel_sam9x60_qspi_caps = { static const struct atmel_qspi_caps atmel_sam9x7_ospi_caps = { .max_speed_hz = SAM9X7_QSPI_MAX_SPEED_HZ, + .init = atmel_qspi_sama7g5_init, .has_gclk = true, .octal = true, .has_dma = true, @@ -1648,6 +1643,7 @@ static const struct atmel_qspi_caps atmel_sam9x7_ospi_caps = { static const struct atmel_qspi_caps atmel_sama7d65_ospi_caps = { .max_speed_hz = SAMA7G5_QSPI0_MAX_SPEED_HZ, + .init = atmel_qspi_sama7g5_init, .has_gclk = true, .octal = true, .has_dma = true, @@ -1658,6 +1654,7 @@ static const struct atmel_qspi_caps atmel_sama7d65_ospi_caps = { static const struct atmel_qspi_caps atmel_sama7d65_qspi_caps = { .max_speed_hz = SAMA7G5_QSPI1_SDR_MAX_SPEED_HZ, + .init = atmel_qspi_sama7g5_init, .has_gclk = true, .has_dma = true, .has_2xgclk = true, @@ -1666,6 +1663,7 @@ static const struct atmel_qspi_caps atmel_sama7d65_qspi_caps = { static const struct atmel_qspi_caps atmel_sama7g5_ospi_caps = { .max_speed_hz = SAMA7G5_QSPI0_MAX_SPEED_HZ, + .init = atmel_qspi_sama7g5_init, .has_gclk = true, .octal = true, .has_dma = true, @@ -1675,6 +1673,7 @@ static const struct atmel_qspi_caps atmel_sama7g5_ospi_caps = { static const struct atmel_qspi_caps atmel_sama7g5_qspi_caps = { .max_speed_hz = SAMA7G5_QSPI1_SDR_MAX_SPEED_HZ, + .init = atmel_qspi_sama7g5_init, .has_gclk = true, .has_dma = true, .has_dllon = true, From a3e4130b6ef964e2cf4d7f0a5b33b5c4862c5279 Mon Sep 17 00:00:00 2001 From: Robert Marko Date: Thu, 9 Jul 2026 13:19:10 +0200 Subject: [PATCH 027/132] spi: atmel-quadspi: add LAN969x QSPI support Microchip LAN969x has two QSPI controllers based on SAMA7G5 QSPI. It requires pad calibration, supports DMA, and supports 100 MHz operation. Signed-off-by: Robert Marko Link: https://patch.msgid.link/20260709112006.390742-5-robert.marko@sartura.hr Signed-off-by: Mark Brown --- drivers/spi/Kconfig | 2 +- drivers/spi/atmel-quadspi.c | 66 ++++++++++++++++++++++++++++++++++++- 2 files changed, 66 insertions(+), 2 deletions(-) diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index 8782514bb89b..bb3773f99ad9 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -189,7 +189,7 @@ config SPI_AT91_USART config SPI_ATMEL_QUADSPI tristate "Atmel Quad SPI Controller" - depends on ARCH_AT91 || COMPILE_TEST + depends on ARCH_MICROCHIP || COMPILE_TEST depends on OF && HAS_IOMEM help This enables support for the Quad SPI controller in master mode. diff --git a/drivers/spi/atmel-quadspi.c b/drivers/spi/atmel-quadspi.c index d1aec14e3978..f05e2617fa89 100644 --- a/drivers/spi/atmel-quadspi.c +++ b/drivers/spi/atmel-quadspi.c @@ -1152,6 +1152,58 @@ static int atmel_qspi_sama7g5_init(struct atmel_qspi *aq) return ret; } +static int atmel_qspi_lan969x_init(struct atmel_qspi *aq) +{ + u32 val; + int ret; + + atmel_qspi_write(QSPI_CR_DLLOFF, aq, QSPI_CR); + ret = readl_poll_timeout(aq->regs + QSPI_SR2, val, + !(val & QSPI_SR2_DLOCK), 40, + ATMEL_QSPI_TIMEOUT); + if (ret) + return ret; + + ret = atmel_qspi_set_gclk(aq); + if (ret) + return ret; + + /* Start the DLL before resetting the controller. */ + atmel_qspi_write(QSPI_CR_DLLON | QSPI_CR_STPCAL, aq, QSPI_CR); + ret = readl_poll_timeout(aq->regs + QSPI_SR2, val, + (val & QSPI_SR2_DLOCK) && + !(val & QSPI_SR2_CALBSY), 40, + ATMEL_QSPI_TIMEOUT); + if (ret) + return ret; + + atmel_qspi_write(QSPI_CR_QSPIDIS, aq, QSPI_CR); + ret = atmel_qspi_reg_sync(aq); + if (ret) + return ret; + + atmel_qspi_write(QSPI_CR_SWRST, aq, QSPI_CR); + ret = atmel_qspi_reg_sync(aq); + if (ret) + return ret; + + ret = atmel_qspi_set_pad_calibration(aq); + if (ret) + return ret; + + aq->mr = 0; + aq->scr = 0; + + ret = atmel_qspi_set_serial_memory_mode(aq); + if (ret) + return ret; + + atmel_qspi_write(QSPI_CR_QSPIEN, aq, QSPI_CR); + return readl_poll_timeout(aq->regs + QSPI_SR2, val, + (val & QSPI_SR2_QSPIENS), 40, + ATMEL_QSPI_TIMEOUT); +} + static int atmel_qspi_sama7g5_setup(struct spi_device *spi) { struct atmel_qspi *aq = spi_controller_get_devdata(spi->controller); @@ -1679,6 +1731,15 @@ static const struct atmel_qspi_caps atmel_sama7g5_qspi_caps = { .has_dllon = true, }; +static const struct atmel_qspi_caps atmel_lan969x_qspi_caps = { + .max_speed_hz = SAM9X7_QSPI_MAX_SPEED_HZ, + .init = atmel_qspi_lan969x_init, + .has_gclk = true, + .has_dma = true, + .has_padcalib = true, + .has_dllon = true, +}; + static const struct of_device_id atmel_qspi_dt_ids[] = { { .compatible = "atmel,sama5d2-qspi", @@ -1708,7 +1769,10 @@ static const struct of_device_id atmel_qspi_dt_ids[] = { .compatible = "microchip,sama7d65-qspi", .data = &atmel_sama7d65_qspi_caps, }, - + { + .compatible = "microchip,lan9691-qspi", + .data = &atmel_lan969x_qspi_caps, + }, { /* sentinel */ } }; From fc46551cfa3b30ac96ee40a0cdb61c35b063ba8c Mon Sep 17 00:00:00 2001 From: Babanpreet Singh Date: Mon, 13 Jul 2026 06:08:06 +0000 Subject: [PATCH 028/132] spi: axiado: merge identical if/else branches in ax_transfer_one() The else-if arm taken for RX-only and full-duplex transfers and the trailing else arm in the RX bookkeeping setup of ax_transfer_one() have identical bodies, so the second condition has no effect: drivers/spi/spi-axiado.c:433:8-10: WARNING: possible condition with no effect (if == else) The trailing else arm (neither TX nor RX buffer) is also unreachable: the SPI core only calls the ->transfer_one() callback for transfers that carry at least one buffer, see spi_transfer_one_message(). Merge the two arms into a single else branch and fold their comments. No functional change. The redundant condition has been present since the driver was added in commit e75a6b00ad79 ("spi: axiado: Add driver for Axiado SPI DB controller"). Reported-by: kernel test robot Reported-by: Julia Lawall Closes: https://lore.kernel.org/r/202607121827.djB0zLAj-lkp@intel.com/ Assisted-by: Claude:claude-fable-5 [coccinelle] Signed-off-by: Babanpreet Singh Link: https://patch.msgid.link/20260713060807.7-2-bbnpreetsingh@gmail.com Signed-off-by: Mark Brown --- drivers/spi/spi-axiado.c | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/drivers/spi/spi-axiado.c b/drivers/spi/spi-axiado.c index 649f149617ce..c4bc7a50d6c9 100644 --- a/drivers/spi/spi-axiado.c +++ b/drivers/spi/spi-axiado.c @@ -430,15 +430,11 @@ static int ax_transfer_one(struct spi_controller *ctlr, /* TX mode: discard all received data */ xspi->rx_discard = transfer->len; xspi->rx_copy_remaining = 0; - } else if ((!transfer->tx_buf && transfer->rx_buf) || - (transfer->tx_buf && transfer->rx_buf)) { - /* RX mode: generate clock by filling TX FIFO with dummy bytes - * Full-duplex mode: generate clock by filling TX FIFO - */ - xspi->rx_discard = 0; - xspi->rx_copy_remaining = transfer->len; } else { - /* No TX and RX */ + /* RX-only or full-duplex mode: copy received data, with the + * clock generated by filling the TX FIFO (with dummy bytes + * in RX-only mode) + */ xspi->rx_discard = 0; xspi->rx_copy_remaining = transfer->len; } From 7720f98d08fddd21369ddfe048900cc89efd99b3 Mon Sep 17 00:00:00 2001 From: Babanpreet Singh Date: Mon, 13 Jul 2026 06:08:07 +0000 Subject: [PATCH 029/132] spi: axiado: fix kernel-doc comments Running kernel-doc -Wall on the driver reports 7 warnings, all present since the driver was added in commit e75a6b00ad79 ("spi: axiado: Add driver for Axiado SPI DB controller"). Two are in spi-axiado.c and also show up in W=1 builds of drivers/spi: Warning: drivers/spi/spi-axiado.c:226 expecting prototype for ax_spi_get_rx_byte(). Prototype was for ax_spi_get_rx_byte_for_irq() instead Warning: drivers/spi/spi-axiado.c:248 This comment starts with '/**', but isn't a kernel-doc comment. Refer to Documentation/doc-guide/kernel-doc.rst The other five are for struct ax_spi in spi-axiado.h, seen when kernel-doc is invoked on the header directly (headers are not scanned by the build-time kernel-doc checks): Warning: drivers/spi/spi-axiado.h:130 Excess struct member 'rxbuf' description in 'ax_spi' Warning: drivers/spi/spi-axiado.h:130 Excess struct member 'txbuf' description in 'ax_spi' Warning: drivers/spi/spi-axiado.h:130 struct member 'clk_rate' not described in 'ax_spi' Warning: drivers/spi/spi-axiado.h:130 struct member 'rx_buf' not described in 'ax_spi' Warning: drivers/spi/spi-axiado.h:130 struct member 'tx_buf' not described in 'ax_spi' Fix the three underlying defects: - The comment on ax_spi_get_rx_byte_for_irq() carries the name of ax_spi_get_rx_byte(), from which it was evidently copied when the IRQ variant was split out; fix the function name. - The comment on ax_spi_process_rx_and_finalize() is opened with the kernel-doc /** marker but has no identifier line; turn it into proper kernel-doc (identifier line, @ctlr, Return:) since it already describes the behavior and the return value. - The struct ax_spi comment documents @txbuf and @rxbuf while the members are named tx_buf and rx_buf, and does not document clk_rate; fix the member names and describe clk_rate. No functional change. Assisted-by: Claude:claude-fable-5 [kernel-doc] Signed-off-by: Babanpreet Singh Acked-By: Tzu-Hao Wei Link: https://patch.msgid.link/20260713060807.7-3-bbnpreetsingh@gmail.com Signed-off-by: Mark Brown --- drivers/spi/spi-axiado.c | 8 ++++++-- drivers/spi/spi-axiado.h | 5 +++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/drivers/spi/spi-axiado.c b/drivers/spi/spi-axiado.c index c4bc7a50d6c9..25561aa9f76f 100644 --- a/drivers/spi/spi-axiado.c +++ b/drivers/spi/spi-axiado.c @@ -213,7 +213,7 @@ static void ax_spi_fill_tx_fifo(struct ax_spi *xspi) } /** - * ax_spi_get_rx_byte - Gets a byte from the RX FIFO buffer + * ax_spi_get_rx_byte_for_irq - Gets a byte from the RX FIFO buffer * @xspi: Controller private data (struct ax_spi *) * * This function handles the logic of extracting bytes from the 32-bit RX FIFO. @@ -246,9 +246,13 @@ static u8 ax_spi_get_rx_byte_for_irq(struct ax_spi *xspi) } /** + * ax_spi_process_rx_and_finalize - Process RX bytes and check for completion + * @ctlr: Pointer to spi_controller structure + * * Helper function to process received bytes and check for transfer completion. * This avoids code duplication and centralizes the completion logic. - * Returns true if the transfer was finalized. + * + * Return: true if the transfer was finalized. */ static bool ax_spi_process_rx_and_finalize(struct spi_controller *ctlr) { diff --git a/drivers/spi/spi-axiado.h b/drivers/spi/spi-axiado.h index 6cf0e5bf5879..d24d31c3a446 100644 --- a/drivers/spi/spi-axiado.h +++ b/drivers/spi/spi-axiado.h @@ -98,9 +98,10 @@ * @regs: Virtual address of the SPI controller registers * @ref_clk: Pointer to the peripheral clock * @pclk: Pointer to the APB clock + * @clk_rate: Reference clock rate in Hz * @speed_hz: Current SPI bus clock speed in Hz - * @txbuf: Pointer to the TX buffer - * @rxbuf: Pointer to the RX buffer + * @tx_buf: Pointer to the TX buffer + * @rx_buf: Pointer to the RX buffer * @tx_bytes: Number of bytes left to transfer * @rx_bytes: Number of bytes requested * @tx_fifo_depth: Depth of the TX FIFO From af4b98e33d5607f80b49e792e0a74cb068d810f0 Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Wed, 15 Jul 2026 23:24:16 +0100 Subject: [PATCH 030/132] spi: spi-rpc-if: Implement 200ms runtime PM autosuspend delay Enable and configure the runtime PM autosuspend infrastructure during the controller probe sequence to safeguard chunked flash operations. The underlying hardware controller handles memory-mapped page programming by dividing data transfers into an automated sequence of consecutive 64-byte chunks. To prevent the power management framework from aggressively gating the interface between these individual chunk frames or during immediate out-of-band status checks, an explicit 200ms delay window is required. Configure this temporal cushion using pm_runtime_set_autosuspend_delay() and pm_runtime_use_autosuspend() at probe time, ensuring proper cleanup via pm_runtime_dont_use_autosuspend() in error and driver removal pathways. This guarantees interface continuity across the full lifecycle of a multi-chunk write operation. Signed-off-by: Lad Prabhakar Acked-by: Mark Brown Link: https://patch.msgid.link/20260715222417.2997712-7-prabhakar.mahadev-lad.rj@bp.renesas.com Signed-off-by: Mark Brown --- drivers/spi/spi-rpc-if.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/spi/spi-rpc-if.c b/drivers/spi/spi-rpc-if.c index b63c7856e758..9a740342d0f7 100644 --- a/drivers/spi/spi-rpc-if.c +++ b/drivers/spi/spi-rpc-if.c @@ -161,6 +161,8 @@ static int rpcif_spi_probe(struct platform_device *pdev) ctlr->dev.of_node = parent->of_node; + pm_runtime_set_autosuspend_delay(rpc->dev, 200); + pm_runtime_use_autosuspend(rpc->dev); pm_runtime_enable(rpc->dev); ctlr->num_chipselect = 1; @@ -183,6 +185,7 @@ static int rpcif_spi_probe(struct platform_device *pdev) return 0; out_disable_rpm: + pm_runtime_dont_use_autosuspend(rpc->dev); pm_runtime_disable(rpc->dev); return error; } @@ -193,6 +196,7 @@ static void rpcif_spi_remove(struct platform_device *pdev) struct rpcif *rpc = spi_controller_get_devdata(ctlr); spi_unregister_controller(ctlr); + pm_runtime_dont_use_autosuspend(rpc->dev); pm_runtime_disable(rpc->dev); } From 6f99efb115269b822d9a13139fbdfbd4b5ca6739 Mon Sep 17 00:00:00 2001 From: Han Xu Date: Wed, 22 Jul 2026 17:00:42 -0500 Subject: [PATCH 031/132] spi: dt-bindings: nxp,imx94-xspi: add DMA properties Add dmas and dma-names to describe TX and RX DMA channels for the i.MX94 XSPI controller. Reviewed-by: Frank Li Acked-by: Conor Dooley Signed-off-by: Han Xu Link: https://patch.msgid.link/20260722-xspi_dma_upstream-v3-1-6b7ff50cae8e@nxp.com Signed-off-by: Mark Brown --- .../devicetree/bindings/spi/nxp,imx94-xspi.yaml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Documentation/devicetree/bindings/spi/nxp,imx94-xspi.yaml b/Documentation/devicetree/bindings/spi/nxp,imx94-xspi.yaml index 16a0598c6d03..ccf841f194c0 100644 --- a/Documentation/devicetree/bindings/spi/nxp,imx94-xspi.yaml +++ b/Documentation/devicetree/bindings/spi/nxp,imx94-xspi.yaml @@ -30,6 +30,16 @@ properties: - const: base - const: mmap + dmas: + items: + - description: Transmit DMA + - description: Receive DMA + + dma-names: + items: + - const: tx + - const: rx + interrupts: items: - description: interrupt for EENV0 From 38d11bb2cd1d902d7ab178f0a6d78d2bafd2d720 Mon Sep 17 00:00:00 2001 From: Han Xu Date: Wed, 22 Jul 2026 17:00:43 -0500 Subject: [PATCH 032/132] spi: nxp-xspi: Improve AHB read performance with DMA Use eDMA for XSPI AHB read to improve the performance. Read with CPU root@imx943evk:~# dd if=/dev/mtd0 of=/dev/null bs=32M count=1 1+0 records in 1+0 records out 33554432 bytes (34 MB, 32 MiB) copied, 0.479719 s, 69.9 MB/s Read with DMA root@imx943evk:~# dd if=/dev/mtd0 of=/dev/null bs=32M count=1 1+0 records in 1+0 records out 33554432 bytes (34 MB, 32 MiB) copied, 0.115788 s, 290 MB/s Signed-off-by: Han Xu Reviewed-by: Frank Li Link: https://patch.msgid.link/20260722-xspi_dma_upstream-v3-2-6b7ff50cae8e@nxp.com Signed-off-by: Mark Brown --- drivers/spi/spi-nxp-xspi.c | 143 ++++++++++++++++++++++++++++++++++++- 1 file changed, 140 insertions(+), 3 deletions(-) diff --git a/drivers/spi/spi-nxp-xspi.c b/drivers/spi/spi-nxp-xspi.c index 037eac24e6fd..70cbc3ef658b 100644 --- a/drivers/spi/spi-nxp-xspi.c +++ b/drivers/spi/spi-nxp-xspi.c @@ -35,6 +35,8 @@ #include #include #include +#include +#include #include #include #include @@ -303,6 +305,12 @@ (opr)) << (((idx) % 2) * OPRND_SHIFT)) #define NXP_XSPI_MIN_IOMAP SZ_4M +/* + * It takes 0.5s to access the entire area with DMA, + * leaving a 10x safety margin + */ +#define NXP_XSPI_DMA_TOUT_MS 5000 +#define NXP_XSPI_DMA_TOUT msecs_to_jiffies(NXP_XSPI_DMA_TOUT_MS) #define NXP_XSPI_MAX_CHIPSELECT 2 #define POLL_TOUT_US 5000 @@ -336,6 +344,9 @@ struct nxp_xspi { /* mutex lock for each operation */ struct mutex lock; int selected; + int dma_copy_align; + struct dma_chan *dma_rx; + struct completion dma_rx_c; #define XSPI_DTR_PROTO BIT(0) int flags; /* Save the previous operation clock rate */ @@ -796,6 +807,95 @@ static int nxp_xspi_ahb_read(struct nxp_xspi *xspi, const struct spi_mem_op *op) return 0; } +static int nxp_xspi_dma_init(struct device *dev, struct nxp_xspi *xspi) +{ + struct dma_chan *rx; + + rx = dma_request_chan(dev, "rx"); + if (IS_ERR(rx)) { + int ret = PTR_ERR(rx); + + if (ret == -EPROBE_DEFER) + return ret; + dev_dbg(dev, "NO DMA RX channel, falling back to CPU read\n"); + xspi->dma_copy_align = 1; + return 0; + } + + xspi->dma_rx = rx; + init_completion(&xspi->dma_rx_c); + xspi->dma_copy_align = 1 << rx->device->copy_align; + + return 0; +} + +static void nxp_xspi_dma_rx_callback(void *data) +{ + struct nxp_xspi *xspi = data; + + complete(&xspi->dma_rx_c); +} + +static int nxp_xspi_ahb_dma_read(struct nxp_xspi *xspi, + const struct spi_mem_op *op) +{ + struct dma_async_tx_descriptor *desc; + struct dma_chan *chan = xspi->dma_rx; + unsigned long timeout; + dma_addr_t src_addr, dst_addr; + int ret = 0; + + dst_addr = dma_map_single(chan->device->dev, + op->data.buf.in, op->data.nbytes, + DMA_FROM_DEVICE); + if (dma_mapping_error(chan->device->dev, dst_addr)) { + dev_err(xspi->dev, "failed to map DMA dst buffer\n"); + return -ENOMEM; + } + + src_addr = dma_map_resource(chan->device->dev, + (phys_addr_t)xspi->memmap_phy + op->addr.val, + op->data.nbytes, + DMA_TO_DEVICE, 0); + if (dma_mapping_error(chan->device->dev, src_addr)) { + dev_err(xspi->dev, "failed to map DMA src resource\n"); + ret = -ENOMEM; + goto err_unmap_dst; + } + + desc = dmaengine_prep_dma_memcpy(chan, dst_addr, src_addr, + op->data.nbytes, + DMA_PREP_INTERRUPT | DMA_CTRL_ACK); + if (!desc) { + dev_err(xspi->dev, "failed to prepare AHB RX DMA descriptor\n"); + ret = -EIO; + goto err_unmap_src; + } + + desc->callback = nxp_xspi_dma_rx_callback; + desc->callback_param = xspi; + reinit_completion(&xspi->dma_rx_c); + dmaengine_submit(desc); + dma_async_issue_pending(chan); + + timeout = wait_for_completion_timeout(&xspi->dma_rx_c, NXP_XSPI_DMA_TOUT); + if (!timeout) { + dev_err(xspi->dev, "AHB RX DMA timeout\n"); + dmaengine_terminate_sync(chan); + ret = -ETIMEDOUT; + } + +err_unmap_src: + dma_unmap_resource(chan->device->dev, src_addr, + op->data.nbytes, DMA_TO_DEVICE, 0); + +err_unmap_dst: + dma_unmap_single(chan->device->dev, dst_addr, + op->data.nbytes, DMA_FROM_DEVICE); + + return ret; +} + static int nxp_xspi_fill_txfifo(struct nxp_xspi *xspi, const struct spi_mem_op *op) { @@ -1016,10 +1116,21 @@ static int nxp_xspi_exec_op(struct spi_mem *mem, const struct spi_mem_op *op) * all use IP write. */ if ((op->data.dir == SPI_MEM_DATA_IN) && !needs_ip_only(xspi) - && ((op->addr.val + op->data.nbytes) <= xspi->memmap_phy_size)) - err = nxp_xspi_ahb_read(xspi, op); - else + && ((op->addr.val + op->data.nbytes) <= xspi->memmap_phy_size)) { + /* use DMA for transfers no less than ahb_buf_size, when DMA is available */ + if (xspi->dma_rx && op->data.nbytes >= xspi->devtype_data->ahb_buf_size) { + err = nxp_xspi_ahb_dma_read(xspi, op); + if (err) { + dev_warn_ratelimited(xspi->dev, + "DMA read failed (%d), falling back to CPU read\n", err); + err = nxp_xspi_ahb_read(xspi, op); + } + } else { + err = nxp_xspi_ahb_read(xspi, op); + } + } else { err = nxp_xspi_do_op(xspi, op); + } nxp_xspi_sw_reset(xspi); @@ -1029,6 +1140,7 @@ static int nxp_xspi_exec_op(struct spi_mem *mem, const struct spi_mem_op *op) static int nxp_xspi_adjust_op_size(struct spi_mem *mem, struct spi_mem_op *op) { struct nxp_xspi *xspi = spi_controller_get_devdata(mem->spi->controller); + int alignment = xspi->dma_copy_align; if (op->data.dir == SPI_MEM_DATA_OUT) { if (op->data.nbytes > xspi->devtype_data->txfifo) @@ -1042,6 +1154,22 @@ static int nxp_xspi_adjust_op_size(struct spi_mem *mem, struct spi_mem_op *op) if (!needs_ip_only(xspi) && (op->addr.val < xspi->memmap_phy_size) && ((op->addr.val + op->data.nbytes) > xspi->memmap_phy_size)) op->data.nbytes = xspi->memmap_phy_size - op->addr.val; + + /* + * For AHB DMA read, align the transfer to the DMA alignment + * boundaries. If the start address is unaligned, shorten this + * transfer so the next one starts on an aligned boundary. + * Otherwise, if the length is unaligned, round it down. + */ + if (xspi->dma_rx && !needs_ip_only(xspi) && + (op->data.nbytes >= xspi->devtype_data->ahb_buf_size)) { + if (op->addr.val & (alignment - 1)) + op->data.nbytes = + ALIGN(op->addr.val, alignment) - op->addr.val; + else if (op->data.nbytes & (alignment - 1)) + op->data.nbytes = + ALIGN_DOWN(op->data.nbytes, alignment); + } } return 0; @@ -1203,6 +1331,10 @@ static void nxp_xspi_cleanup(void *data) if (xspi->ahb_addr) iounmap(xspi->ahb_addr); + if (xspi->dma_rx) { + dmaengine_terminate_sync(xspi->dma_rx); + dma_release_channel(xspi->dma_rx); + } } static int nxp_xspi_probe(struct platform_device *pdev) @@ -1283,6 +1415,11 @@ static int nxp_xspi_probe(struct platform_device *pdev) if (ret) return ret; + /* DMA is optional, failure(other than -EPROBE_DEFER) falls back to CPU */ + ret = nxp_xspi_dma_init(dev, xspi); + if (ret == -EPROBE_DEFER) + return ret; + ctlr->bus_num = -1; ctlr->num_chipselect = NXP_XSPI_MAX_CHIPSELECT; ctlr->mem_ops = &nxp_xspi_mem_ops; From a44a020465e34dacf5ca2f558ee920ec5add3eca Mon Sep 17 00:00:00 2001 From: Shawn Guo Date: Tue, 14 Jul 2026 20:02:26 +0800 Subject: [PATCH 033/132] spi: dt-bindings: qcom,sa8255p-geni-spi: Add compatible for Nord SA8797P Document Nord SA8797P QUP GENI SPI controller, which is compatible with SA8255P controller. Signed-off-by: Shawn Guo Reviewed-by: Bartosz Golaszewski Reviewed-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260714120226.1788391-1-shengchao.guo@oss.qualcomm.com Signed-off-by: Mark Brown --- .../devicetree/bindings/spi/qcom,sa8255p-geni-spi.yaml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/spi/qcom,sa8255p-geni-spi.yaml b/Documentation/devicetree/bindings/spi/qcom,sa8255p-geni-spi.yaml index d9347d780ca4..6552303a4f52 100644 --- a/Documentation/devicetree/bindings/spi/qcom,sa8255p-geni-spi.yaml +++ b/Documentation/devicetree/bindings/spi/qcom,sa8255p-geni-spi.yaml @@ -11,7 +11,11 @@ maintainers: properties: compatible: - const: qcom,sa8255p-geni-spi + oneOf: + - const: qcom,sa8255p-geni-spi + - items: + - const: qcom,sa8797p-geni-spi + - const: qcom,sa8255p-geni-spi reg: maxItems: 1 From ed4b52b24c556cd681b99739e2bbef7cf48fe66e Mon Sep 17 00:00:00 2001 From: Md Sadre Alam Date: Thu, 16 Jul 2026 11:13:05 +0530 Subject: [PATCH 034/132] spi: spi-qpic-snand: Increase ECC strength for large OOB SPI-NAND devices Some SPI-NAND devices with large OOB areas require a stronger ECC configuration than the default one provided through firmware description. For example, Macronix devices with a 256-byte OOB area report that 4-bit ECC is insufficient and trigger: nand: WARNING: the ECC used on your system is too weak compared to the one required by the NAND chip The QPIC SPI-NAND controller supports both 4-bit and 8-bit BCH ECC modes. When a device exposes an OOB area of at least 128 bytes, there is sufficient space to accommodate 8-bit ECC without exceeding the OOB layout constraints. Automatically increase the ECC strength to 8 bits when the detected OOB size is 128 bytes or larger and the current configuration requests a weaker ECC level. This avoids ECC capability mismatches on devices that require stronger error correction and improves reliability. Signed-off-by: Md Sadre Alam Link: https://patch.msgid.link/20260716-8bitecc-v1-1-6eef4bcc3a14@oss.qualcomm.com Signed-off-by: Mark Brown --- drivers/spi/spi-qpic-snand.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/drivers/spi/spi-qpic-snand.c b/drivers/spi/spi-qpic-snand.c index 6cc53586b8a8..19812400124f 100644 --- a/drivers/spi/spi-qpic-snand.c +++ b/drivers/spi/spi-qpic-snand.c @@ -274,6 +274,19 @@ static int qcom_spi_ecc_init_ctx_pipelined(struct nand_device *nand) ecc_cfg->strength = 4; } + /* + * Override ECC strength based on OOB size to avoid weak ECC warning. + * If OOB size is more than 128 bytes, use 8-bit ECC for better + * error correction capability, which is required by chips with + * larger OOB areas like Macronix SPI NAND with 256 bytes OOB. + */ + if (mtd->oobsize >= 128 && ecc_cfg->strength < 8) { + dev_info(snandc->dev, + "Upgrading ECC strength from %d to 8 bits (OOB size: %d bytes)\n", + ecc_cfg->strength, mtd->oobsize); + ecc_cfg->strength = 8; + } + if (ecc_cfg->step_size != NANDC_STEP_SIZE) { dev_err(snandc->dev, "only %u bytes ECC step size is supported\n", From d710f43ce30975d197f73c543bfe47b958d8ba17 Mon Sep 17 00:00:00 2001 From: Fan Wu Date: Sun, 19 Jul 2026 01:00:13 +0000 Subject: [PATCH 035/132] spi: oc-tiny: switch to managed controller allocation The controller is allocated with the non-managed spi_alloc_host() while the interrupt is registered with devm_request_irq(). During removal, spi_bitbang_stop() only unregisters the controller; the subsequent spi_controller_put() then frees the controller together with its embedded driver-private devdata, which is the IRQ handler's dev_id. The devm_request_irq() release action (free_irq()), which drains the handler, does not run until after .remove() returns. A late or latched interrupt can therefore reach tiny_spi_irq() and dereference already-freed memory (e.g. hw->base). Switch to devm_spi_alloc_host() so that the devres LIFO order releases the controller only after free_irq() has drained the handler, and drop the now-redundant spi_controller_put() from .remove(). The probe error path is simplified to direct returns. This issue was found by an in-house static analysis tool. Fixes: ce792580ea2c ("spi: add OpenCores tiny SPI driver") Assisted-by: Codex:gpt-5.6 Signed-off-by: Fan Wu Link: https://patch.msgid.link/20260719010014.3163356-1-fanwu01@zju.edu.cn Signed-off-by: Mark Brown --- drivers/spi/spi-oc-tiny.c | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/drivers/spi/spi-oc-tiny.c b/drivers/spi/spi-oc-tiny.c index 29333b1f82d7..1cd2a934c032 100644 --- a/drivers/spi/spi-oc-tiny.c +++ b/drivers/spi/spi-oc-tiny.c @@ -210,11 +210,11 @@ static int tiny_spi_probe(struct platform_device *pdev) struct tiny_spi_platform_data *platp = dev_get_platdata(&pdev->dev); struct tiny_spi *hw; struct spi_controller *host; - int err = -ENODEV; + int err; - host = spi_alloc_host(&pdev->dev, sizeof(struct tiny_spi)); + host = devm_spi_alloc_host(&pdev->dev, sizeof(struct tiny_spi)); if (!host) - return err; + return -ENOMEM; /* setup the host state. */ host->bus_num = pdev->id; @@ -232,10 +232,8 @@ static int tiny_spi_probe(struct platform_device *pdev) /* find and map our resources */ hw->base = devm_platform_ioremap_resource(pdev, 0); - if (IS_ERR(hw->base)) { - err = PTR_ERR(hw->base); - goto exit; - } + if (IS_ERR(hw->base)) + return PTR_ERR(hw->base); /* irq is optional */ hw->irq = platform_get_irq(pdev, 0); if (hw->irq >= 0) { @@ -243,7 +241,7 @@ static int tiny_spi_probe(struct platform_device *pdev) err = devm_request_irq(&pdev->dev, hw->irq, tiny_spi_irq, 0, pdev->name, hw); if (err) - goto exit; + return err; } /* find platform data */ if (platp) { @@ -252,29 +250,23 @@ static int tiny_spi_probe(struct platform_device *pdev) } else { err = tiny_spi_of_probe(pdev); if (err) - goto exit; + return err; } /* register our spi controller */ err = spi_bitbang_start(&hw->bitbang); if (err) - goto exit; + return err; dev_info(&pdev->dev, "base %p, irq %d\n", hw->base, hw->irq); return 0; - -exit: - spi_controller_put(host); - return err; } static void tiny_spi_remove(struct platform_device *pdev) { struct tiny_spi *hw = platform_get_drvdata(pdev); - struct spi_controller *host = hw->bitbang.ctlr; spi_bitbang_stop(&hw->bitbang); - spi_controller_put(host); } #ifdef CONFIG_OF From fd741c46ae0365d4bfd3c14d3a049b0515b9db23 Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Sun, 19 Jul 2026 17:30:43 -0700 Subject: [PATCH 036/132] spi: ppc4xx: devm-ify probe and drop manual resource management Replace open-coded resource handling with devm helpers: - spi_alloc_host -> devm_spi_alloc_host - of_address_to_resource + ioremap + request_mem_region -> devm_platform_ioremap_resource - request_irq -> devm_request_irq - remove now-unused mapbase/mapsize fields from struct ppc4xx_spi - move of_node_put(opbnp) earlier to simplify error paths - delete the entire error-unwinding goto chain Move devm_platform_ioremap_resource() and platform_get_irq() up in order to avoid doing work if -EPROBE_DEFER is returned. Assisted-by: opencode:big-pickle Signed-off-by: Rosen Penev Link: https://patch.msgid.link/20260720003043.1536358-1-rosenp@gmail.com Signed-off-by: Mark Brown --- drivers/spi/spi-ppc4xx.c | 97 +++++++++------------------------------- 1 file changed, 22 insertions(+), 75 deletions(-) diff --git a/drivers/spi/spi-ppc4xx.c b/drivers/spi/spi-ppc4xx.c index 46ac58dfb3fc..95c034cf2336 100644 --- a/drivers/spi/spi-ppc4xx.c +++ b/drivers/spi/spi-ppc4xx.c @@ -112,8 +112,6 @@ struct ppc4xx_spi { struct spi_bitbang bitbang; struct completion done; - u64 mapbase; - u64 mapsize; int irqnum; /* need this to set the SPI clock */ unsigned int opb_freq; @@ -337,14 +335,24 @@ static int spi_ppc4xx_of_probe(struct platform_device *op) struct ppc4xx_spi *hw; struct spi_controller *host; struct spi_bitbang *bbp; - struct resource resource; struct device_node *np = op->dev.of_node; struct device *dev = &op->dev; struct device_node *opbnp; int ret; const unsigned int *clk; + void __iomem *regs; + int irqnum; - host = spi_alloc_host(dev, sizeof(*hw)); + regs = devm_platform_ioremap_resource(op, 0); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + /* Request IRQ */ + irqnum = platform_get_irq(op, 0); + if (irqnum < 0) + return irqnum; + + host = devm_spi_alloc_host(dev, sizeof(*hw)); if (host == NULL) return -ENOMEM; host->dev.of_node = np; @@ -379,88 +387,31 @@ static int spi_ppc4xx_of_probe(struct platform_device *op) opbnp = of_find_compatible_node(NULL, NULL, "ibm,opb"); if (opbnp == NULL) { dev_err(dev, "OPB: cannot find node\n"); - ret = -ENODEV; - goto free_host; + return -ENODEV; } /* Get the clock (Hz) for the OPB */ clk = of_get_property(opbnp, "clock-frequency", NULL); + of_node_put(opbnp); if (clk == NULL) { dev_err(dev, "OPB: no clock-frequency property set\n"); - of_node_put(opbnp); - ret = -ENODEV; - goto free_host; + return -ENODEV; } hw->opb_freq = *clk; hw->opb_freq >>= 2; - of_node_put(opbnp); - ret = of_address_to_resource(np, 0, &resource); - if (ret) { - dev_err(dev, "error while parsing device node resource\n"); - goto free_host; - } - hw->mapbase = resource.start; - hw->mapsize = resource_size(&resource); + hw->regs = regs; + hw->irqnum = irqnum; - /* Sanity check */ - if (hw->mapsize < sizeof(struct spi_ppc4xx_regs)) { - dev_err(dev, "too small to map registers\n"); - ret = -EINVAL; - goto free_host; - } - - /* Request IRQ */ - ret = platform_get_irq(op, 0); - if (ret < 0) - goto free_host; - hw->irqnum = ret; - - ret = request_irq(hw->irqnum, spi_ppc4xx_int, - 0, "spi_ppc4xx_of", (void *)hw); - if (ret) { - dev_err(dev, "unable to allocate interrupt\n"); - goto free_host; - } - - if (!request_mem_region(hw->mapbase, hw->mapsize, DRIVER_NAME)) { - dev_err(dev, "resource unavailable\n"); - ret = -EBUSY; - goto request_mem_error; - } - - hw->regs = ioremap(hw->mapbase, sizeof(struct spi_ppc4xx_regs)); - - if (!hw->regs) { - dev_err(dev, "unable to memory map registers\n"); - ret = -ENXIO; - goto map_io_error; - } + ret = devm_request_irq(&op->dev, hw->irqnum, spi_ppc4xx_int, + 0, "spi_ppc4xx_of", hw); + if (ret) + return dev_err_probe(dev, ret, "unable to allocate interrupt\n"); spi_ppc4xx_enable(hw); /* Finally register our spi controller */ dev->dma_mask = 0; - ret = spi_bitbang_start(bbp); - if (ret) { - dev_err(dev, "failed to register SPI host\n"); - goto unmap_regs; - } - - dev_info(dev, "driver initialized\n"); - - return 0; - -unmap_regs: - iounmap(hw->regs); -map_io_error: - release_mem_region(hw->mapbase, hw->mapsize); -request_mem_error: - free_irq(hw->irqnum, hw); -free_host: - spi_controller_put(host); - - dev_err(dev, "initialization failed\n"); - return ret; + return spi_bitbang_start(bbp); } static void spi_ppc4xx_of_remove(struct platform_device *op) @@ -469,10 +420,6 @@ static void spi_ppc4xx_of_remove(struct platform_device *op) struct ppc4xx_spi *hw = spi_controller_get_devdata(host); spi_bitbang_stop(&hw->bitbang); - release_mem_region(hw->mapbase, hw->mapsize); - free_irq(hw->irqnum, hw); - iounmap(hw->regs); - spi_controller_put(host); } static const struct of_device_id spi_ppc4xx_of_match[] = { From 8955fbad8aad32d7e81fc75d23918e0e2cf6c5ef Mon Sep 17 00:00:00 2001 From: Pei Xiao Date: Mon, 20 Jul 2026 14:08:43 +0800 Subject: [PATCH 037/132] spi: spacemit: fix dangling TX DMA descriptor on RX prep failure In k1_spi_dma_one(), the TX DMA descriptor is submitted via dmaengine_submit() before the RX descriptor is prepared. If k1_spi_dma_prep() fails for RX, the function jumps to the fallback path without terminating the already-submitted TX descriptor. So terminate the TX channel with dmaengine_terminate_sync() when RX descriptor preparation fails. Fixes: efcd8b9d1111 ("spi: spacemit: introduce SpacemiT K1 SPI controller driver") Signed-off-by: Pei Xiao Link: https://patch.msgid.link/b402223ebff226782afd4c7da7db4ce34807f604.1784527556.git.xiaopei01@kylinos.cn Signed-off-by: Mark Brown --- drivers/spi/spi-spacemit-k1.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/spi/spi-spacemit-k1.c b/drivers/spi/spi-spacemit-k1.c index 215fe66d27b4..cd2955949c5c 100644 --- a/drivers/spi/spi-spacemit-k1.c +++ b/drivers/spi/spi-spacemit-k1.c @@ -289,8 +289,10 @@ static int k1_spi_dma_one(struct spi_controller *host, struct spi_device *spi, /* Prepare the RX descriptor and submit it */ desc = k1_spi_dma_prep(drv_data, transfer, false); - if (!desc) + if (!desc) { + dmaengine_terminate_sync(host->dma_tx); goto fallback; + } /* When RX is complete we also know TX has completed */ desc->callback = k1_spi_dma_callback; From 79052a6a6f5b019d24efbbb934f2d77a1db75211 Mon Sep 17 00:00:00 2001 From: Pei Xiao Date: Mon, 20 Jul 2026 14:08:44 +0800 Subject: [PATCH 038/132] spi: spacemit: drop redundant dev_err_probe() around irq helpers platform_get_irq() and devm_request_irq() already print an error message via dev_err_probe() on failure, so wrapping them with another dev_err_probe() results in duplicate error output. Return the error code directly instead. Signed-off-by: Pei Xiao Link: https://patch.msgid.link/119b5f8e3ac68221307cbfcd227dc8546be0eaeb.1784527556.git.xiaopei01@kylinos.cn Signed-off-by: Mark Brown --- drivers/spi/spi-spacemit-k1.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/spi/spi-spacemit-k1.c b/drivers/spi/spi-spacemit-k1.c index cd2955949c5c..4348d77ec77c 100644 --- a/drivers/spi/spi-spacemit-k1.c +++ b/drivers/spi/spi-spacemit-k1.c @@ -737,12 +737,12 @@ static int k1_spi_probe(struct platform_device *pdev) drv_data->irq = platform_get_irq(pdev, 0); if (drv_data->irq < 0) - return dev_err_probe(dev, drv_data->irq, "error getting IRQ\n"); + return drv_data->irq; ret = devm_request_irq(dev, drv_data->irq, k1_spi_ssp_isr, IRQF_SHARED, dev_name(dev), drv_data); if (ret < 0) - return dev_err_probe(dev, ret, "error requesting IRQ\n"); + return ret; /* Initialize the host structure, then register it */ host->dev.of_node = dev_of_node(dev); From ea408a05dc8f18b4a184b88d6e19d2fd1acc1527 Mon Sep 17 00:00:00 2001 From: Fan Wu Date: Sun, 19 Jul 2026 01:00:14 +0000 Subject: [PATCH 039/132] spi: davinci: switch to managed controller allocation The controller is allocated with the non-managed spi_alloc_host() while the interrupt is registered with devm_request_threaded_irq(). During removal, spi_bitbang_stop() only unregisters the controller; the subsequent spi_controller_put() then frees the controller together with its embedded davinci_spi devdata, which is the IRQ handler's dev_id. The devm_request_threaded_irq() release action (free_irq()), which drains the handler, does not run until after .remove() returns. A late or latched interrupt can therefore reach davinci_spi_irq() and dereference already-freed memory. Switch to devm_spi_alloc_host() so that the devres LIFO order releases the controller only after free_irq() has drained the handler, and drop the now-redundant spi_controller_put() from .remove(). The probe error path is simplified to direct returns. The clock is acquired with devm_clk_get_enabled(), which is registered after the IRQ and thus released before it by the devres LIFO order. Drain the interrupt explicitly with devm_free_irq() before disabling the controller so that a late interrupt cannot access the registers of a clock-gated controller. This issue was found by an in-house static analysis tool. Fixes: 5b3bb5963ff2 ("spi: davinci: Use devm_*() functions") Assisted-by: Codex:gpt-5.6 Signed-off-by: Fan Wu Link: https://patch.msgid.link/20260719010014.3163356-2-fanwu01@zju.edu.cn Signed-off-by: Mark Brown --- drivers/spi/spi-davinci.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/drivers/spi/spi-davinci.c b/drivers/spi/spi-davinci.c index da7f2ae3a570..087735ed9c80 100644 --- a/drivers/spi/spi-davinci.c +++ b/drivers/spi/spi-davinci.c @@ -928,7 +928,7 @@ static int davinci_spi_probe(struct platform_device *pdev) int ret = 0; u32 spipc0; - host = spi_alloc_host(&pdev->dev, sizeof(struct davinci_spi)); + host = devm_spi_alloc_host(&pdev->dev, sizeof(struct davinci_spi)); if (host == NULL) { ret = -ENOMEM; goto err; @@ -1057,7 +1057,6 @@ static int davinci_spi_probe(struct platform_device *pdev) dma_release_channel(dspi->dma_tx); } free_host: - spi_controller_put(host); err: return ret; } @@ -1081,6 +1080,8 @@ static void davinci_spi_remove(struct platform_device *pdev) spi_bitbang_stop(&dspi->bitbang); + devm_free_irq(&pdev->dev, dspi->irq, dspi); + /* This bit needs to be cleared to disable dpsi->clk */ clear_io_bits(dspi->base + SPIGCR1, SPIGCR1_POWERDOWN_MASK); @@ -1088,8 +1089,6 @@ static void davinci_spi_remove(struct platform_device *pdev) dma_release_channel(dspi->dma_rx); dma_release_channel(dspi->dma_tx); } - - spi_controller_put(host); } static struct platform_driver davinci_spi_driver = { From f40e7ab049f4ba588619336886c1867127d9499f Mon Sep 17 00:00:00 2001 From: Praveen Talari Date: Wed, 29 Jul 2026 20:43:25 +0530 Subject: [PATCH 040/132] soc: qcom: geni-se: trace: Add trace event support for GENI SE registers dump Diagnosing GENI SE-based driver (serial, SPI, I2C) failures currently requires reading each hardware register individually, either through ad hoc debug code or a debugger. This is slow, requires the state to remain stable across the multiple reads, and cannot be run non-intrusively during normal operation without adding printk-style noise to each driver. Add a new trace event header for the Qualcomm GENI Serial Engine (SE) framework providing a geni_se_regs tracepoint. This tracepoint captures a comprehensive snapshot of the GENI SE hardware state in a single trace record, making it possible to correlate register values at a precise point in time without multiple sequential reads. The trace event records the following register groups: - Main/secondary command and IRQ status (M_CMD0, S_CMD0, M/S_IRQ_STATUS) - Engine status, IOS, and command control/error registers - TX/RX FIFO status and watermark registers (including RFR watermark) - M/S GP length registers - DMA TX/RX IRQ, enable, length, pointer, attribute, and burst registers - DMA interface enable, general config, QSB trans config, and debug - M/S IRQ enable, GSI event enable, and top-level SE IRQ enable - Serial master/slave clock config, general config, output control, clock control RO, FIFO interface disable, and FW multilock MSA - Clock select register Having all these registers captured atomically in a single ftrace record allows drivers built on top of the GENI SE framework (serial, SPI, I2C) to invoke this tracepoint on error paths and reconstruct the full engine state during post-mortem analysis without instrumenting each driver separately. Acked-by: Steven Rostedt Signed-off-by: Praveen Talari Link: https://patch.msgid.link/20260729-add-tracepoints-for-se-reg-dump-v4-1-08bbd63b0ed2@oss.qualcomm.com Signed-off-by: Mark Brown --- drivers/soc/qcom/qcom-geni-se.c | 5 + include/linux/soc/qcom/geni-se.h | 38 +++++++ include/trace/events/qcom_geni_se.h | 157 ++++++++++++++++++++++++++++ 3 files changed, 200 insertions(+) create mode 100644 include/trace/events/qcom_geni_se.h diff --git a/drivers/soc/qcom/qcom-geni-se.c b/drivers/soc/qcom/qcom-geni-se.c index 15636a8dc907..92b70b20abd8 100644 --- a/drivers/soc/qcom/qcom-geni-se.c +++ b/drivers/soc/qcom/qcom-geni-se.c @@ -7,6 +7,11 @@ /* Disable MMIO tracing to prevent excessive logging of unwanted MMIO traces */ #define __DISABLE_TRACE_MMIO__ +#define CREATE_TRACE_POINTS +#include + +EXPORT_TRACEPOINT_SYMBOL_GPL(geni_se_regs); + #include #include #include diff --git a/include/linux/soc/qcom/geni-se.h b/include/linux/soc/qcom/geni-se.h index c5e6ab85df09..8c08c1917374 100644 --- a/include/linux/soc/qcom/geni-se.h +++ b/include/linux/soc/qcom/geni-se.h @@ -81,13 +81,16 @@ struct geni_se { }; /* Common SE registers */ +#define GENI_GENERAL_CFG 0x10 #define GENI_FORCE_DEFAULT_REG 0x20 #define GENI_OUTPUT_CTRL 0x24 #define SE_GENI_STATUS 0x40 #define GENI_SER_M_CLK_CFG 0x48 #define GENI_SER_S_CLK_CFG 0x4c +#define GENI_CLK_CTRL_RO 0x60 #define GENI_IF_DISABLE_RO 0x64 #define GENI_FW_REVISION_RO 0x68 +#define GENI_FW_MULTILOCK_MSA_RO 0x74 #define SE_GENI_CLK_SEL 0x7c #define SE_GENI_CFG_SEQ_START 0x84 #define SE_GENI_DMA_MODE_EN 0x258 @@ -98,6 +101,8 @@ struct geni_se { #define SE_GENI_M_IRQ_CLEAR 0x618 #define SE_GENI_M_IRQ_EN_SET 0x61c #define SE_GENI_M_IRQ_EN_CLEAR 0x620 +#define M_CMD_ERR_STATUS 0x624 +#define M_FW_ERR_STATUS 0x628 #define SE_GENI_S_CMD0 0x630 #define SE_GENI_S_CMD_CTRL_REG 0x634 #define SE_GENI_S_IRQ_STATUS 0x640 @@ -115,15 +120,42 @@ struct geni_se { #define SE_GENI_IOS 0x908 #define SE_GENI_M_GP_LENGTH 0x910 #define SE_GENI_S_GP_LENGTH 0x914 +/* TX DMA registers */ +#define SE_DMA_TX_PTR_L 0xc30 +#define SE_DMA_TX_PTR_H 0xc34 +#define SE_DMA_TX_ATTR 0xc38 +#define SE_DMA_TX_LEN 0xc3c #define SE_DMA_TX_IRQ_STAT 0xc40 #define SE_DMA_TX_IRQ_CLR 0xc44 +#define SE_DMA_TX_IRQ_EN 0xc48 +#define SE_DMA_TX_IRQ_EN_SET 0xc4c +#define SE_DMA_TX_IRQ_EN_CLR 0xc50 +#define SE_DMA_TX_LEN_IN 0xc54 #define SE_DMA_TX_FSM_RST 0xc58 +#define SE_DMA_TX_MAX_BURST 0xc5c +/* RX DMA registers */ +#define SE_DMA_RX_PTR_L 0xd30 +#define SE_DMA_RX_PTR_H 0xd34 +#define SE_DMA_RX_ATTR 0xd38 +#define SE_DMA_RX_LEN 0xd3c #define SE_DMA_RX_IRQ_STAT 0xd40 #define SE_DMA_RX_IRQ_CLR 0xd44 +#define SE_DMA_RX_IRQ_EN 0xd48 +#define SE_DMA_RX_IRQ_EN_SET 0xd4c +#define SE_DMA_RX_IRQ_EN_CLR 0xd50 #define SE_DMA_RX_LEN_IN 0xd54 #define SE_DMA_RX_FSM_RST 0xd58 +#define SE_DMA_RX_MAX_BURST 0xd5c +/* DMA general / debug registers */ +#define SE_GSI_EVENT_EN 0xe18 +#define SE_IRQ_EN 0xe1c +#define DMA_IF_EN_RO 0xe20 #define SE_HW_PARAM_0 0xe24 #define SE_HW_PARAM_1 0xe28 +#define DMA_GENERAL_CFG 0xe30 +#define SE_DMA_QSB_TRANS_CFG 0xe38 +#define SE_DMA_DEBUG_REG0 0xe40 +#define SE_DMA_IF_EN 0x2004 /* GENI_FORCE_DEFAULT_REG fields */ #define FORCE_DEFAULT BIT(0) @@ -269,6 +301,12 @@ struct geni_se { #define RX_GENI_GP_IRQ_EXT GENMASK(13, 12) #define RX_GENI_CANCEL_IRQ BIT(14) +/* SE_DMA_DEBUG_REG0 fields */ +#define DMA_TX_ACTIVE BIT(0) +#define DMA_RX_ACTIVE BIT(1) +#define DMA_TX_STATE GENMASK(7, 4) +#define DMA_RX_STATE GENMASK(11, 8) + /* SE_HW_PARAM_0 fields */ #define TX_FIFO_WIDTH_MSK GENMASK(29, 24) #define TX_FIFO_WIDTH_SHFT 24 diff --git a/include/trace/events/qcom_geni_se.h b/include/trace/events/qcom_geni_se.h new file mode 100644 index 000000000000..4a6e1ba2d147 --- /dev/null +++ b/include/trace/events/qcom_geni_se.h @@ -0,0 +1,157 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ +#undef TRACE_SYSTEM +#define TRACE_SYSTEM qcom_geni_se + +#if !defined(_TRACE_QCOM_GENI_SE_H) || defined(TRACE_HEADER_MULTI_READ) +#define _TRACE_QCOM_GENI_SE_H + +#include +#include +#include + +TRACE_EVENT(geni_se_regs, + TP_PROTO(struct geni_se *se), + + TP_ARGS(se), + + TP_STRUCT__entry(__string(geni_se_name, dev_name(se->dev)) + __field(u32, geni_se_m_cmd0) + __field(u32, geni_se_m_irq_status) + __field(u32, geni_se_s_cmd0) + __field(u32, geni_se_s_irq_status) + __field(u32, geni_se_status) + __field(u32, geni_se_ios) + __field(u32, geni_se_m_cmd_ctrl) + __field(u32, geni_se_m_cmd_err) + __field(u32, geni_se_m_fw_err) + __field(u32, geni_se_tx_fifo_status) + __field(u32, geni_se_rx_fifo_status) + __field(u32, geni_se_tx_watermark) + __field(u32, geni_se_rx_watermark) + __field(u32, geni_se_rx_watermark_rfr) + __field(u32, geni_se_m_gp_length) + __field(u32, geni_se_s_gp_length) + __field(u32, geni_se_dma_tx_irq) + __field(u32, geni_se_dma_rx_irq) + __field(u32, geni_se_dma_tx_irq_en) + __field(u32, geni_se_dma_rx_irq_en) + __field(u32, geni_se_dma_rx_len) + __field(u32, geni_se_dma_rx_len_in) + __field(u32, geni_se_dma_tx_len) + __field(u32, geni_se_dma_tx_len_in) + __field(u32, geni_se_dma_tx_ptr_l) + __field(u32, geni_se_dma_tx_ptr_h) + __field(u32, geni_se_dma_rx_ptr_l) + __field(u32, geni_se_dma_rx_ptr_h) + __field(u32, geni_se_dma_tx_attr) + __field(u32, geni_se_dma_tx_max_burst) + __field(u32, geni_se_dma_rx_attr) + __field(u32, geni_se_dma_rx_max_burst) + __field(u32, geni_se_dma_if_en) + __field(u32, geni_se_dma_if_en_ro) + __field(u32, geni_se_dma_general_cfg) + __field(u32, geni_se_dma_qsb_trans_cfg) + __field(u32, geni_se_dma_dbg) + __field(u32, geni_se_m_irq_en) + __field(u32, geni_se_s_irq_en) + __field(u32, geni_se_gsi_event_en) + __field(u32, geni_se_irq_en) + __field(u32, geni_se_ser_m_clk_cfg) + __field(u32, geni_se_ser_s_clk_cfg) + __field(u32, geni_se_general_cfg) + __field(u32, geni_se_output_ctrl) + __field(u32, geni_se_clk_ctrl_ro) + __field(u32, geni_se_fifo_if_disable) + __field(u32, geni_se_fw_multilock_msa) + __field(u32, geni_se_clk_sel) + ), + + TP_fast_assign(__assign_str(geni_se_name); + __entry->geni_se_m_cmd0 = readl(se->base + SE_GENI_M_CMD0); + __entry->geni_se_m_irq_status = readl(se->base + SE_GENI_M_IRQ_STATUS); + __entry->geni_se_s_cmd0 = readl(se->base + SE_GENI_S_CMD0); + __entry->geni_se_s_irq_status = readl(se->base + SE_GENI_S_IRQ_STATUS); + __entry->geni_se_status = readl(se->base + SE_GENI_STATUS); + __entry->geni_se_ios = readl(se->base + SE_GENI_IOS); + __entry->geni_se_m_cmd_ctrl = readl(se->base + SE_GENI_M_CMD_CTRL_REG); + __entry->geni_se_m_cmd_err = readl(se->base + M_CMD_ERR_STATUS); + __entry->geni_se_m_fw_err = readl(se->base + M_FW_ERR_STATUS); + __entry->geni_se_tx_fifo_status = readl(se->base + SE_GENI_TX_FIFO_STATUS); + __entry->geni_se_rx_fifo_status = readl(se->base + SE_GENI_RX_FIFO_STATUS); + __entry->geni_se_tx_watermark = readl(se->base + SE_GENI_TX_WATERMARK_REG); + __entry->geni_se_rx_watermark = readl(se->base + SE_GENI_RX_WATERMARK_REG); + __entry->geni_se_rx_watermark_rfr = readl(se->base + SE_GENI_RX_RFR_WATERMARK_REG); + __entry->geni_se_m_gp_length = readl(se->base + SE_GENI_M_GP_LENGTH); + __entry->geni_se_s_gp_length = readl(se->base + SE_GENI_S_GP_LENGTH); + __entry->geni_se_dma_tx_irq = readl(se->base + SE_DMA_TX_IRQ_STAT); + __entry->geni_se_dma_rx_irq = readl(se->base + SE_DMA_RX_IRQ_STAT); + __entry->geni_se_dma_tx_irq_en = readl(se->base + SE_DMA_TX_IRQ_EN); + __entry->geni_se_dma_rx_irq_en = readl(se->base + SE_DMA_RX_IRQ_EN); + __entry->geni_se_dma_rx_len = readl(se->base + SE_DMA_RX_LEN); + __entry->geni_se_dma_rx_len_in = readl(se->base + SE_DMA_RX_LEN_IN); + __entry->geni_se_dma_tx_len = readl(se->base + SE_DMA_TX_LEN); + __entry->geni_se_dma_tx_len_in = readl(se->base + SE_DMA_TX_LEN_IN); + __entry->geni_se_dma_tx_ptr_l = readl(se->base + SE_DMA_TX_PTR_L); + __entry->geni_se_dma_tx_ptr_h = readl(se->base + SE_DMA_TX_PTR_H); + __entry->geni_se_dma_rx_ptr_l = readl(se->base + SE_DMA_RX_PTR_L); + __entry->geni_se_dma_rx_ptr_h = readl(se->base + SE_DMA_RX_PTR_H); + __entry->geni_se_dma_tx_attr = readl(se->base + SE_DMA_TX_ATTR); + __entry->geni_se_dma_tx_max_burst = readl(se->base + SE_DMA_TX_MAX_BURST); + __entry->geni_se_dma_rx_attr = readl(se->base + SE_DMA_RX_ATTR); + __entry->geni_se_dma_rx_max_burst = readl(se->base + SE_DMA_RX_MAX_BURST); + __entry->geni_se_dma_if_en = readl(se->base + SE_DMA_IF_EN); + __entry->geni_se_dma_if_en_ro = readl(se->base + DMA_IF_EN_RO); + __entry->geni_se_dma_general_cfg = readl(se->base + DMA_GENERAL_CFG); + __entry->geni_se_dma_qsb_trans_cfg = readl(se->base + SE_DMA_QSB_TRANS_CFG); + __entry->geni_se_dma_dbg = readl(se->base + SE_DMA_DEBUG_REG0); + __entry->geni_se_m_irq_en = readl(se->base + SE_GENI_M_IRQ_EN); + __entry->geni_se_s_irq_en = readl(se->base + SE_GENI_S_IRQ_EN); + __entry->geni_se_gsi_event_en = readl(se->base + SE_GSI_EVENT_EN); + __entry->geni_se_irq_en = readl(se->base + SE_IRQ_EN); + __entry->geni_se_ser_m_clk_cfg = readl(se->base + GENI_SER_M_CLK_CFG); + __entry->geni_se_ser_s_clk_cfg = readl(se->base + GENI_SER_S_CLK_CFG); + __entry->geni_se_general_cfg = readl(se->base + GENI_GENERAL_CFG); + __entry->geni_se_output_ctrl = readl(se->base + GENI_OUTPUT_CTRL); + __entry->geni_se_clk_ctrl_ro = readl(se->base + GENI_CLK_CTRL_RO); + __entry->geni_se_fifo_if_disable = readl(se->base + GENI_IF_DISABLE_RO); + __entry->geni_se_fw_multilock_msa = readl(se->base + GENI_FW_MULTILOCK_MSA_RO); + __entry->geni_se_clk_sel = readl(se->base + SE_GENI_CLK_SEL); + ), + + TP_printk("%s: m_cmd0=0x%08x m_irq_status=0x%08x s_cmd0=0x%08x s_irq_status=0x%08x geni_status=0x%08x geni_ios=0x%08x m_cmd_ctrl=0x%08x m_cmd_err=0x%08x m_fw_err=0x%08x tx_fifo_sts=0x%08x rx_fifo_sts=0x%08x tx_watermark=0x%08x rx_watermark=0x%08x rx_watermark_rfr=0x%08x m_gp_length=0x%08x s_gp_length=0x%08x dma_tx_irq=0x%08x dma_rx_irq=0x%08x dma_tx_irq_en=0x%08x dma_rx_irq_en=0x%08x dma_rx_len=0x%08x dma_rx_len_in=0x%08x dma_tx_len=0x%08x dma_tx_len_in=0x%08x dma_tx_ptr_l=0x%08x dma_tx_ptr_h=0x%08x dma_rx_ptr_l=0x%08x dma_rx_ptr_h=0x%08x dma_tx_attr=0x%08x dma_tx_max_burst=0x%08x dma_rx_attr=0x%08x dma_rx_max_burst=0x%08x dma_if_en=0x%08x dma_if_en_ro=0x%08x dma_general_cfg=0x%08x dma_qsb_trans_cfg=0x%08x dma_dbg=0x%08x m_irq_en=0x%08x s_irq_en=0x%08x gsi_event_en=0x%08x se_irq_en=0x%08x ser_m_clk_cfg=0x%08x ser_s_clk_cfg=0x%08x general_cfg=0x%08x output_ctrl=0x%08x clk_ctrl_ro=0x%08x fifo_if_dis=0x%08x fw_multilock_msa=0x%08x clk_sel=0x%08x", + __get_str(geni_se_name), + __entry->geni_se_m_cmd0, __entry->geni_se_m_irq_status, + __entry->geni_se_s_cmd0, __entry->geni_se_s_irq_status, + __entry->geni_se_status, __entry->geni_se_ios, + __entry->geni_se_m_cmd_ctrl, + __entry->geni_se_m_cmd_err, __entry->geni_se_m_fw_err, + __entry->geni_se_tx_fifo_status, __entry->geni_se_rx_fifo_status, + __entry->geni_se_tx_watermark, __entry->geni_se_rx_watermark, + __entry->geni_se_rx_watermark_rfr, + __entry->geni_se_m_gp_length, __entry->geni_se_s_gp_length, + __entry->geni_se_dma_tx_irq, __entry->geni_se_dma_rx_irq, + __entry->geni_se_dma_tx_irq_en, __entry->geni_se_dma_rx_irq_en, + __entry->geni_se_dma_rx_len, __entry->geni_se_dma_rx_len_in, + __entry->geni_se_dma_tx_len, __entry->geni_se_dma_tx_len_in, + __entry->geni_se_dma_tx_ptr_l, __entry->geni_se_dma_tx_ptr_h, + __entry->geni_se_dma_rx_ptr_l, __entry->geni_se_dma_rx_ptr_h, + __entry->geni_se_dma_tx_attr, __entry->geni_se_dma_tx_max_burst, + __entry->geni_se_dma_rx_attr, __entry->geni_se_dma_rx_max_burst, + __entry->geni_se_dma_if_en, __entry->geni_se_dma_if_en_ro, + __entry->geni_se_dma_general_cfg, __entry->geni_se_dma_qsb_trans_cfg, + __entry->geni_se_dma_dbg, + __entry->geni_se_m_irq_en, __entry->geni_se_s_irq_en, + __entry->geni_se_gsi_event_en, __entry->geni_se_irq_en, + __entry->geni_se_ser_m_clk_cfg, __entry->geni_se_ser_s_clk_cfg, + __entry->geni_se_general_cfg, __entry->geni_se_output_ctrl, + __entry->geni_se_clk_ctrl_ro, __entry->geni_se_fifo_if_disable, + __entry->geni_se_fw_multilock_msa, __entry->geni_se_clk_sel) +); + +#endif /* _TRACE_QCOM_GENI_SE_H */ + +/* This part must be outside protection */ +#include From 607b41c1e0fe68bcc1b09cd544a4e2af43b20dea Mon Sep 17 00:00:00 2001 From: Praveen Talari Date: Wed, 29 Jul 2026 20:43:26 +0530 Subject: [PATCH 041/132] spi: qcom-geni: add GENI SE registers trace event on error paths The GENI SPI driver reports various transfer failures such as command timeouts, DMA reset timeouts, DMA transaction errors, and unexpected interrupt conditions. However, diagnosing the root cause of these failures is difficult as the hardware state is not captured when the error occurs. Add trace_geni_se_regs() calls at critical SPI error handling paths to automatically capture GENI serial engine debug registers when failures are detected. This includes: - M_CMD abort/cancel timeout - DMA TX/RX FSM reset timeout - DMA transaction failures and pending residue conditions - Unexpected interrupt error status - Premature transfer completion with pending TX/RX data Dumping the SE debug registers at the time of failure provides additional hardware context and significantly improves post-mortem analysis of SPI transfer issues without affecting normal operation. Acked-by: Mukesh Kumar Savaliya Reviewed-by: Konrad Dybcio Signed-off-by: Praveen Talari Link: https://patch.msgid.link/20260729-add-tracepoints-for-se-reg-dump-v4-2-08bbd63b0ed2@oss.qualcomm.com Signed-off-by: Mark Brown --- drivers/spi/spi-geni-qcom.c | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/drivers/spi/spi-geni-qcom.c b/drivers/spi/spi-geni-qcom.c index 26e723cfea61..2c657cd990b4 100644 --- a/drivers/spi/spi-geni-qcom.c +++ b/drivers/spi/spi-geni-qcom.c @@ -1,6 +1,8 @@ // SPDX-License-Identifier: GPL-2.0 // Copyright (c) 2017-2018, The Linux foundation. All rights reserved. +#include + #define CREATE_TRACE_POINTS #include @@ -184,6 +186,7 @@ static void handle_se_timeout(struct spi_controller *spi) time_left = wait_for_completion_timeout(&mas->abort_done, HZ); if (!time_left) { dev_err(mas->dev, "Failed to cancel/abort m_cmd\n"); + trace_geni_se_regs(se); /* * No need for a lock since SPI core has a lock and we never @@ -201,8 +204,10 @@ static void handle_se_timeout(struct spi_controller *spi) writel(1, se->base + SE_DMA_TX_FSM_RST); spin_unlock_irq(&mas->lock); time_left = wait_for_completion_timeout(&mas->tx_reset_done, HZ); - if (!time_left) + if (!time_left) { dev_err(mas->dev, "DMA TX RESET failed\n"); + trace_geni_se_regs(se); + } } if (xfer->rx_buf) { spin_lock_irq(&mas->lock); @@ -210,8 +215,10 @@ static void handle_se_timeout(struct spi_controller *spi) writel(1, se->base + SE_DMA_RX_FSM_RST); spin_unlock_irq(&mas->lock); time_left = wait_for_completion_timeout(&mas->rx_reset_done, HZ); - if (!time_left) + if (!time_left) { dev_err(mas->dev, "DMA RX RESET failed\n"); + trace_geni_se_regs(se); + } } } else { /* @@ -382,10 +389,12 @@ static void spi_gsi_callback_result(void *cb, const struct dmaengine_result *result) { struct spi_controller *spi = cb; + struct spi_geni_master *mas = spi_controller_get_devdata(spi); spi->cur_msg->status = -EIO; if (result->result != DMA_TRANS_NOERROR) { dev_err(&spi->dev, "DMA txn failed: %d\n", result->result); + trace_geni_se_regs(&mas->se); spi_finalize_current_transfer(spi); return; } @@ -395,6 +404,7 @@ spi_gsi_callback_result(void *cb, const struct dmaengine_result *result) dev_dbg(&spi->dev, "DMA txn completed\n"); } else { dev_err(&spi->dev, "DMA xfer has pending: %d\n", result->residue); + trace_geni_se_regs(&mas->se); } spi_finalize_current_transfer(spi); @@ -941,8 +951,10 @@ static irqreturn_t geni_spi_isr(int irq, void *data) if (m_irq & (M_CMD_OVERRUN_EN | M_ILLEGAL_CMD_EN | M_CMD_FAILURE_EN | M_RX_FIFO_RD_ERR_EN | M_RX_FIFO_WR_ERR_EN | - M_TX_FIFO_RD_ERR_EN | M_TX_FIFO_WR_ERR_EN)) + M_TX_FIFO_RD_ERR_EN | M_TX_FIFO_WR_ERR_EN)) { dev_warn(mas->dev, "Unexpected IRQ err status %#010x\n", m_irq); + trace_geni_se_regs(se); + } spin_lock(&mas->lock); @@ -971,13 +983,16 @@ static irqreturn_t geni_spi_isr(int irq, void *data) * weren't written correctly. */ if (mas->tx_rem_bytes) { + trace_geni_se_regs(se); writel(0, se->base + SE_GENI_TX_WATERMARK_REG); dev_err(mas->dev, "Premature done. tx_rem = %d bpw%d\n", mas->tx_rem_bytes, mas->cur_bits_per_word); } - if (mas->rx_rem_bytes) + if (mas->rx_rem_bytes) { dev_err(mas->dev, "Premature done. rx_rem = %d bpw%d\n", mas->rx_rem_bytes, mas->cur_bits_per_word); + trace_geni_se_regs(se); + } } else { complete(&mas->cs_done); } From 09d083d6757ab31a38245d316a1032c69687843c Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Mon, 27 Jul 2026 12:47:14 -0700 Subject: [PATCH 042/132] spi: spi-mpc52xx: use platform_get_irq_optional for IRQ lookup Replace irq_of_parse_and_map() calls with platform_get_irq_optional() to fetch both IRQs in the probe function. This is a more portable approach that works with both DT and non-DT platforms and follows modern platform API conventions. The -EPROBE_DEFER case is handled properly so deferred probe works as expected, and invalid IRQ numbers are normalized to 0. Signed-off-by: Rosen Penev Link: https://patch.msgid.link/20260727194714.11354-1-rosenp@gmail.com Signed-off-by: Mark Brown --- drivers/spi/spi-mpc52xx.c | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/drivers/spi/spi-mpc52xx.c b/drivers/spi/spi-mpc52xx.c index 70d8e17e8c43..563b1f627be9 100644 --- a/drivers/spi/spi-mpc52xx.c +++ b/drivers/spi/spi-mpc52xx.c @@ -392,6 +392,18 @@ static int mpc52xx_spi_probe(struct platform_device *op) void __iomem *regs; u8 ctrl1; int rc, i = 0; + int irq0; + int irq1; + + irq0 = platform_get_irq_optional(op, 0); + if (irq0 == -EPROBE_DEFER) + return irq0; + irq0 = max(irq0, 0); + + irq1 = platform_get_irq_optional(op, 1); + if (irq1 == -EPROBE_DEFER) + return irq1; + irq1 = max(irq1, 0); /* MMIO registers */ dev_dbg(&op->dev, "probing mpc5200 SPI device\n"); @@ -436,8 +448,8 @@ static int mpc52xx_spi_probe(struct platform_device *op) ms = spi_controller_get_devdata(host); ms->host = host; ms->regs = regs; - ms->irq0 = irq_of_parse_and_map(op->dev.of_node, 0); - ms->irq1 = irq_of_parse_and_map(op->dev.of_node, 1); + ms->irq0 = irq0; + ms->irq1 = irq1; ms->state = mpc52xx_spi_fsmstate_idle; ms->ipb_freq = mpc5xxx_get_bus_frequency(&op->dev); ms->gpio_cs_count = gpiod_count(&op->dev, NULL); From 26bbeaa8b3b9a4c2b6aa44b1a726b0b4e828d349 Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Tue, 28 Jul 2026 13:29:32 -0700 Subject: [PATCH 043/132] spi: ppc4xx: use of_property_read_u32 for clock frequency Replace of_get_property with of_property_read_u32 for the "clock-frequency" property, which is the modern typed DT accessor that returns an error code directly and avoids the raw const unsigned int * pointer dance. Also drop the dev_err_probe wrapper on devm_request_irq failure, returning ret directly since the core already logs probe errors. Get rid of irqnum in the private struct. It's only used in _probe. Was used in _remove before devm to free the irq. Assisted-by: opencode:big-pickle Signed-off-by: Rosen Penev Link: https://patch.msgid.link/20260728202932.16207-1-rosenp@gmail.com Signed-off-by: Mark Brown --- drivers/spi/spi-ppc4xx.c | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/drivers/spi/spi-ppc4xx.c b/drivers/spi/spi-ppc4xx.c index 95c034cf2336..8a7d5aec12c1 100644 --- a/drivers/spi/spi-ppc4xx.c +++ b/drivers/spi/spi-ppc4xx.c @@ -112,7 +112,6 @@ struct ppc4xx_spi { struct spi_bitbang bitbang; struct completion done; - int irqnum; /* need this to set the SPI clock */ unsigned int opb_freq; @@ -339,7 +338,7 @@ static int spi_ppc4xx_of_probe(struct platform_device *op) struct device *dev = &op->dev; struct device_node *opbnp; int ret; - const unsigned int *clk; + unsigned int opb_freq; void __iomem *regs; int irqnum; @@ -390,22 +389,21 @@ static int spi_ppc4xx_of_probe(struct platform_device *op) return -ENODEV; } /* Get the clock (Hz) for the OPB */ - clk = of_get_property(opbnp, "clock-frequency", NULL); + ret = of_property_read_u32(opbnp, "clock-frequency", &opb_freq); of_node_put(opbnp); - if (clk == NULL) { + if (ret) { dev_err(dev, "OPB: no clock-frequency property set\n"); return -ENODEV; } - hw->opb_freq = *clk; + + hw->opb_freq = opb_freq; hw->opb_freq >>= 2; - hw->regs = regs; - hw->irqnum = irqnum; - ret = devm_request_irq(&op->dev, hw->irqnum, spi_ppc4xx_int, + ret = devm_request_irq(&op->dev, irqnum, spi_ppc4xx_int, 0, "spi_ppc4xx_of", hw); if (ret) - return dev_err_probe(dev, ret, "unable to allocate interrupt\n"); + return ret; spi_ppc4xx_enable(hw); From d458c752cf6cdaa2e83c2758e40fc3ab496d3ab8 Mon Sep 17 00:00:00 2001 From: Chi-Wen Weng Date: Fri, 31 Jul 2026 10:38:17 +0800 Subject: [PATCH 044/132] dt-bindings: spi: nuvoton,ma35d1-qspi: Add Nuvoton MA35D1 QSPI Add a devicetree binding for the Quad SPI controller found in Nuvoton MA35D1 SoCs. The controller supports SPI memory devices such as SPI NOR and SPI NAND flashes. It has one register range, one clock input and one reset line, and supports up to two chip selects. Reviewed-by: Krzysztof Kozlowski Signed-off-by: Chi-Wen Weng Link: https://patch.msgid.link/20260731023818.1626075-2-cwweng.linux@gmail.com Signed-off-by: Mark Brown --- .../bindings/spi/nuvoton,ma35d1-qspi.yaml | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 Documentation/devicetree/bindings/spi/nuvoton,ma35d1-qspi.yaml diff --git a/Documentation/devicetree/bindings/spi/nuvoton,ma35d1-qspi.yaml b/Documentation/devicetree/bindings/spi/nuvoton,ma35d1-qspi.yaml new file mode 100644 index 000000000000..377ccf4fb224 --- /dev/null +++ b/Documentation/devicetree/bindings/spi/nuvoton,ma35d1-qspi.yaml @@ -0,0 +1,63 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/spi/nuvoton,ma35d1-qspi.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Nuvoton MA35D1 Quad SPI Controller + +maintainers: + - Chi-Wen Weng + +allOf: + - $ref: /schemas/spi/spi-controller.yaml# + +properties: + compatible: + const: nuvoton,ma35d1-qspi + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + clocks: + maxItems: 1 + + resets: + maxItems: 1 + + num-cs: + minimum: 1 + maximum: 2 + default: 2 + +required: + - compatible + - reg + - clocks + - resets + +unevaluatedProperties: false + +examples: + - | + #include + #include + #include + + soc { + #address-cells = <2>; + #size-cells = <2>; + + spi@40680000 { + compatible = "nuvoton,ma35d1-qspi"; + reg = <0 0x40680000 0 0x100>; + interrupts = ; + clocks = <&clk QSPI0_GATE>; + resets = <&sys MA35D1_RESET_QSPI0>; + #address-cells = <1>; + #size-cells = <0>; + }; + }; From 78b16af159ae6d1f5506697c1f08d8172afdc037 Mon Sep 17 00:00:00 2001 From: Chi-Wen Weng Date: Fri, 31 Jul 2026 10:38:18 +0800 Subject: [PATCH 045/132] spi: ma35d1-qspi: Add Nuvoton MA35D1 QSPI controller support Add SPI controller driver support for the Quad SPI controller found in Nuvoton MA35D1 SoCs. The controller provides two native chip selects and supports standard SPI and spi-mem operations, including single-bit full-duplex transfers and dual or quad half-duplex transfers. Implement 8-bit PIO transfers, per-operation clock configuration, clock and reset handling, and direct execution of SPI memory operations. Limit individual transfers to 4 KiB and SPI messages to 8 KiB to keep the PIO polling path bounded. Read the optional num-cs property and fall back to the hardware default of two chip selects when it is not present. Active-high chip select and DMA transfers are not supported by this initial implementation. Signed-off-by: Chi-Wen Weng Link: https://patch.msgid.link/20260731023818.1626075-3-cwweng.linux@gmail.com Signed-off-by: Mark Brown --- drivers/spi/Kconfig | 10 + drivers/spi/Makefile | 1 + drivers/spi/spi-ma35d1-qspi.c | 657 ++++++++++++++++++++++++++++++++++ 3 files changed, 668 insertions(+) create mode 100644 drivers/spi/spi-ma35d1-qspi.c diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index 8782514bb89b..b367aa69f4ff 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -694,6 +694,16 @@ config SPI_LJCA This driver can also be built as a module. If so, the module will be called spi-ljca. +config SPI_MA35D1_QSPI + tristate "Nuvoton MA35D1 QSPI controller" + depends on ARCH_MA35 || COMPILE_TEST + help + This enables support for the Quad SPI controller found in + Nuvoton MA35D1 SoCs. + + The controller supports SPI memory devices such as SPI NOR and + SPI NAND flashes in single, dual and quad I/O modes. + config SPI_MESON_SPICC tristate "Amlogic Meson SPICC controller" depends on COMMON_CLK diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index 9fa12498ce8c..cb94935524d5 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -85,6 +85,7 @@ obj-$(CONFIG_SPI_LOONGSON_CORE) += spi-loongson-core.o obj-$(CONFIG_SPI_LOONGSON_PCI) += spi-loongson-pci.o obj-$(CONFIG_SPI_LOONGSON_PLATFORM) += spi-loongson-plat.o obj-$(CONFIG_SPI_LP8841_RTC) += spi-lp8841-rtc.o +obj-$(CONFIG_SPI_MA35D1_QSPI) += spi-ma35d1-qspi.o obj-$(CONFIG_SPI_MESON_SPICC) += spi-meson-spicc.o obj-$(CONFIG_SPI_MESON_SPIFC) += spi-meson-spifc.o obj-$(CONFIG_SPI_MICROCHIP_CORE_QSPI) += spi-microchip-core-qspi.o diff --git a/drivers/spi/spi-ma35d1-qspi.c b/drivers/spi/spi-ma35d1-qspi.c new file mode 100644 index 000000000000..541d5d72484c --- /dev/null +++ b/drivers/spi/spi-ma35d1-qspi.c @@ -0,0 +1,657 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// +// Nuvoton MA35D1 QSPI controller driver +// +// Copyright (c) 2026 Nuvoton Technology Corp. +// Author: Chi-Wen Weng + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Register offset definitions */ +#define NUVOTON_QSPI_CTL_OFFSET 0x00 /* Control Register, RW */ +#define NUVOTON_QSPI_CLKDIV_OFFSET 0x04 /* Clock Divider Register, RW */ +#define NUVOTON_QSPI_SSCTL_OFFSET 0x08 /* Slave Select Register, RW */ +#define NUVOTON_QSPI_FIFOCTL_OFFSET 0x10 /* FIFO Control Register, RW */ +#define NUVOTON_QSPI_STATUS_OFFSET 0x14 /* Status Register, RW */ +#define NUVOTON_QSPI_TX_OFFSET 0x20 /* Data Transmit Register, WO */ +#define NUVOTON_QSPI_RX_OFFSET 0x30 /* Data Receive Register, RO */ + +/* QSPI Control Register bit masks */ +#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 */ +#define NUVOTON_QSPI_CTL_REORDER_MASK BIT(19) /* Byte Reorder Function Enable */ +#define NUVOTON_QSPI_CTL_LSB_MASK BIT(13) /* Send LSB First */ +#define NUVOTON_QSPI_CTL_DWIDTH_MASK GENMASK(12, 8) /* Data Width */ +#define NUVOTON_QSPI_CTL_SUSPITV_MASK GENMASK(7, 4) /* Suspend Interval */ +#define NUVOTON_QSPI_CTL_CLKPOL_MASK BIT(3) /* Clock Polarity */ +#define NUVOTON_QSPI_CTL_TXNEG_MASK BIT(2) /* Transmit on Negative Edge */ +#define NUVOTON_QSPI_CTL_RXNEG_MASK BIT(1) /* Receive on Negative Edge */ +#define NUVOTON_QSPI_CTL_SPIEN_MASK BIT(0) /* QSPI Transfer Control Enable */ + +/* QSPI Clock Divider Register bit masks */ +#define NUVOTON_QSPI_CLKDIV_MASK GENMASK(8, 0) /* Clock Divider */ + +/* QSPI Slave Select Control Register bit masks */ +#define NUVOTON_QSPI_SSCTL_SS1_MASK BIT(1) /* Slave Selection 1 Control */ +#define NUVOTON_QSPI_SSCTL_SS0_MASK BIT(0) /* Slave Selection 0 Control */ + +/* QSPI FIFO Control Register bit masks */ +#define NUVOTON_QSPI_FIFOCTL_TXRST_MASK BIT(1) /* Transmit Reset */ +#define NUVOTON_QSPI_FIFOCTL_RXRST_MASK BIT(0) /* Receive Reset */ + +/* QSPI Status Register bit masks */ +#define NUVOTON_QSPI_STATUS_TXRXRST_MASK BIT(23) /* TX or RX Reset Status */ +#define NUVOTON_QSPI_STATUS_TXFULL_MASK BIT(17) /* Transmit FIFO Full */ +#define NUVOTON_QSPI_STATUS_SPIENSTS_MASK BIT(15) /* QSPI Enable Status */ +#define NUVOTON_QSPI_STATUS_RXEMPTY_MASK BIT(8) /* Receive FIFO Empty */ +#define NUVOTON_QSPI_STATUS_BUSY_MASK BIT(0) /* Busy Status */ + +#define NUVOTON_QSPI_MAX_NUM_CS 2 +#define NUVOTON_QSPI_DEFAULT_NUM_CS 2 +#define NUVOTON_QSPI_DEFAULT_BPW 8 +/* Bound PIO operations to avoid long atomic polling loops. */ +#define NUVOTON_QSPI_MAX_TRANSFER_SIZE SZ_4K +#define NUVOTON_QSPI_MAX_MESSAGE_SIZE SZ_8K +#define NUVOTON_QSPI_TIMEOUT_US 10000 + +struct nuvoton_qspi { + void __iomem *regs; + struct clk *clk; + struct device *dev; + + /* Protects read-modify-write accesses to the SSCTL register. */ + spinlock_t ssctl_lock; + u32 speed_hz; +}; + +static u32 nuvoton_qspi_read(struct nuvoton_qspi *qspi, u32 reg) +{ + return readl(qspi->regs + reg); +} + +static void nuvoton_qspi_write(struct nuvoton_qspi *qspi, u32 val, u32 reg) +{ + writel(val, qspi->regs + reg); +} + +static void nuvoton_qspi_update_bits(struct nuvoton_qspi *qspi, u32 reg, + u32 mask, u32 val) +{ + u32 tmp; + + tmp = nuvoton_qspi_read(qspi, reg); + tmp &= ~mask; + tmp |= val & mask; + nuvoton_qspi_write(qspi, tmp, reg); +} + +static int nuvoton_qspi_wait_ready(struct nuvoton_qspi *qspi) +{ + u32 val; + + return readl_poll_timeout(qspi->regs + NUVOTON_QSPI_STATUS_OFFSET, + val, + !(val & NUVOTON_QSPI_STATUS_BUSY_MASK), + 0, NUVOTON_QSPI_TIMEOUT_US); +} + +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); + + /* + * Give the controller a short time to latch the FIFO reset request + * before polling the reset status bit. + */ + udelay(1); + + return readl_poll_timeout_atomic(qspi->regs + NUVOTON_QSPI_STATUS_OFFSET, + val, + !(val & NUVOTON_QSPI_STATUS_TXRXRST_MASK), + 1, NUVOTON_QSPI_TIMEOUT_US); +} + +static int nuvoton_qspi_set_speed(struct nuvoton_qspi *qspi, u32 speed_hz) +{ + unsigned long clk_rate; + u32 div; + + if (!speed_hz) + return -EINVAL; + + if (qspi->speed_hz == speed_hz) + return 0; + + clk_rate = clk_get_rate(qspi->clk); + if (!clk_rate) { + dev_err(qspi->dev, "failed to get clock rate\n"); + return -EINVAL; + } + + div = DIV_ROUND_UP(clk_rate, speed_hz) - 1; + if (div > FIELD_MAX(NUVOTON_QSPI_CLKDIV_MASK)) { + dev_err(qspi->dev, "unsupported SPI clock %u Hz\n", speed_hz); + return -EINVAL; + } + + nuvoton_qspi_write(qspi, FIELD_PREP(NUVOTON_QSPI_CLKDIV_MASK, div), + NUVOTON_QSPI_CLKDIV_OFFSET); + qspi->speed_hz = speed_hz; + + return 0; +} + +static int nuvoton_qspi_set_bits_per_word(struct nuvoton_qspi *qspi, u8 bpw) +{ + if (bpw != NUVOTON_QSPI_DEFAULT_BPW) + return -EINVAL; + + nuvoton_qspi_update_bits(qspi, NUVOTON_QSPI_CTL_OFFSET, + NUVOTON_QSPI_CTL_DWIDTH_MASK | + NUVOTON_QSPI_CTL_REORDER_MASK, + FIELD_PREP(NUVOTON_QSPI_CTL_DWIDTH_MASK, bpw)); + + return 0; +} + +static int nuvoton_qspi_setup_transfer(struct spi_device *spi, + u32 speed_hz, 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; + + if (mode == SPI_MODE_0 || mode == SPI_MODE_3) + ctl |= NUVOTON_QSPI_CTL_TXNEG_MASK; + else + ctl |= NUVOTON_QSPI_CTL_RXNEG_MASK; + + if (spi->mode & SPI_CPOL) + ctl |= NUVOTON_QSPI_CTL_CLKPOL_MASK; + + if (spi->mode & SPI_LSB_FIRST) + ctl |= NUVOTON_QSPI_CTL_LSB_MASK; + + nuvoton_qspi_update_bits(qspi, NUVOTON_QSPI_CTL_OFFSET, + NUVOTON_QSPI_CTL_TXNEG_MASK | + NUVOTON_QSPI_CTL_RXNEG_MASK | + NUVOTON_QSPI_CTL_CLKPOL_MASK | + NUVOTON_QSPI_CTL_LSB_MASK, ctl); + + return 0; +} + +static void nuvoton_qspi_set_bus_width(struct nuvoton_qspi *qspi, + unsigned int buswidth, + enum spi_mem_data_dir dir) +{ + u32 ctl = 0; + + if (buswidth == 4) + ctl |= NUVOTON_QSPI_CTL_QUADIOEN_MASK; + else if (buswidth == 2) + ctl |= NUVOTON_QSPI_CTL_DUALIOEN_MASK; + + if (buswidth > 1 && dir == SPI_MEM_DATA_OUT) + ctl |= NUVOTON_QSPI_CTL_DATDIR_MASK; + + nuvoton_qspi_update_bits(qspi, NUVOTON_QSPI_CTL_OFFSET, + NUVOTON_QSPI_CTL_QUADIOEN_MASK | + NUVOTON_QSPI_CTL_DUALIOEN_MASK | + NUVOTON_QSPI_CTL_DATDIR_MASK, ctl); +} + +static u32 nuvoton_qspi_tx_byte(const void *txbuf, unsigned int idx) +{ + if (!txbuf) + return 0; + + return ((const u8 *)txbuf)[idx]; +} + +static void nuvoton_qspi_rx_byte(void *rxbuf, unsigned int idx, u32 val) +{ + if (rxbuf) + ((u8 *)rxbuf)[idx] = val; +} + +static int nuvoton_qspi_wait_tx_not_full(struct nuvoton_qspi *qspi) +{ + u32 val; + + return readl_poll_timeout_atomic(qspi->regs + NUVOTON_QSPI_STATUS_OFFSET, + val, + !(val & NUVOTON_QSPI_STATUS_TXFULL_MASK), + 0, NUVOTON_QSPI_TIMEOUT_US); +} + +static int nuvoton_qspi_wait_rx_not_empty(struct nuvoton_qspi *qspi) +{ + u32 val; + + return readl_poll_timeout_atomic(qspi->regs + NUVOTON_QSPI_STATUS_OFFSET, + val, + !(val & NUVOTON_QSPI_STATUS_RXEMPTY_MASK), + 0, NUVOTON_QSPI_TIMEOUT_US); +} + +static int nuvoton_qspi_txrx(struct nuvoton_qspi *qspi, const void *txbuf, + void *rxbuf, unsigned int len) +{ + unsigned int i; + u32 val; + int ret; + + if (!len) + return 0; + + if (len > NUVOTON_QSPI_MAX_TRANSFER_SIZE) + return -EMSGSIZE; + + ret = nuvoton_qspi_reset_fifo(qspi); + if (ret) { + dev_err(qspi->dev, "FIFO reset timed out\n"); + return ret; + } + + /* + * Use conservative byte-by-byte PIO access. This keeps the initial driver + * simple and avoids relying on FIFO threshold interrupts or DMA support. + * + * The MA35D1 QSPI controller pushes one RX FIFO entry for each TX byte in + * single, dual-output and quad-output modes. Drain RX after every TX byte + * and discard the value for TX-only transfers to avoid RX FIFO overflow. + */ + for (i = 0; i < len; i++) { + ret = nuvoton_qspi_wait_tx_not_full(qspi); + if (ret) { + dev_err(qspi->dev, "TX FIFO full timeout\n"); + return ret; + } + + nuvoton_qspi_write(qspi, nuvoton_qspi_tx_byte(txbuf, i), + NUVOTON_QSPI_TX_OFFSET); + + ret = nuvoton_qspi_wait_rx_not_empty(qspi); + if (ret) { + dev_err(qspi->dev, "RX FIFO empty timeout\n"); + return ret; + } + + val = nuvoton_qspi_read(qspi, NUVOTON_QSPI_RX_OFFSET); + if (rxbuf) + nuvoton_qspi_rx_byte(rxbuf, i, val); + } + + ret = nuvoton_qspi_wait_ready(qspi); + if (ret) + dev_err(qspi->dev, "controller busy timeout\n"); + + return ret; +} + +static int nuvoton_qspi_hw_init(struct nuvoton_qspi *qspi) +{ + u32 val; + int ret; + + ret = nuvoton_qspi_set_bits_per_word(qspi, NUVOTON_QSPI_DEFAULT_BPW); + if (ret) + return ret; + + nuvoton_qspi_update_bits(qspi, NUVOTON_QSPI_CTL_OFFSET, + NUVOTON_QSPI_CTL_SUSPITV_MASK | + NUVOTON_QSPI_CTL_TXNEG_MASK | + NUVOTON_QSPI_CTL_RXNEG_MASK | + NUVOTON_QSPI_CTL_CLKPOL_MASK | + 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); + + ret = readl_poll_timeout(qspi->regs + NUVOTON_QSPI_STATUS_OFFSET, val, + (val & NUVOTON_QSPI_STATUS_SPIENSTS_MASK), + 1, NUVOTON_QSPI_TIMEOUT_US); + if (ret) { + dev_err(qspi->dev, "failed to enable controller\n"); + return ret; + } + + ret = nuvoton_qspi_reset_fifo(qspi); + if (ret) + dev_err(qspi->dev, "FIFO reset timed out\n"); + + return ret; +} + +static size_t nuvoton_qspi_max_transfer_size(struct spi_device *spi) +{ + return NUVOTON_QSPI_MAX_TRANSFER_SIZE; +} + +static size_t nuvoton_qspi_max_message_size(struct spi_device *spi) +{ + return NUVOTON_QSPI_MAX_MESSAGE_SIZE; +} + +static int nuvoton_qspi_mem_adjust_op_size(struct spi_mem *mem, + struct spi_mem_op *op) +{ + if (op->data.nbytes > NUVOTON_QSPI_MAX_TRANSFER_SIZE) + op->data.nbytes = NUVOTON_QSPI_MAX_TRANSFER_SIZE; + + return 0; +} + +static bool nuvoton_qspi_mem_supports_op(struct spi_mem *mem, + const struct spi_mem_op *op) +{ + if (!spi_mem_default_supports_op(mem, op)) + return false; + + if (op->cmd.buswidth > 4 || op->addr.buswidth > 4 || + op->dummy.buswidth > 4 || op->data.buswidth > 4) + return false; + + if (op->cmd.nbytes != 1) + return false; + + if (op->addr.nbytes > 4) + return false; + + return true; +} + +static void nuvoton_qspi_set_cs_level(struct nuvoton_qspi *qspi, + unsigned int cs, bool assert) +{ + unsigned long flags; + u32 mask; + u32 val; + + switch (cs) { + case 0: + mask = NUVOTON_QSPI_SSCTL_SS0_MASK; + break; + case 1: + mask = NUVOTON_QSPI_SSCTL_SS1_MASK; + break; + default: + dev_warn(qspi->dev, "invalid chip select %u\n", cs); + return; + } + + spin_lock_irqsave(&qspi->ssctl_lock, flags); + + val = nuvoton_qspi_read(qspi, NUVOTON_QSPI_SSCTL_OFFSET); + if (assert) + val |= mask; + else + val &= ~mask; + nuvoton_qspi_write(qspi, val, NUVOTON_QSPI_SSCTL_OFFSET); + + spin_unlock_irqrestore(&qspi->ssctl_lock, flags); +} + +static void nuvoton_qspi_set_cs(struct spi_device *spi, bool level) +{ + struct nuvoton_qspi *qspi = spi_controller_get_devdata(spi->controller); + + /* + * The SPI core passes the physical CS level to ->set_cs(). This + * initial driver only supports active-low native chip selects. + */ + nuvoton_qspi_set_cs_level(qspi, spi_get_chipselect(spi, 0), !level); +} + +static void nuvoton_qspi_mem_set_cs(struct spi_device *spi, bool assert) +{ + struct nuvoton_qspi *qspi = spi_controller_get_devdata(spi->controller); + + /* The direct spi-mem path passes a logical assertion state. */ + nuvoton_qspi_set_cs_level(qspi, spi_get_chipselect(spi, 0), assert); +} + +static int nuvoton_qspi_mem_exec_op(struct spi_mem *mem, + const struct spi_mem_op *op) +{ + struct spi_device *spi = mem->spi; + struct nuvoton_qspi *qspi = spi_controller_get_devdata(spi->controller); + u8 opcode = op->cmd.opcode; + u8 addr[4]; + int ret; + int i; + + ret = nuvoton_qspi_setup_transfer(spi, op->max_freq, + 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_txrx(qspi, &opcode, NULL, 1); + if (ret) + goto out_deassert_cs; + + if (op->addr.nbytes) { + 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_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_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_txrx(qspi, + op->data.dir == SPI_MEM_DATA_OUT ? + op->data.buf.out : NULL, + op->data.dir == SPI_MEM_DATA_IN ? + op->data.buf.in : NULL, + op->data.nbytes); + } + +out_deassert_cs: + nuvoton_qspi_set_bus_width(qspi, 1, SPI_MEM_DATA_IN); + nuvoton_qspi_mem_set_cs(spi, false); + + return ret; +} + +static const struct spi_controller_mem_ops nuvoton_qspi_mem_ops = { + .adjust_op_size = nuvoton_qspi_mem_adjust_op_size, + .supports_op = nuvoton_qspi_mem_supports_op, + .exec_op = nuvoton_qspi_mem_exec_op, +}; + +static const struct spi_controller_mem_caps nuvoton_qspi_mem_caps = { + .per_op_freq = true, +}; + +static int nuvoton_qspi_transfer_one(struct spi_controller *ctlr, + struct spi_device *spi, + struct spi_transfer *xfer) +{ + struct nuvoton_qspi *qspi = spi_controller_get_devdata(ctlr); + unsigned int tx_nbits = xfer->tx_nbits ?: SPI_NBITS_SINGLE; + unsigned int rx_nbits = xfer->rx_nbits ?: SPI_NBITS_SINGLE; + enum spi_mem_data_dir dir = SPI_MEM_DATA_IN; + unsigned int buswidth = 1; + int ret; + + ret = nuvoton_qspi_setup_transfer(spi, xfer->speed_hz, + xfer->bits_per_word); + if (ret) + return ret; + + if (xfer->tx_buf && xfer->rx_buf && + (tx_nbits != SPI_NBITS_SINGLE || + rx_nbits != SPI_NBITS_SINGLE)) + return -EOPNOTSUPP; + + if (xfer->tx_buf) { + dir = SPI_MEM_DATA_OUT; + + if (tx_nbits == SPI_NBITS_QUAD) + buswidth = 4; + else if (tx_nbits == SPI_NBITS_DUAL) + buswidth = 2; + } else if (xfer->rx_buf) { + if (rx_nbits == SPI_NBITS_QUAD) + buswidth = 4; + else if (rx_nbits == SPI_NBITS_DUAL) + buswidth = 2; + } + + 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; +} + +static int nuvoton_qspi_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct spi_controller *ctlr; + struct nuvoton_qspi *qspi; + struct reset_control *rst; + u32 num_cs = NUVOTON_QSPI_DEFAULT_NUM_CS; + int ret; + + ctlr = devm_spi_alloc_host(dev, sizeof(*qspi)); + if (!ctlr) + return -ENOMEM; + + platform_set_drvdata(pdev, ctlr); + + qspi = spi_controller_get_devdata(ctlr); + qspi->dev = dev; + spin_lock_init(&qspi->ssctl_lock); + + qspi->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(qspi->regs)) + return PTR_ERR(qspi->regs); + + rst = devm_reset_control_get_exclusive(dev, NULL); + if (IS_ERR(rst)) + return dev_err_probe(dev, PTR_ERR(rst), + "failed to get reset\n"); + + qspi->clk = devm_clk_get_enabled(dev, NULL); + if (IS_ERR(qspi->clk)) + return dev_err_probe(dev, PTR_ERR(qspi->clk), + "failed to get and enable clock\n"); + + ret = reset_control_assert(rst); + if (ret) + return dev_err_probe(dev, ret, "failed to assert reset\n"); + + udelay(2); + + ret = reset_control_deassert(rst); + if (ret) + return dev_err_probe(dev, ret, "failed to deassert reset\n"); + + ret = device_property_read_u32(dev, "num-cs", &num_cs); + if (ret && ret != -EINVAL) + return dev_err_probe(dev, ret, "failed to read num-cs\n"); + + if (!num_cs || num_cs > NUVOTON_QSPI_MAX_NUM_CS) + return dev_err_probe(dev, -EINVAL, "invalid num-cs %u\n", + num_cs); + + ctlr->num_chipselect = num_cs; + ctlr->max_transfer_size = nuvoton_qspi_max_transfer_size; + ctlr->max_message_size = nuvoton_qspi_max_message_size; + ctlr->mem_ops = &nuvoton_qspi_mem_ops; + ctlr->mem_caps = &nuvoton_qspi_mem_caps; + ctlr->set_cs = nuvoton_qspi_set_cs; + ctlr->transfer_one = nuvoton_qspi_transfer_one; + ctlr->bits_per_word_mask = SPI_BPW_MASK(8); + ctlr->mode_bits = SPI_CPOL | SPI_CPHA | SPI_LSB_FIRST | + SPI_RX_DUAL | SPI_TX_DUAL | + SPI_RX_QUAD | SPI_TX_QUAD; + ctlr->dev.of_node = dev->of_node; + + ret = nuvoton_qspi_hw_init(qspi); + if (ret) + return ret; + + ret = devm_spi_register_controller(dev, ctlr); + if (ret) + return dev_err_probe(dev, ret, + "failed to register spi controller\n"); + + return 0; +} + +static const struct of_device_id nuvoton_qspi_of_match[] = { + { .compatible = "nuvoton,ma35d1-qspi" }, + { } +}; +MODULE_DEVICE_TABLE(of, nuvoton_qspi_of_match); + +static struct platform_driver nuvoton_qspi_driver = { + .driver = { + .name = "ma35d1-qspi", + .of_match_table = nuvoton_qspi_of_match, + }, + .probe = nuvoton_qspi_probe, +}; +module_platform_driver(nuvoton_qspi_driver); + +MODULE_DESCRIPTION("Nuvoton MA35D1 QSPI controller driver"); +MODULE_AUTHOR("Chi-Wen Weng "); +MODULE_LICENSE("GPL"); From 98e69afa6d571b1e7981d5a88911f8a7e2e3cb7d Mon Sep 17 00:00:00 2001 From: Chi-Wen Weng Date: Mon, 3 Aug 2026 10:40:48 +0800 Subject: [PATCH 046/132] spi: dt-bindings: nuvoton,ma35d1-qspi: Disallow GPIO chip selects The generic SPI controller binding allows GPIO-based chip selects, but the MA35D1 QSPI driver currently supports only the controller's native chip selects. Disallow the cs-gpios property so that the binding matches the functionality provided by the driver. Signed-off-by: Chi-Wen Weng Link: https://patch.msgid.link/20260803024048.1641756-1-cwweng.linux@gmail.com Signed-off-by: Mark Brown --- Documentation/devicetree/bindings/spi/nuvoton,ma35d1-qspi.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/spi/nuvoton,ma35d1-qspi.yaml b/Documentation/devicetree/bindings/spi/nuvoton,ma35d1-qspi.yaml index 377ccf4fb224..40965ec5163b 100644 --- a/Documentation/devicetree/bindings/spi/nuvoton,ma35d1-qspi.yaml +++ b/Documentation/devicetree/bindings/spi/nuvoton,ma35d1-qspi.yaml @@ -33,6 +33,8 @@ properties: maximum: 2 default: 2 + cs-gpios: false + required: - compatible - reg From c87b4ba09702762ea69187561bd4d76ae602f8bd Mon Sep 17 00:00:00 2001 From: Sudip Mukherjee Date: Mon, 3 Aug 2026 05:40:34 -0700 Subject: [PATCH 047/132] spi: dw: Introduce spi_frf and STD_SPI The DW APB SSI controllers of v4.x and newer and DW AHB SSI controllers supports enhanced SPI modes which can be defined from SPI_FRF of DW_SPI_CTRLR0 register. Without enhanced mode, these controllers will work in the standard spi mode. Signed-off-by: Sudip Mukherjee Co-developed-by: Changhuang Liang Signed-off-by: Changhuang Liang Link: https://patch.msgid.link/20260803124044.156998-2-changhuang.liang@starfivetech.com Signed-off-by: Mark Brown --- drivers/spi/spi-dw-core.c | 13 ++++++++++++- drivers/spi/spi-dw.h | 7 +++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/drivers/spi/spi-dw-core.c b/drivers/spi/spi-dw-core.c index ad8aad958c76..2c52209c9095 100644 --- a/drivers/spi/spi-dw-core.c +++ b/drivers/spi/spi-dw-core.c @@ -330,6 +330,16 @@ void dw_spi_update_config(struct dw_spi *dws, struct spi_device *spi, /* CTRLR0[11:10] Transfer Mode */ cr0 |= FIELD_PREP(DW_HSSI_CTRLR0_TMOD_MASK, cfg->tmode); + if (dw_spi_ver_is_ge(dws, HSSI, 103A)) { + cr0 &= ~DW_HSSI_CTRLR0_SPI_FRF_MASK; + cr0 |= FIELD_PREP(DW_HSSI_CTRLR0_SPI_FRF_MASK, + cfg->spi_frf); + } else if (dw_spi_ver_is_ge(dws, PSSI, 400A)) { + cr0 &= ~DW_PSSI_CTRLR0_SPI_FRF_MASK; + cr0 |= FIELD_PREP(DW_PSSI_CTRLR0_SPI_FRF_MASK, + cfg->spi_frf); + } + dw_writel(dws, DW_SPI_CTRLR0, cr0); if (spi_controller_is_target(dws->ctlr)) @@ -422,6 +432,7 @@ static int dw_spi_transfer_one(struct spi_controller *ctlr, .tmode = DW_SPI_CTRLR0_TMOD_TR, .dfs = transfer->bits_per_word, .freq = transfer->speed_hz, + .spi_frf = DW_SPI_CTRLR0_SPI_FRF_STD_SPI, }; int ret; @@ -677,7 +688,7 @@ static void dw_spi_stop_mem_op(struct dw_spi *dws, struct spi_device *spi) static int dw_spi_exec_mem_op(struct spi_mem *mem, const struct spi_mem_op *op) { struct dw_spi *dws = spi_controller_get_devdata(mem->spi->controller); - struct dw_spi_cfg cfg; + struct dw_spi_cfg cfg = {0}; unsigned long flags; int ret; diff --git a/drivers/spi/spi-dw.h b/drivers/spi/spi-dw.h index 2f2debc64e73..19cf1b1a5d4f 100644 --- a/drivers/spi/spi-dw.h +++ b/drivers/spi/spi-dw.h @@ -17,6 +17,9 @@ /* Synopsys DW SSI component versions (FourCC sequence) */ #define DW_HSSI_102A 0x3130322a +#define DW_HSSI_103A 0x3130332a +#define DW_HSSI_200A 0x3230302a +#define DW_PSSI_400A 0x3430302a /* DW SSI IP-core ID and version check helpers */ #define dw_spi_ip_is(_dws, _ip) \ @@ -94,6 +97,9 @@ #define DW_HSSI_CTRLR0_TMOD_MASK GENMASK(11, 10) #define DW_HSSI_CTRLR0_SRL BIT(13) #define DW_HSSI_CTRLR0_MST BIT(31) +#define DW_HSSI_CTRLR0_SPI_FRF_MASK GENMASK(23, 22) +#define DW_PSSI_CTRLR0_SPI_FRF_MASK GENMASK(22, 21) +#define DW_SPI_CTRLR0_SPI_FRF_STD_SPI 0x0 /* Bit fields in CTRLR1 */ #define DW_SPI_NDF_MASK GENMASK(15, 0) @@ -135,6 +141,7 @@ struct dw_spi_cfg { u8 dfs; u32 ndf; u32 freq; + u8 spi_frf; }; struct dw_spi; From 607687a869e60333d7a6cc3621c406b6e9dc2af8 Mon Sep 17 00:00:00 2001 From: Sudip Mukherjee Date: Mon, 3 Aug 2026 05:40:35 -0700 Subject: [PATCH 048/132] spi: dw: update SPI_CTRLR0 register If the SPI transfer is being done in enhanced mode then SPI_CTRLR0 register needs to be updated to mention the instruction length, address length, address and instruction transfer format, wait cycles. And, we also need to enable clock stretching. Signed-off-by: Sudip Mukherjee Co-developed-by: Changhuang Liang Signed-off-by: Changhuang Liang Link: https://patch.msgid.link/20260803124044.156998-3-changhuang.liang@starfivetech.com Signed-off-by: Mark Brown --- drivers/spi/spi-dw-core.c | 15 ++++++++++++--- drivers/spi/spi-dw.h | 17 ++++++++++++++++- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/drivers/spi/spi-dw-core.c b/drivers/spi/spi-dw-core.c index 2c52209c9095..1cdd6cc6c892 100644 --- a/drivers/spi/spi-dw-core.c +++ b/drivers/spi/spi-dw-core.c @@ -313,7 +313,7 @@ static u32 dw_spi_prepare_cr0(struct dw_spi *dws, struct spi_device *spi) } void dw_spi_update_config(struct dw_spi *dws, struct spi_device *spi, - struct dw_spi_cfg *cfg) + struct dw_spi_cfg *cfg, struct dw_spi_enh_cfg *enh_cfg) { struct dw_spi_chip_data *chip = spi_get_ctldata(spi); u32 cr0 = chip->cr0; @@ -363,6 +363,15 @@ void dw_spi_update_config(struct dw_spi *dws, struct spi_device *spi, dw_writel(dws, DW_SPI_RX_SAMPLE_DLY, chip->rx_sample_dly); dws->cur_rx_sample_dly = chip->rx_sample_dly; } + + if (enh_cfg) { + cr0 = DW_SPI_ENH_CTRLR0_CLK_STRETCH_EN; + cr0 |= FIELD_PREP(DW_SPI_ENH_CTRLR0_WAIT_CYCLE_MASK, enh_cfg->wait_c); + cr0 |= FIELD_PREP(DW_SPI_ENH_CTRLR0_INST_L_MASK, enh_cfg->inst_l); + cr0 |= FIELD_PREP(DW_SPI_ENH_CTRLR0_ADDR_L_MASK, enh_cfg->addr_l); + cr0 |= FIELD_PREP(DW_SPI_ENH_CTRLR0_TRANS_TYPE_MASK, enh_cfg->trans_t); + dw_writel(dws, DW_SPI_SPI_CTRLR0, cr0); + } } EXPORT_SYMBOL_NS_GPL(dw_spi_update_config, "SPI_DW_CORE"); @@ -448,7 +457,7 @@ static int dw_spi_transfer_one(struct spi_controller *ctlr, dw_spi_enable_chip(dws, 0); - dw_spi_update_config(dws, spi, &cfg); + dw_spi_update_config(dws, spi, &cfg, NULL); transfer->effective_speed_hz = dws->current_freq; @@ -715,7 +724,7 @@ static int dw_spi_exec_mem_op(struct spi_mem *mem, const struct spi_mem_op *op) dw_spi_enable_chip(dws, 0); - dw_spi_update_config(dws, mem->spi, &cfg); + dw_spi_update_config(dws, mem->spi, &cfg, NULL); dw_spi_mask_intr(dws, 0xff); diff --git a/drivers/spi/spi-dw.h b/drivers/spi/spi-dw.h index 19cf1b1a5d4f..16a8c7ab7364 100644 --- a/drivers/spi/spi-dw.h +++ b/drivers/spi/spi-dw.h @@ -63,6 +63,7 @@ #define DW_SPI_VERSION 0x5c #define DW_SPI_DR 0x60 #define DW_SPI_RX_SAMPLE_DLY 0xf0 +#define DW_SPI_SPI_CTRLR0 0xf4 #define DW_SPI_CS_OVERRIDE 0xf4 /* Bit fields in CTRLR0 (DWC APB SSI) */ @@ -127,6 +128,13 @@ #define DW_SPI_DMACR_RDMAE BIT(0) #define DW_SPI_DMACR_TDMAE BIT(1) +/* Bit fields in SPI_CTRLR0 */ +#define DW_SPI_ENH_CTRLR0_CLK_STRETCH_EN BIT(30) +#define DW_SPI_ENH_CTRLR0_WAIT_CYCLE_MASK GENMASK(15, 11) +#define DW_SPI_ENH_CTRLR0_INST_L_MASK GENMASK(9, 8) +#define DW_SPI_ENH_CTRLR0_ADDR_L_MASK GENMASK(5, 2) +#define DW_SPI_ENH_CTRLR0_TRANS_TYPE_MASK GENMASK(1, 0) + /* Mem/DMA operations helpers */ #define DW_SPI_WAIT_RETRIES 5 #define DW_SPI_BUF_SIZE \ @@ -144,6 +152,13 @@ struct dw_spi_cfg { u8 spi_frf; }; +struct dw_spi_enh_cfg { + u8 wait_c; + u8 inst_l; + u8 addr_l; + u8 trans_t; +}; + struct dw_spi; struct dw_spi_dma_ops { int (*dma_init)(struct device *dev, struct dw_spi *dws); @@ -294,7 +309,7 @@ static inline void dw_spi_shutdown_chip(struct dw_spi *dws) extern void dw_spi_set_cs(struct spi_device *spi, bool enable); extern void dw_spi_update_config(struct dw_spi *dws, struct spi_device *spi, - struct dw_spi_cfg *cfg); + struct dw_spi_cfg *cfg, struct dw_spi_enh_cfg *enh_cfg); extern int dw_spi_check_status(struct dw_spi *dws, bool raw); extern int dw_spi_add_controller(struct device *dev, struct dw_spi *dws); extern void dw_spi_remove_controller(struct dw_spi *dws); From f6b36efcbe01c880bd1962f95725c8e54fca065b Mon Sep 17 00:00:00 2001 From: Sudip Mukherjee Date: Mon, 3 Aug 2026 05:40:36 -0700 Subject: [PATCH 049/132] spi: dw: add check for support of enhanced spi Before doing the mem op, spi controller will be queried about the buswidths it supports. Add the single/dual/quad/octal if the controller has the DW_SPI_CAP_EMODE capability. The DW_SPI_CAP_EMODE capability will be enabled in a later patch. Signed-off-by: Sudip Mukherjee Co-developed-by: Changhuang Liang Signed-off-by: Changhuang Liang Link: https://patch.msgid.link/20260803124044.156998-4-changhuang.liang@starfivetech.com Signed-off-by: Mark Brown --- drivers/spi/spi-dw-core.c | 34 +++++++++++++++++++++++++++++++++- drivers/spi/spi-dw.h | 1 + 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/drivers/spi/spi-dw-core.c b/drivers/spi/spi-dw-core.c index 1cdd6cc6c892..e720d4276de1 100644 --- a/drivers/spi/spi-dw-core.c +++ b/drivers/spi/spi-dw-core.c @@ -518,6 +518,35 @@ static int dw_spi_adjust_mem_op_size(struct spi_mem *mem, struct spi_mem_op *op) return 0; } +static bool dw_spi_supports_enh_mem_op(struct spi_mem *mem, + const struct spi_mem_op *op) +{ + if (op->addr.nbytes != 0 && op->addr.buswidth != 1 && + op->addr.buswidth != op->data.buswidth) + return false; + + if (op->addr.nbytes >= 8) + return false; + + if (op->cmd.buswidth != 1 && op->cmd.buswidth != op->addr.buswidth && + op->cmd.buswidth != op->data.buswidth) + return false; + + if (op->dummy.nbytes && !op->dummy.buswidth) + return false; + + if (op->dummy.nbytes != 0 && op->data.dir == SPI_MEM_DATA_OUT) + return false; + + /* WAIT_CYCLES is a 5-bit field in SPI_CTRLR0 */ + if (op->dummy.nbytes != 0 && + op->dummy.nbytes * BITS_PER_BYTE / op->dummy.buswidth > + FIELD_MAX(DW_SPI_ENH_CTRLR0_WAIT_CYCLE_MASK)) + return false; + + return spi_mem_default_supports_op(mem, op); +} + static bool dw_spi_supports_mem_op(struct spi_mem *mem, const struct spi_mem_op *op) { @@ -800,7 +829,10 @@ static void dw_spi_init_mem_ops(struct dw_spi *dws) if (!dws->mem_ops.exec_op && !(dws->caps & DW_SPI_CAP_CS_OVERRIDE) && !dws->set_cs) { dws->mem_ops.adjust_op_size = dw_spi_adjust_mem_op_size; - dws->mem_ops.supports_op = dw_spi_supports_mem_op; + if (dws->caps & DW_SPI_CAP_EMODE) + dws->mem_ops.supports_op = dw_spi_supports_enh_mem_op; + else + dws->mem_ops.supports_op = dw_spi_supports_mem_op; dws->mem_ops.exec_op = dw_spi_exec_mem_op; if (!dws->max_mem_freq) dws->max_mem_freq = dws->max_freq; diff --git a/drivers/spi/spi-dw.h b/drivers/spi/spi-dw.h index 16a8c7ab7364..81a433ab759b 100644 --- a/drivers/spi/spi-dw.h +++ b/drivers/spi/spi-dw.h @@ -35,6 +35,7 @@ /* DW SPI controller capabilities */ #define DW_SPI_CAP_CS_OVERRIDE BIT(0) #define DW_SPI_CAP_DFS32 BIT(1) +#define DW_SPI_CAP_EMODE BIT(2) /* Register offsets (Generic for both DWC APB SSI and DWC SSI IP-cores) */ #define DW_SPI_CTRLR0 0x00 From 3d7fd27c970e757fadc96be08453e2e29c226051 Mon Sep 17 00:00:00 2001 From: Sudip Mukherjee Date: Mon, 3 Aug 2026 05:40:37 -0700 Subject: [PATCH 050/132] spi: dw: adjust size of mem_op In enhanced mode adjust the size of the data that can be sent or received as this will then be used to set the NDF. Signed-off-by: Sudip Mukherjee Co-developed-by: Changhuang Liang Signed-off-by: Changhuang Liang Link: https://patch.msgid.link/20260803124044.156998-5-changhuang.liang@starfivetech.com Signed-off-by: Mark Brown --- drivers/spi/spi-dw-core.c | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/drivers/spi/spi-dw-core.c b/drivers/spi/spi-dw-core.c index e720d4276de1..f1b7328fed77 100644 --- a/drivers/spi/spi-dw-core.c +++ b/drivers/spi/spi-dw-core.c @@ -510,6 +510,16 @@ static int dw_spi_target_abort(struct spi_controller *ctlr) return 0; } +static int dw_spi_adjust_enh_mem_op_size(struct spi_mem *mem, struct spi_mem_op *op) +{ + if (op->data.dir == SPI_MEM_DATA_IN) + op->data.nbytes = clamp_val(op->data.nbytes, 0, DW_SPI_NDF_MASK + 1); + else + op->data.nbytes = clamp_val(op->data.nbytes, 0, DW_SPI_NDF_MASK); + + return 0; +} + static int dw_spi_adjust_mem_op_size(struct spi_mem *mem, struct spi_mem_op *op) { if (op->data.dir == SPI_MEM_DATA_IN) @@ -829,10 +839,14 @@ static void dw_spi_init_mem_ops(struct dw_spi *dws) if (!dws->mem_ops.exec_op && !(dws->caps & DW_SPI_CAP_CS_OVERRIDE) && !dws->set_cs) { dws->mem_ops.adjust_op_size = dw_spi_adjust_mem_op_size; - if (dws->caps & DW_SPI_CAP_EMODE) + if (dws->caps & DW_SPI_CAP_EMODE) { dws->mem_ops.supports_op = dw_spi_supports_enh_mem_op; - else + dws->mem_ops.adjust_op_size = dw_spi_adjust_enh_mem_op_size; + } else { dws->mem_ops.supports_op = dw_spi_supports_mem_op; + dws->mem_ops.adjust_op_size = dw_spi_adjust_mem_op_size; + } + dws->mem_ops.exec_op = dw_spi_exec_mem_op; if (!dws->max_mem_freq) dws->max_mem_freq = dws->max_freq; From 8678095362ccddf538d33acdd697ce6a904c87e9 Mon Sep 17 00:00:00 2001 From: Sudip Mukherjee Date: Mon, 3 Aug 2026 05:40:38 -0700 Subject: [PATCH 051/132] spi: dw: update NDF while using enhanced spi mode If the transfer of Transmit only mode is using enhanced SPI then NDF needs to be updated with the number of data frames. If the Transmit FIFO goes empty in-between, DWC_ssi masks the serial clock and wait for rest of the data until the programmed amount of frames are transferred successfully. In receive mode, the number of data frames received is equal to this register value plus 1. In transmit mode, the number of data frames is equal this register value. Signed-off-by: Sudip Mukherjee Co-developed-by: Changhuang Liang Signed-off-by: Changhuang Liang Link: https://patch.msgid.link/20260803124044.156998-6-changhuang.liang@starfivetech.com Signed-off-by: Mark Brown --- drivers/spi/spi-dw-core.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/spi/spi-dw-core.c b/drivers/spi/spi-dw-core.c index f1b7328fed77..d654cb748646 100644 --- a/drivers/spi/spi-dw-core.c +++ b/drivers/spi/spi-dw-core.c @@ -348,6 +348,9 @@ void dw_spi_update_config(struct dw_spi *dws, struct spi_device *spi, if (cfg->tmode == DW_SPI_CTRLR0_TMOD_EPROMREAD || cfg->tmode == DW_SPI_CTRLR0_TMOD_RO) dw_writel(dws, DW_SPI_CTRLR1, cfg->ndf ? cfg->ndf - 1 : 0); + else if (cfg->tmode == DW_SPI_CTRLR0_TMOD_TO && + dws->caps & DW_SPI_CAP_EMODE) + dw_writel(dws, DW_SPI_CTRLR1, cfg->ndf); /* Note DW APB SSI clock divider doesn't support odd numbers */ clk_div = (DIV_ROUND_UP(dws->max_freq, cfg->freq) + 1) & 0xfffe; From 4cd882e1da799e249300ccaacbd15a3c19c35869 Mon Sep 17 00:00:00 2001 From: Sudip Mukherjee Date: Mon, 3 Aug 2026 05:40:39 -0700 Subject: [PATCH 052/132] spi: dw: Introduce enhanced single/dual/quad/octal spi If the spi transfer is using enhanced single/dual/quad/octal spi mode, then we need to update the SPI_CTRLR0 register. The SPI_CTRLR0 register will be updated in dw_spi_update_config() via the values in dw_spi_enh_cfg. Signed-off-by: Sudip Mukherjee Co-developed-by: Changhuang Liang Signed-off-by: Changhuang Liang Link: https://patch.msgid.link/20260803124044.156998-7-changhuang.liang@starfivetech.com Signed-off-by: Mark Brown --- drivers/spi/spi-dw-core.c | 87 ++++++++++++++++++++++++++++++++++++++- drivers/spi/spi-dw.h | 9 ++++ 2 files changed, 95 insertions(+), 1 deletion(-) diff --git a/drivers/spi/spi-dw-core.c b/drivers/spi/spi-dw-core.c index d654cb748646..dbe8779ced86 100644 --- a/drivers/spi/spi-dw-core.c +++ b/drivers/spi/spi-dw-core.c @@ -828,6 +828,90 @@ static int dw_spi_exec_mem_op(struct spi_mem *mem, const struct spi_mem_op *op) return ret; } +static void dw_spi_init_enh_mem_buf(struct dw_spi *dws, const struct spi_mem_op *op) +{ + dws->n_bytes = 1; + if (op->data.dir == SPI_MEM_DATA_IN) { + dws->rx = op->data.buf.in; + dws->rx_len = op->data.nbytes; + dws->tx = NULL; + dws->tx_len = 0; + } else if (op->data.dir == SPI_MEM_DATA_OUT) { + dws->tx_len = op->data.nbytes; + dws->tx = (void *)op->data.buf.out; + dws->rx = NULL; + dws->rx_len = 0; + } else { + dws->rx = NULL; + dws->rx_len = 0; + dws->tx = NULL; + dws->tx_len = 0; + } +} + +static int dw_spi_exec_enh_mem_op(struct spi_mem *mem, const struct spi_mem_op *op) +{ + struct spi_controller *ctlr = mem->spi->controller; + struct dw_spi *dws = spi_controller_get_devdata(ctlr); + struct dw_spi_enh_cfg enh_cfg = {0}; + struct dw_spi_cfg cfg = {0}; + + switch (op->data.buswidth) { + case 0: + case 1: + cfg.spi_frf = DW_SPI_CTRLR0_SPI_FRF_STD_SPI; + break; + case 2: + cfg.spi_frf = DW_SPI_CTRLR0_SPI_FRF_DUAL_SPI; + break; + case 4: + cfg.spi_frf = DW_SPI_CTRLR0_SPI_FRF_QUAD_SPI; + break; + case 8: + cfg.spi_frf = DW_SPI_CTRLR0_SPI_FRF_OCT_SPI; + break; + default: + return -EINVAL; + } + + dw_spi_init_enh_mem_buf(dws, op); + + cfg.dfs = 8; + cfg.freq = clamp(op->max_freq, 0U, dws->max_mem_freq); + cfg.ndf = op->data.nbytes; + if (op->data.dir == SPI_MEM_DATA_IN) + cfg.tmode = DW_SPI_CTRLR0_TMOD_RO; + else + cfg.tmode = DW_SPI_CTRLR0_TMOD_TO; + + if (op->data.buswidth == op->addr.buswidth && + op->data.buswidth == op->cmd.buswidth) + enh_cfg.trans_t = DW_SPI_ENH_CTRLR0_TRANS_TYPE_TT2; + else if (op->data.buswidth == op->addr.buswidth) + enh_cfg.trans_t = DW_SPI_ENH_CTRLR0_TRANS_TYPE_TT1; + else + enh_cfg.trans_t = DW_SPI_ENH_CTRLR0_TRANS_TYPE_TT0; + + enh_cfg.addr_l = op->addr.nbytes << 1; + if (op->cmd.nbytes == 2) + enh_cfg.inst_l = DW_SPI_ENH_CTRLR0_INST_L_INST_L16; + else if (op->cmd.nbytes == 1) + enh_cfg.inst_l = DW_SPI_ENH_CTRLR0_INST_L_INST_L8; + else + enh_cfg.inst_l = DW_SPI_ENH_CTRLR0_INST_L_INST_L0; + + if (op->dummy.buswidth) + enh_cfg.wait_c = op->dummy.nbytes * BITS_PER_BYTE / op->dummy.buswidth; + + dw_spi_enable_chip(dws, 0); + + dw_spi_update_config(dws, mem->spi, &cfg, &enh_cfg); + + dw_spi_enable_chip(dws, 1); + + return 0; +} + /* * Initialize the default memory operations if a glue layer hasn't specified * custom ones. Direct mapping operations will be preserved anyway since DW SPI @@ -843,14 +927,15 @@ static void dw_spi_init_mem_ops(struct dw_spi *dws) !dws->set_cs) { dws->mem_ops.adjust_op_size = dw_spi_adjust_mem_op_size; if (dws->caps & DW_SPI_CAP_EMODE) { + dws->mem_ops.exec_op = dw_spi_exec_enh_mem_op; dws->mem_ops.supports_op = dw_spi_supports_enh_mem_op; dws->mem_ops.adjust_op_size = dw_spi_adjust_enh_mem_op_size; } else { + dws->mem_ops.exec_op = dw_spi_exec_mem_op; dws->mem_ops.supports_op = dw_spi_supports_mem_op; dws->mem_ops.adjust_op_size = dw_spi_adjust_mem_op_size; } - dws->mem_ops.exec_op = dw_spi_exec_mem_op; if (!dws->max_mem_freq) dws->max_mem_freq = dws->max_freq; } diff --git a/drivers/spi/spi-dw.h b/drivers/spi/spi-dw.h index 81a433ab759b..2dae81c15423 100644 --- a/drivers/spi/spi-dw.h +++ b/drivers/spi/spi-dw.h @@ -102,6 +102,9 @@ #define DW_HSSI_CTRLR0_SPI_FRF_MASK GENMASK(23, 22) #define DW_PSSI_CTRLR0_SPI_FRF_MASK GENMASK(22, 21) #define DW_SPI_CTRLR0_SPI_FRF_STD_SPI 0x0 +#define DW_SPI_CTRLR0_SPI_FRF_DUAL_SPI 0x1 +#define DW_SPI_CTRLR0_SPI_FRF_QUAD_SPI 0x2 +#define DW_SPI_CTRLR0_SPI_FRF_OCT_SPI 0x3 /* Bit fields in CTRLR1 */ #define DW_SPI_NDF_MASK GENMASK(15, 0) @@ -133,7 +136,13 @@ #define DW_SPI_ENH_CTRLR0_CLK_STRETCH_EN BIT(30) #define DW_SPI_ENH_CTRLR0_WAIT_CYCLE_MASK GENMASK(15, 11) #define DW_SPI_ENH_CTRLR0_INST_L_MASK GENMASK(9, 8) +#define DW_SPI_ENH_CTRLR0_INST_L_INST_L0 0x0 +#define DW_SPI_ENH_CTRLR0_INST_L_INST_L8 0x2 +#define DW_SPI_ENH_CTRLR0_INST_L_INST_L16 0x3 #define DW_SPI_ENH_CTRLR0_ADDR_L_MASK GENMASK(5, 2) +#define DW_SPI_ENH_CTRLR0_TRANS_TYPE_TT0 0x0 +#define DW_SPI_ENH_CTRLR0_TRANS_TYPE_TT1 0x1 +#define DW_SPI_ENH_CTRLR0_TRANS_TYPE_TT2 0x2 #define DW_SPI_ENH_CTRLR0_TRANS_TYPE_MASK GENMASK(1, 0) /* Mem/DMA operations helpers */ From 51119176c2524c34b524bb63fbfa46f95775ae51 Mon Sep 17 00:00:00 2001 From: Sudip Mukherjee Date: Mon, 3 Aug 2026 05:40:40 -0700 Subject: [PATCH 053/132] spi: dw: send cmd and addr to start the spi transfer In enhanced spi mode, read or write will start by sending the cmd and address (if present). Signed-off-by: Sudip Mukherjee Co-developed-by: Changhuang Liang Signed-off-by: Changhuang Liang Link: https://patch.msgid.link/20260803124044.156998-8-changhuang.liang@starfivetech.com Signed-off-by: Mark Brown --- drivers/spi/spi-dw-core.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/drivers/spi/spi-dw-core.c b/drivers/spi/spi-dw-core.c index dbe8779ced86..3388f62dc695 100644 --- a/drivers/spi/spi-dw-core.c +++ b/drivers/spi/spi-dw-core.c @@ -849,6 +849,19 @@ 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) +{ + /* 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)); + } + } +} + static int dw_spi_exec_enh_mem_op(struct spi_mem *mem, const struct spi_mem_op *op) { struct spi_controller *ctlr = mem->spi->controller; @@ -909,6 +922,8 @@ static int dw_spi_exec_enh_mem_op(struct spi_mem *mem, const struct spi_mem_op * dw_spi_enable_chip(dws, 1); + dw_spi_enh_write_cmd_addr(dws, op); + return 0; } From e017fae89e6d89f15f99e0df74377187ca1d9dee Mon Sep 17 00:00:00 2001 From: Sudip Mukherjee Date: Mon, 3 Aug 2026 05:40:41 -0700 Subject: [PATCH 054/132] spi: dw: use irq handler for enhanced spi Introduce the interrupt handler for enhanced spi to read or write based on the generated irq. Also, use the xfer_completion from spi_controller to wait for a timeout or completion from irq handler. In enhanced mode we need to calculate RXFTLR based on the length of data we are expecting to receive or the fifo length. Signed-off-by: Sudip Mukherjee Co-developed-by: Changhuang Liang Signed-off-by: Changhuang Liang Link: https://patch.msgid.link/20260803124044.156998-9-changhuang.liang@starfivetech.com Signed-off-by: Mark Brown --- drivers/spi/spi-dw-core.c | 106 +++++++++++++++++++++++++++++++++++++- 1 file changed, 104 insertions(+), 2 deletions(-) diff --git a/drivers/spi/spi-dw-core.c b/drivers/spi/spi-dw-core.c index 3388f62dc695..cca8f4ba1004 100644 --- a/drivers/spi/spi-dw-core.c +++ b/drivers/spi/spi-dw-core.c @@ -248,6 +248,29 @@ static irqreturn_t dw_spi_transfer_handler(struct dw_spi *dws) return IRQ_HANDLED; } +static irqreturn_t dw_spi_enh_handler(struct dw_spi *dws) +{ + u16 irq_status = dw_readl(dws, DW_SPI_ISR); + + if (irq_status & DW_SPI_INT_RXFI) { + dw_reader(dws); + if (dws->rx_len && dws->rx_len <= dw_readl(dws, DW_SPI_RXFTLR)) + dw_writel(dws, DW_SPI_RXFTLR, dws->rx_len - 1); + } + + if (irq_status & DW_SPI_INT_TXEI) + dw_writer(dws); + + if (!dws->tx_len && dws->rx_len) { + dw_spi_mask_intr(dws, DW_SPI_INT_TXEI); + } else if (!dws->rx_len && !dws->tx_len) { + dw_spi_mask_intr(dws, 0xff); + spi_finalize_current_transfer(dws->ctlr); + } + + return IRQ_HANDLED; +} + static irqreturn_t dw_spi_irq(int irq, void *dev_id) { struct spi_controller *ctlr = dev_id; @@ -257,10 +280,17 @@ static irqreturn_t dw_spi_irq(int irq, void *dev_id) if (!irq_status) return IRQ_NONE; - if (!ctlr->cur_msg) { + if (!dws->transfer_handler || + (!ctlr->cur_msg && dws->transfer_handler == dw_spi_transfer_handler)) { dw_spi_mask_intr(dws, 0xff); return IRQ_HANDLED; } + if (dws->transfer_handler == dw_spi_enh_handler && + !dws->rx_len && !dws->tx_len) { + dw_spi_mask_intr(dws, 0xff); + spi_finalize_current_transfer(ctlr); + return IRQ_HANDLED; + } return dws->transfer_handler(dws); } @@ -399,6 +429,33 @@ static void dw_spi_irq_setup(struct dw_spi *dws) dw_spi_umask_intr(dws, imask); } +static void dw_spi_enh_irq_setup(struct dw_spi *dws) +{ + u16 level; + u8 imask; + + /* + * Originally Tx and Rx data lengths match. Rx FIFO Threshold level + * will be adjusted at the final stage of the IRQ-based SPI transfer + * execution so not to lose the leftover of the incoming data. + */ + level = min_t(unsigned int, dws->fifo_len / 2, dws->tx_len); + dw_writel(dws, DW_SPI_TXFTLR, level); + + /* + * In enhanced mode if we are reading then tx_len is 0 as we + * have nothing to transmit. Calculate DW_SPI_RXFTLR with + * rx_len. + */ + level = min_t(unsigned int, dws->fifo_len / 2, dws->rx_len); + dw_writel(dws, DW_SPI_RXFTLR, level ? level - 1 : 0); + + dws->transfer_handler = dw_spi_enh_handler; + + imask = DW_SPI_INT_TXEI | DW_SPI_INT_RXFI; + dw_spi_umask_intr(dws, imask); +} + /* * The iterative procedure of the poll-based transfer is simple: write as much * as possible to the Tx FIFO, wait until the pending to receive data is ready @@ -868,6 +925,8 @@ static int dw_spi_exec_enh_mem_op(struct spi_mem *mem, const struct spi_mem_op * struct dw_spi *dws = spi_controller_get_devdata(ctlr); struct dw_spi_enh_cfg enh_cfg = {0}; struct dw_spi_cfg cfg = {0}; + unsigned long long ms; + int ret; switch (op->data.buswidth) { case 0: @@ -920,11 +979,54 @@ static int dw_spi_exec_enh_mem_op(struct spi_mem *mem, const struct spi_mem_op * dw_spi_update_config(dws, mem->spi, &cfg, &enh_cfg); + 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); - return 0; + /* + * FIXME: The exact reason for this delay is not fully understood, + * but empirical testing shows it significantly improves the stability + * of read/write operations. Without this delay, occasional transfer + * errors or timeouts may occur under certain conditions. + * Keeping it as a safeguard based on practical validation. + */ + udelay(5); + + dw_spi_enh_irq_setup(dws); + + /* Use timeout calculation from spi_transfer_wait() */ + ms = 8LL * MSEC_PER_SEC * (dws->rx_len ? dws->rx_len : dws->tx_len); + do_div(ms, dws->current_freq); + + /* + * Increase it twice and add 200 ms tolerance, use + * predefined maximum in case of overflow. + */ + ms += ms + 200; + if (ms > UINT_MAX) + ms = UINT_MAX; + + ms = wait_for_completion_timeout(&ctlr->xfer_completion, + msecs_to_jiffies(ms)); + if (ms == 0) { + dw_spi_mask_intr(dws, 0xff); + synchronize_irq(dws->irq); + dws->rx = NULL; + dws->tx = NULL; + dws->rx_len = 0; + dws->tx_len = 0; + dw_spi_stop_mem_op(dws, mem->spi); + return -EIO; + } + + ret = dw_spi_wait_mem_op_done(dws); + + dw_spi_stop_mem_op(dws, mem->spi); + + return ret; } /* From defdd0f3d09a0676bdbff230c6783bf066fbddac Mon Sep 17 00:00:00 2001 From: Sudip Mukherjee Date: Mon, 3 Aug 2026 05:40:42 -0700 Subject: [PATCH 055/132] spi: dw: detect enhanced spi mode All the SSI controllers supporting enhanced spi modes might not support all the three dual or quad or octal modes. Detect the modes that are supported and finally enable the DW_SPI_CAP_EMODE capability which will start using all the enhanced spi functions that has been added. Signed-off-by: Sudip Mukherjee Co-developed-by: Changhuang Liang Signed-off-by: Changhuang Liang Link: https://patch.msgid.link/20260803124044.156998-10-changhuang.liang@starfivetech.com Signed-off-by: Mark Brown --- drivers/spi/spi-dw-core.c | 75 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 72 insertions(+), 3 deletions(-) diff --git a/drivers/spi/spi-dw-core.c b/drivers/spi/spi-dw-core.c index cca8f4ba1004..bfa017df7673 100644 --- a/drivers/spi/spi-dw-core.c +++ b/drivers/spi/spi-dw-core.c @@ -1029,6 +1029,12 @@ static int dw_spi_exec_enh_mem_op(struct spi_mem *mem, const struct spi_mem_op * return ret; } +static bool dw_spi_can_use_mem_ops(struct dw_spi *dws) +{ + return !dws->mem_ops.exec_op && !(dws->caps & DW_SPI_CAP_CS_OVERRIDE) && + !dws->set_cs; +} + /* * Initialize the default memory operations if a glue layer hasn't specified * custom ones. Direct mapping operations will be preserved anyway since DW SPI @@ -1040,8 +1046,7 @@ static int dw_spi_exec_enh_mem_op(struct spi_mem *mem, const struct spi_mem_op * */ static void dw_spi_init_mem_ops(struct dw_spi *dws) { - if (!dws->mem_ops.exec_op && !(dws->caps & DW_SPI_CAP_CS_OVERRIDE) && - !dws->set_cs) { + if (dw_spi_can_use_mem_ops(dws)) { dws->mem_ops.adjust_op_size = dw_spi_adjust_mem_op_size; if (dws->caps & DW_SPI_CAP_EMODE) { dws->mem_ops.exec_op = dw_spi_exec_enh_mem_op; @@ -1103,6 +1108,66 @@ static void dw_spi_cleanup(struct spi_device *spi) spi_set_ctldata(spi, NULL); } +static u16 detect_enh_mode(struct dw_spi *dws) +{ + u32 tmp_spi_ctrlr0, tmp_ctrlr0; + u32 tmp_val, frf_shift; + u16 mode = 0; + + if (dw_spi_ver_is_ge(dws, HSSI, 103A)) + frf_shift = __bf_shf(DW_HSSI_CTRLR0_SPI_FRF_MASK); + else if (dw_spi_ver_is_ge(dws, PSSI, 400A)) + frf_shift = __bf_shf(DW_PSSI_CTRLR0_SPI_FRF_MASK); + else + return 0; + + tmp_ctrlr0 = dw_readl(dws, DW_SPI_CTRLR0); + tmp_spi_ctrlr0 = dw_readl(dws, DW_SPI_SPI_CTRLR0); + dw_spi_enable_chip(dws, 0); + + /* test dual mode */ + tmp_val = DW_SPI_CTRLR0_SPI_FRF_DUAL_SPI << frf_shift; + dw_writel(dws, DW_SPI_CTRLR0, tmp_val); + if ((tmp_val & dw_readl(dws, DW_SPI_CTRLR0)) == tmp_val) + mode |= SPI_TX_DUAL | SPI_RX_DUAL; + + /* test quad mode */ + tmp_val = DW_SPI_CTRLR0_SPI_FRF_QUAD_SPI << frf_shift; + dw_writel(dws, DW_SPI_CTRLR0, tmp_val); + if ((tmp_val & dw_readl(dws, DW_SPI_CTRLR0)) == tmp_val) + mode |= SPI_TX_QUAD | SPI_RX_QUAD; + + /* test octal mode */ + tmp_val = DW_SPI_CTRLR0_SPI_FRF_OCT_SPI << frf_shift; + dw_writel(dws, DW_SPI_CTRLR0, tmp_val); + if ((tmp_val & dw_readl(dws, DW_SPI_CTRLR0)) == tmp_val) + mode |= SPI_TX_OCTAL | SPI_RX_OCTAL; + + if (!mode) + goto disable_enh; + + /* test clock stretching */ + dw_writel(dws, DW_SPI_SPI_CTRLR0, DW_SPI_ENH_CTRLR0_CLK_STRETCH_EN); + if ((DW_SPI_ENH_CTRLR0_CLK_STRETCH_EN & dw_readl(dws, DW_SPI_SPI_CTRLR0)) != + DW_SPI_ENH_CTRLR0_CLK_STRETCH_EN) { + /* + * If clock stretching is not enabled then do not use + * enhanced mode. + */ + mode = 0; + goto disable_enh; + } + + dws->caps |= DW_SPI_CAP_EMODE; + +disable_enh: + dw_writel(dws, DW_SPI_CTRLR0, tmp_ctrlr0); + dw_writel(dws, DW_SPI_SPI_CTRLR0, tmp_spi_ctrlr0); + dw_spi_enable_chip(dws, 1); + + return mode; +} + /* Restart the controller, disable all interrupts, clean rx fifo */ static void dw_spi_hw_init(struct device *dev, struct dw_spi *dws) { @@ -1182,6 +1247,11 @@ static void dw_spi_hw_init(struct device *dev, struct dw_spi *dws) dws->caps |= DW_SPI_CAP_DFS32; } + dws->ctlr->mode_bits |= SPI_CPOL | SPI_CPHA; + + if (!spi_controller_is_target(dws->ctlr) && dw_spi_can_use_mem_ops(dws)) + dws->ctlr->mode_bits |= detect_enh_mode(dws); + /* enable HW fixup for explicit CS deselect for Amazon's alpine chip */ if (dws->caps & DW_SPI_CAP_CS_OVERRIDE) dw_writel(dws, DW_SPI_CS_OVERRIDE, 0xF); @@ -1226,7 +1296,6 @@ int dw_spi_add_controller(struct device *dev, struct dw_spi *dws) dw_spi_init_mem_ops(dws); - ctlr->mode_bits = SPI_CPOL | SPI_CPHA; if (dws->caps & DW_SPI_CAP_DFS32) ctlr->bits_per_word_mask = SPI_BPW_RANGE_MASK(4, 32); else From 8edce30fc953f5ea06493435a2c26f4709d76e90 Mon Sep 17 00:00:00 2001 From: Changhuang Liang Date: Mon, 3 Aug 2026 05:40:43 -0700 Subject: [PATCH 056/132] spi: dt-bindings: snps,dw-apb-ssi: Add starfive,jhb100-sfc Add a new compatible string "starfive,jhb100-sfc" for the StarFive JHB100 SPI Flash Controller, it based on the Synopsys DesignWare SSI version 2.00a but with minor modifications. Due to these minor modifications, it only supports access for flash memory and requires a system controller register to configure the address mode filter for SPI NOR flash devices. The starfive,sfc-filter-syscon property is required to provide a phandle to the system controller that manages switching between 3-byte and 4-byte addressing modes, essential for supporting SPI NOR flash devices with different address width requirements. Signed-off-by: Changhuang Liang Acked-by: Conor Dooley Link: https://patch.msgid.link/20260803124044.156998-11-changhuang.liang@starfivetech.com Signed-off-by: Mark Brown --- .../bindings/spi/snps,dw-apb-ssi.yaml | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/Documentation/devicetree/bindings/spi/snps,dw-apb-ssi.yaml b/Documentation/devicetree/bindings/spi/snps,dw-apb-ssi.yaml index 95a5bd894e93..883711ccb386 100644 --- a/Documentation/devicetree/bindings/spi/snps,dw-apb-ssi.yaml +++ b/Documentation/devicetree/bindings/spi/snps,dw-apb-ssi.yaml @@ -33,6 +33,17 @@ allOf: else: properties: amd,pensando-elba-syscon: false + - if: + properties: + compatible: + contains: + const: starfive,jhb100-sfc + then: + required: + - starfive,sfc-filter-syscon + else: + properties: + starfive,sfc-filter-syscon: false properties: compatible: @@ -67,6 +78,8 @@ properties: const: amd,pensando-elba-spi - description: Canaan Kendryte K210 SoS SPI Controller const: canaan,k210-spi + - description: StarFive JHB100 SoC SPI Flash Controller + const: starfive,jhb100-sfc - description: Renesas RZ/N1 SPI Controller items: - const: renesas,r9a06g032-spi # RZ/N1D @@ -141,6 +154,15 @@ properties: Block address to control SPI chip-selects. The Elba SoC system controller provides an interface to override the native DWC SSI CS control. + starfive,sfc-filter-syscon: + $ref: /schemas/types.yaml#/definitions/phandle-array + description: + Phandle to the system controller register that controls the SPI NOR flash + address mode filter. This system controller interface provides additional + configuration to switch between 3-byte and 4-byte addressing modes, which + is required when accessing SPI NOR flash devices with different address + width requirements. + patternProperties: "@[0-9a-f]$": type: object From 012c1b04f528c865a372884d6f6240279b065627 Mon Sep 17 00:00:00 2001 From: Changhuang Liang Date: Mon, 3 Aug 2026 05:40:44 -0700 Subject: [PATCH 057/132] spi: dw: Add support for StarFive JHB100 SoC SFC 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 Link: https://patch.msgid.link/20260803124044.156998-12-changhuang.liang@starfivetech.com Signed-off-by: Mark Brown --- 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 bfa017df7673..dfce2fa20adf 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 From 94d031d26168560e951f8c88cbfb7aa53a57092a Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Mon, 3 Aug 2026 22:07:25 +0800 Subject: [PATCH 058/132] spi: cadence-xspi: group marvell support code together We will remove the 64BIT dependency from cadence-xspi for non marvell platform soon. No functionality change. Signed-off-by: Jisheng Zhang Link: https://patch.msgid.link/20260803140728.12747-2-jszhang@kernel.org Signed-off-by: Mark Brown --- drivers/spi/spi-cadence-xspi.c | 455 ++++++++++++++++----------------- 1 file changed, 227 insertions(+), 228 deletions(-) diff --git a/drivers/spi/spi-cadence-xspi.c b/drivers/spi/spi-cadence-xspi.c index e1b337789fce..19429b27875a 100644 --- a/drivers/spi/spi-cadence-xspi.c +++ b/drivers/spi/spi-cadence-xspi.c @@ -315,39 +315,10 @@ struct cdns_xspi_driver_data { u32 rfile_phy_dll_slave_ctrl; }; -static struct cdns_xspi_driver_data marvell_driver_data = { - .mrvl_hw_overlay = true, - .dll_phy_ctrl = MARVELL_REGS_DLL_PHY_CTRL, - .ctb_rfile_phy_ctrl = MARVELL_CTB_RFILE_PHY_CTRL, - .rfile_phy_tsel = MARVELL_RFILE_PHY_TSEL, - .rfile_phy_dq_timing = MARVELL_RFILE_PHY_DQ_TIMING, - .rfile_phy_dqs_timing = MARVELL_RFILE_PHY_DQS_TIMING, - .rfile_phy_gate_lpbk_ctrl = MARVELL_RFILE_PHY_GATE_LPBK_CTRL, - .rfile_phy_dll_master_ctrl = MARVELL_RFILE_PHY_DLL_MASTER_CTRL, - .rfile_phy_dll_slave_ctrl = MARVELL_RFILE_PHY_DLL_SLAVE_CTRL, -}; - static struct cdns_xspi_driver_data cdns_driver_data = { .mrvl_hw_overlay = false, }; -static const int cdns_mrvl_xspi_clk_div_list[] = { - 4, //0x0 = Divide by 4. SPI clock is 200 MHz. - 6, //0x1 = Divide by 6. SPI clock is 133.33 MHz. - 8, //0x2 = Divide by 8. SPI clock is 100 MHz. - 10, //0x3 = Divide by 10. SPI clock is 80 MHz. - 12, //0x4 = Divide by 12. SPI clock is 66.666 MHz. - 16, //0x5 = Divide by 16. SPI clock is 50 MHz. - 18, //0x6 = Divide by 18. SPI clock is 44.44 MHz. - 20, //0x7 = Divide by 20. SPI clock is 40 MHz. - 24, //0x8 = Divide by 24. SPI clock is 33.33 MHz. - 32, //0x9 = Divide by 32. SPI clock is 25 MHz. - 40, //0xA = Divide by 40. SPI clock is 20 MHz. - 50, //0xB = Divide by 50. SPI clock is 16 MHz. - 64, //0xC = Divide by 64. SPI clock is 12.5 MHz. - 128 //0xD = Divide by 128. SPI clock is 6.25 MHz. -}; - struct cdns_xspi_dev { struct platform_device *pdev; struct spi_controller *host; @@ -382,92 +353,6 @@ struct cdns_xspi_dev { int current_xfer_qword; }; -static void cdns_xspi_reset_dll(struct cdns_xspi_dev *cdns_xspi) -{ - u32 dll_cntrl = readl(cdns_xspi->iobase + - CDNS_XSPI_RF_MINICTRL_REGS_DLL_PHY_CTRL); - - /* Reset DLL */ - dll_cntrl |= CDNS_XSPI_DLL_RST_N; - writel(dll_cntrl, cdns_xspi->iobase + - CDNS_XSPI_RF_MINICTRL_REGS_DLL_PHY_CTRL); -} - -static bool cdns_xspi_is_dll_locked(struct cdns_xspi_dev *cdns_xspi) -{ - u32 dll_lock; - - return !readl_relaxed_poll_timeout(cdns_xspi->iobase + - CDNS_XSPI_INTR_STATUS_REG, - dll_lock, ((dll_lock & CDNS_XSPI_DLL_LOCK) == 1), 10, 10000); -} - -/* Static configuration of PHY */ -static bool cdns_xspi_configure_phy(struct cdns_xspi_dev *cdns_xspi) -{ - writel(cdns_xspi->driver_data->dll_phy_ctrl, - cdns_xspi->iobase + CDNS_XSPI_RF_MINICTRL_REGS_DLL_PHY_CTRL); - writel(cdns_xspi->driver_data->ctb_rfile_phy_ctrl, - cdns_xspi->auxbase + CDNS_XSPI_PHY_CTB_RFILE_PHY_CTRL); - writel(cdns_xspi->driver_data->rfile_phy_tsel, - cdns_xspi->auxbase + CDNS_XSPI_PHY_CTB_RFILE_PHY_TSEL); - writel(cdns_xspi->driver_data->rfile_phy_dq_timing, - cdns_xspi->auxbase + CDNS_XSPI_PHY_DATASLICE_RFILE_PHY_DQ_TIMING); - writel(cdns_xspi->driver_data->rfile_phy_dqs_timing, - cdns_xspi->auxbase + CDNS_XSPI_PHY_DATASLICE_RFILE_PHY_DQS_TIMING); - writel(cdns_xspi->driver_data->rfile_phy_gate_lpbk_ctrl, - cdns_xspi->auxbase + CDNS_XSPI_PHY_DATASLICE_RFILE_PHY_GATE_LPBK_CTRL); - writel(cdns_xspi->driver_data->rfile_phy_dll_master_ctrl, - cdns_xspi->auxbase + CDNS_XSPI_PHY_DATASLICE_RFILE_PHY_DLL_MASTER_CTRL); - writel(cdns_xspi->driver_data->rfile_phy_dll_slave_ctrl, - cdns_xspi->auxbase + CDNS_XSPI_PHY_DATASLICE_RFILE_PHY_DLL_SLAVE_CTRL); - - cdns_xspi_reset_dll(cdns_xspi); - - return cdns_xspi_is_dll_locked(cdns_xspi); -} - -static bool cdns_mrvl_xspi_setup_clock(struct cdns_xspi_dev *cdns_xspi, - int requested_clk) -{ - int i = 0; - int clk_val; - u32 clk_reg; - bool update_clk = false; - - while (i < (ARRAY_SIZE(cdns_mrvl_xspi_clk_div_list) - 1)) { - clk_val = MRVL_XSPI_CLOCK_DIVIDED( - cdns_mrvl_xspi_clk_div_list[i]); - if (clk_val <= requested_clk) - break; - i++; - } - - dev_dbg(cdns_xspi->dev, "Found clk div: %d, clk val: %d\n", - cdns_mrvl_xspi_clk_div_list[i], - MRVL_XSPI_CLOCK_DIVIDED( - cdns_mrvl_xspi_clk_div_list[i])); - - clk_reg = readl(cdns_xspi->auxbase + MRVL_XSPI_CLK_CTRL_AUX_REG); - - if (FIELD_GET(MRVL_XSPI_CLK_DIV, clk_reg) != i) { - clk_reg &= ~MRVL_XSPI_CLK_ENABLE; - writel(clk_reg, - cdns_xspi->auxbase + MRVL_XSPI_CLK_CTRL_AUX_REG); - clk_reg = FIELD_PREP(MRVL_XSPI_CLK_DIV, i); - FIELD_MODIFY(MRVL_XSPI_CLK_DIV, &clk_reg, i); - clk_reg |= MRVL_XSPI_CLK_ENABLE; - clk_reg |= MRVL_XSPI_IRQ_ENABLE; - update_clk = true; - } - - if (update_clk) - writel(clk_reg, - cdns_xspi->auxbase + MRVL_XSPI_CLK_CTRL_AUX_REG); - - return update_clk; -} - static int cdns_xspi_wait_for_controller_idle(struct cdns_xspi_dev *cdns_xspi) { u32 ctrl_stat; @@ -540,23 +425,6 @@ static void cdns_xspi_set_interrupts(struct cdns_xspi_dev *cdns_xspi, writel(intr_enable, cdns_xspi->iobase + CDNS_XSPI_INTR_ENABLE_REG); } -static void marvell_xspi_set_interrupts(struct cdns_xspi_dev *cdns_xspi, - bool enabled) -{ - u32 intr_enable; - u32 irq_status; - - irq_status = readl(cdns_xspi->iobase + CDNS_XSPI_INTR_STATUS_REG); - writel(irq_status, cdns_xspi->iobase + CDNS_XSPI_INTR_STATUS_REG); - - intr_enable = readl(cdns_xspi->iobase + CDNS_XSPI_INTR_ENABLE_REG); - if (enabled) - intr_enable |= CDNS_XSPI_INTR_MASK; - else - intr_enable &= ~CDNS_XSPI_INTR_MASK; - writel(intr_enable, cdns_xspi->iobase + CDNS_XSPI_INTR_ENABLE_REG); -} - static int cdns_xspi_controller_init(struct cdns_xspi_dev *cdns_xspi) { u32 ctrl_ver; @@ -644,78 +512,6 @@ static void cdns_xspi_sdma_handle(struct cdns_xspi_dev *cdns_xspi) } } -static void m_ioreadq(void __iomem *addr, void *buf, int len) -{ - if (IS_ALIGNED((long)buf, 8) && len >= 8) { - u64 full_ops = len / 8; - u64 *buffer = buf; - - len -= full_ops * 8; - buf += full_ops * 8; - - do { - u64 b = readq(addr); - *buffer++ = b; - } while (--full_ops); - } - - - while (len) { - u64 tmp_buf; - - tmp_buf = readq(addr); - memcpy(buf, &tmp_buf, min(len, 8)); - len = len > 8 ? len - 8 : 0; - buf += 8; - } -} - -static void m_iowriteq(void __iomem *addr, const void *buf, int len) -{ - if (IS_ALIGNED((long)buf, 8) && len >= 8) { - u64 full_ops = len / 8; - const u64 *buffer = buf; - - len -= full_ops * 8; - buf += full_ops * 8; - - do { - writeq(*buffer++, addr); - } while (--full_ops); - } - - while (len) { - u64 tmp_buf; - - memcpy(&tmp_buf, buf, min(len, 8)); - writeq(tmp_buf, addr); - len = len > 8 ? len - 8 : 0; - buf += 8; - } -} - -static void marvell_xspi_sdma_handle(struct cdns_xspi_dev *cdns_xspi) -{ - u32 sdma_size, sdma_trd_info; - u8 sdma_dir; - - sdma_size = readl(cdns_xspi->iobase + CDNS_XSPI_SDMA_SIZE_REG); - sdma_trd_info = readl(cdns_xspi->iobase + CDNS_XSPI_SDMA_TRD_INFO_REG); - sdma_dir = FIELD_GET(CDNS_XSPI_SDMA_DIR, sdma_trd_info); - - switch (sdma_dir) { - case CDNS_XSPI_SDMA_DIR_READ: - m_ioreadq(cdns_xspi->sdmabase, - cdns_xspi->in_buffer, sdma_size); - break; - - case CDNS_XSPI_SDMA_DIR_WRITE: - m_iowriteq(cdns_xspi->sdmabase, - cdns_xspi->out_buffer, sdma_size); - break; - } -} - static int cdns_xspi_send_stig_command(struct cdns_xspi_dev *cdns_xspi, const struct spi_mem_op *op, bool data_phase) @@ -805,20 +601,6 @@ static int cdns_xspi_mem_op_execute(struct spi_mem *mem, return ret; } -static int marvell_xspi_mem_op_execute(struct spi_mem *mem, - const struct spi_mem_op *op) -{ - struct cdns_xspi_dev *cdns_xspi = - spi_controller_get_devdata(mem->spi->controller); - int ret = 0; - - cdns_mrvl_xspi_setup_clock(cdns_xspi, mem->spi->max_speed_hz); - - ret = cdns_xspi_mem_op(cdns_xspi, mem, op); - - return ret; -} - static bool cdns_xspi_supports_op(struct spi_mem *mem, const struct spi_mem_op *op) { @@ -886,12 +668,6 @@ static const struct spi_controller_mem_ops cadence_xspi_mem_ops = { .adjust_op_size = cdns_xspi_adjust_mem_op_size, }; -static const struct spi_controller_mem_ops marvell_xspi_mem_ops = { - .supports_op = PTR_IF(IS_ENABLED(CONFIG_ACPI), cdns_xspi_supports_op), - .exec_op = marvell_xspi_mem_op_execute, - .adjust_op_size = cdns_xspi_adjust_mem_op_size, -}; - static irqreturn_t cdns_xspi_irq_handler(int this_irq, void *dev) { struct cdns_xspi_dev *cdns_xspi = dev; @@ -973,6 +749,230 @@ static void cdns_xspi_print_phy_config(struct cdns_xspi_dev *cdns_xspi) readl(cdns_xspi->auxbase + CDNS_XSPI_CCP_PHY_DLL_SLAVE_CTRL)); } +static struct cdns_xspi_driver_data marvell_driver_data = { + .mrvl_hw_overlay = true, + .dll_phy_ctrl = MARVELL_REGS_DLL_PHY_CTRL, + .ctb_rfile_phy_ctrl = MARVELL_CTB_RFILE_PHY_CTRL, + .rfile_phy_tsel = MARVELL_RFILE_PHY_TSEL, + .rfile_phy_dq_timing = MARVELL_RFILE_PHY_DQ_TIMING, + .rfile_phy_dqs_timing = MARVELL_RFILE_PHY_DQS_TIMING, + .rfile_phy_gate_lpbk_ctrl = MARVELL_RFILE_PHY_GATE_LPBK_CTRL, + .rfile_phy_dll_master_ctrl = MARVELL_RFILE_PHY_DLL_MASTER_CTRL, + .rfile_phy_dll_slave_ctrl = MARVELL_RFILE_PHY_DLL_SLAVE_CTRL, +}; + +static const int cdns_mrvl_xspi_clk_div_list[] = { + 4, //0x0 = Divide by 4. SPI clock is 200 MHz. + 6, //0x1 = Divide by 6. SPI clock is 133.33 MHz. + 8, //0x2 = Divide by 8. SPI clock is 100 MHz. + 10, //0x3 = Divide by 10. SPI clock is 80 MHz. + 12, //0x4 = Divide by 12. SPI clock is 66.666 MHz. + 16, //0x5 = Divide by 16. SPI clock is 50 MHz. + 18, //0x6 = Divide by 18. SPI clock is 44.44 MHz. + 20, //0x7 = Divide by 20. SPI clock is 40 MHz. + 24, //0x8 = Divide by 24. SPI clock is 33.33 MHz. + 32, //0x9 = Divide by 32. SPI clock is 25 MHz. + 40, //0xA = Divide by 40. SPI clock is 20 MHz. + 50, //0xB = Divide by 50. SPI clock is 16 MHz. + 64, //0xC = Divide by 64. SPI clock is 12.5 MHz. + 128 //0xD = Divide by 128. SPI clock is 6.25 MHz. +}; + +static void cdns_xspi_reset_dll(struct cdns_xspi_dev *cdns_xspi) +{ + u32 dll_cntrl = readl(cdns_xspi->iobase + + CDNS_XSPI_RF_MINICTRL_REGS_DLL_PHY_CTRL); + + /* Reset DLL */ + dll_cntrl |= CDNS_XSPI_DLL_RST_N; + writel(dll_cntrl, cdns_xspi->iobase + + CDNS_XSPI_RF_MINICTRL_REGS_DLL_PHY_CTRL); +} + +static bool cdns_xspi_is_dll_locked(struct cdns_xspi_dev *cdns_xspi) +{ + u32 dll_lock; + + return !readl_relaxed_poll_timeout(cdns_xspi->iobase + + CDNS_XSPI_INTR_STATUS_REG, + dll_lock, ((dll_lock & CDNS_XSPI_DLL_LOCK) == 1), 10, 10000); +} + +/* Static configuration of PHY */ +static bool cdns_xspi_configure_phy(struct cdns_xspi_dev *cdns_xspi) +{ + writel(cdns_xspi->driver_data->dll_phy_ctrl, + cdns_xspi->iobase + CDNS_XSPI_RF_MINICTRL_REGS_DLL_PHY_CTRL); + writel(cdns_xspi->driver_data->ctb_rfile_phy_ctrl, + cdns_xspi->auxbase + CDNS_XSPI_PHY_CTB_RFILE_PHY_CTRL); + writel(cdns_xspi->driver_data->rfile_phy_tsel, + cdns_xspi->auxbase + CDNS_XSPI_PHY_CTB_RFILE_PHY_TSEL); + writel(cdns_xspi->driver_data->rfile_phy_dq_timing, + cdns_xspi->auxbase + CDNS_XSPI_PHY_DATASLICE_RFILE_PHY_DQ_TIMING); + writel(cdns_xspi->driver_data->rfile_phy_dqs_timing, + cdns_xspi->auxbase + CDNS_XSPI_PHY_DATASLICE_RFILE_PHY_DQS_TIMING); + writel(cdns_xspi->driver_data->rfile_phy_gate_lpbk_ctrl, + cdns_xspi->auxbase + CDNS_XSPI_PHY_DATASLICE_RFILE_PHY_GATE_LPBK_CTRL); + writel(cdns_xspi->driver_data->rfile_phy_dll_master_ctrl, + cdns_xspi->auxbase + CDNS_XSPI_PHY_DATASLICE_RFILE_PHY_DLL_MASTER_CTRL); + writel(cdns_xspi->driver_data->rfile_phy_dll_slave_ctrl, + cdns_xspi->auxbase + CDNS_XSPI_PHY_DATASLICE_RFILE_PHY_DLL_SLAVE_CTRL); + + cdns_xspi_reset_dll(cdns_xspi); + + return cdns_xspi_is_dll_locked(cdns_xspi); +} + +static bool cdns_mrvl_xspi_setup_clock(struct cdns_xspi_dev *cdns_xspi, + int requested_clk) +{ + int i = 0; + int clk_val; + u32 clk_reg; + bool update_clk = false; + + while (i < (ARRAY_SIZE(cdns_mrvl_xspi_clk_div_list) - 1)) { + clk_val = MRVL_XSPI_CLOCK_DIVIDED( + cdns_mrvl_xspi_clk_div_list[i]); + if (clk_val <= requested_clk) + break; + i++; + } + + dev_dbg(cdns_xspi->dev, "Found clk div: %d, clk val: %d\n", + cdns_mrvl_xspi_clk_div_list[i], + MRVL_XSPI_CLOCK_DIVIDED( + cdns_mrvl_xspi_clk_div_list[i])); + + clk_reg = readl(cdns_xspi->auxbase + MRVL_XSPI_CLK_CTRL_AUX_REG); + + if (FIELD_GET(MRVL_XSPI_CLK_DIV, clk_reg) != i) { + clk_reg &= ~MRVL_XSPI_CLK_ENABLE; + writel(clk_reg, + cdns_xspi->auxbase + MRVL_XSPI_CLK_CTRL_AUX_REG); + clk_reg = FIELD_PREP(MRVL_XSPI_CLK_DIV, i); + FIELD_MODIFY(MRVL_XSPI_CLK_DIV, &clk_reg, i); + clk_reg |= MRVL_XSPI_CLK_ENABLE; + clk_reg |= MRVL_XSPI_IRQ_ENABLE; + update_clk = true; + } + + if (update_clk) + writel(clk_reg, + cdns_xspi->auxbase + MRVL_XSPI_CLK_CTRL_AUX_REG); + + return update_clk; +} + +static void marvell_xspi_set_interrupts(struct cdns_xspi_dev *cdns_xspi, + bool enabled) +{ + u32 intr_enable; + u32 irq_status; + + irq_status = readl(cdns_xspi->iobase + CDNS_XSPI_INTR_STATUS_REG); + writel(irq_status, cdns_xspi->iobase + CDNS_XSPI_INTR_STATUS_REG); + + intr_enable = readl(cdns_xspi->iobase + CDNS_XSPI_INTR_ENABLE_REG); + if (enabled) + intr_enable |= CDNS_XSPI_INTR_MASK; + else + intr_enable &= ~CDNS_XSPI_INTR_MASK; + writel(intr_enable, cdns_xspi->iobase + CDNS_XSPI_INTR_ENABLE_REG); +} + +static int marvell_xspi_mem_op_execute(struct spi_mem *mem, + const struct spi_mem_op *op) +{ + struct cdns_xspi_dev *cdns_xspi = + spi_controller_get_devdata(mem->spi->controller); + int ret = 0; + + cdns_mrvl_xspi_setup_clock(cdns_xspi, mem->spi->max_speed_hz); + + ret = cdns_xspi_mem_op(cdns_xspi, mem, op); + + return ret; +} + +static void m_ioreadq(void __iomem *addr, void *buf, int len) +{ + if (IS_ALIGNED((long)buf, 8) && len >= 8) { + u64 full_ops = len / 8; + u64 *buffer = buf; + + len -= full_ops * 8; + buf += full_ops * 8; + + do { + u64 b = readq(addr); + *buffer++ = b; + } while (--full_ops); + } + + + while (len) { + u64 tmp_buf; + + tmp_buf = readq(addr); + memcpy(buf, &tmp_buf, min(len, 8)); + len = len > 8 ? len - 8 : 0; + buf += 8; + } +} + +static void m_iowriteq(void __iomem *addr, const void *buf, int len) +{ + if (IS_ALIGNED((long)buf, 8) && len >= 8) { + u64 full_ops = len / 8; + const u64 *buffer = buf; + + len -= full_ops * 8; + buf += full_ops * 8; + + do { + writeq(*buffer++, addr); + } while (--full_ops); + } + + while (len) { + u64 tmp_buf; + + memcpy(&tmp_buf, buf, min(len, 8)); + writeq(tmp_buf, addr); + len = len > 8 ? len - 8 : 0; + buf += 8; + } +} + +static void marvell_xspi_sdma_handle(struct cdns_xspi_dev *cdns_xspi) +{ + u32 sdma_size, sdma_trd_info; + u8 sdma_dir; + + sdma_size = readl(cdns_xspi->iobase + CDNS_XSPI_SDMA_SIZE_REG); + sdma_trd_info = readl(cdns_xspi->iobase + CDNS_XSPI_SDMA_TRD_INFO_REG); + sdma_dir = FIELD_GET(CDNS_XSPI_SDMA_DIR, sdma_trd_info); + + switch (sdma_dir) { + case CDNS_XSPI_SDMA_DIR_READ: + m_ioreadq(cdns_xspi->sdmabase, + cdns_xspi->in_buffer, sdma_size); + break; + + case CDNS_XSPI_SDMA_DIR_WRITE: + m_iowriteq(cdns_xspi->sdmabase, + cdns_xspi->out_buffer, sdma_size); + break; + } +} + +static const struct spi_controller_mem_ops marvell_xspi_mem_ops = { + .supports_op = PTR_IF(IS_ENABLED(CONFIG_ACPI), cdns_xspi_supports_op), + .exec_op = marvell_xspi_mem_op_execute, + .adjust_op_size = cdns_xspi_adjust_mem_op_size, +}; + static int cdns_xspi_prepare_generic(int cs, const void *dout, int len, int glue, u32 *cmd_regs) { u8 *data = (u8 *)dout; @@ -1192,15 +1192,14 @@ static int cdns_xspi_probe(struct platform_device *pdev) if (!cdns_xspi->driver_data) return -ENODEV; + host->mem_ops = &cadence_xspi_mem_ops; + cdns_xspi->sdma_handler = &cdns_xspi_sdma_handle; + cdns_xspi->set_interrupts_handler = &cdns_xspi_set_interrupts; if (cdns_xspi->driver_data->mrvl_hw_overlay) { host->mem_ops = &marvell_xspi_mem_ops; host->transfer_one_message = cdns_xspi_transfer_one_message_b0; cdns_xspi->sdma_handler = &marvell_xspi_sdma_handle; cdns_xspi->set_interrupts_handler = &marvell_xspi_set_interrupts; - } else { - host->mem_ops = &cadence_xspi_mem_ops; - cdns_xspi->sdma_handler = &cdns_xspi_sdma_handle; - cdns_xspi->set_interrupts_handler = &cdns_xspi_set_interrupts; } host->bus_num = -1; From 63371e187e9d3f7581e81021b9168a6c33cc6c0e Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Mon, 3 Aug 2026 22:07:26 +0800 Subject: [PATCH 059/132] spi: cadence-xspi: put marvell support code under CONFIG_64BIT Currently, cadence-xspi depends on 64BIT. This dependency isn't from cadence xspi controller itself, but from marvell support code and 64bit slave dma interface performance optimization. Put marvell support code under CONFIG_64BIT to remove one reason of the 64BIT dependency. Signed-off-by: Jisheng Zhang Link: https://patch.msgid.link/20260803140728.12747-3-jszhang@kernel.org Signed-off-by: Mark Brown --- drivers/spi/spi-cadence-xspi.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/drivers/spi/spi-cadence-xspi.c b/drivers/spi/spi-cadence-xspi.c index 19429b27875a..784619f17b40 100644 --- a/drivers/spi/spi-cadence-xspi.c +++ b/drivers/spi/spi-cadence-xspi.c @@ -749,6 +749,7 @@ static void cdns_xspi_print_phy_config(struct cdns_xspi_dev *cdns_xspi) readl(cdns_xspi->auxbase + CDNS_XSPI_CCP_PHY_DLL_SLAVE_CTRL)); } +#ifdef CONFIG_64BIT static struct cdns_xspi_driver_data marvell_driver_data = { .mrvl_hw_overlay = true, .dll_phy_ctrl = MARVELL_REGS_DLL_PHY_CTRL, @@ -1170,6 +1171,7 @@ static int cdns_xspi_transfer_one_message_b0(struct spi_controller *controller, return 0; } +#endif static int cdns_xspi_probe(struct platform_device *pdev) { @@ -1195,12 +1197,14 @@ static int cdns_xspi_probe(struct platform_device *pdev) host->mem_ops = &cadence_xspi_mem_ops; cdns_xspi->sdma_handler = &cdns_xspi_sdma_handle; cdns_xspi->set_interrupts_handler = &cdns_xspi_set_interrupts; +#ifdef CONFIG_64BIT if (cdns_xspi->driver_data->mrvl_hw_overlay) { host->mem_ops = &marvell_xspi_mem_ops; host->transfer_one_message = cdns_xspi_transfer_one_message_b0; cdns_xspi->sdma_handler = &marvell_xspi_sdma_handle; cdns_xspi->set_interrupts_handler = &marvell_xspi_set_interrupts; } +#endif host->bus_num = -1; platform_set_drvdata(pdev, cdns_xspi); @@ -1246,6 +1250,7 @@ static int cdns_xspi_probe(struct platform_device *pdev) } } +#ifdef CONFIG_64BIT if (cdns_xspi->driver_data->mrvl_hw_overlay) { cdns_xspi->xferbase = devm_platform_ioremap_resource_byname(pdev, "xfer"); if (IS_ERR(cdns_xspi->xferbase)) { @@ -1257,6 +1262,7 @@ static int cdns_xspi_probe(struct platform_device *pdev) } } } +#endif cdns_xspi->irq = platform_get_irq(pdev, 0); if (cdns_xspi->irq < 0) @@ -1269,10 +1275,12 @@ static int cdns_xspi_probe(struct platform_device *pdev) return ret; } +#ifdef CONFIG_64BIT if (cdns_xspi->driver_data->mrvl_hw_overlay) { cdns_mrvl_xspi_setup_clock(cdns_xspi, MRVL_DEFAULT_CLK); cdns_xspi_configure_phy(cdns_xspi); } +#endif cdns_xspi_print_phy_config(cdns_xspi); @@ -1306,10 +1314,12 @@ static int cdns_xspi_resume(struct device *dev) { struct cdns_xspi_dev *cdns_xspi = dev_get_drvdata(dev); +#ifdef CONFIG_64BIT if (cdns_xspi->driver_data->mrvl_hw_overlay) { cdns_mrvl_xspi_setup_clock(cdns_xspi, MRVL_DEFAULT_CLK); cdns_xspi_configure_phy(cdns_xspi); } +#endif cdns_xspi->set_interrupts_handler(cdns_xspi, false); @@ -1324,10 +1334,12 @@ static const struct of_device_id cdns_xspi_of_match[] = { .compatible = "cdns,xspi-nor", .data = &cdns_driver_data, }, +#ifdef CONFIG_64BIT { .compatible = "marvell,cn10-xspi-nor", .data = &marvell_driver_data, }, +#endif { /* end of table */} }; MODULE_DEVICE_TABLE(of, cdns_xspi_of_match); From 0ab32c1c2b6db53a39c742b9ea5d488dca57cde2 Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Mon, 3 Aug 2026 22:07:27 +0800 Subject: [PATCH 060/132] spi: cadence-xspi: only use readsq/writesq under 64BIT Currently, cadence-xspi depends on 64BIT. This dependency isn't from cadence xspi controller itself, but from marvell support code and 64bit slave dma interface performance optimization. readsq and writesq are only available under 64BIT. For 32BIT platforms, we can fallback to ioread32_rep/iowrite32_rep. So we can remove another reason of the 64BIT dependency. Signed-off-by: Jisheng Zhang Link: https://patch.msgid.link/20260803140728.12747-4-jszhang@kernel.org Signed-off-by: Mark Brown --- drivers/spi/spi-cadence-xspi.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/spi/spi-cadence-xspi.c b/drivers/spi/spi-cadence-xspi.c index 784619f17b40..39c868a5b171 100644 --- a/drivers/spi/spi-cadence-xspi.c +++ b/drivers/spi/spi-cadence-xspi.c @@ -454,18 +454,20 @@ static inline void cdns_xspi_sdma_read(struct cdns_xspi_dev *cdns_xspi, size_t l void *buf = cdns_xspi->in_buffer; size_t offset = 0; - if (cdns_xspi->dma_data_width == 4) { + if (!IS_ENABLED(CONFIG_64BIT) || cdns_xspi->dma_data_width == 4) { if (IS_ALIGNED((uintptr_t)src, 4) && IS_ALIGNED((uintptr_t)buf, 4)) { ioread32_rep(src, buf, len >> 2); offset = len & ~0x3; len -= offset; } +#ifdef CONFIG_64BIT } else { if (IS_ALIGNED((uintptr_t)src, 8) && IS_ALIGNED((uintptr_t)buf, 8)) { readsq(src, buf, len >> 3); offset = len & ~0x7; len -= offset; } +#endif } ioread8_rep(src, (u8 *)buf + offset, len); } @@ -476,18 +478,20 @@ static inline void cdns_xspi_sdma_write(struct cdns_xspi_dev *cdns_xspi, size_t const void *buf = cdns_xspi->out_buffer; size_t offset = 0; - if (cdns_xspi->dma_data_width == 4) { + if (!IS_ENABLED(CONFIG_64BIT) || cdns_xspi->dma_data_width == 4) { if (IS_ALIGNED((uintptr_t)dst, 4) && IS_ALIGNED((uintptr_t)buf, 4)) { iowrite32_rep(dst, buf, len >> 2); offset = len & ~0x3; len -= offset; } +#ifdef CONFIG_64BIT } else { if (IS_ALIGNED((uintptr_t)dst, 8) && IS_ALIGNED((uintptr_t)buf, 8)) { writesq(dst, buf, len >> 3); offset = len & ~0x7; len -= offset; } +#endif } iowrite8_rep(dst, (const u8 *)buf + offset, len); } From 54a6b7fe101ce70dcbfbc2607bcc58041a71c95c Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Mon, 3 Aug 2026 22:07:28 +0800 Subject: [PATCH 061/132] spi: cadence-xspi: remove 64BIT Kconfig dependency Now everything is ready, we can remove 64BIT Kconfig dependency now. Signed-off-by: Jisheng Zhang Link: https://patch.msgid.link/20260803140728.12747-5-jszhang@kernel.org Signed-off-by: Mark Brown --- drivers/spi/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index f0c719f87d11..3826fe83000c 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -321,7 +321,7 @@ config SPI_CADENCE_QUADSPI config SPI_CADENCE_XSPI tristate "Cadence XSPI controller" - depends on OF && HAS_IOMEM && 64BIT + depends on OF && HAS_IOMEM depends on SPI_MEM help Enable support for the Cadence XSPI Flash controller. From 82024a7ae057351db26493813f0dbca438a5c76c Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Tue, 4 Aug 2026 09:16:57 +0200 Subject: [PATCH 062/132] spi: dt-bindings: andestech,ae350-spi: Drop redundant num-cs 'num-cs' property is a single integer, thus the binding should not set maxItems. This could be replaced with proper min/max constraints for values, except that hardware (as expressed in driver) supports only one native chipselect, thus constraint of "const:1" would make the property implied by the compatible. spi-controller.yaml schema already defines this property, so its presence here is redundant. Signed-off-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260804-spi-num-cs-v1-1-4ccb13dcc7b0@oss.qualcomm.com Signed-off-by: Mark Brown --- .../devicetree/bindings/spi/andestech,ae350-spi.yaml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Documentation/devicetree/bindings/spi/andestech,ae350-spi.yaml b/Documentation/devicetree/bindings/spi/andestech,ae350-spi.yaml index 8e441742cee6..381042e07fd0 100644 --- a/Documentation/devicetree/bindings/spi/andestech,ae350-spi.yaml +++ b/Documentation/devicetree/bindings/spi/andestech,ae350-spi.yaml @@ -24,10 +24,6 @@ properties: clocks: maxItems: 1 - num-cs: - description: Number of chip selects supported - maxItems: 1 - dmas: items: - description: Transmit FIFO DMA channel From 9ec27e118da72aafc3c4427661c7df65093642e3 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Tue, 4 Aug 2026 09:16:58 +0200 Subject: [PATCH 063/132] spi: dt-bindings: marvell,armada-3700-spi: Drop redundant num-cs 'num-cs' property is a single integer, thus the binding should not set maxItems. This could be replaced with proper min/max constraints for values, except that such constraints are not known. spi-controller.yaml schema already defines this property, so its presence here is redundant. Signed-off-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260804-spi-num-cs-v1-2-4ccb13dcc7b0@oss.qualcomm.com Signed-off-by: Mark Brown --- .../devicetree/bindings/spi/marvell,armada-3700-spi.yaml | 3 --- 1 file changed, 3 deletions(-) diff --git a/Documentation/devicetree/bindings/spi/marvell,armada-3700-spi.yaml b/Documentation/devicetree/bindings/spi/marvell,armada-3700-spi.yaml index 61caa1d86188..75437c46e65a 100644 --- a/Documentation/devicetree/bindings/spi/marvell,armada-3700-spi.yaml +++ b/Documentation/devicetree/bindings/spi/marvell,armada-3700-spi.yaml @@ -28,9 +28,6 @@ properties: clocks: maxItems: 1 - num-cs: - maxItems: 1 - required: - compatible - reg From 577ada5875dc29e995ab36665835ee7ec7d8a8cb Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Tue, 4 Aug 2026 09:16:59 +0200 Subject: [PATCH 064/132] spi: dt-bindings: axiado,ax3000-spi: Drop duplicated type/ref spi-controller.yaml schema already defines this $ref for this property, so binding does not have to. Signed-off-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260804-spi-num-cs-v1-3-4ccb13dcc7b0@oss.qualcomm.com Signed-off-by: Mark Brown --- Documentation/devicetree/bindings/spi/axiado,ax3000-spi.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/Documentation/devicetree/bindings/spi/axiado,ax3000-spi.yaml b/Documentation/devicetree/bindings/spi/axiado,ax3000-spi.yaml index cd2aac66fca2..be258c1f36b1 100644 --- a/Documentation/devicetree/bindings/spi/axiado,ax3000-spi.yaml +++ b/Documentation/devicetree/bindings/spi/axiado,ax3000-spi.yaml @@ -37,7 +37,6 @@ properties: num-cs: description: | Number of chip selects used. - $ref: /schemas/types.yaml#/definitions/uint32 minimum: 1 maximum: 4 default: 4 From ad0dcba3ab9cd4690b056545c2ff96cdc0f6bf75 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Tue, 4 Aug 2026 09:17:00 +0200 Subject: [PATCH 065/132] spi: dt-bindings: spi-cadence: Drop duplicated type/ref spi-controller.yaml schema already defines this $ref for this property, so binding does not have to. Signed-off-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260804-spi-num-cs-v1-4-4ccb13dcc7b0@oss.qualcomm.com Signed-off-by: Mark Brown --- Documentation/devicetree/bindings/spi/spi-cadence.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/Documentation/devicetree/bindings/spi/spi-cadence.yaml b/Documentation/devicetree/bindings/spi/spi-cadence.yaml index 347bed0c4956..429cd824156c 100644 --- a/Documentation/devicetree/bindings/spi/spi-cadence.yaml +++ b/Documentation/devicetree/bindings/spi/spi-cadence.yaml @@ -43,7 +43,6 @@ properties: Number of chip selects used. If a decoder is used, this will be the number of chip selects after the decoder. - $ref: /schemas/types.yaml#/definitions/uint32 minimum: 1 maximum: 4 default: 4 From 463c840f54f85182da90b22bd71f1867f4a13b90 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Tue, 4 Aug 2026 09:17:01 +0200 Subject: [PATCH 066/132] spi: atcspi200: Drop redundant andestech,qilai-spi compatible "andestech,qilai-spi" compatible can only be used with a fallback, which is already matched by the driver's OF device ID table, thus it is redundant in the driver. Signed-off-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260804-spi-num-cs-v1-5-4ccb13dcc7b0@oss.qualcomm.com Signed-off-by: Mark Brown --- drivers/spi/spi-atcspi200.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/spi/spi-atcspi200.c b/drivers/spi/spi-atcspi200.c index 3c5098421ba3..5e2de5970110 100644 --- a/drivers/spi/spi-atcspi200.c +++ b/drivers/spi/spi-atcspi200.c @@ -635,7 +635,6 @@ static int atcspi_resume(struct device *dev) static DEFINE_SIMPLE_DEV_PM_OPS(atcspi_pm_ops, atcspi_suspend, atcspi_resume); static const struct of_device_id atcspi_of_match[] = { - { .compatible = "andestech,qilai-spi", }, { .compatible = "andestech,ae350-spi", }, { /* sentinel */ } }; From 3b0cee02664041aea7e4f787c66cb86c82eb4e97 Mon Sep 17 00:00:00 2001 From: Can Peng Date: Tue, 4 Aug 2026 15:20:17 +0800 Subject: [PATCH 067/132] spi: bcm63xx-hsspi: disable clocks on resume failure bcm63xx_hsspi_resume() enables the HSSPI clock, and optionally the PLL clock, before restarting the SPI controller queue. If spi_controller_resume() fails, the function currently reports success and leaves those clocks enabled. Propagate the error and disable the clocks before returning. Fixes: 142168eba9dc ("spi: bcm63xx-hsspi: add bcm63xx HSSPI driver") Cc: stable@vger.kernel.org Signed-off-by: Can Peng Reviewed-by: Kursad Oney Link: https://patch.msgid.link/20260804072017.860974-1-pengcan@kylinos.cn Signed-off-by: Mark Brown --- drivers/spi/spi-bcm63xx-hsspi.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/spi/spi-bcm63xx-hsspi.c b/drivers/spi/spi-bcm63xx-hsspi.c index 37b3534d0b0a..633164e5a856 100644 --- a/drivers/spi/spi-bcm63xx-hsspi.c +++ b/drivers/spi/spi-bcm63xx-hsspi.c @@ -919,7 +919,13 @@ static int bcm63xx_hsspi_resume(struct device *dev) } } - spi_controller_resume(host); + ret = spi_controller_resume(host); + if (ret) { + if (bs->pll_clk) + clk_disable_unprepare(bs->pll_clk); + clk_disable_unprepare(bs->clk); + return ret; + } return 0; } From 2b62c2c134fa32d9d3a9e7323c8ac74518eeb4ac Mon Sep 17 00:00:00 2001 From: Can Peng Date: Tue, 4 Aug 2026 15:18:31 +0800 Subject: [PATCH 068/132] spi: bcm63xx: disable clock on resume failure bcm63xx_spi_resume() enables the controller clock before restarting the SPI controller queue. If spi_controller_resume() fails, the function currently reports success and leaves the clock enabled. Propagate the error and disable the clock before returning. Fixes: b42dfed83d95 ("spi: add Broadcom BCM63xx SPI controller driver") Cc: stable@vger.kernel.org Signed-off-by: Can Peng Link: https://patch.msgid.link/20260804071831.860784-1-pengcan@kylinos.cn Signed-off-by: Mark Brown --- drivers/spi/spi-bcm63xx.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/spi/spi-bcm63xx.c b/drivers/spi/spi-bcm63xx.c index 8cef725aab26..1d8e257bad30 100644 --- a/drivers/spi/spi-bcm63xx.c +++ b/drivers/spi/spi-bcm63xx.c @@ -653,7 +653,11 @@ static int bcm63xx_spi_resume(struct device *dev) if (ret) return ret; - spi_controller_resume(host); + ret = spi_controller_resume(host); + if (ret) { + clk_disable_unprepare(bs->clk); + return ret; + } return 0; } From d2f5a606710ad70c341dc609430a20a5645618d5 Mon Sep 17 00:00:00 2001 From: Can Peng Date: Tue, 4 Aug 2026 15:19:04 +0800 Subject: [PATCH 069/132] spi: bcmbca-hsspi: disable clocks on resume failure bcmbca_hsspi_resume() enables the HSSPI clock, and optionally the PLL clock, before restarting the SPI controller queue. If spi_controller_resume() fails, the function currently reports success and leaves those clocks enabled. Propagate the error and disable the clocks before returning. Fixes: a38a2233f23b ("spi: bcmbca-hsspi: Add driver for newer HSSPI controller") Cc: stable@vger.kernel.org Signed-off-by: Can Peng Reviewed-by: Kursad Oney Link: https://patch.msgid.link/20260804071904.860842-1-pengcan@kylinos.cn Signed-off-by: Mark Brown --- drivers/spi/spi-bcmbca-hsspi.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/spi/spi-bcmbca-hsspi.c b/drivers/spi/spi-bcmbca-hsspi.c index af88ce04948b..2aed876e5deb 100644 --- a/drivers/spi/spi-bcmbca-hsspi.c +++ b/drivers/spi/spi-bcmbca-hsspi.c @@ -598,7 +598,13 @@ static int bcmbca_hsspi_resume(struct device *dev) } } - spi_controller_resume(host); + ret = spi_controller_resume(host); + if (ret) { + if (bs->pll_clk) + clk_disable_unprepare(bs->pll_clk); + clk_disable_unprepare(bs->clk); + return ret; + } return 0; } From 362a11ef1beadbc301ea949b85748e6bd1e0f384 Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Mon, 27 Jul 2026 13:15:43 -0700 Subject: [PATCH 070/132] spi: ath79: use devm everywhere in probe Use devm_spi_alloc_host() instead of spi_alloc_host() so that the spi controller is automatically freed on probe failure or device removal. This lets us eliminate the err_put_host label and goto error path in probe, as well as the spi_controller_put() call in remove. It's also a bit weird as everything in probe is using devm except the first allocating function. Might fix an ordering issue on teardown. Assisted-by: opencode:big-pickle Signed-off-by: Rosen Penev Link: https://patch.msgid.link/20260727201543.18857-1-rosenp@gmail.com Signed-off-by: Mark Brown --- drivers/spi/spi-ath79.c | 24 +++++++----------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/drivers/spi/spi-ath79.c b/drivers/spi/spi-ath79.c index 2f61e5b9943c..1f5d0fb03917 100644 --- a/drivers/spi/spi-ath79.c +++ b/drivers/spi/spi-ath79.c @@ -173,7 +173,7 @@ static int ath79_spi_probe(struct platform_device *pdev) unsigned long rate; int ret; - host = spi_alloc_host(&pdev->dev, sizeof(*sp)); + host = devm_spi_alloc_host(&pdev->dev, sizeof(*sp)); if (host == NULL) { dev_err(&pdev->dev, "failed to allocate spi host\n"); return -ENOMEM; @@ -194,22 +194,16 @@ static int ath79_spi_probe(struct platform_device *pdev) sp->bitbang.flags = SPI_CS_HIGH; sp->base = devm_platform_ioremap_resource(pdev, 0); - if (IS_ERR(sp->base)) { - ret = PTR_ERR(sp->base); - goto err_put_host; - } + if (IS_ERR(sp->base)) + return PTR_ERR(sp->base); sp->clk = devm_clk_get_enabled(&pdev->dev, "ahb"); - if (IS_ERR(sp->clk)) { - ret = PTR_ERR(sp->clk); - goto err_put_host; - } + if (IS_ERR(sp->clk)) + return PTR_ERR(sp->clk); rate = DIV_ROUND_UP(clk_get_rate(sp->clk), MHZ); - if (!rate) { - ret = -EINVAL; - goto err_put_host; - } + if (!rate) + return -EINVAL; sp->rrw_delay = ATH79_SPI_RRW_DELAY_FACTOR / rate; dev_dbg(&pdev->dev, "register read/write delay is %u nsecs\n", @@ -224,9 +218,6 @@ static int ath79_spi_probe(struct platform_device *pdev) err_disable: ath79_spi_disable(sp); -err_put_host: - spi_controller_put(host); - return ret; } @@ -236,7 +227,6 @@ static void ath79_spi_remove(struct platform_device *pdev) spi_bitbang_stop(&sp->bitbang); ath79_spi_disable(sp); - spi_controller_put(sp->bitbang.ctlr); } static void ath79_spi_shutdown(struct platform_device *pdev) From e0db3f62dbe6bd683a3533fff955165da1c143f7 Mon Sep 17 00:00:00 2001 From: Vishwaroop A Date: Mon, 3 Aug 2026 10:46:13 +0000 Subject: [PATCH 071/132] spi: add new_device/delete_device sysfs interface Development boards such as the Jetson AGX Orin expose SPI buses on expansion headers (e.g. the 40-pin header) so that users can connect and interact with SPI peripherals from userspace. The standard way to get /dev/spidevB.C character device nodes for this purpose is to register spi_device instances backed by the spidev driver. Today there is no viable way to do this on upstream kernels: - The spidev driver rejects the bare "spidev" compatible string in DT, since spidev is a Linux software interface and not a description of real hardware. - Vendor-specific compatible strings (e.g. "nvidia,tegra-spidev") have been rejected by DT maintainers for the same reason. The I2C subsystem solved an analogous problem by exposing new_device/delete_device sysfs attributes on each adapter. Add the same interface to SPI host controllers, so that userspace (e.g. a systemd unit at boot) can instantiate SPI devices at runtime without needing anything in device-tree. The new_device file accepts: [ []] where chip_select is required, while max_speed_hz and mode are optional and default to 0 if omitted. max_speed_hz == 0 is clamped to the controller's maximum by spi_setup(); mode == 0 selects SPI mode 0 (CPOL=0, CPHA=0). The modalias is used both as the device identifier and as a driver_override, so that the device binds to the named driver directly. This is necessary because some drivers like spidev deliberately exclude generic names from their id_table. Devices created this way are limited compared to those declared via DT or board files: - No IRQ is assigned (the device gets IRQ 0 / no interrupt). - No platform_data or device properties are attached. - No OF node is associated with the device. These limitations are acceptable for spidev, which only needs a registered spi_device to expose a character device to userspace. Only devices created via new_device can be removed through delete_device; DT and platform devices are unaffected. The sysfs attributes are gated behind CONFIG_SPI_DYNAMIC since this feature adds a new way of dynamically instantiating and removing SPI devices, and the add_lock locking in spi_unregister_controller() is already conditional on CONFIG_SPI_DYNAMIC. The userspace sysfs group is created manually as the last step of spi_register_controller() and removed as the first step of spi_unregister_controller(). Removing the group before taking add_lock means kernfs_drain() completes any in-flight new_device_store()/delete_device_store() calls before add_lock is acquired, so unregister never blocks on a store that is itself waiting for add_lock and no store can be touching an spi_device that is about to be torn down. Non-sysfs callers of __spi_add_device() (DT/ACPI dynamic add, ancillary registration) continue to be protected by the pre-existing !device_is_registered(&ctlr->dev) check added in commit ddf75be47ca7 ("spi: Prevent adding devices below an unregistering controller"): device_del(&ctlr->dev) runs inside add_lock in spi_unregister_controller() so state_in_sysfs flips to 0 before add_lock is released. Link: https://lore.kernel.org/linux-tegra/909f0c92-d110-4253-903e-5c81e21e12c9@nvidia.com/ Signed-off-by: Vishwaroop A Link: https://patch.msgid.link/20260803104614.2548375-2-va@nvidia.com Signed-off-by: Mark Brown --- drivers/spi/spi.c | 246 ++++++++++++++++++++++++++++++++++++++++ include/linux/spi/spi.h | 18 +++ 2 files changed, 264 insertions(+) diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c index d9e6b4b87c89..07c572e1df5d 100644 --- a/drivers/spi/spi.c +++ b/drivers/spi/spi.c @@ -43,6 +43,7 @@ EXPORT_TRACEPOINT_SYMBOL(spi_transfer_stop); #include "internals.h" static int __spi_setup(struct spi_device *spi, bool initial_setup); +static int __spi_add_device(struct spi_device *spi, struct spi_device *parent); static DEFINE_IDR(spi_controller_idr); @@ -297,6 +298,192 @@ static const struct attribute_group spi_controller_statistics_group = { .attrs = spi_controller_statistics_attrs, }; +#if IS_ENABLED(CONFIG_SPI_DYNAMIC) + +/* + * new_device_store - instantiate a new SPI device from userspace + * + * Takes parameters: [ []] + * + * Examples: + * echo spidev 0 > new_device + * echo spidev 0 10000000 > new_device + * echo spidev 0 10000000 3 > new_device + */ +static ssize_t +new_device_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct spi_controller *ctlr = container_of(dev, struct spi_controller, + dev); + struct spi_device *spi; + char modalias[SPI_NAME_SIZE]; + unsigned int chip_select; + u32 max_speed_hz = 0; + u32 mode = 0; + char *blank; + int status; + + blank = strchr(buf, ' '); + if (!blank) { + dev_err(dev, "new_device: Missing parameters\n"); + return -EINVAL; + } + + if (blank == buf || blank - buf > SPI_NAME_SIZE - 1) { + dev_err(dev, "new_device: Invalid device name\n"); + return -EINVAL; + } + + memset(modalias, 0, sizeof(modalias)); + memcpy(modalias, buf, blank - buf); + + /* + * sscanf fills only the fields it matches; unmatched optional + * fields (max_speed_hz, mode) stay zero from initialisation above. + * max_speed_hz == 0 is clamped to the controller max by spi_setup(). + * mode == 0 selects SPI mode 0 (CPOL=0, CPHA=0). + */ + if (sscanf(++blank, "%u %u %u", &chip_select, &max_speed_hz, &mode) < 1) { + dev_err(dev, "new_device: Can't parse chip select\n"); + return -EINVAL; + } + + /* + * spi_device.chip_select[] is u8, so cap at U8_MAX independently of + * ctlr->num_chipselect (which is u16 and may exceed 255). Without + * this, values in (U8_MAX, num_chipselect) would silently truncate + * inside spi_set_chipselect() and select the wrong CS. + */ + if (chip_select > U8_MAX || chip_select >= ctlr->num_chipselect) { + dev_err(dev, "new_device: Chip select %u out of range (num_chipselect=%u)\n", + chip_select, ctlr->num_chipselect); + return -EINVAL; + } + + /* + * Reject kernel-internal mode bits (SPI_NO_TX, SPI_NO_RX, + * SPI_TPM_HW_FLOW, ...). These are set only by in-kernel drivers + * that know they are safe on their controller/device pair and must + * not be settable through a userspace-writable sysfs. Matches + * spidev's SPI_IOC_WR_MODE32 handling (drivers/spi/spidev.c). + */ + if (mode & ~(u32)SPI_MODE_USER_MASK) { + dev_err(dev, "new_device: Invalid mode bits 0x%x\n", + mode & ~(u32)SPI_MODE_USER_MASK); + return -EINVAL; + } + + spi = spi_alloc_device(ctlr); + if (!spi) + return -ENOMEM; + + spi_set_chipselect(spi, 0, chip_select); + spi->max_speed_hz = max_speed_hz; + spi->mode = mode; + spi->cs_index_mask = BIT(0); + strscpy(spi->modalias, modalias, sizeof(spi->modalias)); + + /* + * Set driver_override so that the device binds to the driver + * named by modalias regardless of whether that driver's + * id_table contains a matching entry. This is needed because + * some drivers (e.g. spidev) deliberately omit generic names + * from their id_table. + */ + status = device_set_driver_override(&spi->dev, modalias); + if (status) { + spi_dev_put(spi); + return status; + } + + /* + * spi_unregister_controller() removes the new_device/delete_device + * sysfs group before taking add_lock, so kernfs_drain() has already + * completed by the time we get here and we cannot be racing with + * teardown. Take add_lock to serialise the __spi_add_device() and + * list insertion with respect to non-sysfs callers of + * __spi_add_device() (DT/ACPI, ancillary), which check + * device_is_registered(&ctlr->dev) under the same lock. + */ + mutex_lock(&ctlr->add_lock); + + status = __spi_add_device(spi, NULL); + if (status) { + mutex_unlock(&ctlr->add_lock); + spi_dev_put(spi); + return status; + } + + list_add_tail(&spi->userspace_node, &ctlr->userspace_clients); + mutex_unlock(&ctlr->add_lock); + + dev_info(dev, "new_device: Instantiated device %s at CS%u\n", + modalias, chip_select); + return count; +} +static DEVICE_ATTR_IGNORE_LOCKDEP(new_device, 0200, NULL, new_device_store); + +static ssize_t +delete_device_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct spi_controller *ctlr = container_of(dev, struct spi_controller, + dev); + struct spi_device *spi, *next; + unsigned short cs; + char end; + int res; + + res = sscanf(buf, "%hu%c", &cs, &end); + if (res < 1) { + dev_err(dev, "delete_device: Can't parse chip select\n"); + return -EINVAL; + } + if (res > 1 && end != '\n') { + dev_err(dev, "delete_device: Unexpected parameters\n"); + return -EINVAL; + } + + res = -ENOENT; + mutex_lock(&ctlr->add_lock); + list_for_each_entry_safe(spi, next, &ctlr->userspace_clients, + userspace_node) { + if (spi_get_chipselect(spi, 0) == cs) { + dev_info(dev, "delete_device: Deleting device %s at CS%u\n", + spi->modalias, cs); + list_del(&spi->userspace_node); + spi_unregister_device(spi); + res = count; + break; + } + } + mutex_unlock(&ctlr->add_lock); + + if (res < 0) + dev_err(dev, "delete_device: Can't find device in list\n"); + return res; +} +static DEVICE_ATTR_IGNORE_LOCKDEP(delete_device, 0200, NULL, + delete_device_store); + +static struct attribute *spi_controller_userspace_attrs[] = { + &dev_attr_new_device.attr, + &dev_attr_delete_device.attr, + NULL, +}; + +static const struct attribute_group spi_controller_userspace_group = { + .attrs = spi_controller_userspace_attrs, +}; + +#endif /* CONFIG_SPI_DYNAMIC */ + +/* + * spi_controller_userspace_group is registered manually for host controllers + * at the end of spi_register_controller() so new_device/delete_device only + * appear after DT/ACPI children and the queue are set up. + */ static const struct attribute_group *spi_controller_groups[] = { &spi_controller_statistics_group, NULL, @@ -3259,6 +3446,9 @@ struct spi_controller *__spi_alloc_controller(struct device *dev, mutex_init(&ctlr->bus_lock_mutex); mutex_init(&ctlr->io_mutex); mutex_init(&ctlr->add_lock); +#if IS_ENABLED(CONFIG_SPI_DYNAMIC) + INIT_LIST_HEAD(&ctlr->userspace_clients); +#endif ctlr->bus_num = -1; ctlr->num_chipselect = 1; ctlr->num_data_lanes = 1; @@ -3552,6 +3742,29 @@ int spi_register_controller(struct spi_controller *ctlr) of_register_spi_devices(ctlr); acpi_register_spi_devices(ctlr); +#if IS_ENABLED(CONFIG_SPI_DYNAMIC) + /* + * Register the new_device/delete_device sysfs interface as the + * final step of host controller bringup, only after the queue, + * boardinfo matching and DT/ACPI enumeration have all completed. + * If this fails, the controller is otherwise usable, so log and + * carry on rather than tearing everything down. + */ + if (!spi_controller_is_target(ctlr)) { + status = sysfs_create_group(&ctlr->dev.kobj, + &spi_controller_userspace_group); + if (status) { + dev_warn(&ctlr->dev, + "Failed to create userspace client interface: %d\n", + status); + } else { + ctlr->userspace_registered = true; + /* Notify userspace that the new attributes are available. */ + kobject_uevent(&ctlr->dev.kobj, KOBJ_CHANGE); + } + } +#endif + return 0; del_ctrl: @@ -3616,10 +3829,43 @@ void spi_unregister_controller(struct spi_controller *ctlr) struct spi_controller *found; int id = ctlr->bus_num; + /* + * Drain in-flight new_device/delete_device sysfs stores and + * prevent new ones from starting. Must happen before we take + * add_lock so kernfs_drain doesn't wait on a store that is + * itself blocked on add_lock. + */ +#if IS_ENABLED(CONFIG_SPI_DYNAMIC) + if (ctlr->userspace_registered) { + sysfs_remove_group(&ctlr->dev.kobj, + &spi_controller_userspace_group); + ctlr->userspace_registered = false; + } +#endif + /* Prevent addition of new devices, unregister existing ones */ if (IS_ENABLED(CONFIG_SPI_DYNAMIC)) mutex_lock(&ctlr->add_lock); +#if IS_ENABLED(CONFIG_SPI_DYNAMIC) + /* + * Drain userspace_clients before __unregister since + * spi_unregister_device() doesn't do list_del() itself. The + * userspace sysfs group has already been removed above and + * kernfs_drain() has completed, so no new entries can appear + * here. + */ + while (!list_empty(&ctlr->userspace_clients)) { + struct spi_device *spi; + + spi = list_first_entry(&ctlr->userspace_clients, + struct spi_device, + userspace_node); + list_del(&spi->userspace_node); + spi_unregister_device(spi); + } +#endif + device_for_each_child(&ctlr->dev, NULL, __unregister); /* First make sure that this controller was ever added */ diff --git a/include/linux/spi/spi.h b/include/linux/spi/spi.h index 4c285d3ede1d..88d17fce02dc 100644 --- a/include/linux/spi/spi.h +++ b/include/linux/spi/spi.h @@ -179,6 +179,8 @@ extern void spi_transfer_cs_change_delay_exec(struct spi_message *msg, * @num_tx_lanes: Number of transmit lanes wired up. * @rx_lane_map: Map of peripheral lanes (index) to controller lanes (value). * @num_rx_lanes: Number of receive lanes wired up. + * @userspace_node: entry on the parent controller's userspace_clients list + * when this device was instantiated via the sysfs new_device interface * * A @spi_device is used to interchange data between an SPI target device * (usually a discrete chip) and CPU memory. @@ -252,6 +254,10 @@ struct spi_device { u8 rx_lane_map[SPI_DEVICE_DATA_LANE_CNT_MAX]; u8 num_rx_lanes; +#if IS_ENABLED(CONFIG_SPI_DYNAMIC) + struct list_head userspace_node; +#endif + /* * Likely need more hooks for more protocol options affecting how * the controller talks to each chip, like: @@ -555,6 +561,11 @@ extern struct spi_device *devm_spi_new_ancillary_device(struct spi_device *spi, * @defer_optimize_message: set to true if controller cannot pre-optimize messages * and needs to defer the optimization step until the message is actually * being transferred + * @userspace_clients: list of SPI devices instantiated from userspace via + * the sysfs new_device interface; protected by @add_lock + * @userspace_registered: true once the new_device/delete_device sysfs + * group has been added by spi_register_controller(); used by + * spi_unregister_controller() to know whether to remove it * * Each SPI controller can communicate with one or more @spi_device * children. These make a small bus, sharing MOSI, MISO and SCK signals @@ -807,6 +818,13 @@ struct spi_controller { bool queue_empty; bool must_async; bool defer_optimize_message; + +#if IS_ENABLED(CONFIG_SPI_DYNAMIC) + /* List of userspace-instantiated devices; protected by @add_lock */ + struct list_head userspace_clients; + /* True after new_device/delete_device sysfs group is created */ + bool userspace_registered; +#endif }; static inline void *spi_controller_get_devdata(struct spi_controller *ctlr) From 036d4b059be01eb67b65b03131855fc42153de6a Mon Sep 17 00:00:00 2001 From: Vishwaroop A Date: Mon, 3 Aug 2026 10:46:14 +0000 Subject: [PATCH 072/132] docs: spi: add documentation for userspace device instantiation Document the new_device and delete_device sysfs attributes on SPI controllers: - Documentation/spi/instantiating-devices.rst: describes when and why this interface is needed, accepted parameters, usage examples, and limitations. - Documentation/ABI/testing/sysfs-class-spi-master: formal ABI entry for both attributes. Signed-off-by: Vishwaroop A Link: https://patch.msgid.link/20260803104614.2548375-3-va@nvidia.com Signed-off-by: Mark Brown --- .../ABI/testing/sysfs-class-spi-master | 34 +++++++ Documentation/spi/index.rst | 1 + Documentation/spi/instantiating-devices.rst | 88 +++++++++++++++++++ 3 files changed, 123 insertions(+) create mode 100644 Documentation/ABI/testing/sysfs-class-spi-master create mode 100644 Documentation/spi/instantiating-devices.rst diff --git a/Documentation/ABI/testing/sysfs-class-spi-master b/Documentation/ABI/testing/sysfs-class-spi-master new file mode 100644 index 000000000000..0a524fcd96d2 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-class-spi-master @@ -0,0 +1,34 @@ +What: /sys/class/spi_master/spiB/new_device +Date: July 2026 +KernelVersion: 7.3 +Contact: linux-spi@vger.kernel.org +Description: (WO) Instantiate a new SPI device on bus B, where B + is the bus number (0, 1, 2, ...). Takes parameters + in the format: + + [ []] + + where modalias is the driver name, chip_select is the + CS line number, and max_speed_hz and mode are optional. + + The device can later be removed with delete_device. + + Only devices created via this interface can be removed + with delete_device; platform and DT devices are not + affected. + + Example: + # echo spidev 0 > /sys/class/spi_master/spi0/new_device + # echo spidev 0 10000000 > /sys/class/spi_master/spi0/new_device + # echo spidev 0 10000000 3 > /sys/class/spi_master/spi0/new_device + +What: /sys/class/spi_master/spiB/delete_device +Date: July 2026 +KernelVersion: 7.3 +Contact: linux-spi@vger.kernel.org +Description: (WO) Remove a SPI device previously created via + new_device. Takes a single parameter: the chip select + number of the device to remove. + + Example: + # echo 0 > /sys/class/spi_master/spi0/delete_device diff --git a/Documentation/spi/index.rst b/Documentation/spi/index.rst index ac0c2233ce48..3f723e2c07da 100644 --- a/Documentation/spi/index.rst +++ b/Documentation/spi/index.rst @@ -8,6 +8,7 @@ Serial Peripheral Interface (SPI) :maxdepth: 1 spi-summary + instantiating-devices spidev multiple-data-lanes butterfly diff --git a/Documentation/spi/instantiating-devices.rst b/Documentation/spi/instantiating-devices.rst new file mode 100644 index 000000000000..ec07353b425d --- /dev/null +++ b/Documentation/spi/instantiating-devices.rst @@ -0,0 +1,88 @@ +.. SPDX-License-Identifier: GPL-2.0 + +============================== +How to instantiate SPI devices +============================== + +SPI devices are normally declared statically via device-tree, ACPI, or +board files. When the SPI controller is registered, these devices are +instantiated automatically by the SPI core. This is the preferred method +for any device with a proper kernel driver. + +Instantiate from user-space +--------------------------- + +In certain cases a SPI device cannot be declared statically: + +* The ``spidev`` driver, which provides raw userspace access to SPI + buses, explicitly rejects the bare ``"spidev"`` compatible string in + device-tree because spidev is a Linux implementation detail, not a + hardware description. Vendor-specific compatible strings for spidev + (e.g. ``"vendor,board-spidev"``) are also generally not accepted + upstream. Device-tree overlays do not help here either, since the + spidev driver performs the same compatible check regardless of how + the DT node was loaded. + +* You are developing or testing a SPI device on a development board + where the SPI bus is exposed on expansion headers, and the connected + device may change frequently. + +For these cases, a sysfs interface is provided on each SPI host controller +(similar to the I2C ``new_device``/``delete_device`` interface described +in Documentation/i2c/instantiating-devices.rst). Two write-only +attribute files are created in every SPI host controller directory: +``new_device`` and ``delete_device``. + +File ``new_device`` takes 2 to 4 parameters: the name of the SPI +device (a string), the chip select number, and optionally +``max_speed_hz`` and ``mode``:: + + [ []] + +The modalias is set both as the device's ``modalias`` field and as its +``driver_override``. This ensures that the device binds to the named +driver directly, bypassing the normal bus matching logic (OF, ACPI, +and ``id_table``). This is necessary because drivers like ``spidev`` +deliberately exclude generic names from their ``id_table``. + +If ``max_speed_hz`` is omitted or 0, ``spi_setup()`` clamps it to +the controller's maximum speed. If ``mode`` is omitted, SPI mode 0 +(CPOL=0, CPHA=0) is used. + +File ``delete_device`` takes a single parameter: the chip select +number. As no two devices can share a chip select on a given SPI bus, +the chip select is sufficient to uniquely identify the device. + +Examples:: + + # Create a spidev device on SPI bus 0, chip select 0 + echo spidev 0 > /sys/class/spi_master/spi0/new_device + + # Create with explicit clock rate and SPI mode + echo spidev 0 10000000 3 > /sys/class/spi_master/spi0/new_device + + # Remove the device + echo 0 > /sys/class/spi_master/spi0/delete_device + +The attributes are added after the host controller and its firmware-described +devices have been registered. Their addition emits a ``change`` uevent, +allowing a udev rule to write to ``new_device`` when the interface is ready. + +Limitations +^^^^^^^^^^^ + +Devices created through this interface have the following limitations +compared to devices declared via device-tree: + +* No interrupt (IRQ) support. +* No additional properties such as ``spi-max-frequency`` DT bindings + or controller-specific configuration. +* No platform data or software nodes. + +For ``spidev`` usage these limitations are not relevant, since spidev +provides a raw byte-level interface that does not require any of these +features. + +Only devices created via ``new_device`` can be removed through +``delete_device``. Devices declared via device-tree, ACPI, or board +files are not affected by this interface. From c4a452dd40aa6c8e30925eb734c1027bd07e9931 Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Mon, 3 Aug 2026 22:19:25 +0800 Subject: [PATCH 073/132] spi: atmel-quadspi: use modern PM macros Use the modern PM macros for the suspend and resume functions to be automatically dropped by the compiler when CONFIG_PM or CONFIG_PM_SLEEP are disabled, without having to use __maybe_unused. Signed-off-by: Jisheng Zhang Link: https://patch.msgid.link/20260803142003.12857-2-jszhang@kernel.org Signed-off-by: Mark Brown --- drivers/spi/atmel-quadspi.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/drivers/spi/atmel-quadspi.c b/drivers/spi/atmel-quadspi.c index 98369091eef7..ec19fa0180f0 100644 --- a/drivers/spi/atmel-quadspi.c +++ b/drivers/spi/atmel-quadspi.c @@ -1578,7 +1578,7 @@ static void atmel_qspi_remove(struct platform_device *pdev) } } -static int __maybe_unused atmel_qspi_suspend(struct device *dev) +static int atmel_qspi_suspend(struct device *dev) { struct spi_controller *ctrl = dev_get_drvdata(dev); struct atmel_qspi *aq = spi_controller_get_devdata(ctrl); @@ -1605,7 +1605,7 @@ static int __maybe_unused atmel_qspi_suspend(struct device *dev) return 0; } -static int __maybe_unused atmel_qspi_resume(struct device *dev) +static int atmel_qspi_resume(struct device *dev) { struct spi_controller *ctrl = dev_get_drvdata(dev); struct atmel_qspi *aq = spi_controller_get_devdata(ctrl); @@ -1637,7 +1637,7 @@ static int __maybe_unused atmel_qspi_resume(struct device *dev) return 0; } -static int __maybe_unused atmel_qspi_runtime_suspend(struct device *dev) +static int atmel_qspi_runtime_suspend(struct device *dev) { struct spi_controller *ctrl = dev_get_drvdata(dev); struct atmel_qspi *aq = spi_controller_get_devdata(ctrl); @@ -1648,7 +1648,7 @@ static int __maybe_unused atmel_qspi_runtime_suspend(struct device *dev) return 0; } -static int __maybe_unused atmel_qspi_runtime_resume(struct device *dev) +static int atmel_qspi_runtime_resume(struct device *dev) { struct spi_controller *ctrl = dev_get_drvdata(dev); struct atmel_qspi *aq = spi_controller_get_devdata(ctrl); @@ -1665,10 +1665,10 @@ static int __maybe_unused atmel_qspi_runtime_resume(struct device *dev) return ret; } -static const struct dev_pm_ops __maybe_unused atmel_qspi_pm_ops = { - SET_SYSTEM_SLEEP_PM_OPS(atmel_qspi_suspend, atmel_qspi_resume) - SET_RUNTIME_PM_OPS(atmel_qspi_runtime_suspend, - atmel_qspi_runtime_resume, NULL) +static const struct dev_pm_ops atmel_qspi_pm_ops = { + SYSTEM_SLEEP_PM_OPS(atmel_qspi_suspend, atmel_qspi_resume) + RUNTIME_PM_OPS(atmel_qspi_runtime_suspend, + atmel_qspi_runtime_resume, NULL) }; static const struct atmel_qspi_caps atmel_sama5d2_qspi_caps = {}; From 6fe48b995296ea525fd71db504739c865d07fd77 Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Mon, 3 Aug 2026 22:19:26 +0800 Subject: [PATCH 074/132] spi: amlogic-spifc-a1: use modern PM macros Use the modern PM macros for the suspend and resume functions to be automatically dropped by the compiler when CONFIG_PM or CONFIG_PM_SLEEP are disabled, without having to use #ifdef guards. This has the advantage of always compiling these functions in, independently of any Kconfig option. Thanks to that, bugs and other regressions are subsequently easier to catch. Signed-off-by: Jisheng Zhang Link: https://patch.msgid.link/20260803142003.12857-3-jszhang@kernel.org Signed-off-by: Mark Brown --- drivers/spi/spi-amlogic-spifc-a1.c | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/drivers/spi/spi-amlogic-spifc-a1.c b/drivers/spi/spi-amlogic-spifc-a1.c index 77a2c11bec5e..388bd2472dc0 100644 --- a/drivers/spi/spi-amlogic-spifc-a1.c +++ b/drivers/spi/spi-amlogic-spifc-a1.c @@ -374,7 +374,6 @@ static int amlogic_spifc_a1_probe(struct platform_device *pdev) return 0; } -#ifdef CONFIG_PM_SLEEP static int amlogic_spifc_a1_suspend(struct device *dev) { struct amlogic_spifc_a1 *spifc = dev_get_drvdata(dev); @@ -409,9 +408,7 @@ static int amlogic_spifc_a1_resume(struct device *dev) return ret; } -#endif /* CONFIG_PM_SLEEP */ -#ifdef CONFIG_PM static int amlogic_spifc_a1_runtime_suspend(struct device *dev) { struct amlogic_spifc_a1 *spifc = dev_get_drvdata(dev); @@ -432,14 +429,12 @@ static int amlogic_spifc_a1_runtime_resume(struct device *dev) return ret; } -#endif /* CONFIG_PM */ static const struct dev_pm_ops amlogic_spifc_a1_pm_ops = { - SET_SYSTEM_SLEEP_PM_OPS(amlogic_spifc_a1_suspend, - amlogic_spifc_a1_resume) - SET_RUNTIME_PM_OPS(amlogic_spifc_a1_runtime_suspend, - amlogic_spifc_a1_runtime_resume, - NULL) + SYSTEM_SLEEP_PM_OPS(amlogic_spifc_a1_suspend, amlogic_spifc_a1_resume) + RUNTIME_PM_OPS(amlogic_spifc_a1_runtime_suspend, + amlogic_spifc_a1_runtime_resume, + NULL) }; #ifdef CONFIG_OF @@ -455,7 +450,7 @@ static struct platform_driver amlogic_spifc_a1_driver = { .driver = { .name = "amlogic-spifc-a1", .of_match_table = of_match_ptr(amlogic_spifc_a1_dt_match), - .pm = &amlogic_spifc_a1_pm_ops, + .pm = pm_ptr(&amlogic_spifc_a1_pm_ops), }, }; module_platform_driver(amlogic_spifc_a1_driver); From 363557bf4d1fb445e3c3015c746e48f98d7b0689 Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Mon, 3 Aug 2026 22:19:27 +0800 Subject: [PATCH 075/132] spi: at91-usart: use modern PM macros Use the modern PM macros for the suspend and resume functions to be automatically dropped by the compiler when CONFIG_PM or CONFIG_PM_SLEEP are disabled, without having to use __maybe_unused. Signed-off-by: Jisheng Zhang Link: https://patch.msgid.link/20260803142003.12857-4-jszhang@kernel.org Signed-off-by: Mark Brown --- drivers/spi/spi-at91-usart.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/spi/spi-at91-usart.c b/drivers/spi/spi-at91-usart.c index 77cad4118202..804f6194c830 100644 --- a/drivers/spi/spi-at91-usart.c +++ b/drivers/spi/spi-at91-usart.c @@ -571,7 +571,7 @@ static int at91_usart_spi_probe(struct platform_device *pdev) return ret; } -__maybe_unused static int at91_usart_spi_suspend(struct device *dev) +static int at91_usart_spi_suspend(struct device *dev) { struct spi_controller *ctrl = dev_get_drvdata(dev); struct at91_usart_spi *aus = spi_controller_get_devdata(ctrl); @@ -587,7 +587,7 @@ __maybe_unused static int at91_usart_spi_suspend(struct device *dev) return 0; } -__maybe_unused static int at91_usart_spi_resume(struct device *dev) +static int at91_usart_spi_resume(struct device *dev) { struct spi_controller *ctrl = dev_get_drvdata(dev); struct at91_usart_spi *aus = spi_controller_get_devdata(ctrl); @@ -616,13 +616,13 @@ static void at91_usart_spi_remove(struct platform_device *pdev) } static const struct dev_pm_ops at91_usart_spi_pm_ops = { - SET_SYSTEM_SLEEP_PM_OPS(at91_usart_spi_suspend, at91_usart_spi_resume) + SYSTEM_SLEEP_PM_OPS(at91_usart_spi_suspend, at91_usart_spi_resume) }; static struct platform_driver at91_usart_spi_driver = { .driver = { .name = "at91_usart_spi", - .pm = &at91_usart_spi_pm_ops, + .pm = pm_sleep_ptr(&at91_usart_spi_pm_ops), }, .probe = at91_usart_spi_probe, .remove = at91_usart_spi_remove, From a7fe79fd31d555b57a5d78322852306ceefb5483 Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Mon, 3 Aug 2026 22:19:28 +0800 Subject: [PATCH 076/132] spi: axiado: use modern PM macros Use the modern PM macros for the suspend and resume functions to be automatically dropped by the compiler when CONFIG_PM or CONFIG_PM_SLEEP are disabled, without having to use __maybe_unused. Signed-off-by: Jisheng Zhang Link: https://patch.msgid.link/20260803142003.12857-5-jszhang@kernel.org Signed-off-by: Mark Brown --- drivers/spi/spi-axiado.c | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/drivers/spi/spi-axiado.c b/drivers/spi/spi-axiado.c index 25561aa9f76f..4a781b53f372 100644 --- a/drivers/spi/spi-axiado.c +++ b/drivers/spi/spi-axiado.c @@ -902,7 +902,7 @@ static void ax_spi_remove(struct platform_device *pdev) * * Return: 0 on success and error value on error */ -static int __maybe_unused ax_spi_suspend(struct device *dev) +static int ax_spi_suspend(struct device *dev) { struct spi_controller *ctlr = dev_get_drvdata(dev); @@ -917,7 +917,7 @@ static int __maybe_unused ax_spi_suspend(struct device *dev) * * Return: 0 on success and error value on error */ -static int __maybe_unused ax_spi_resume(struct device *dev) +static int ax_spi_resume(struct device *dev) { struct spi_controller *ctlr = dev_get_drvdata(dev); struct ax_spi *xspi = spi_controller_get_devdata(ctlr); @@ -934,7 +934,7 @@ static int __maybe_unused ax_spi_resume(struct device *dev) * * Return: 0 on success and error value on error */ -static int __maybe_unused ax_spi_runtime_resume(struct device *dev) +static int ax_spi_runtime_resume(struct device *dev) { struct spi_controller *ctlr = dev_get_drvdata(dev); struct ax_spi *xspi = spi_controller_get_devdata(ctlr); @@ -963,7 +963,7 @@ static int __maybe_unused ax_spi_runtime_resume(struct device *dev) * * Return: Always 0 */ -static int __maybe_unused ax_spi_runtime_suspend(struct device *dev) +static int ax_spi_runtime_suspend(struct device *dev) { struct spi_controller *ctlr = dev_get_drvdata(dev); struct ax_spi *xspi = spi_controller_get_devdata(ctlr); @@ -975,9 +975,8 @@ static int __maybe_unused ax_spi_runtime_suspend(struct device *dev) } static const struct dev_pm_ops ax_spi_dev_pm_ops = { - SET_RUNTIME_PM_OPS(ax_spi_runtime_suspend, - ax_spi_runtime_resume, NULL) - SET_SYSTEM_SLEEP_PM_OPS(ax_spi_suspend, ax_spi_resume) + RUNTIME_PM_OPS(ax_spi_runtime_suspend, ax_spi_runtime_resume, NULL) + SYSTEM_SLEEP_PM_OPS(ax_spi_suspend, ax_spi_resume) }; static const struct of_device_id ax_spi_of_match[] = { @@ -993,7 +992,7 @@ static struct platform_driver ax_spi_driver = { .driver = { .name = AX_SPI_NAME, .of_match_table = ax_spi_of_match, - .pm = &ax_spi_dev_pm_ops, + .pm = pm_ptr(&ax_spi_dev_pm_ops), }, }; From 6062e0f2045e2bab3542feb5e648d09866c70dc8 Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Mon, 3 Aug 2026 22:19:29 +0800 Subject: [PATCH 077/132] spi: bcm-qspi: use modern PM macros Use the modern PM macros for the suspend and resume functions to be automatically dropped by the compiler when CONFIG_PM or CONFIG_PM_SLEEP are disabled, without having to use __maybe_unused. Signed-off-by: Jisheng Zhang Link: https://patch.msgid.link/20260803142003.12857-6-jszhang@kernel.org Signed-off-by: Mark Brown --- drivers/spi/spi-bcm-qspi.c | 6 +++--- drivers/spi/spi-brcmstb-qspi.c | 2 +- drivers/spi/spi-iproc-qspi.c | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/spi/spi-bcm-qspi.c b/drivers/spi/spi-bcm-qspi.c index ae1ffe13fa46..0f96ac59da11 100644 --- a/drivers/spi/spi-bcm-qspi.c +++ b/drivers/spi/spi-bcm-qspi.c @@ -1689,7 +1689,7 @@ void bcm_qspi_remove(struct platform_device *pdev) /* function to be called by SoC specific platform driver remove() */ EXPORT_SYMBOL_GPL(bcm_qspi_remove); -static int __maybe_unused bcm_qspi_suspend(struct device *dev) +static int bcm_qspi_suspend(struct device *dev) { struct bcm_qspi *qspi = dev_get_drvdata(dev); int ret; @@ -1709,7 +1709,7 @@ static int __maybe_unused bcm_qspi_suspend(struct device *dev) return 0; }; -static int __maybe_unused bcm_qspi_resume(struct device *dev) +static int bcm_qspi_resume(struct device *dev) { struct bcm_qspi *qspi = dev_get_drvdata(dev); int ret = 0; @@ -1728,7 +1728,7 @@ static int __maybe_unused bcm_qspi_resume(struct device *dev) return ret; } -SIMPLE_DEV_PM_OPS(bcm_qspi_pm_ops, bcm_qspi_suspend, bcm_qspi_resume); +DEFINE_SIMPLE_DEV_PM_OPS(bcm_qspi_pm_ops, bcm_qspi_suspend, bcm_qspi_resume); /* pm_ops to be called by SoC specific platform driver */ EXPORT_SYMBOL_GPL(bcm_qspi_pm_ops); diff --git a/drivers/spi/spi-brcmstb-qspi.c b/drivers/spi/spi-brcmstb-qspi.c index 7a33b479c1f7..651a73446cff 100644 --- a/drivers/spi/spi-brcmstb-qspi.c +++ b/drivers/spi/spi-brcmstb-qspi.c @@ -31,7 +31,7 @@ static struct platform_driver brcmstb_qspi_driver = { .remove = brcmstb_qspi_remove, .driver = { .name = "brcmstb_qspi", - .pm = &bcm_qspi_pm_ops, + .pm = pm_sleep_ptr(&bcm_qspi_pm_ops), .of_match_table = brcmstb_qspi_of_match, } }; diff --git a/drivers/spi/spi-iproc-qspi.c b/drivers/spi/spi-iproc-qspi.c index 392acc4026ab..5255d5124385 100644 --- a/drivers/spi/spi-iproc-qspi.c +++ b/drivers/spi/spi-iproc-qspi.c @@ -141,7 +141,7 @@ static struct platform_driver bcm_iproc_driver = { .remove = bcm_iproc_remove, .driver = { .name = "bcm_iproc", - .pm = &bcm_qspi_pm_ops, + .pm = pm_sleep_ptr(&bcm_qspi_pm_ops), .of_match_table = bcm_iproc_of_match, } }; From 408ba1d06ee1c0e413ed841532074222e82621f2 Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Mon, 3 Aug 2026 22:19:30 +0800 Subject: [PATCH 078/132] spi: bcm63xx-hsspi: use modern PM macros Use the modern PM macros for the suspend and resume functions to be automatically dropped by the compiler when CONFIG_PM or CONFIG_PM_SLEEP are disabled, without having to use #ifdef guards. This has the advantage of always compiling these functions in, independently of any Kconfig option. Thanks to that, bugs and other regressions are subsequently easier to catch. Signed-off-by: Jisheng Zhang Acked-by: William Zhang Link: https://patch.msgid.link/20260803142003.12857-7-jszhang@kernel.org Signed-off-by: Mark Brown --- drivers/spi/spi-bcm63xx-hsspi.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/drivers/spi/spi-bcm63xx-hsspi.c b/drivers/spi/spi-bcm63xx-hsspi.c index 37b3534d0b0a..b0b65783265f 100644 --- a/drivers/spi/spi-bcm63xx-hsspi.c +++ b/drivers/spi/spi-bcm63xx-hsspi.c @@ -884,7 +884,6 @@ static void bcm63xx_hsspi_remove(struct platform_device *pdev) sysfs_remove_group(&pdev->dev.kobj, &bcm63xx_hsspi_group); } -#ifdef CONFIG_PM_SLEEP static int bcm63xx_hsspi_suspend(struct device *dev) { struct spi_controller *host = dev_get_drvdata(dev); @@ -923,10 +922,9 @@ static int bcm63xx_hsspi_resume(struct device *dev) return 0; } -#endif -static SIMPLE_DEV_PM_OPS(bcm63xx_hsspi_pm_ops, bcm63xx_hsspi_suspend, - bcm63xx_hsspi_resume); +static DEFINE_SIMPLE_DEV_PM_OPS(bcm63xx_hsspi_pm_ops, bcm63xx_hsspi_suspend, + bcm63xx_hsspi_resume); static const struct of_device_id bcm63xx_hsspi_of_match[] = { { .compatible = "brcm,bcm6328-hsspi", }, @@ -938,7 +936,7 @@ MODULE_DEVICE_TABLE(of, bcm63xx_hsspi_of_match); static struct platform_driver bcm63xx_hsspi_driver = { .driver = { .name = "bcm63xx-hsspi", - .pm = &bcm63xx_hsspi_pm_ops, + .pm = pm_sleep_ptr(&bcm63xx_hsspi_pm_ops), .of_match_table = bcm63xx_hsspi_of_match, }, .probe = bcm63xx_hsspi_probe, From f95bf30ed173ab16156c68dbe23859d64d0f657e Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Mon, 3 Aug 2026 22:19:31 +0800 Subject: [PATCH 079/132] spi: bcmbca-hsspi: use modern PM macros Use the modern PM macros for the suspend and resume functions to be automatically dropped by the compiler when CONFIG_PM or CONFIG_PM_SLEEP are disabled, without having to use #ifdef guards. This has the advantage of always compiling these functions in, independently of any Kconfig option. Thanks to that, bugs and other regressions are subsequently easier to catch. Signed-off-by: Jisheng Zhang Acked-by: William Zhang Link: https://patch.msgid.link/20260803142003.12857-8-jszhang@kernel.org Signed-off-by: Mark Brown --- drivers/spi/spi-bcmbca-hsspi.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/drivers/spi/spi-bcmbca-hsspi.c b/drivers/spi/spi-bcmbca-hsspi.c index af88ce04948b..b019f3ff3547 100644 --- a/drivers/spi/spi-bcmbca-hsspi.c +++ b/drivers/spi/spi-bcmbca-hsspi.c @@ -563,7 +563,6 @@ static void bcmbca_hsspi_remove(struct platform_device *pdev) sysfs_remove_group(&pdev->dev.kobj, &bcmbca_hsspi_group); } -#ifdef CONFIG_PM_SLEEP static int bcmbca_hsspi_suspend(struct device *dev) { struct spi_controller *host = dev_get_drvdata(dev); @@ -602,10 +601,9 @@ static int bcmbca_hsspi_resume(struct device *dev) return 0; } -#endif -static SIMPLE_DEV_PM_OPS(bcmbca_hsspi_pm_ops, bcmbca_hsspi_suspend, - bcmbca_hsspi_resume); +static DEFINE_SIMPLE_DEV_PM_OPS(bcmbca_hsspi_pm_ops, bcmbca_hsspi_suspend, + bcmbca_hsspi_resume); static const struct of_device_id bcmbca_hsspi_of_match[] = { { .compatible = "brcm,bcmbca-hsspi-v1.1", }, @@ -617,7 +615,7 @@ MODULE_DEVICE_TABLE(of, bcmbca_hsspi_of_match); static struct platform_driver bcmbca_hsspi_driver = { .driver = { .name = "bcmbca-hsspi", - .pm = &bcmbca_hsspi_pm_ops, + .pm = pm_sleep_ptr(&bcmbca_hsspi_pm_ops), .of_match_table = bcmbca_hsspi_of_match, }, .probe = bcmbca_hsspi_probe, From 1e64a06711595124889ffe0bd0c9150430a00403 Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Mon, 3 Aug 2026 22:19:32 +0800 Subject: [PATCH 080/132] spi: cadence: use modern PM macros Use the modern PM macros for the suspend and resume functions to be automatically dropped by the compiler when CONFIG_PM or CONFIG_PM_SLEEP are disabled, without having to use __maybe_unused. Signed-off-by: Jisheng Zhang Link: https://patch.msgid.link/20260803142003.12857-9-jszhang@kernel.org Signed-off-by: Mark Brown --- drivers/spi/spi-cadence.c | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/drivers/spi/spi-cadence.c b/drivers/spi/spi-cadence.c index af1a05e78492..50ef3a7871c2 100644 --- a/drivers/spi/spi-cadence.c +++ b/drivers/spi/spi-cadence.c @@ -807,7 +807,7 @@ static void cdns_spi_remove(struct platform_device *pdev) * * Return: 0 on success and error value on error */ -static int __maybe_unused cdns_spi_suspend(struct device *dev) +static int cdns_spi_suspend(struct device *dev) { struct spi_controller *ctlr = dev_get_drvdata(dev); @@ -822,7 +822,7 @@ static int __maybe_unused cdns_spi_suspend(struct device *dev) * * Return: 0 on success and error value on error */ -static int __maybe_unused cdns_spi_resume(struct device *dev) +static int cdns_spi_resume(struct device *dev) { struct spi_controller *ctlr = dev_get_drvdata(dev); struct cdns_spi *xspi = spi_controller_get_devdata(ctlr); @@ -839,7 +839,7 @@ static int __maybe_unused cdns_spi_resume(struct device *dev) * * Return: 0 on success and error value on error */ -static int __maybe_unused cdns_spi_runtime_resume(struct device *dev) +static int cdns_spi_runtime_resume(struct device *dev) { struct spi_controller *ctlr = dev_get_drvdata(dev); struct cdns_spi *xspi = spi_controller_get_devdata(ctlr); @@ -868,7 +868,7 @@ static int __maybe_unused cdns_spi_runtime_resume(struct device *dev) * * Return: Always 0 */ -static int __maybe_unused cdns_spi_runtime_suspend(struct device *dev) +static int cdns_spi_runtime_suspend(struct device *dev) { struct spi_controller *ctlr = dev_get_drvdata(dev); struct cdns_spi *xspi = spi_controller_get_devdata(ctlr); @@ -880,9 +880,8 @@ static int __maybe_unused cdns_spi_runtime_suspend(struct device *dev) } static const struct dev_pm_ops cdns_spi_dev_pm_ops = { - SET_RUNTIME_PM_OPS(cdns_spi_runtime_suspend, - cdns_spi_runtime_resume, NULL) - SET_SYSTEM_SLEEP_PM_OPS(cdns_spi_suspend, cdns_spi_resume) + RUNTIME_PM_OPS(cdns_spi_runtime_suspend, cdns_spi_runtime_resume, NULL) + SYSTEM_SLEEP_PM_OPS(cdns_spi_suspend, cdns_spi_resume) }; static const struct of_device_id cdns_spi_of_match[] = { @@ -900,7 +899,7 @@ static struct platform_driver cdns_spi_driver = { .driver = { .name = CDNS_SPI_NAME, .of_match_table = cdns_spi_of_match, - .pm = &cdns_spi_dev_pm_ops, + .pm = pm_ptr(&cdns_spi_dev_pm_ops), }, }; From 7975bfcf50dd90e36bb32996a14ffdeb2ca22208 Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Mon, 3 Aug 2026 22:19:33 +0800 Subject: [PATCH 081/132] spi: coldfire-qspi: use modern PM macros Use the modern PM macros for the suspend and resume functions to be automatically dropped by the compiler when CONFIG_PM or CONFIG_PM_SLEEP are disabled, without having to use #ifdef guards. This has the advantage of always compiling these functions in, independently of any Kconfig option. Thanks to that, bugs and other regressions are subsequently easier to catch. Signed-off-by: Jisheng Zhang Link: https://patch.msgid.link/20260803142003.12857-10-jszhang@kernel.org Signed-off-by: Mark Brown --- drivers/spi/spi-coldfire-qspi.c | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/drivers/spi/spi-coldfire-qspi.c b/drivers/spi/spi-coldfire-qspi.c index 3b175c1da36b..3c8290e08b6d 100644 --- a/drivers/spi/spi-coldfire-qspi.c +++ b/drivers/spi/spi-coldfire-qspi.c @@ -437,7 +437,6 @@ static void mcfqspi_remove(struct platform_device *pdev) mcfqspi_cs_teardown(mcfqspi); } -#ifdef CONFIG_PM_SLEEP static int mcfqspi_suspend(struct device *dev) { struct spi_controller *host = dev_get_drvdata(dev); @@ -462,9 +461,7 @@ static int mcfqspi_resume(struct device *dev) return spi_controller_resume(host); } -#endif -#ifdef CONFIG_PM static int mcfqspi_runtime_suspend(struct device *dev) { struct spi_controller *host = dev_get_drvdata(dev); @@ -484,17 +481,16 @@ static int mcfqspi_runtime_resume(struct device *dev) return 0; } -#endif static const struct dev_pm_ops mcfqspi_pm = { - SET_SYSTEM_SLEEP_PM_OPS(mcfqspi_suspend, mcfqspi_resume) - SET_RUNTIME_PM_OPS(mcfqspi_runtime_suspend, mcfqspi_runtime_resume, - NULL) + SYSTEM_SLEEP_PM_OPS(mcfqspi_suspend, mcfqspi_resume) + RUNTIME_PM_OPS(mcfqspi_runtime_suspend, mcfqspi_runtime_resume, + NULL) }; static struct platform_driver mcfqspi_driver = { .driver.name = DRIVER_NAME, - .driver.pm = &mcfqspi_pm, + .driver.pm = pm_ptr(&mcfqspi_pm), .probe = mcfqspi_probe, .remove = mcfqspi_remove, }; From a0b764e3ec7635dcad8912739b72c5be54b4a070 Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Mon, 3 Aug 2026 22:19:34 +0800 Subject: [PATCH 082/132] spi: dln2: use modern PM macros Use the modern PM macros for the suspend and resume functions to be automatically dropped by the compiler when CONFIG_PM or CONFIG_PM_SLEEP are disabled, without having to use #ifdef guards. This has the advantage of always compiling these functions in, independently of any Kconfig option. Thanks to that, bugs and other regressions are subsequently easier to catch. Signed-off-by: Jisheng Zhang Link: https://patch.msgid.link/20260803142003.12857-11-jszhang@kernel.org Signed-off-by: Mark Brown --- drivers/spi/spi-dln2.c | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/drivers/spi/spi-dln2.c b/drivers/spi/spi-dln2.c index 8333dda7d1e8..60085ed04a06 100644 --- a/drivers/spi/spi-dln2.c +++ b/drivers/spi/spi-dln2.c @@ -787,7 +787,6 @@ static void dln2_spi_remove(struct platform_device *pdev) dev_err(&pdev->dev, "Failed to disable SPI module\n"); } -#ifdef CONFIG_PM_SLEEP static int dln2_spi_suspend(struct device *dev) { int ret; @@ -834,9 +833,7 @@ static int dln2_spi_resume(struct device *dev) return spi_controller_resume(host); } -#endif /* CONFIG_PM_SLEEP */ -#ifdef CONFIG_PM static int dln2_spi_runtime_suspend(struct device *dev) { struct spi_controller *host = dev_get_drvdata(dev); @@ -852,18 +849,16 @@ static int dln2_spi_runtime_resume(struct device *dev) return dln2_spi_enable(dln2, true); } -#endif /* CONFIG_PM */ static const struct dev_pm_ops dln2_spi_pm = { - SET_SYSTEM_SLEEP_PM_OPS(dln2_spi_suspend, dln2_spi_resume) - SET_RUNTIME_PM_OPS(dln2_spi_runtime_suspend, - dln2_spi_runtime_resume, NULL) + SYSTEM_SLEEP_PM_OPS(dln2_spi_suspend, dln2_spi_resume) + RUNTIME_PM_OPS(dln2_spi_runtime_suspend, dln2_spi_runtime_resume, NULL) }; static struct platform_driver spi_dln2_driver = { .driver = { .name = "dln2-spi", - .pm = &dln2_spi_pm, + .pm = pm_ptr(&dln2_spi_pm), }, .probe = dln2_spi_probe, .remove = dln2_spi_remove, From 65172b5d381a37801c982892a784ad3df246a28d Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Mon, 3 Aug 2026 22:19:35 +0800 Subject: [PATCH 083/132] spi: dw-pci: use modern PM macros Use the modern PM macros for the suspend and resume functions to be automatically dropped by the compiler when CONFIG_PM or CONFIG_PM_SLEEP are disabled, without having to use #ifdef guards. This has the advantage of always compiling these functions in, independently of any Kconfig option. Thanks to that, bugs and other regressions are subsequently easier to catch. Signed-off-by: Jisheng Zhang Link: https://patch.msgid.link/20260803142003.12857-12-jszhang@kernel.org Signed-off-by: Mark Brown --- drivers/spi/spi-dw-pci.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/spi/spi-dw-pci.c b/drivers/spi/spi-dw-pci.c index bfb874f96a26..2f3bea51f1e6 100644 --- a/drivers/spi/spi-dw-pci.c +++ b/drivers/spi/spi-dw-pci.c @@ -154,7 +154,6 @@ static void dw_spi_pci_remove(struct pci_dev *pdev) dw_spi_remove_controller(dws); } -#ifdef CONFIG_PM_SLEEP static int dw_spi_pci_suspend(struct device *dev) { struct dw_spi *dws = dev_get_drvdata(dev); @@ -168,9 +167,8 @@ static int dw_spi_pci_resume(struct device *dev) return dw_spi_resume_controller(dws); } -#endif -static SIMPLE_DEV_PM_OPS(dw_spi_pci_pm_ops, dw_spi_pci_suspend, dw_spi_pci_resume); +static DEFINE_SIMPLE_DEV_PM_OPS(dw_spi_pci_pm_ops, dw_spi_pci_suspend, dw_spi_pci_resume); static const struct pci_device_id dw_spi_pci_ids[] = { /* Intel MID platform SPI controller 0 */ @@ -197,7 +195,7 @@ static struct pci_driver dw_spi_pci_driver = { .probe = dw_spi_pci_probe, .remove = dw_spi_pci_remove, .driver = { - .pm = &dw_spi_pci_pm_ops, + .pm = pm_sleep_ptr(&dw_spi_pci_pm_ops), }, }; module_pci_driver(dw_spi_pci_driver); From cda6ef242a6f6776a86047130e5500ed66902a8a Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Mon, 3 Aug 2026 22:19:36 +0800 Subject: [PATCH 084/132] spi: spi-fsl-dspi: use modern PM macros Use the modern PM macros for the suspend and resume functions to be automatically dropped by the compiler when CONFIG_PM or CONFIG_PM_SLEEP are disabled, without having to use #ifdef guards. This has the advantage of always compiling these functions in, independently of any Kconfig option. Thanks to that, bugs and other regressions are subsequently easier to catch. Signed-off-by: Jisheng Zhang Link: https://patch.msgid.link/20260803142003.12857-13-jszhang@kernel.org Signed-off-by: Mark Brown --- drivers/spi/spi-fsl-dspi.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/spi/spi-fsl-dspi.c b/drivers/spi/spi-fsl-dspi.c index c2d283876ef8..23e6bc5f202e 100644 --- a/drivers/spi/spi-fsl-dspi.c +++ b/drivers/spi/spi-fsl-dspi.c @@ -1460,7 +1460,6 @@ static int dspi_init(struct fsl_dspi *dspi) return 0; } -#ifdef CONFIG_PM_SLEEP static int dspi_suspend(struct device *dev) { struct fsl_dspi *dspi = dev_get_drvdata(dev); @@ -1515,9 +1514,8 @@ static int dspi_resume(struct device *dev) clk_disable_unprepare(dspi->clk); return ret; } -#endif /* CONFIG_PM_SLEEP */ -static SIMPLE_DEV_PM_OPS(dspi_pm, dspi_suspend, dspi_resume); +static DEFINE_SIMPLE_DEV_PM_OPS(dspi_pm, dspi_suspend, dspi_resume); static int dspi_target_abort(struct spi_controller *host) { @@ -1742,7 +1740,7 @@ static void dspi_shutdown(struct platform_device *pdev) static struct platform_driver fsl_dspi_driver = { .driver.name = DRIVER_NAME, .driver.of_match_table = fsl_dspi_dt_ids, - .driver.pm = &dspi_pm, + .driver.pm = pm_sleep_ptr(&dspi_pm), .probe = dspi_probe, .remove = dspi_remove, .shutdown = dspi_shutdown, From 4669d74be6891feca6c13fb58e37b182462f4be8 Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Mon, 3 Aug 2026 22:19:37 +0800 Subject: [PATCH 085/132] spi: fsl-espi: use modern PM macros Use the modern PM macros for the suspend and resume functions to be automatically dropped by the compiler when CONFIG_PM or CONFIG_PM_SLEEP are disabled, without having to use #ifdef guards. This has the advantage of always compiling these functions in, independently of any Kconfig option. Thanks to that, bugs and other regressions are subsequently easier to catch. Signed-off-by: Jisheng Zhang Link: https://patch.msgid.link/20260803142003.12857-14-jszhang@kernel.org Signed-off-by: Mark Brown --- drivers/spi/spi-fsl-espi.c | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/drivers/spi/spi-fsl-espi.c b/drivers/spi/spi-fsl-espi.c index 1341bdd7db75..4ac8f9b06783 100644 --- a/drivers/spi/spi-fsl-espi.c +++ b/drivers/spi/spi-fsl-espi.c @@ -580,7 +580,6 @@ static irqreturn_t fsl_espi_irq(s32 irq, void *context_data) return IRQ_HANDLED; } -#ifdef CONFIG_PM static int fsl_espi_runtime_suspend(struct device *dev) { struct spi_controller *host = dev_get_drvdata(dev); @@ -606,7 +605,6 @@ static int fsl_espi_runtime_resume(struct device *dev) return 0; } -#endif static size_t fsl_espi_max_message_size(struct spi_device *spi) { @@ -785,7 +783,6 @@ static void of_fsl_espi_remove(struct platform_device *dev) pm_runtime_disable(&dev->dev); } -#ifdef CONFIG_PM_SLEEP static int of_fsl_espi_suspend(struct device *dev) { struct spi_controller *host = dev_get_drvdata(dev); @@ -811,12 +808,10 @@ static int of_fsl_espi_resume(struct device *dev) return spi_controller_resume(host); } -#endif /* CONFIG_PM_SLEEP */ static const struct dev_pm_ops espi_pm = { - SET_RUNTIME_PM_OPS(fsl_espi_runtime_suspend, - fsl_espi_runtime_resume, NULL) - SET_SYSTEM_SLEEP_PM_OPS(of_fsl_espi_suspend, of_fsl_espi_resume) + RUNTIME_PM_OPS(fsl_espi_runtime_suspend, fsl_espi_runtime_resume, NULL) + SYSTEM_SLEEP_PM_OPS(of_fsl_espi_suspend, of_fsl_espi_resume) }; static const struct of_device_id of_fsl_espi_match[] = { @@ -829,7 +824,7 @@ static struct platform_driver fsl_espi_driver = { .driver = { .name = "fsl_espi", .of_match_table = of_fsl_espi_match, - .pm = &espi_pm, + .pm = pm_ptr(&espi_pm), }, .probe = of_fsl_espi_probe, .remove = of_fsl_espi_remove, From 4f6d93537f0108a73e37cee3e1466d2b556ab6a4 Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Mon, 3 Aug 2026 22:19:38 +0800 Subject: [PATCH 086/132] spi: fsl-lpspi: use modern PM macros Use the modern PM macros for the suspend and resume functions to be automatically dropped by the compiler when CONFIG_PM or CONFIG_PM_SLEEP are disabled, without having to use #ifdef guards. This has the advantage of always compiling these functions in, independently of any Kconfig option. Thanks to that, bugs and other regressions are subsequently easier to catch. Signed-off-by: Jisheng Zhang Link: https://patch.msgid.link/20260803142003.12857-15-jszhang@kernel.org Signed-off-by: Mark Brown --- drivers/spi/spi-fsl-lpspi.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/spi/spi-fsl-lpspi.c b/drivers/spi/spi-fsl-lpspi.c index e14753144e19..5aa0a75b4e57 100644 --- a/drivers/spi/spi-fsl-lpspi.c +++ b/drivers/spi/spi-fsl-lpspi.c @@ -842,7 +842,6 @@ static irqreturn_t fsl_lpspi_isr(int irq, void *dev_id) return IRQ_NONE; } -#ifdef CONFIG_PM static int fsl_lpspi_runtime_resume(struct device *dev) { struct spi_controller *controller = dev_get_drvdata(dev); @@ -876,7 +875,6 @@ static int fsl_lpspi_runtime_suspend(struct device *dev) return 0; } -#endif static int fsl_lpspi_init_rpm(struct fsl_lpspi_data *fsl_lpspi) { @@ -1056,8 +1054,8 @@ static int fsl_lpspi_resume(struct device *dev) } static const struct dev_pm_ops fsl_lpspi_pm_ops = { - SET_RUNTIME_PM_OPS(fsl_lpspi_runtime_suspend, - fsl_lpspi_runtime_resume, NULL) + RUNTIME_PM_OPS(fsl_lpspi_runtime_suspend, + fsl_lpspi_runtime_resume, NULL) SYSTEM_SLEEP_PM_OPS(fsl_lpspi_suspend, fsl_lpspi_resume) }; From 527adbf1a193af77b740732abe0e36dccdc11935 Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Mon, 3 Aug 2026 22:19:39 +0800 Subject: [PATCH 087/132] spi: qcom-geni: use modern PM macros Use the modern PM macros for the suspend and resume functions to be automatically dropped by the compiler when CONFIG_PM or CONFIG_PM_SLEEP are disabled, without having to use __maybe_unused. Signed-off-by: Jisheng Zhang Link: https://patch.msgid.link/20260803142003.12857-16-jszhang@kernel.org Signed-off-by: Mark Brown --- drivers/spi/spi-geni-qcom.c | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/drivers/spi/spi-geni-qcom.c b/drivers/spi/spi-geni-qcom.c index 6566975eb24f..6c57f8309a3b 100644 --- a/drivers/spi/spi-geni-qcom.c +++ b/drivers/spi/spi-geni-qcom.c @@ -1157,7 +1157,7 @@ static int spi_geni_probe(struct platform_device *pdev) return devm_spi_register_controller(dev, spi); } -static int __maybe_unused spi_geni_runtime_suspend(struct device *dev) +static int spi_geni_runtime_suspend(struct device *dev) { struct spi_controller *spi = dev_get_drvdata(dev); struct spi_geni_master *mas = spi_controller_get_devdata(spi); @@ -1166,7 +1166,7 @@ static int __maybe_unused spi_geni_runtime_suspend(struct device *dev) mas->dev_data->power_off(&mas->se) : 0; } -static int __maybe_unused spi_geni_runtime_resume(struct device *dev) +static int spi_geni_runtime_resume(struct device *dev) { struct spi_controller *spi = dev_get_drvdata(dev); struct spi_geni_master *mas = spi_controller_get_devdata(spi); @@ -1184,7 +1184,7 @@ static int __maybe_unused spi_geni_runtime_resume(struct device *dev) return 0; } -static int __maybe_unused spi_geni_suspend(struct device *dev) +static int spi_geni_suspend(struct device *dev) { struct spi_controller *spi = dev_get_drvdata(dev); int ret; @@ -1200,7 +1200,7 @@ static int __maybe_unused spi_geni_suspend(struct device *dev) return ret; } -static int __maybe_unused spi_geni_resume(struct device *dev) +static int spi_geni_resume(struct device *dev) { struct spi_controller *spi = dev_get_drvdata(dev); int ret; @@ -1217,9 +1217,8 @@ static int __maybe_unused spi_geni_resume(struct device *dev) } static const struct dev_pm_ops spi_geni_pm_ops = { - SET_RUNTIME_PM_OPS(spi_geni_runtime_suspend, - spi_geni_runtime_resume, NULL) - SET_SYSTEM_SLEEP_PM_OPS(spi_geni_suspend, spi_geni_resume) + RUNTIME_PM_OPS(spi_geni_runtime_suspend, spi_geni_runtime_resume, NULL) + SYSTEM_SLEEP_PM_OPS(spi_geni_suspend, spi_geni_resume) }; static const struct geni_spi_desc geni_spi = { @@ -1245,7 +1244,7 @@ static struct platform_driver spi_geni_driver = { .probe = spi_geni_probe, .driver = { .name = "geni_spi", - .pm = &spi_geni_pm_ops, + .pm = pm_ptr(&spi_geni_pm_ops), .of_match_table = spi_geni_dt_match, }, }; From 88672e90a9457f99a5aa2c6f0f9cb87f19c81147 Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Mon, 3 Aug 2026 22:19:40 +0800 Subject: [PATCH 088/132] spi: img-spfi: use modern PM macros Use the modern PM macros for the suspend and resume functions to be automatically dropped by the compiler when CONFIG_PM or CONFIG_PM_SLEEP are disabled, without having to use #ifdef guards. This has the advantage of always compiling these functions in, independently of any Kconfig option. Thanks to that, bugs and other regressions are subsequently easier to catch. Signed-off-by: Jisheng Zhang Link: https://patch.msgid.link/20260803142003.12857-17-jszhang@kernel.org Signed-off-by: Mark Brown --- drivers/spi/spi-img-spfi.c | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/drivers/spi/spi-img-spfi.c b/drivers/spi/spi-img-spfi.c index aec724e3f824..19c8250ddb64 100644 --- a/drivers/spi/spi-img-spfi.c +++ b/drivers/spi/spi-img-spfi.c @@ -677,7 +677,6 @@ static void img_spfi_remove(struct platform_device *pdev) } } -#ifdef CONFIG_PM static int img_spfi_runtime_suspend(struct device *dev) { struct spi_controller *host = dev_get_drvdata(dev); @@ -706,9 +705,7 @@ static int img_spfi_runtime_resume(struct device *dev) return 0; } -#endif /* CONFIG_PM */ -#ifdef CONFIG_PM_SLEEP static int img_spfi_suspend(struct device *dev) { struct spi_controller *host = dev_get_drvdata(dev); @@ -730,12 +727,10 @@ static int img_spfi_resume(struct device *dev) return spi_controller_resume(host); } -#endif /* CONFIG_PM_SLEEP */ static const struct dev_pm_ops img_spfi_pm_ops = { - SET_RUNTIME_PM_OPS(img_spfi_runtime_suspend, img_spfi_runtime_resume, - NULL) - SET_SYSTEM_SLEEP_PM_OPS(img_spfi_suspend, img_spfi_resume) + RUNTIME_PM_OPS(img_spfi_runtime_suspend, img_spfi_runtime_resume, NULL) + SYSTEM_SLEEP_PM_OPS(img_spfi_suspend, img_spfi_resume) }; static const struct of_device_id img_spfi_of_match[] = { @@ -747,7 +742,7 @@ MODULE_DEVICE_TABLE(of, img_spfi_of_match); static struct platform_driver img_spfi_driver = { .driver = { .name = "img-spfi", - .pm = &img_spfi_pm_ops, + .pm = pm_ptr(&img_spfi_pm_ops), .of_match_table = of_match_ptr(img_spfi_of_match), }, .probe = img_spfi_probe, From 8de18aced458db42f847d0da2ae08e654ba2f6c1 Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Mon, 3 Aug 2026 22:19:41 +0800 Subject: [PATCH 089/132] spi: loongson: use modern PM macros Use the modern PM macros for the suspend and resume functions to be automatically dropped by the compiler when CONFIG_PM or CONFIG_PM_SLEEP are disabled, without having to use __maybe_unused. Signed-off-by: Jisheng Zhang Link: https://patch.msgid.link/20260803142003.12857-18-jszhang@kernel.org Signed-off-by: Mark Brown --- drivers/spi/spi-loongson-core.c | 4 ++-- drivers/spi/spi-loongson-pci.c | 2 +- drivers/spi/spi-loongson-plat.c | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/spi/spi-loongson-core.c b/drivers/spi/spi-loongson-core.c index f50423c3db4c..30b6a7176ce9 100644 --- a/drivers/spi/spi-loongson-core.c +++ b/drivers/spi/spi-loongson-core.c @@ -229,7 +229,7 @@ int loongson_spi_init_controller(struct device *dev, void __iomem *regs) } EXPORT_SYMBOL_NS_GPL(loongson_spi_init_controller, "SPI_LOONGSON_CORE"); -static int __maybe_unused loongson_spi_suspend(struct device *dev) +static int loongson_spi_suspend(struct device *dev) { struct loongson_spi *loongson_spi; struct spi_controller *controller; @@ -249,7 +249,7 @@ static int __maybe_unused loongson_spi_suspend(struct device *dev) return 0; } -static int __maybe_unused loongson_spi_resume(struct device *dev) +static int loongson_spi_resume(struct device *dev) { struct loongson_spi *loongson_spi; struct spi_controller *controller; diff --git a/drivers/spi/spi-loongson-pci.c b/drivers/spi/spi-loongson-pci.c index 2fe5419a8dd2..ccc26efd88fe 100644 --- a/drivers/spi/spi-loongson-pci.c +++ b/drivers/spi/spi-loongson-pci.c @@ -43,7 +43,7 @@ static struct pci_driver loongson_spi_pci_driver = { .probe = loongson_spi_pci_register, .driver = { .bus = &pci_bus_type, - .pm = &loongson_spi_dev_pm_ops, + .pm = pm_sleep_ptr(&loongson_spi_dev_pm_ops), }, }; module_pci_driver(loongson_spi_pci_driver); diff --git a/drivers/spi/spi-loongson-plat.c b/drivers/spi/spi-loongson-plat.c index 550b237838c0..6f8202264341 100644 --- a/drivers/spi/spi-loongson-plat.c +++ b/drivers/spi/spi-loongson-plat.c @@ -35,7 +35,7 @@ static struct platform_driver loongson_spi_plat_driver = { .driver = { .name = "loongson-spi", .bus = &platform_bus_type, - .pm = &loongson_spi_dev_pm_ops, + .pm = pm_sleep_ptr(&loongson_spi_dev_pm_ops), .of_match_table = loongson_spi_id_table, }, }; From aceceea2a44a6b4997ddf59a13028fa97ec87792 Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Mon, 3 Aug 2026 22:19:42 +0800 Subject: [PATCH 090/132] spi: meson-spifc: use modern PM macros Use the modern PM macros for the suspend and resume functions to be automatically dropped by the compiler when CONFIG_PM or CONFIG_PM_SLEEP are disabled, without having to use #ifdef guards. This has the advantage of always compiling these functions in, independently of any Kconfig option. Thanks to that, bugs and other regressions are subsequently easier to catch. Signed-off-by: Jisheng Zhang Link: https://patch.msgid.link/20260803142003.12857-19-jszhang@kernel.org Signed-off-by: Mark Brown --- drivers/spi/spi-meson-spifc.c | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/drivers/spi/spi-meson-spifc.c b/drivers/spi/spi-meson-spifc.c index e36aa94bbdae..2ef090dec7ba 100644 --- a/drivers/spi/spi-meson-spifc.c +++ b/drivers/spi/spi-meson-spifc.c @@ -348,7 +348,6 @@ static void meson_spifc_remove(struct platform_device *pdev) pm_runtime_put_noidle(&pdev->dev); } -#ifdef CONFIG_PM_SLEEP static int meson_spifc_suspend(struct device *dev) { struct spi_controller *host = dev_get_drvdata(dev); @@ -385,9 +384,7 @@ static int meson_spifc_resume(struct device *dev) return ret; } -#endif /* CONFIG_PM_SLEEP */ -#ifdef CONFIG_PM static int meson_spifc_runtime_suspend(struct device *dev) { struct spi_controller *host = dev_get_drvdata(dev); @@ -405,13 +402,12 @@ static int meson_spifc_runtime_resume(struct device *dev) return clk_prepare_enable(spifc->clk); } -#endif /* CONFIG_PM */ static const struct dev_pm_ops meson_spifc_pm_ops = { - SET_SYSTEM_SLEEP_PM_OPS(meson_spifc_suspend, meson_spifc_resume) - SET_RUNTIME_PM_OPS(meson_spifc_runtime_suspend, - meson_spifc_runtime_resume, - NULL) + SYSTEM_SLEEP_PM_OPS(meson_spifc_suspend, meson_spifc_resume) + RUNTIME_PM_OPS(meson_spifc_runtime_suspend, + meson_spifc_runtime_resume, + NULL) }; static const struct of_device_id meson_spifc_dt_match[] = { @@ -427,7 +423,7 @@ static struct platform_driver meson_spifc_driver = { .driver = { .name = "meson-spifc", .of_match_table = of_match_ptr(meson_spifc_dt_match), - .pm = &meson_spifc_pm_ops, + .pm = pm_ptr(&meson_spifc_pm_ops), }, }; From 06f9d950cb4c4edbb41e388fc32a0640fd5175a1 Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Mon, 3 Aug 2026 22:19:43 +0800 Subject: [PATCH 091/132] spi: mt65xx: use modern PM macros Use the modern PM macros for the suspend and resume functions to be automatically dropped by the compiler when CONFIG_PM or CONFIG_PM_SLEEP are disabled, without having to use #ifdef guards. This has the advantage of always compiling these functions in, independently of any Kconfig option. Thanks to that, bugs and other regressions are subsequently easier to catch. Signed-off-by: Jisheng Zhang Link: https://patch.msgid.link/20260803142003.12857-20-jszhang@kernel.org Signed-off-by: Mark Brown --- drivers/spi/spi-mt65xx.c | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/drivers/spi/spi-mt65xx.c b/drivers/spi/spi-mt65xx.c index 96f8555be983..b845a599f7c6 100644 --- a/drivers/spi/spi-mt65xx.c +++ b/drivers/spi/spi-mt65xx.c @@ -1367,7 +1367,6 @@ static void mtk_spi_remove(struct platform_device *pdev) pm_runtime_disable(&pdev->dev); } -#ifdef CONFIG_PM_SLEEP static int mtk_spi_suspend(struct device *dev) { int ret; @@ -1419,9 +1418,7 @@ static int mtk_spi_resume(struct device *dev) return ret; } -#endif /* CONFIG_PM_SLEEP */ -#ifdef CONFIG_PM static int mtk_spi_runtime_suspend(struct device *dev) { struct spi_controller *host = dev_get_drvdata(dev); @@ -1473,18 +1470,16 @@ static int mtk_spi_runtime_resume(struct device *dev) return 0; } -#endif /* CONFIG_PM */ static const struct dev_pm_ops mtk_spi_pm = { - SET_SYSTEM_SLEEP_PM_OPS(mtk_spi_suspend, mtk_spi_resume) - SET_RUNTIME_PM_OPS(mtk_spi_runtime_suspend, - mtk_spi_runtime_resume, NULL) + SYSTEM_SLEEP_PM_OPS(mtk_spi_suspend, mtk_spi_resume) + RUNTIME_PM_OPS(mtk_spi_runtime_suspend, mtk_spi_runtime_resume, NULL) }; static struct platform_driver mtk_spi_driver = { .driver = { .name = "mtk-spi", - .pm = &mtk_spi_pm, + .pm = pm_ptr(&mtk_spi_pm), .of_match_table = mtk_spi_of_match, }, .probe = mtk_spi_probe, From fcb6a6e73e588310fef575373f8f5255bbf4a9be Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Mon, 3 Aug 2026 22:19:44 +0800 Subject: [PATCH 092/132] spi: mtk-nor: use modern PM macros Use the modern PM macros for the suspend and resume functions to be automatically dropped by the compiler when CONFIG_PM or CONFIG_PM_SLEEP are disabled, without having to use __maybe_unused. Signed-off-by: Jisheng Zhang Link: https://patch.msgid.link/20260803142003.12857-21-jszhang@kernel.org Signed-off-by: Mark Brown --- drivers/spi/spi-mtk-nor.c | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/drivers/spi/spi-mtk-nor.c b/drivers/spi/spi-mtk-nor.c index 74f34537b02c..5e1fdbb40ffb 100644 --- a/drivers/spi/spi-mtk-nor.c +++ b/drivers/spi/spi-mtk-nor.c @@ -947,7 +947,7 @@ static void mtk_nor_remove(struct platform_device *pdev) mtk_nor_disable_clk(sp); } -static int __maybe_unused mtk_nor_runtime_suspend(struct device *dev) +static int mtk_nor_runtime_suspend(struct device *dev) { struct spi_controller *ctlr = dev_get_drvdata(dev); struct mtk_nor *sp = spi_controller_get_devdata(ctlr); @@ -957,7 +957,7 @@ static int __maybe_unused mtk_nor_runtime_suspend(struct device *dev) return 0; } -static int __maybe_unused mtk_nor_runtime_resume(struct device *dev) +static int mtk_nor_runtime_resume(struct device *dev) { struct spi_controller *ctlr = dev_get_drvdata(dev); struct mtk_nor *sp = spi_controller_get_devdata(ctlr); @@ -965,12 +965,12 @@ static int __maybe_unused mtk_nor_runtime_resume(struct device *dev) return mtk_nor_enable_clk(sp); } -static int __maybe_unused mtk_nor_suspend(struct device *dev) +static int mtk_nor_suspend(struct device *dev) { return pm_runtime_force_suspend(dev); } -static int __maybe_unused mtk_nor_resume(struct device *dev) +static int mtk_nor_resume(struct device *dev) { struct spi_controller *ctlr = dev_get_drvdata(dev); struct mtk_nor *sp = spi_controller_get_devdata(ctlr); @@ -986,16 +986,15 @@ static int __maybe_unused mtk_nor_resume(struct device *dev) } static const struct dev_pm_ops mtk_nor_pm_ops = { - SET_RUNTIME_PM_OPS(mtk_nor_runtime_suspend, - mtk_nor_runtime_resume, NULL) - SET_SYSTEM_SLEEP_PM_OPS(mtk_nor_suspend, mtk_nor_resume) + RUNTIME_PM_OPS(mtk_nor_runtime_suspend, mtk_nor_runtime_resume, NULL) + SYSTEM_SLEEP_PM_OPS(mtk_nor_suspend, mtk_nor_resume) }; static struct platform_driver mtk_nor_driver = { .driver = { .name = DRIVER_NAME, .of_match_table = mtk_nor_match, - .pm = &mtk_nor_pm_ops, + .pm = pm_ptr(&mtk_nor_pm_ops), }, .probe = mtk_nor_probe, .remove = mtk_nor_remove, From 169d9f0fd9849a9ec8a0e4e889a8ad7953d0f334 Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Mon, 3 Aug 2026 22:19:45 +0800 Subject: [PATCH 093/132] spi: mxic: use modern PM macros Use the modern PM macros for the suspend and resume functions to be automatically dropped by the compiler when CONFIG_PM or CONFIG_PM_SLEEP are disabled, without having to use __maybe_unused. Signed-off-by: Jisheng Zhang Link: https://patch.msgid.link/20260803142003.12857-22-jszhang@kernel.org Signed-off-by: Mark Brown --- drivers/spi/spi-mxic.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/drivers/spi/spi-mxic.c b/drivers/spi/spi-mxic.c index 83b688e65284..e3c85e5191e3 100644 --- a/drivers/spi/spi-mxic.c +++ b/drivers/spi/spi-mxic.c @@ -721,7 +721,7 @@ static int mxic_spi_mem_ecc_probe(struct platform_device *pdev, return 0; } -static int __maybe_unused mxic_spi_runtime_suspend(struct device *dev) +static int mxic_spi_runtime_suspend(struct device *dev) { struct spi_controller *host = dev_get_drvdata(dev); struct mxic_spi *mxic = spi_controller_get_devdata(host); @@ -732,7 +732,7 @@ static int __maybe_unused mxic_spi_runtime_suspend(struct device *dev) return 0; } -static int __maybe_unused mxic_spi_runtime_resume(struct device *dev) +static int mxic_spi_runtime_resume(struct device *dev) { struct spi_controller *host = dev_get_drvdata(dev); struct mxic_spi *mxic = spi_controller_get_devdata(host); @@ -748,8 +748,7 @@ static int __maybe_unused mxic_spi_runtime_resume(struct device *dev) } static const struct dev_pm_ops mxic_spi_dev_pm_ops = { - SET_RUNTIME_PM_OPS(mxic_spi_runtime_suspend, - mxic_spi_runtime_resume, NULL) + RUNTIME_PM_OPS(mxic_spi_runtime_suspend, mxic_spi_runtime_resume, NULL) }; static int mxic_spi_probe(struct platform_device *pdev) @@ -850,7 +849,7 @@ static struct platform_driver mxic_spi_driver = { .driver = { .name = "mxic-spi", .of_match_table = mxic_spi_of_ids, - .pm = &mxic_spi_dev_pm_ops, + .pm = pm_ptr(&mxic_spi_dev_pm_ops), }, }; module_platform_driver(mxic_spi_driver); From aa9d02583a919d8b2f58362107b70fc724588f29 Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Mon, 3 Aug 2026 22:19:46 +0800 Subject: [PATCH 094/132] spi: omap2-mcspi: use modern PM macros Use the modern PM macros for the suspend and resume functions to be automatically dropped by the compiler when CONFIG_PM or CONFIG_PM_SLEEP are disabled, without having to use __maybe_unused. Signed-off-by: Jisheng Zhang Link: https://patch.msgid.link/20260803142003.12857-23-jszhang@kernel.org Signed-off-by: Mark Brown --- drivers/spi/spi-omap2-mcspi.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/drivers/spi/spi-omap2-mcspi.c b/drivers/spi/spi-omap2-mcspi.c index 9cc078acc13c..a3355147f5de 100644 --- a/drivers/spi/spi-omap2-mcspi.c +++ b/drivers/spi/spi-omap2-mcspi.c @@ -1620,7 +1620,7 @@ static void omap2_mcspi_remove(struct platform_device *pdev) /* work with hotplug and coldplug */ MODULE_ALIAS("platform:omap2_mcspi"); -static int __maybe_unused omap2_mcspi_suspend(struct device *dev) +static int omap2_mcspi_suspend(struct device *dev) { struct spi_controller *ctlr = dev_get_drvdata(dev); struct omap2_mcspi *mcspi = spi_controller_get_devdata(ctlr); @@ -1639,7 +1639,7 @@ static int __maybe_unused omap2_mcspi_suspend(struct device *dev) return pm_runtime_force_suspend(dev); } -static int __maybe_unused omap2_mcspi_resume(struct device *dev) +static int omap2_mcspi_resume(struct device *dev) { struct spi_controller *ctlr = dev_get_drvdata(dev); struct omap2_mcspi *mcspi = spi_controller_get_devdata(ctlr); @@ -1654,8 +1654,7 @@ static int __maybe_unused omap2_mcspi_resume(struct device *dev) } static const struct dev_pm_ops omap2_mcspi_pm_ops = { - SET_SYSTEM_SLEEP_PM_OPS(omap2_mcspi_suspend, - omap2_mcspi_resume) + SYSTEM_SLEEP_PM_OPS(omap2_mcspi_suspend, omap2_mcspi_resume) .runtime_suspend = omap_mcspi_runtime_suspend, .runtime_resume = omap_mcspi_runtime_resume, }; @@ -1663,7 +1662,7 @@ static const struct dev_pm_ops omap2_mcspi_pm_ops = { static struct platform_driver omap2_mcspi_driver = { .driver = { .name = "omap2_mcspi", - .pm = &omap2_mcspi_pm_ops, + .pm = pm_ptr(&omap2_mcspi_pm_ops), .of_match_table = omap_mcspi_of_match, }, .probe = omap2_mcspi_probe, From 4fbaf6e6f11238d70c413d1428706bce45432c06 Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Mon, 3 Aug 2026 22:19:47 +0800 Subject: [PATCH 095/132] spi: orion: use modern PM macros Use the modern PM macros for the suspend and resume functions to be automatically dropped by the compiler when CONFIG_PM or CONFIG_PM_SLEEP are disabled, without having to use #ifdef guards. This has the advantage of always compiling these functions in, independently of any Kconfig option. Thanks to that, bugs and other regressions are subsequently easier to catch. Signed-off-by: Jisheng Zhang Link: https://patch.msgid.link/20260803142003.12857-24-jszhang@kernel.org Signed-off-by: Mark Brown --- drivers/spi/spi-orion.c | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/drivers/spi/spi-orion.c b/drivers/spi/spi-orion.c index e5e05512a1fd..26a9f268b2d3 100644 --- a/drivers/spi/spi-orion.c +++ b/drivers/spi/spi-orion.c @@ -100,10 +100,8 @@ struct orion_spi { struct orion_child_options child[ORION_NUM_CHIPSELECTS]; }; -#ifdef CONFIG_PM static int orion_spi_runtime_suspend(struct device *dev); static int orion_spi_runtime_resume(struct device *dev); -#endif static inline void __iomem *spi_reg(struct orion_spi *orion_spi, u32 reg) { @@ -804,7 +802,6 @@ static void orion_spi_remove(struct platform_device *pdev) MODULE_ALIAS("platform:" DRIVER_NAME); -#ifdef CONFIG_PM static int orion_spi_runtime_suspend(struct device *dev) { struct spi_controller *host = dev_get_drvdata(dev); @@ -823,18 +820,17 @@ static int orion_spi_runtime_resume(struct device *dev) clk_prepare_enable(spi->axi_clk); return clk_prepare_enable(spi->clk); } -#endif static const struct dev_pm_ops orion_spi_pm_ops = { - SET_RUNTIME_PM_OPS(orion_spi_runtime_suspend, - orion_spi_runtime_resume, - NULL) + RUNTIME_PM_OPS(orion_spi_runtime_suspend, + orion_spi_runtime_resume, + NULL) }; static struct platform_driver orion_spi_driver = { .driver = { .name = DRIVER_NAME, - .pm = &orion_spi_pm_ops, + .pm = pm_ptr(&orion_spi_pm_ops), .of_match_table = of_match_ptr(orion_spi_of_match_table), }, .probe = orion_spi_probe, From 9329021dcb44920b73ce6fdd6f78c8c0c0c7bb21 Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Mon, 3 Aug 2026 22:19:48 +0800 Subject: [PATCH 096/132] spi: pl022: use modern PM macros Use the modern PM macros for the suspend and resume functions to be automatically dropped by the compiler when CONFIG_PM or CONFIG_PM_SLEEP are disabled, without having to use #ifdef guards. This has the advantage of always compiling these functions in, independently of any Kconfig option. Thanks to that, bugs and other regressions are subsequently easier to catch. Signed-off-by: Jisheng Zhang Link: https://patch.msgid.link/20260803142003.12857-25-jszhang@kernel.org Signed-off-by: Mark Brown --- drivers/spi/spi-pl022.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/drivers/spi/spi-pl022.c b/drivers/spi/spi-pl022.c index 95652df5fd09..4877ab909c51 100644 --- a/drivers/spi/spi-pl022.c +++ b/drivers/spi/spi-pl022.c @@ -2011,7 +2011,6 @@ pl022_remove(struct amba_device *adev) amba_release_regions(adev); } -#ifdef CONFIG_PM_SLEEP static int pl022_suspend(struct device *dev) { struct pl022 *pl022 = dev_get_drvdata(dev); @@ -2049,9 +2048,7 @@ static int pl022_resume(struct device *dev) return ret; } -#endif -#ifdef CONFIG_PM static int pl022_runtime_suspend(struct device *dev) { struct pl022 *pl022 = dev_get_drvdata(dev); @@ -2071,11 +2068,10 @@ static int pl022_runtime_resume(struct device *dev) return 0; } -#endif static const struct dev_pm_ops pl022_dev_pm_ops = { - SET_SYSTEM_SLEEP_PM_OPS(pl022_suspend, pl022_resume) - SET_RUNTIME_PM_OPS(pl022_runtime_suspend, pl022_runtime_resume, NULL) + SYSTEM_SLEEP_PM_OPS(pl022_suspend, pl022_resume) + RUNTIME_PM_OPS(pl022_runtime_suspend, pl022_runtime_resume, NULL) }; static struct vendor_data vendor_arm = { @@ -2166,7 +2162,7 @@ MODULE_DEVICE_TABLE(amba, pl022_ids); static struct amba_driver pl022_driver = { .drv = { .name = "ssp-pl022", - .pm = &pl022_dev_pm_ops, + .pm = pm_ptr(&pl022_dev_pm_ops), }, .id_table = pl022_ids, .probe = pl022_probe, From 498e32cba46592ed1704fd832ee59d627b3f970f Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Mon, 3 Aug 2026 22:19:49 +0800 Subject: [PATCH 097/132] spi: spi-qcom-qspi: use modern PM macros Use the modern PM macros for the suspend and resume functions to be automatically dropped by the compiler when CONFIG_PM or CONFIG_PM_SLEEP are disabled, without having to use __maybe_unused. Signed-off-by: Jisheng Zhang Link: https://patch.msgid.link/20260803142003.12857-26-jszhang@kernel.org Signed-off-by: Mark Brown --- drivers/spi/spi-qcom-qspi.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/drivers/spi/spi-qcom-qspi.c b/drivers/spi/spi-qcom-qspi.c index e9fdefa9d65d..e5b7755ae4f9 100644 --- a/drivers/spi/spi-qcom-qspi.c +++ b/drivers/spi/spi-qcom-qspi.c @@ -829,7 +829,7 @@ static void qcom_qspi_remove(struct platform_device *pdev) pm_runtime_disable(&pdev->dev); } -static int __maybe_unused qcom_qspi_runtime_suspend(struct device *dev) +static int qcom_qspi_runtime_suspend(struct device *dev) { struct spi_controller *host = dev_get_drvdata(dev); struct qcom_qspi *ctrl = spi_controller_get_devdata(host); @@ -873,7 +873,7 @@ static int __maybe_unused qcom_qspi_runtime_suspend(struct device *dev) return ret; } -static int __maybe_unused qcom_qspi_runtime_resume(struct device *dev) +static int qcom_qspi_runtime_resume(struct device *dev) { struct spi_controller *host = dev_get_drvdata(dev); struct qcom_qspi *ctrl = spi_controller_get_devdata(host); @@ -917,7 +917,7 @@ static int __maybe_unused qcom_qspi_runtime_resume(struct device *dev) return ret; } -static int __maybe_unused qcom_qspi_suspend(struct device *dev) +static int qcom_qspi_suspend(struct device *dev) { struct spi_controller *host = dev_get_drvdata(dev); int ret; @@ -933,7 +933,7 @@ static int __maybe_unused qcom_qspi_suspend(struct device *dev) return ret; } -static int __maybe_unused qcom_qspi_resume(struct device *dev) +static int qcom_qspi_resume(struct device *dev) { struct spi_controller *host = dev_get_drvdata(dev); int ret; @@ -950,9 +950,9 @@ static int __maybe_unused qcom_qspi_resume(struct device *dev) } static const struct dev_pm_ops qcom_qspi_dev_pm_ops = { - SET_RUNTIME_PM_OPS(qcom_qspi_runtime_suspend, - qcom_qspi_runtime_resume, NULL) - SET_SYSTEM_SLEEP_PM_OPS(qcom_qspi_suspend, qcom_qspi_resume) + RUNTIME_PM_OPS(qcom_qspi_runtime_suspend, + qcom_qspi_runtime_resume, NULL) + SYSTEM_SLEEP_PM_OPS(qcom_qspi_suspend, qcom_qspi_resume) }; static const struct of_device_id qcom_qspi_dt_match[] = { @@ -964,7 +964,7 @@ MODULE_DEVICE_TABLE(of, qcom_qspi_dt_match); static struct platform_driver qcom_qspi_driver = { .driver = { .name = "qcom_qspi", - .pm = &qcom_qspi_dev_pm_ops, + .pm = pm_ptr(&qcom_qspi_dev_pm_ops), .of_match_table = qcom_qspi_dt_match, }, .probe = qcom_qspi_probe, From d87ae277834df0c531d77e138079a0b95c426b77 Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Mon, 3 Aug 2026 22:19:50 +0800 Subject: [PATCH 098/132] spi: qup: use modern PM macros Use the modern PM macros for the suspend and resume functions to be automatically dropped by the compiler when CONFIG_PM or CONFIG_PM_SLEEP are disabled, without having to use #ifdef guards. This has the advantage of always compiling these functions in, independently of any Kconfig option. Thanks to that, bugs and other regressions are subsequently easier to catch. Signed-off-by: Jisheng Zhang Link: https://patch.msgid.link/20260803142003.12857-27-jszhang@kernel.org Signed-off-by: Mark Brown --- drivers/spi/spi-qup.c | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/drivers/spi/spi-qup.c b/drivers/spi/spi-qup.c index 034546b3f058..e8b076b1622d 100644 --- a/drivers/spi/spi-qup.c +++ b/drivers/spi/spi-qup.c @@ -1211,7 +1211,6 @@ static int spi_qup_probe(struct platform_device *pdev) return ret; } -#ifdef CONFIG_PM static int spi_qup_pm_suspend_runtime(struct device *device) { struct spi_controller *host = dev_get_drvdata(device); @@ -1253,9 +1252,7 @@ static int spi_qup_pm_resume_runtime(struct device *device) writel_relaxed(config, controller->base + QUP_CONFIG); return 0; } -#endif /* CONFIG_PM */ -#ifdef CONFIG_PM_SLEEP static int spi_qup_suspend(struct device *device) { struct spi_controller *host = dev_get_drvdata(device); @@ -1312,7 +1309,6 @@ static int spi_qup_resume(struct device *device) clk_disable_unprepare(controller->iclk); return ret; } -#endif /* CONFIG_PM_SLEEP */ static void spi_qup_remove(struct platform_device *pdev) { @@ -1352,16 +1348,16 @@ static const struct of_device_id spi_qup_dt_match[] = { MODULE_DEVICE_TABLE(of, spi_qup_dt_match); static const struct dev_pm_ops spi_qup_dev_pm_ops = { - SET_SYSTEM_SLEEP_PM_OPS(spi_qup_suspend, spi_qup_resume) - SET_RUNTIME_PM_OPS(spi_qup_pm_suspend_runtime, - spi_qup_pm_resume_runtime, - NULL) + SYSTEM_SLEEP_PM_OPS(spi_qup_suspend, spi_qup_resume) + RUNTIME_PM_OPS(spi_qup_pm_suspend_runtime, + spi_qup_pm_resume_runtime, + NULL) }; static struct platform_driver spi_qup_driver = { .driver = { .name = "spi_qup", - .pm = &spi_qup_dev_pm_ops, + .pm = pm_ptr(&spi_qup_dev_pm_ops), .of_match_table = spi_qup_dt_match, }, .probe = spi_qup_probe, From 07d3854aded4d50834f25bd9620a3c059ebfc28c Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Mon, 3 Aug 2026 22:19:51 +0800 Subject: [PATCH 099/132] spi: rockchip-sfc: use modern PM macros Use the modern PM macros for the suspend and resume functions to be automatically dropped by the compiler when CONFIG_PM or CONFIG_PM_SLEEP are disabled, without having to use #ifdef guards. This has the advantage of always compiling these functions in, independently of any Kconfig option. Thanks to that, bugs and other regressions are subsequently easier to catch. Signed-off-by: Jisheng Zhang Link: https://patch.msgid.link/20260803142003.12857-28-jszhang@kernel.org Signed-off-by: Mark Brown --- drivers/spi/spi-rockchip-sfc.c | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/drivers/spi/spi-rockchip-sfc.c b/drivers/spi/spi-rockchip-sfc.c index 174995042f53..662a994da60b 100644 --- a/drivers/spi/spi-rockchip-sfc.c +++ b/drivers/spi/spi-rockchip-sfc.c @@ -751,7 +751,6 @@ static void rockchip_sfc_remove(struct platform_device *pdev) clk_disable_unprepare(sfc->hclk); } -#ifdef CONFIG_PM static int rockchip_sfc_runtime_suspend(struct device *dev) { struct rockchip_sfc *sfc = dev_get_drvdata(dev); @@ -777,9 +776,7 @@ static int rockchip_sfc_runtime_resume(struct device *dev) return ret; } -#endif /* CONFIG_PM */ -#ifdef CONFIG_PM_SLEEP static int rockchip_sfc_suspend(struct device *dev) { pinctrl_pm_select_sleep_state(dev); @@ -810,12 +807,11 @@ static int rockchip_sfc_resume(struct device *dev) return 0; } -#endif /* CONFIG_PM_SLEEP */ static const struct dev_pm_ops rockchip_sfc_pm_ops = { - SET_RUNTIME_PM_OPS(rockchip_sfc_runtime_suspend, - rockchip_sfc_runtime_resume, NULL) - SET_SYSTEM_SLEEP_PM_OPS(rockchip_sfc_suspend, rockchip_sfc_resume) + RUNTIME_PM_OPS(rockchip_sfc_runtime_suspend, + rockchip_sfc_runtime_resume, NULL) + SYSTEM_SLEEP_PM_OPS(rockchip_sfc_suspend, rockchip_sfc_resume) }; static const struct of_device_id rockchip_sfc_dt_ids[] = { @@ -828,7 +824,7 @@ static struct platform_driver rockchip_sfc_driver = { .driver = { .name = "rockchip-sfc", .of_match_table = rockchip_sfc_dt_ids, - .pm = &rockchip_sfc_pm_ops, + .pm = pm_ptr(&rockchip_sfc_pm_ops), }, .probe = rockchip_sfc_probe, .remove = rockchip_sfc_remove, From c88fb943219869531d4e4c6b739fb6f0d93620f9 Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Mon, 3 Aug 2026 22:19:52 +0800 Subject: [PATCH 100/132] spi: rockchip: use modern PM macros Use the modern PM macros for the suspend and resume functions to be automatically dropped by the compiler when CONFIG_PM or CONFIG_PM_SLEEP are disabled, without having to use #ifdef guards. This has the advantage of always compiling these functions in, independently of any Kconfig option. Thanks to that, bugs and other regressions are subsequently easier to catch. Signed-off-by: Jisheng Zhang Link: https://patch.msgid.link/20260803142003.12857-29-jszhang@kernel.org Signed-off-by: Mark Brown --- drivers/spi/spi-rockchip.c | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/drivers/spi/spi-rockchip.c b/drivers/spi/spi-rockchip.c index 231fbcf0e7aa..1a81fd94b439 100644 --- a/drivers/spi/spi-rockchip.c +++ b/drivers/spi/spi-rockchip.c @@ -938,7 +938,6 @@ static void rockchip_spi_remove(struct platform_device *pdev) dma_release_channel(ctlr->dma_rx); } -#ifdef CONFIG_PM_SLEEP static int rockchip_spi_suspend(struct device *dev) { int ret; @@ -972,9 +971,7 @@ static int rockchip_spi_resume(struct device *dev) return spi_controller_resume(ctlr); } -#endif /* CONFIG_PM_SLEEP */ -#ifdef CONFIG_PM static int rockchip_spi_runtime_suspend(struct device *dev) { struct spi_controller *ctlr = dev_get_drvdata(dev); @@ -1002,12 +999,11 @@ static int rockchip_spi_runtime_resume(struct device *dev) return 0; } -#endif /* CONFIG_PM */ static const struct dev_pm_ops rockchip_spi_pm = { - SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(rockchip_spi_suspend, rockchip_spi_resume) - SET_RUNTIME_PM_OPS(rockchip_spi_runtime_suspend, - rockchip_spi_runtime_resume, NULL) + NOIRQ_SYSTEM_SLEEP_PM_OPS(rockchip_spi_suspend, rockchip_spi_resume) + RUNTIME_PM_OPS(rockchip_spi_runtime_suspend, + rockchip_spi_runtime_resume, NULL) }; static const struct of_device_id rockchip_spi_dt_match[] = { @@ -1030,7 +1026,7 @@ MODULE_DEVICE_TABLE(of, rockchip_spi_dt_match); static struct platform_driver rockchip_spi_driver = { .driver = { .name = DRIVER_NAME, - .pm = &rockchip_spi_pm, + .pm = pm_ptr(&rockchip_spi_pm), .of_match_table = of_match_ptr(rockchip_spi_dt_match), }, .probe = rockchip_spi_probe, From 1c892a2f00a74c69d4e3b6fdf4ae4aa4eee48f1a Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Mon, 3 Aug 2026 22:19:53 +0800 Subject: [PATCH 101/132] spi: s3c64xx: use modern PM macros Use the modern PM macros for the suspend and resume functions to be automatically dropped by the compiler when CONFIG_PM or CONFIG_PM_SLEEP are disabled, without having to use #ifdef guards. This has the advantage of always compiling these functions in, independently of any Kconfig option. Thanks to that, bugs and other regressions are subsequently easier to catch. Signed-off-by: Jisheng Zhang Link: https://patch.msgid.link/20260803142003.12857-30-jszhang@kernel.org Signed-off-by: Mark Brown --- drivers/spi/spi-s3c64xx.c | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/drivers/spi/spi-s3c64xx.c b/drivers/spi/spi-s3c64xx.c index 28c56b06fa99..8d4120cf9df1 100644 --- a/drivers/spi/spi-s3c64xx.c +++ b/drivers/spi/spi-s3c64xx.c @@ -1408,7 +1408,6 @@ static void s3c64xx_spi_remove(struct platform_device *pdev) pm_runtime_set_suspended(&pdev->dev); } -#ifdef CONFIG_PM_SLEEP static int s3c64xx_spi_suspend(struct device *dev) { struct spi_controller *host = dev_get_drvdata(dev); @@ -1444,9 +1443,7 @@ static int s3c64xx_spi_resume(struct device *dev) return spi_controller_resume(host); } -#endif /* CONFIG_PM_SLEEP */ -#ifdef CONFIG_PM static int s3c64xx_spi_runtime_suspend(struct device *dev) { struct spi_controller *host = dev_get_drvdata(dev); @@ -1494,12 +1491,11 @@ static int s3c64xx_spi_runtime_resume(struct device *dev) return ret; } -#endif /* CONFIG_PM */ static const struct dev_pm_ops s3c64xx_spi_pm = { - SET_SYSTEM_SLEEP_PM_OPS(s3c64xx_spi_suspend, s3c64xx_spi_resume) - SET_RUNTIME_PM_OPS(s3c64xx_spi_runtime_suspend, - s3c64xx_spi_runtime_resume, NULL) + SYSTEM_SLEEP_PM_OPS(s3c64xx_spi_suspend, s3c64xx_spi_resume) + RUNTIME_PM_OPS(s3c64xx_spi_runtime_suspend, + s3c64xx_spi_runtime_resume, NULL) }; static const struct s3c64xx_spi_port_config s3c6410_spi_port_config = { @@ -1655,7 +1651,7 @@ MODULE_DEVICE_TABLE(of, s3c64xx_spi_dt_match); static struct platform_driver s3c64xx_spi_driver = { .driver = { .name = "s3c64xx-spi", - .pm = &s3c64xx_spi_pm, + .pm = pm_ptr(&s3c64xx_spi_pm), .of_match_table = of_match_ptr(s3c64xx_spi_dt_match), }, .probe = s3c64xx_spi_probe, From abc34022d658ccd414665f507c0abb608f6ca97c Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Mon, 3 Aug 2026 22:19:54 +0800 Subject: [PATCH 102/132] spi: slave-mt27xx: use modern PM macros Use the modern PM macros for the suspend and resume functions to be automatically dropped by the compiler when CONFIG_PM or CONFIG_PM_SLEEP are disabled, without having to use #ifdef guards. This has the advantage of always compiling these functions in, independently of any Kconfig option. Thanks to that, bugs and other regressions are subsequently easier to catch. Signed-off-by: Jisheng Zhang Link: https://patch.msgid.link/20260803142003.12857-31-jszhang@kernel.org Signed-off-by: Mark Brown --- drivers/spi/spi-slave-mt27xx.c | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/drivers/spi/spi-slave-mt27xx.c b/drivers/spi/spi-slave-mt27xx.c index e60ab4c18bed..256b510fa676 100644 --- a/drivers/spi/spi-slave-mt27xx.c +++ b/drivers/spi/spi-slave-mt27xx.c @@ -471,7 +471,6 @@ static void mtk_spi_slave_remove(struct platform_device *pdev) pm_runtime_disable(&pdev->dev); } -#ifdef CONFIG_PM_SLEEP static int mtk_spi_slave_suspend(struct device *dev) { struct spi_controller *ctlr = dev_get_drvdata(dev); @@ -508,9 +507,7 @@ static int mtk_spi_slave_resume(struct device *dev) return ret; } -#endif /* CONFIG_PM_SLEEP */ -#ifdef CONFIG_PM static int mtk_spi_slave_runtime_suspend(struct device *dev) { struct spi_controller *ctlr = dev_get_drvdata(dev); @@ -535,18 +532,17 @@ static int mtk_spi_slave_runtime_resume(struct device *dev) return 0; } -#endif /* CONFIG_PM */ static const struct dev_pm_ops mtk_spi_slave_pm = { - SET_SYSTEM_SLEEP_PM_OPS(mtk_spi_slave_suspend, mtk_spi_slave_resume) - SET_RUNTIME_PM_OPS(mtk_spi_slave_runtime_suspend, - mtk_spi_slave_runtime_resume, NULL) + SYSTEM_SLEEP_PM_OPS(mtk_spi_slave_suspend, mtk_spi_slave_resume) + RUNTIME_PM_OPS(mtk_spi_slave_runtime_suspend, + mtk_spi_slave_runtime_resume, NULL) }; static struct platform_driver mtk_spi_slave_driver = { .driver = { .name = "mtk-spi-slave", - .pm = &mtk_spi_slave_pm, + .pm = pm_ptr(&mtk_spi_slave_pm), .of_match_table = mtk_spi_slave_of_match, }, .probe = mtk_spi_slave_probe, From 96e9434dee1d97396f454d2b265087e91faf5447 Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Mon, 3 Aug 2026 22:19:55 +0800 Subject: [PATCH 103/132] spi: sprd: use modern PM macros Use the modern PM macros for the suspend and resume functions to be automatically dropped by the compiler when CONFIG_PM or CONFIG_PM_SLEEP are disabled, without having to use __maybe_unused. Signed-off-by: Jisheng Zhang Link: https://patch.msgid.link/20260803142003.12857-32-jszhang@kernel.org Signed-off-by: Mark Brown --- drivers/spi/spi-sprd.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/drivers/spi/spi-sprd.c b/drivers/spi/spi-sprd.c index 29380938ef65..12b09174bb24 100644 --- a/drivers/spi/spi-sprd.c +++ b/drivers/spi/spi-sprd.c @@ -1016,7 +1016,7 @@ static void sprd_spi_remove(struct platform_device *pdev) pm_runtime_disable(&pdev->dev); } -static int __maybe_unused sprd_spi_runtime_suspend(struct device *dev) +static int sprd_spi_runtime_suspend(struct device *dev) { struct spi_controller *sctlr = dev_get_drvdata(dev); struct sprd_spi *ss = spi_controller_get_devdata(sctlr); @@ -1029,7 +1029,7 @@ static int __maybe_unused sprd_spi_runtime_suspend(struct device *dev) return 0; } -static int __maybe_unused sprd_spi_runtime_resume(struct device *dev) +static int sprd_spi_runtime_resume(struct device *dev) { struct spi_controller *sctlr = dev_get_drvdata(dev); struct sprd_spi *ss = spi_controller_get_devdata(sctlr); @@ -1050,8 +1050,7 @@ static int __maybe_unused sprd_spi_runtime_resume(struct device *dev) } static const struct dev_pm_ops sprd_spi_pm_ops = { - SET_RUNTIME_PM_OPS(sprd_spi_runtime_suspend, - sprd_spi_runtime_resume, NULL) + RUNTIME_PM_OPS(sprd_spi_runtime_suspend, sprd_spi_runtime_resume, NULL) }; static const struct of_device_id sprd_spi_of_match[] = { @@ -1064,7 +1063,7 @@ static struct platform_driver sprd_spi_driver = { .driver = { .name = "sprd-spi", .of_match_table = sprd_spi_of_match, - .pm = &sprd_spi_pm_ops, + .pm = pm_ptr(&sprd_spi_pm_ops), }, .probe = sprd_spi_probe, .remove = sprd_spi_remove, From 3c71dabf596ab257511505e61a08323e25b1871b Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Mon, 3 Aug 2026 22:19:56 +0800 Subject: [PATCH 104/132] spi: sunplus-sp7021: use modern PM macros Use the modern PM macros for the suspend and resume functions to be automatically dropped by the compiler when CONFIG_PM or CONFIG_PM_SLEEP are disabled, without having to use #ifdef guards or __maybe_unused. This has the advantage of always compiling these functions in, independently of any Kconfig option. Thanks to that, bugs and other regressions are subsequently easier to catch. Signed-off-by: Jisheng Zhang Link: https://patch.msgid.link/20260803142003.12857-33-jszhang@kernel.org Signed-off-by: Mark Brown --- drivers/spi/spi-sunplus-sp7021.c | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/drivers/spi/spi-sunplus-sp7021.c b/drivers/spi/spi-sunplus-sp7021.c index c1870322d976..f16fcda187dd 100644 --- a/drivers/spi/spi-sunplus-sp7021.c +++ b/drivers/spi/spi-sunplus-sp7021.c @@ -498,7 +498,7 @@ static void sp7021_spi_controller_remove(struct platform_device *pdev) pm_runtime_set_suspended(&pdev->dev); } -static int __maybe_unused sp7021_spi_controller_suspend(struct device *dev) +static int sp7021_spi_controller_suspend(struct device *dev) { struct spi_controller *ctlr = dev_get_drvdata(dev); struct sp7021_spi_ctlr *pspim = spi_controller_get_devdata(ctlr); @@ -506,7 +506,7 @@ static int __maybe_unused sp7021_spi_controller_suspend(struct device *dev) return reset_control_assert(pspim->rstc); } -static int __maybe_unused sp7021_spi_controller_resume(struct device *dev) +static int sp7021_spi_controller_resume(struct device *dev) { struct spi_controller *ctlr = dev_get_drvdata(dev); struct sp7021_spi_ctlr *pspim = spi_controller_get_devdata(ctlr); @@ -515,7 +515,6 @@ static int __maybe_unused sp7021_spi_controller_resume(struct device *dev) return clk_prepare_enable(pspim->spi_clk); } -#ifdef CONFIG_PM static int sp7021_spi_runtime_suspend(struct device *dev) { struct spi_controller *ctlr = dev_get_drvdata(dev); @@ -531,13 +530,12 @@ static int sp7021_spi_runtime_resume(struct device *dev) return reset_control_deassert(pspim->rstc); } -#endif static const struct dev_pm_ops sp7021_spi_pm_ops = { - SET_RUNTIME_PM_OPS(sp7021_spi_runtime_suspend, - sp7021_spi_runtime_resume, NULL) - SET_SYSTEM_SLEEP_PM_OPS(sp7021_spi_controller_suspend, - sp7021_spi_controller_resume) + RUNTIME_PM_OPS(sp7021_spi_runtime_suspend, + sp7021_spi_runtime_resume, NULL) + SYSTEM_SLEEP_PM_OPS(sp7021_spi_controller_suspend, + sp7021_spi_controller_resume) }; static const struct of_device_id sp7021_spi_controller_ids[] = { @@ -552,7 +550,7 @@ static struct platform_driver sp7021_spi_controller_driver = { .driver = { .name = "sunplus,sp7021-spi-controller", .of_match_table = sp7021_spi_controller_ids, - .pm = &sp7021_spi_pm_ops, + .pm = pm_ptr(&sp7021_spi_pm_ops), }, }; module_platform_driver(sp7021_spi_controller_driver); From 7bf07ac9be73d6cc63f98aac92bfd4ec6d01da8f Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Mon, 3 Aug 2026 22:19:57 +0800 Subject: [PATCH 105/132] spi: syncuacer: use modern PM macros Use the modern PM macros for the suspend and resume functions to be automatically dropped by the compiler when CONFIG_PM or CONFIG_PM_SLEEP are disabled, without having to use __maybe_unused. Signed-off-by: Jisheng Zhang Link: https://patch.msgid.link/20260803142003.12857-34-jszhang@kernel.org Signed-off-by: Mark Brown --- drivers/spi/spi-synquacer.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/spi/spi-synquacer.c b/drivers/spi/spi-synquacer.c index c14225e39fd1..6d0cd11637b5 100644 --- a/drivers/spi/spi-synquacer.c +++ b/drivers/spi/spi-synquacer.c @@ -738,7 +738,7 @@ static void synquacer_spi_remove(struct platform_device *pdev) clk_disable_unprepare(sspi->clk); } -static int __maybe_unused synquacer_spi_suspend(struct device *dev) +static int synquacer_spi_suspend(struct device *dev) { struct spi_controller *host = dev_get_drvdata(dev); struct synquacer_spi *sspi = spi_controller_get_devdata(host); @@ -754,7 +754,7 @@ static int __maybe_unused synquacer_spi_suspend(struct device *dev) return ret; } -static int __maybe_unused synquacer_spi_resume(struct device *dev) +static int synquacer_spi_resume(struct device *dev) { struct spi_controller *host = dev_get_drvdata(dev); struct synquacer_spi *sspi = spi_controller_get_devdata(host); @@ -786,8 +786,8 @@ static int __maybe_unused synquacer_spi_resume(struct device *dev) return ret; } -static SIMPLE_DEV_PM_OPS(synquacer_spi_pm_ops, synquacer_spi_suspend, - synquacer_spi_resume); +static DEFINE_SIMPLE_DEV_PM_OPS(synquacer_spi_pm_ops, synquacer_spi_suspend, + synquacer_spi_resume); static const struct of_device_id synquacer_spi_of_match[] = { {.compatible = "socionext,synquacer-spi"}, @@ -806,7 +806,7 @@ MODULE_DEVICE_TABLE(acpi, synquacer_hsspi_acpi_ids); static struct platform_driver synquacer_spi_driver = { .driver = { .name = "synquacer-spi", - .pm = &synquacer_spi_pm_ops, + .pm = pm_sleep_ptr(&synquacer_spi_pm_ops), .of_match_table = synquacer_spi_of_match, .acpi_match_table = ACPI_PTR(synquacer_hsspi_acpi_ids), }, From 46aec256a5f06228dcf6da6246d28a2c604af1a4 Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Mon, 3 Aug 2026 22:19:58 +0800 Subject: [PATCH 106/132] spi: tegra114: use modern PM macros Use the modern PM macros for the suspend and resume functions to be automatically dropped by the compiler when CONFIG_PM or CONFIG_PM_SLEEP are disabled, without having to use #ifdef guards. This has the advantage of always compiling these functions in, independently of any Kconfig option. Thanks to that, bugs and other regressions are subsequently easier to catch. Signed-off-by: Jisheng Zhang Link: https://patch.msgid.link/20260803142003.12857-35-jszhang@kernel.org Signed-off-by: Mark Brown --- drivers/spi/spi-tegra114.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/drivers/spi/spi-tegra114.c b/drivers/spi/spi-tegra114.c index aa44ffd09e61..333fb549214e 100644 --- a/drivers/spi/spi-tegra114.c +++ b/drivers/spi/spi-tegra114.c @@ -1450,7 +1450,6 @@ static void tegra_spi_remove(struct platform_device *pdev) tegra_spi_runtime_suspend(&pdev->dev); } -#ifdef CONFIG_PM_SLEEP static int tegra_spi_suspend(struct device *dev) { struct spi_controller *host = dev_get_drvdata(dev); @@ -1476,7 +1475,6 @@ static int tegra_spi_resume(struct device *dev) return spi_controller_resume(host); } -#endif static int tegra_spi_runtime_suspend(struct device *dev) { @@ -1505,14 +1503,14 @@ static int tegra_spi_runtime_resume(struct device *dev) } static const struct dev_pm_ops tegra_spi_pm_ops = { - SET_RUNTIME_PM_OPS(tegra_spi_runtime_suspend, - tegra_spi_runtime_resume, NULL) - SET_SYSTEM_SLEEP_PM_OPS(tegra_spi_suspend, tegra_spi_resume) + RUNTIME_PM_OPS(tegra_spi_runtime_suspend, + tegra_spi_runtime_resume, NULL) + SYSTEM_SLEEP_PM_OPS(tegra_spi_suspend, tegra_spi_resume) }; static struct platform_driver tegra_spi_driver = { .driver = { .name = "spi-tegra114", - .pm = &tegra_spi_pm_ops, + .pm = pm_ptr(&tegra_spi_pm_ops), .of_match_table = tegra_spi_of_match, }, .probe = tegra_spi_probe, From f4bc18af3b3cec130e0db25649eb0eb25158f78e Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Mon, 3 Aug 2026 22:19:59 +0800 Subject: [PATCH 107/132] spi: tegra20-sflash: use modern PM macros Use the modern PM macros for the suspend and resume functions to be automatically dropped by the compiler when CONFIG_PM or CONFIG_PM_SLEEP are disabled, without having to use #ifdef guards. This has the advantage of always compiling these functions in, independently of any Kconfig option. Thanks to that, bugs and other regressions are subsequently easier to catch. Signed-off-by: Jisheng Zhang Link: https://patch.msgid.link/20260803142003.12857-36-jszhang@kernel.org Signed-off-by: Mark Brown --- drivers/spi/spi-tegra20-sflash.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/drivers/spi/spi-tegra20-sflash.c b/drivers/spi/spi-tegra20-sflash.c index 2caa33f0a52c..2a5e3acea06b 100644 --- a/drivers/spi/spi-tegra20-sflash.c +++ b/drivers/spi/spi-tegra20-sflash.c @@ -533,7 +533,6 @@ static void tegra_sflash_remove(struct platform_device *pdev) tegra_sflash_runtime_suspend(&pdev->dev); } -#ifdef CONFIG_PM_SLEEP static int tegra_sflash_suspend(struct device *dev) { struct spi_controller *host = dev_get_drvdata(dev); @@ -557,7 +556,6 @@ static int tegra_sflash_resume(struct device *dev) return spi_controller_resume(host); } -#endif static int tegra_sflash_runtime_suspend(struct device *dev) { @@ -586,14 +584,14 @@ static int tegra_sflash_runtime_resume(struct device *dev) } static const struct dev_pm_ops slink_pm_ops = { - SET_RUNTIME_PM_OPS(tegra_sflash_runtime_suspend, - tegra_sflash_runtime_resume, NULL) - SET_SYSTEM_SLEEP_PM_OPS(tegra_sflash_suspend, tegra_sflash_resume) + RUNTIME_PM_OPS(tegra_sflash_runtime_suspend, + tegra_sflash_runtime_resume, NULL) + SYSTEM_SLEEP_PM_OPS(tegra_sflash_suspend, tegra_sflash_resume) }; static struct platform_driver tegra_sflash_driver = { .driver = { .name = "spi-tegra-sflash", - .pm = &slink_pm_ops, + .pm = pm_ptr(&slink_pm_ops), .of_match_table = tegra_sflash_of_match, }, .probe = tegra_sflash_probe, From 355848a0f90af8b7f55d7d9803946740ba246007 Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Mon, 3 Aug 2026 22:20:00 +0800 Subject: [PATCH 108/132] spi: tegra20-slink: use modern PM macros Use the modern PM macros for the suspend and resume functions to be automatically dropped by the compiler when CONFIG_PM or CONFIG_PM_SLEEP are disabled, without having to use #ifdef guards or __maybe_unused. This has the advantage of always compiling these functions in, independently of any Kconfig option. Thanks to that, bugs and other regressions are subsequently easier to catch. Signed-off-by: Jisheng Zhang Link: https://patch.msgid.link/20260803142003.12857-37-jszhang@kernel.org Signed-off-by: Mark Brown --- drivers/spi/spi-tegra20-slink.c | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/drivers/spi/spi-tegra20-slink.c b/drivers/spi/spi-tegra20-slink.c index c15c076295cd..ed1fcf57dd30 100644 --- a/drivers/spi/spi-tegra20-slink.c +++ b/drivers/spi/spi-tegra20-slink.c @@ -1144,7 +1144,6 @@ static void tegra_slink_remove(struct platform_device *pdev) tegra_slink_deinit_dma_param(tspi, true); } -#ifdef CONFIG_PM_SLEEP static int tegra_slink_suspend(struct device *dev) { struct spi_controller *host = dev_get_drvdata(dev); @@ -1169,9 +1168,8 @@ static int tegra_slink_resume(struct device *dev) return spi_controller_resume(host); } -#endif -static int __maybe_unused tegra_slink_runtime_suspend(struct device *dev) +static int tegra_slink_runtime_suspend(struct device *dev) { struct spi_controller *host = dev_get_drvdata(dev); struct tegra_slink_data *tspi = spi_controller_get_devdata(host); @@ -1183,7 +1181,7 @@ static int __maybe_unused tegra_slink_runtime_suspend(struct device *dev) return 0; } -static int __maybe_unused tegra_slink_runtime_resume(struct device *dev) +static int tegra_slink_runtime_resume(struct device *dev) { struct spi_controller *host = dev_get_drvdata(dev); struct tegra_slink_data *tspi = spi_controller_get_devdata(host); @@ -1198,14 +1196,14 @@ static int __maybe_unused tegra_slink_runtime_resume(struct device *dev) } static const struct dev_pm_ops slink_pm_ops = { - SET_RUNTIME_PM_OPS(tegra_slink_runtime_suspend, - tegra_slink_runtime_resume, NULL) - SET_SYSTEM_SLEEP_PM_OPS(tegra_slink_suspend, tegra_slink_resume) + RUNTIME_PM_OPS(tegra_slink_runtime_suspend, + tegra_slink_runtime_resume, NULL) + SYSTEM_SLEEP_PM_OPS(tegra_slink_suspend, tegra_slink_resume) }; static struct platform_driver tegra_slink_driver = { .driver = { .name = "spi-tegra-slink", - .pm = &slink_pm_ops, + .pm = pm_ptr(&slink_pm_ops), .of_match_table = tegra_slink_of_match, }, .probe = tegra_slink_probe, From e3f3d046f3f6467785ae35cefef33e3b7c774be4 Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Mon, 3 Aug 2026 22:20:01 +0800 Subject: [PATCH 109/132] spi: tegra210-quad: use modern PM macros Use the modern PM macros for the suspend and resume functions to be automatically dropped by the compiler when CONFIG_PM or CONFIG_PM_SLEEP are disabled, without having to use __maybe_unused. Signed-off-by: Jisheng Zhang Link: https://patch.msgid.link/20260803142003.12857-38-jszhang@kernel.org Signed-off-by: Mark Brown --- drivers/spi/spi-tegra210-quad.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/drivers/spi/spi-tegra210-quad.c b/drivers/spi/spi-tegra210-quad.c index 588a929a9785..8ede864c3d3c 100644 --- a/drivers/spi/spi-tegra210-quad.c +++ b/drivers/spi/spi-tegra210-quad.c @@ -1837,14 +1837,14 @@ static void tegra_qspi_remove(struct platform_device *pdev) tegra_qspi_deinit_dma(tqspi); } -static int __maybe_unused tegra_qspi_suspend(struct device *dev) +static int tegra_qspi_suspend(struct device *dev) { struct spi_controller *host = dev_get_drvdata(dev); return spi_controller_suspend(host); } -static int __maybe_unused tegra_qspi_resume(struct device *dev) +static int tegra_qspi_resume(struct device *dev) { struct spi_controller *host = dev_get_drvdata(dev); struct tegra_qspi *tqspi = spi_controller_get_devdata(host); @@ -1863,7 +1863,7 @@ static int __maybe_unused tegra_qspi_resume(struct device *dev) return spi_controller_resume(host); } -static int __maybe_unused tegra_qspi_runtime_suspend(struct device *dev) +static int tegra_qspi_runtime_suspend(struct device *dev) { struct spi_controller *host = dev_get_drvdata(dev); struct tegra_qspi *tqspi = spi_controller_get_devdata(host); @@ -1879,7 +1879,7 @@ static int __maybe_unused tegra_qspi_runtime_suspend(struct device *dev) return 0; } -static int __maybe_unused tegra_qspi_runtime_resume(struct device *dev) +static int tegra_qspi_runtime_resume(struct device *dev) { struct spi_controller *host = dev_get_drvdata(dev); struct tegra_qspi *tqspi = spi_controller_get_devdata(host); @@ -1896,14 +1896,14 @@ static int __maybe_unused tegra_qspi_runtime_resume(struct device *dev) } static const struct dev_pm_ops tegra_qspi_pm_ops = { - SET_RUNTIME_PM_OPS(tegra_qspi_runtime_suspend, tegra_qspi_runtime_resume, NULL) - SET_SYSTEM_SLEEP_PM_OPS(tegra_qspi_suspend, tegra_qspi_resume) + RUNTIME_PM_OPS(tegra_qspi_runtime_suspend, tegra_qspi_runtime_resume, NULL) + SYSTEM_SLEEP_PM_OPS(tegra_qspi_suspend, tegra_qspi_resume) }; static struct platform_driver tegra_qspi_driver = { .driver = { .name = "tegra-qspi", - .pm = &tegra_qspi_pm_ops, + .pm = pm_ptr(&tegra_qspi_pm_ops), .of_match_table = tegra_qspi_of_match, .acpi_match_table = ACPI_PTR(tegra_qspi_acpi_match), }, From 2dae40e9dcfbacd6362244de68868538c11967a1 Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Mon, 3 Aug 2026 22:20:02 +0800 Subject: [PATCH 110/132] spi: topcliff-pch: use modern PM macros Use the modern PM macros for the suspend and resume functions to be automatically dropped by the compiler when CONFIG_PM or CONFIG_PM_SLEEP are disabled, without having to use #ifdef guards. This has the advantage of always compiling these functions in, independently of any Kconfig option. Thanks to that, bugs and other regressions are subsequently easier to catch. At the same time, for pch_spi_pd_driver, we also switch to modern driver.pm for pm ops. Signed-off-by: Jisheng Zhang Link: https://patch.msgid.link/20260803142003.12857-39-jszhang@kernel.org Signed-off-by: Mark Brown --- drivers/spi/spi-topcliff-pch.c | 47 +++++++++++++++------------------- 1 file changed, 20 insertions(+), 27 deletions(-) diff --git a/drivers/spi/spi-topcliff-pch.c b/drivers/spi/spi-topcliff-pch.c index c5409ca7faef..98723042c16f 100644 --- a/drivers/spi/spi-topcliff-pch.c +++ b/drivers/spi/spi-topcliff-pch.c @@ -1438,19 +1438,17 @@ static void pch_spi_pd_remove(struct platform_device *plat_dev) spi_controller_put(data->host); } -#ifdef CONFIG_PM -static int pch_spi_pd_suspend(struct platform_device *pd_dev, - pm_message_t state) + +static int pch_spi_pd_suspend(struct device *dev) { u8 count; - struct pch_spi_board_data *board_dat = dev_get_platdata(&pd_dev->dev); - struct pch_spi_data *data = platform_get_drvdata(pd_dev); + struct pch_spi_board_data *board_dat = dev_get_platdata(dev); + struct pch_spi_data *data = dev_get_drvdata(dev); - dev_dbg(&pd_dev->dev, "%s ENTRY\n", __func__); + dev_dbg(dev, "%s ENTRY\n", __func__); if (!board_dat) { - dev_err(&pd_dev->dev, - "%s pci_get_drvdata returned NULL\n", __func__); + dev_err(dev, "%s pci_get_drvdata returned NULL\n", __func__); return -EFAULT; } @@ -1471,22 +1469,20 @@ static int pch_spi_pd_suspend(struct platform_device *pd_dev, free_irq(board_dat->pdev->irq, data); data->irq_reg_sts = false; - dev_dbg(&pd_dev->dev, - "%s free_irq invoked successfully.\n", __func__); + dev_dbg(dev, "%s free_irq invoked successfully.\n", __func__); } return 0; } -static int pch_spi_pd_resume(struct platform_device *pd_dev) +static int pch_spi_pd_resume(struct device *dev) { - struct pch_spi_board_data *board_dat = dev_get_platdata(&pd_dev->dev); - struct pch_spi_data *data = platform_get_drvdata(pd_dev); + struct pch_spi_board_data *board_dat = dev_get_platdata(dev); + struct pch_spi_data *data = dev_get_drvdata(dev); int retval; if (!board_dat) { - dev_err(&pd_dev->dev, - "%s pci_get_drvdata returned NULL\n", __func__); + dev_err(dev, "%s pci_get_drvdata returned NULL\n", __func__); return -EFAULT; } @@ -1495,8 +1491,7 @@ static int pch_spi_pd_resume(struct platform_device *pd_dev) retval = request_irq(board_dat->pdev->irq, pch_spi_handler, IRQF_SHARED, KBUILD_MODNAME, data); if (retval < 0) { - dev_err(&pd_dev->dev, - "%s request_irq failed\n", __func__); + dev_err(dev, "%s request_irq failed\n", __func__); return retval; } @@ -1507,19 +1502,17 @@ static int pch_spi_pd_resume(struct platform_device *pd_dev) } return 0; } -#else -#define pch_spi_pd_suspend NULL -#define pch_spi_pd_resume NULL -#endif + +static DEFINE_SIMPLE_DEV_PM_OPS(pch_spi_pd_pm_ops, + pch_spi_pd_suspend, pch_spi_pd_resume); static struct platform_driver pch_spi_pd_driver = { .driver = { .name = "pch-spi", + .pm = pm_sleep_ptr(&pch_spi_pd_pm_ops), }, .probe = pch_spi_pd_probe, .remove = pch_spi_pd_remove, - .suspend = pch_spi_pd_suspend, - .resume = pch_spi_pd_resume }; static int pch_spi_probe(struct pci_dev *pdev, const struct pci_device_id *id) @@ -1618,7 +1611,7 @@ static void pch_spi_remove(struct pci_dev *pdev) kfree(pd_dev_save); } -static int __maybe_unused pch_spi_suspend(struct device *dev) +static int pch_spi_suspend(struct device *dev) { struct pch_pd_dev_save *pd_dev_save = dev_get_drvdata(dev); @@ -1629,7 +1622,7 @@ static int __maybe_unused pch_spi_suspend(struct device *dev) return 0; } -static int __maybe_unused pch_spi_resume(struct device *dev) +static int pch_spi_resume(struct device *dev) { struct pch_pd_dev_save *pd_dev_save = dev_get_drvdata(dev); @@ -1641,14 +1634,14 @@ static int __maybe_unused pch_spi_resume(struct device *dev) return 0; } -static SIMPLE_DEV_PM_OPS(pch_spi_pm_ops, pch_spi_suspend, pch_spi_resume); +static DEFINE_SIMPLE_DEV_PM_OPS(pch_spi_pm_ops, pch_spi_suspend, pch_spi_resume); static struct pci_driver pch_spi_pcidev_driver = { .name = "pch_spi", .id_table = pch_spi_pcidev_id, .probe = pch_spi_probe, .remove = pch_spi_remove, - .driver.pm = &pch_spi_pm_ops, + .driver.pm = pm_sleep_ptr(&pch_spi_pm_ops), }; static int __init pch_spi_init(void) From bec2ec4186d0ce0a921ca23fc03969305fc76564 Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Mon, 3 Aug 2026 22:20:03 +0800 Subject: [PATCH 111/132] spi: zynqmp-gqspi: use modern PM macros Use the modern PM macros for the suspend and resume functions to be automatically dropped by the compiler when CONFIG_PM or CONFIG_PM_SLEEP are disabled, without having to use __maybe_unused. Signed-off-by: Jisheng Zhang Link: https://patch.msgid.link/20260803142003.12857-40-jszhang@kernel.org Signed-off-by: Mark Brown --- drivers/spi/spi-zynqmp-gqspi.c | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/drivers/spi/spi-zynqmp-gqspi.c b/drivers/spi/spi-zynqmp-gqspi.c index f9a1427dabad..4d55090fa443 100644 --- a/drivers/spi/spi-zynqmp-gqspi.c +++ b/drivers/spi/spi-zynqmp-gqspi.c @@ -935,7 +935,7 @@ static int zynqmp_qspi_read_op(struct zynqmp_qspi *xqspi, u8 rx_nbits, * * Return: Always 0 */ -static int __maybe_unused zynqmp_qspi_suspend(struct device *dev) +static int zynqmp_qspi_suspend(struct device *dev) { struct zynqmp_qspi *xqspi = dev_get_drvdata(dev); struct spi_controller *ctlr = xqspi->ctlr; @@ -959,7 +959,7 @@ static int __maybe_unused zynqmp_qspi_suspend(struct device *dev) * * Return: 0 on success; error value otherwise */ -static int __maybe_unused zynqmp_qspi_resume(struct device *dev) +static int zynqmp_qspi_resume(struct device *dev) { struct zynqmp_qspi *xqspi = dev_get_drvdata(dev); struct spi_controller *ctlr = xqspi->ctlr; @@ -979,7 +979,7 @@ static int __maybe_unused zynqmp_qspi_resume(struct device *dev) * * Return: Always 0 */ -static int __maybe_unused zynqmp_runtime_suspend(struct device *dev) +static int zynqmp_runtime_suspend(struct device *dev) { struct zynqmp_qspi *xqspi = dev_get_drvdata(dev); @@ -997,7 +997,7 @@ static int __maybe_unused zynqmp_runtime_suspend(struct device *dev) * * Return: 0 on success and error value on error */ -static int __maybe_unused zynqmp_runtime_resume(struct device *dev) +static int zynqmp_runtime_resume(struct device *dev) { struct zynqmp_qspi *xqspi = dev_get_drvdata(dev); int ret; @@ -1186,9 +1186,8 @@ static int zynqmp_qspi_exec_op(struct spi_mem *mem, } static const struct dev_pm_ops zynqmp_qspi_dev_pm_ops = { - SET_RUNTIME_PM_OPS(zynqmp_runtime_suspend, - zynqmp_runtime_resume, NULL) - SET_SYSTEM_SLEEP_PM_OPS(zynqmp_qspi_suspend, zynqmp_qspi_resume) + RUNTIME_PM_OPS(zynqmp_runtime_suspend, zynqmp_runtime_resume, NULL) + SYSTEM_SLEEP_PM_OPS(zynqmp_qspi_suspend, zynqmp_qspi_resume) }; static const struct qspi_platform_data versal_qspi_def = { @@ -1382,7 +1381,7 @@ static struct platform_driver zynqmp_qspi_driver = { .driver = { .name = "zynqmp-qspi", .of_match_table = zynqmp_qspi_of_match, - .pm = &zynqmp_qspi_dev_pm_ops, + .pm = pm_ptr(&zynqmp_qspi_dev_pm_ops), }, }; From 39114474232476503f95bd21837a8b8b63d26810 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Mon, 10 Aug 2026 17:10:42 +0200 Subject: [PATCH 112/132] spi: spi-mem: Flag DQS capability DQS is a typical SPI memory signal used to help with reading the data on the bus at high speeds (especially in DTR mode) by avoiding clock skews. The chip generates a clock signal synchronized with its data output fronts, also called data strobe. SPI NOR and SPI NAND cores must set this flag in order to indicate to other layers that DQS is available. Create a getter and a setter to reach this capability. Signed-off-by: Miquel Raynal Link: https://patch.msgid.link/20260810-winbond-nand-next-phy-tuning-v3-1-a97c3a61e675@bootlin.com Signed-off-by: Mark Brown --- drivers/spi/spi-mem.c | 32 ++++++++++++++++++++++++++++++++ include/linux/spi/spi-mem.h | 4 ++++ 2 files changed, 36 insertions(+) diff --git a/drivers/spi/spi-mem.c b/drivers/spi/spi-mem.c index a88b9f038356..5f973ebfb8b6 100644 --- a/drivers/spi/spi-mem.c +++ b/drivers/spi/spi-mem.c @@ -541,6 +541,38 @@ const char *spi_mem_get_name(struct spi_mem *mem) } EXPORT_SYMBOL_GPL(spi_mem_get_name); +/** + * spi_mem_set_dqs() - Mark DQS as being available + * @mem: the SPI memory + * + * When reading at high frequencies (> 100MHz), especially when DTR is enabled, + * transfer speed is limited due to clock skews. In particular, the controller + * does not know the board propagation delay nor the memory chip internal delay + * (clock in to data out) and thus cannot optimize its sampling points. + * Mitigating this limitation is possible with the addition of a data strobe + * signal, commonly named DQS. + * + * Set the DQS boolean if the feature is available and configured at the chip + * level. Controllers may query this value. + */ +void spi_mem_set_dqs(struct spi_mem *mem) +{ + mem->dqs = true; +} +EXPORT_SYMBOL_GPL(spi_mem_set_dqs); + +/** + * spi_mem_has_dqs() - Query whether the DQS is available or not + * @mem: the SPI memory + * + * Return: a boolean indicating whether the DQS signal is available or not. + */ +bool spi_mem_has_dqs(struct spi_mem *mem) +{ + return mem->dqs; +} +EXPORT_SYMBOL_GPL(spi_mem_has_dqs); + /** * spi_mem_adjust_op_size() - Adjust the data size of a SPI mem operation to * match controller limitations diff --git a/include/linux/spi/spi-mem.h b/include/linux/spi/spi-mem.h index f660bb2e9f85..0cce6b57242a 100644 --- a/include/linux/spi/spi-mem.h +++ b/include/linux/spi/spi-mem.h @@ -275,6 +275,7 @@ struct spi_mem_dirmap_desc { * @spi: the underlying SPI device * @drvpriv: spi_mem_driver private data * @name: name of the SPI memory device + * @dqs: extra data trobe pin available for high frequency read operations * * Extra information that describe the SPI memory device and may be needed by * the controller to properly handle this device should be placed here. @@ -286,6 +287,7 @@ struct spi_mem { struct spi_device *spi; void *drvpriv; const char *name; + bool dqs; }; /** @@ -461,6 +463,8 @@ bool spi_mem_default_supports_op(struct spi_mem *mem, } #endif /* CONFIG_SPI_MEM */ +void spi_mem_set_dqs(struct spi_mem *mem); +bool spi_mem_has_dqs(struct spi_mem *mem); int spi_mem_adjust_op_size(struct spi_mem *mem, struct spi_mem_op *op); void spi_mem_adjust_op_freq(struct spi_mem *mem, struct spi_mem_op *op); u64 spi_mem_calc_op_duration(struct spi_mem *mem, struct spi_mem_op *op); From f092e1c935015ee0a0fea1a0374f4cab7b71953c Mon Sep 17 00:00:00 2001 From: Babanpreet Singh Date: Wed, 29 Jul 2026 05:35:43 +0000 Subject: [PATCH 113/132] spi: sprd-adi: Fix probe succeeding without registering the controller With CONFIG_HWSPINLOCK=n the of_hwspin_lock_get_id() stub returns 0 unconditionally. In sprd_adi_probe() the guard if (ret > 0 || (IS_ENABLED(CONFIG_HWSPINLOCK) && ret == 0)) is false for that 0, so it takes the else branch, where the switch has no case for 0 and lands in default: return dev_err_probe(&pdev->dev, ret, "failed to find hwlock id\n"); dev_err_probe() returns its err argument unchanged, so probe logs "failed to find hwlock id" and then returns 0, reporting success. sprd_adi_hw_init(), the restart handler and devm_spi_register_controller() are all skipped: the device binds but no SPI controller is ever registered. The hardware spinlock is optional for this controller and the -ENOENT arm already covers "no hardware spinlock supplied". Treat the stub's 0 the same way and continue without a lock; all four users of sadi->hwlock already test it for NULL. This is not reachable on production kernels. Kconfig has depends on HWSPINLOCK || (COMPILE_TEST && !HWSPINLOCK) so the affected configuration exists only under COMPILE_TEST, where no real hardware is present. Found by smatch: drivers/spi/spi-sprd-adi.c:560 sprd_adi_probe() warn: passing zero to 'dev_err_probe' Fixes: f9adf61e983f ("spi: sprd: adi: Change hwlock to be optional") Assisted-by: Claude:claude-opus-5 Reviewed-by: Baolin Wang Signed-off-by: Babanpreet Singh Link: https://patch.msgid.link/20260729053543.7-1-bbnpreetsingh@gmail.com Signed-off-by: Mark Brown --- drivers/spi/spi-sprd-adi.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/spi/spi-sprd-adi.c b/drivers/spi/spi-sprd-adi.c index e7d83c16b46c..a6baaf02b3c8 100644 --- a/drivers/spi/spi-sprd-adi.c +++ b/drivers/spi/spi-sprd-adi.c @@ -553,6 +553,12 @@ static int sprd_adi_probe(struct platform_device *pdev) return -ENXIO; } else { switch (ret) { + case 0: + /* + * Only reachable with CONFIG_HWSPINLOCK=n, where the + * of_hwspin_lock_get_id() stub returns 0. + */ + fallthrough; case -ENOENT: dev_info(&pdev->dev, "no hardware spinlock supplied\n"); break; From c10b7f5e0cb1deeba89af670d7fe19cf2454aa2b Mon Sep 17 00:00:00 2001 From: Felix Gu Date: Sun, 2 Aug 2026 01:51:24 +0800 Subject: [PATCH 114/132] spi: meson-spifc: use devm_pm_runtime_set_active_enabled Use devm_pm_runtime_set_active_enabled to replace pm_runtime_set_active() + pm_runtime_enable() and drop the out_pm error label. Signed-off-by: Felix Gu Link: https://patch.msgid.link/20260722-spifc-v1-1-e4462a4c6a06@gmail.com Link: https://patch.msgid.link/20260802-spifc-v2-1-46e9d06a3217@gmail.com Signed-off-by: Mark Brown --- drivers/spi/spi-meson-spifc.c | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/drivers/spi/spi-meson-spifc.c b/drivers/spi/spi-meson-spifc.c index 2ef090dec7ba..1580797f4a84 100644 --- a/drivers/spi/spi-meson-spifc.c +++ b/drivers/spi/spi-meson-spifc.c @@ -325,26 +325,20 @@ static int meson_spifc_probe(struct platform_device *pdev) meson_spifc_hw_init(spifc); - pm_runtime_set_active(spifc->dev); - pm_runtime_enable(spifc->dev); + ret = devm_pm_runtime_set_active_enabled(spifc->dev); + if (ret) + return dev_err_probe(spifc->dev, ret, "failed to set runtime PM\n"); ret = devm_spi_register_controller(spifc->dev, host); - if (ret) { - dev_err(spifc->dev, "failed to register spi host\n"); - goto out_pm; - } + if (ret) + return dev_err_probe(spifc->dev, ret, "failed to register spi host\n"); return 0; -out_pm: - pm_runtime_disable(spifc->dev); - - return ret; } static void meson_spifc_remove(struct platform_device *pdev) { pm_runtime_get_sync(&pdev->dev); - pm_runtime_disable(&pdev->dev); pm_runtime_put_noidle(&pdev->dev); } From e4702b6ff30e2c0c52688d2c76090ee73c6e0299 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Fri, 7 Aug 2026 17:38:47 +0700 Subject: [PATCH 115/132] spi: hisi-sfc-v3xx: Propagate errors from optional IRQ lookup platform_get_irq_optional() returns a positive IRQ number on success or a negative error code on failure. For an optional IRQ, -ENXIO indicates that no IRQ is available, while other errors should be propagated. Instead of only checking for -EPROBE_DEFER, propagate all error codes returned by platform_get_irq_optional() other than -ENXIO, so that failures are properly reported to the caller. Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260807103848.46315-1-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- drivers/spi/spi-hisi-sfc-v3xx.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/spi/spi-hisi-sfc-v3xx.c b/drivers/spi/spi-hisi-sfc-v3xx.c index d60db579b0fb..0790c7179868 100644 --- a/drivers/spi/spi-hisi-sfc-v3xx.c +++ b/drivers/spi/spi-hisi-sfc-v3xx.c @@ -454,8 +454,8 @@ static int hisi_sfc_v3xx_probe(struct platform_device *pdev) return PTR_ERR(host->regbase); host->irq = platform_get_irq_optional(pdev, 0); - if (host->irq == -EPROBE_DEFER) - return -EPROBE_DEFER; + if (host->irq < 0 && host->irq != -ENXIO) + return host->irq; hisi_sfc_v3xx_disable_int(host); From 0d51ff742b416ea42ac36c73c0476f968c1a787a Mon Sep 17 00:00:00 2001 From: Md Sadre Alam Date: Fri, 7 Aug 2026 21:33:55 +0530 Subject: [PATCH 116/132] spi: spi-qpic-snand: move command mapping helper Move qcom_spi_cmd_mapping() above qcom_spi_read_page() so it can be used by read path changes added in a subsequent patch. No functional change. Signed-off-by: Md Sadre Alam Link: https://patch.msgid.link/20260807-quad-v2-1-8ec821e2f22b@oss.qualcomm.com Signed-off-by: Mark Brown --- drivers/spi/spi-qpic-snand.c | 96 ++++++++++++++++++------------------ 1 file changed, 48 insertions(+), 48 deletions(-) diff --git a/drivers/spi/spi-qpic-snand.c b/drivers/spi/spi-qpic-snand.c index 1e2fc875cd66..db105b3fd601 100644 --- a/drivers/spi/spi-qpic-snand.c +++ b/drivers/spi/spi-qpic-snand.c @@ -1004,6 +1004,54 @@ static int qcom_spi_read_page_oob(struct qcom_nand_controller *snandc, return qcom_spi_check_error(snandc); } +static int qcom_spi_cmd_mapping(struct qcom_nand_controller *snandc, u32 opcode, u32 *cmd) +{ + switch (opcode) { + case SPINAND_RESET: + *cmd = (SPI_WP | SPI_HOLD | SPI_TRANSFER_MODE_x1 | OP_RESET_DEVICE); + break; + case SPINAND_READID: + *cmd = (SPI_WP | SPI_HOLD | SPI_TRANSFER_MODE_x1 | OP_FETCH_ID); + break; + case SPINAND_GET_FEATURE: + *cmd = (SPI_TRANSFER_MODE_x1 | SPI_WP | SPI_HOLD | ACC_FEATURE); + break; + case SPINAND_SET_FEATURE: + *cmd = (SPI_TRANSFER_MODE_x1 | SPI_WP | SPI_HOLD | ACC_FEATURE | + QPIC_SET_FEATURE); + break; + case SPINAND_READ: + if (snandc->qspi->raw_rw) { + *cmd = (PAGE_ACC | LAST_PAGE | SPI_TRANSFER_MODE_x1 | + SPI_WP | SPI_HOLD | OP_PAGE_READ); + } else { + *cmd = (PAGE_ACC | LAST_PAGE | SPI_TRANSFER_MODE_x1 | + SPI_WP | SPI_HOLD | OP_PAGE_READ_WITH_ECC); + } + + break; + case SPINAND_ERASE: + *cmd = OP_BLOCK_ERASE | PAGE_ACC | LAST_PAGE | SPI_WP | + SPI_HOLD | SPI_TRANSFER_MODE_x1; + break; + case SPINAND_WRITE_EN: + *cmd = SPINAND_WRITE_EN; + break; + case SPINAND_PROGRAM_EXECUTE: + *cmd = (PAGE_ACC | LAST_PAGE | SPI_TRANSFER_MODE_x1 | + SPI_WP | SPI_HOLD | OP_PROGRAM_PAGE); + break; + case SPINAND_PROGRAM_LOAD: + *cmd = SPINAND_PROGRAM_LOAD; + break; + default: + dev_err(snandc->dev, "Opcode not supported: %u\n", opcode); + return -EOPNOTSUPP; + } + + return 0; +} + static int qcom_spi_read_page(struct qcom_nand_controller *snandc, const struct spi_mem_op *op) { @@ -1268,54 +1316,6 @@ static int qcom_spi_program_execute(struct qcom_nand_controller *snandc, return 0; } -static int qcom_spi_cmd_mapping(struct qcom_nand_controller *snandc, u32 opcode, u32 *cmd) -{ - switch (opcode) { - case SPINAND_RESET: - *cmd = (SPI_WP | SPI_HOLD | SPI_TRANSFER_MODE_x1 | OP_RESET_DEVICE); - break; - case SPINAND_READID: - *cmd = (SPI_WP | SPI_HOLD | SPI_TRANSFER_MODE_x1 | OP_FETCH_ID); - break; - case SPINAND_GET_FEATURE: - *cmd = (SPI_TRANSFER_MODE_x1 | SPI_WP | SPI_HOLD | ACC_FEATURE); - break; - case SPINAND_SET_FEATURE: - *cmd = (SPI_TRANSFER_MODE_x1 | SPI_WP | SPI_HOLD | ACC_FEATURE | - QPIC_SET_FEATURE); - break; - case SPINAND_READ: - if (snandc->qspi->raw_rw) { - *cmd = (PAGE_ACC | LAST_PAGE | SPI_TRANSFER_MODE_x1 | - SPI_WP | SPI_HOLD | OP_PAGE_READ); - } else { - *cmd = (PAGE_ACC | LAST_PAGE | SPI_TRANSFER_MODE_x1 | - SPI_WP | SPI_HOLD | OP_PAGE_READ_WITH_ECC); - } - - break; - case SPINAND_ERASE: - *cmd = OP_BLOCK_ERASE | PAGE_ACC | LAST_PAGE | SPI_WP | - SPI_HOLD | SPI_TRANSFER_MODE_x1; - break; - case SPINAND_WRITE_EN: - *cmd = SPINAND_WRITE_EN; - break; - case SPINAND_PROGRAM_EXECUTE: - *cmd = (PAGE_ACC | LAST_PAGE | SPI_TRANSFER_MODE_x1 | - SPI_WP | SPI_HOLD | OP_PROGRAM_PAGE); - break; - case SPINAND_PROGRAM_LOAD: - *cmd = SPINAND_PROGRAM_LOAD; - break; - default: - dev_err(snandc->dev, "Opcode not supported: %u\n", opcode); - return -EOPNOTSUPP; - } - - return 0; -} - static int qcom_spi_write_page(struct qcom_nand_controller *snandc, const struct spi_mem_op *op) { From cdb1dbba490205c32f1e72aaaad26715c8f98f80 Mon Sep 17 00:00:00 2001 From: Md Sadre Alam Date: Fri, 7 Aug 2026 21:33:56 +0530 Subject: [PATCH 117/132] spi: spi-qpic-snand: add quad mode support Add support for quad (x4) transfer mode in the QPIC SPI NAND driver. The controller supports both single (x1) and quad (x4) SPI transfers, but the driver currently operates only in x1 mode. Track the QUAD enable state from the device configuration register (0xB0) and switch the data transfer width accordingly. When the core enables quad mode, use x4 transfers for read and program operations to improve throughput. Introduce a quad_mode flag in struct qpic_spi_nand to cache the current device state. The flag is updated based on GET_FEATURE responses from the configuration register. Signed-off-by: Md Sadre Alam Link: https://patch.msgid.link/20260807-quad-v2-2-8ec821e2f22b@oss.qualcomm.com Signed-off-by: Mark Brown --- drivers/spi/spi-qpic-snand.c | 67 +++++++++++++++++++++++++++++++----- 1 file changed, 58 insertions(+), 9 deletions(-) diff --git a/drivers/spi/spi-qpic-snand.c b/drivers/spi/spi-qpic-snand.c index db105b3fd601..6ad2f96dde8c 100644 --- a/drivers/spi/spi-qpic-snand.c +++ b/drivers/spi/spi-qpic-snand.c @@ -46,14 +46,19 @@ #define SPINAND_RESET 0xff #define SPINAND_READID 0x9f +#define SPINAND_FEATURE_ADDR 0xb0 #define SPINAND_GET_FEATURE 0x0f #define SPINAND_SET_FEATURE 0x1f +#define SPINAND_READ_CACHE 0x0b #define SPINAND_READ 0x13 +#define SPINAND_READ_QUAD 0xeb #define SPINAND_ERASE 0xd8 #define SPINAND_WRITE_EN 0x06 #define SPINAND_PROGRAM_EXECUTE 0x10 #define SPINAND_PROGRAM_LOAD 0x84 +#define SPINAND_PROGRAM_LOAD_QUAD 0x34 +#define QUAD_WIDTH 0x4 #define ACC_FEATURE 0xe #define BAD_BLOCK_MARKER_SIZE 0x2 #define OOB_BUF_SIZE 128 @@ -114,6 +119,7 @@ struct qpic_spi_nand { bool oob_rw; bool page_rw; bool raw_rw; + bool quad_mode; }; static void qcom_spi_set_read_loc_first(struct qcom_nand_controller *snandc, @@ -1004,8 +1010,15 @@ static int qcom_spi_read_page_oob(struct qcom_nand_controller *snandc, return qcom_spi_check_error(snandc); } -static int qcom_spi_cmd_mapping(struct qcom_nand_controller *snandc, u32 opcode, u32 *cmd) +static int qcom_spi_cmd_mapping(struct qcom_nand_controller *snandc, + const struct spi_mem_op *op, u32 *cmd) { + u32 opcode = op->cmd.opcode; + u32 transfer_mode = SPI_TRANSFER_MODE_x1; + + if (snandc->qspi->quad_mode && op->data.buswidth == QUAD_WIDTH) + transfer_mode = SPI_TRANSFER_MODE_x4; + switch (opcode) { case SPINAND_RESET: *cmd = (SPI_WP | SPI_HOLD | SPI_TRANSFER_MODE_x1 | OP_RESET_DEVICE); @@ -1021,11 +1034,13 @@ static int qcom_spi_cmd_mapping(struct qcom_nand_controller *snandc, u32 opcode, QPIC_SET_FEATURE); break; case SPINAND_READ: + case SPINAND_READ_QUAD: + case SPINAND_READ_CACHE: if (snandc->qspi->raw_rw) { - *cmd = (PAGE_ACC | LAST_PAGE | SPI_TRANSFER_MODE_x1 | + *cmd = (PAGE_ACC | LAST_PAGE | transfer_mode | SPI_WP | SPI_HOLD | OP_PAGE_READ); } else { - *cmd = (PAGE_ACC | LAST_PAGE | SPI_TRANSFER_MODE_x1 | + *cmd = (PAGE_ACC | LAST_PAGE | transfer_mode | SPI_WP | SPI_HOLD | OP_PAGE_READ_WITH_ECC); } @@ -1038,12 +1053,15 @@ static int qcom_spi_cmd_mapping(struct qcom_nand_controller *snandc, u32 opcode, *cmd = SPINAND_WRITE_EN; break; case SPINAND_PROGRAM_EXECUTE: - *cmd = (PAGE_ACC | LAST_PAGE | SPI_TRANSFER_MODE_x1 | - SPI_WP | SPI_HOLD | OP_PROGRAM_PAGE); + *cmd = (PAGE_ACC | LAST_PAGE | transfer_mode | + SPI_WP | SPI_HOLD | OP_PROGRAM_PAGE); break; case SPINAND_PROGRAM_LOAD: *cmd = SPINAND_PROGRAM_LOAD; break; + case SPINAND_PROGRAM_LOAD_QUAD: + *cmd = SPINAND_PROGRAM_LOAD_QUAD; + break; default: dev_err(snandc->dev, "Opcode not supported: %u\n", opcode); return -EOPNOTSUPP; @@ -1055,6 +1073,16 @@ static int qcom_spi_cmd_mapping(struct qcom_nand_controller *snandc, u32 opcode, static int qcom_spi_read_page(struct qcom_nand_controller *snandc, const struct spi_mem_op *op) { + int ret; + u32 cmd; + + /* Update the cached command for the cache-read opcode and bus width. */ + ret = qcom_spi_cmd_mapping(snandc, op, &cmd); + if (ret < 0) + return ret; + + snandc->qspi->cmd = cpu_to_le32(cmd); + if (snandc->qspi->page_rw && snandc->qspi->raw_rw) return qcom_spi_read_page_raw(snandc, op); @@ -1322,11 +1350,12 @@ static int qcom_spi_write_page(struct qcom_nand_controller *snandc, int ret; u32 cmd; - ret = qcom_spi_cmd_mapping(snandc, op->cmd.opcode, &cmd); + ret = qcom_spi_cmd_mapping(snandc, op, &cmd); if (ret < 0) return ret; - if (op->cmd.opcode == SPINAND_PROGRAM_LOAD) + if (op->cmd.opcode == SPINAND_PROGRAM_LOAD || + op->cmd.opcode == SPINAND_PROGRAM_LOAD_QUAD) snandc->qspi->data_buf = (u8 *)op->data.buf.out; return 0; @@ -1338,7 +1367,7 @@ static int qcom_spi_send_cmdaddr(struct qcom_nand_controller *snandc, u32 cmd; int ret, opcode; - ret = qcom_spi_cmd_mapping(snandc, op->cmd.opcode, &cmd); + ret = qcom_spi_cmd_mapping(snandc, op, &cmd); if (ret < 0) return ret; @@ -1457,6 +1486,22 @@ static int qcom_spi_io_op(struct qcom_nand_controller *snandc, const struct spi_ val = le32_to_cpu(*(__le32 *)snandc->reg_read_buf); val >>= 8; memcpy(op->data.buf.in, &val, snandc->buf_count); + + /* + * Track QUAD mode state from configuration register. + * When core layer reads register 0xB0 (CFG), check if + * QUAD enable bit (bit 0) is set and update our state + * accordingly for future READ/WRITE operations. + */ + if (op->addr.val == SPINAND_FEATURE_ADDR) { + bool quad_enabled = !!((u8)val & BIT(0)); + + if (snandc->qspi->quad_mode != quad_enabled) { + snandc->qspi->quad_mode = quad_enabled; + dev_info(snandc->dev, "SPI NAND QUAD mode: %s\n", + quad_enabled ? "enabled" : "disabled"); + } + } } return 0; @@ -1497,7 +1542,8 @@ static bool qcom_spi_supports_op(struct spi_mem *mem, const struct spi_mem_op *o return ((!op->addr.nbytes || op->addr.buswidth == 1) && (!op->dummy.nbytes || op->dummy.buswidth == 1) && - (!op->data.nbytes || op->data.buswidth == 1)); + (!op->data.nbytes || op->data.buswidth == 1 || + op->data.buswidth == 4)); } static int qcom_spi_exec_op(struct spi_mem *mem, const struct spi_mem_op *op) @@ -1548,6 +1594,9 @@ static int qcom_spi_probe(struct platform_device *pdev) if (!qspi) return -ENOMEM; + /* Initialize QUAD mode state */ + qspi->quad_mode = false; + ctlr = __devm_spi_alloc_controller(dev, sizeof(*snandc), false); if (!ctlr) return -ENOMEM; From 7e3ffa9f46b7157d4f4c562f46b200336c34e25b Mon Sep 17 00:00:00 2001 From: Md Sadre Alam Date: Fri, 7 Aug 2026 21:33:57 +0530 Subject: [PATCH 118/132] spi: spi-qpic-snand: Handle Macronix quad read opcode 0x6b Macronix SPI-NAND devices use opcode 0x6b for quad output cache reads, while most other devices use opcode 0xeb. The QPIC SPI-NAND driver does not currently recognize opcode 0x6b, causing read operations to fail when Macronix devices select this cache read variant. Add the Macronix-specific read opcode to the command mapping logic and treat it the same as the existing quad read operations. This allows Macronix SPI-NAND devices to operate correctly in quad read mode. Signed-off-by: Md Sadre Alam Link: https://patch.msgid.link/20260807-quad-v2-3-8ec821e2f22b@oss.qualcomm.com Signed-off-by: Mark Brown --- drivers/spi/spi-qpic-snand.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/spi/spi-qpic-snand.c b/drivers/spi/spi-qpic-snand.c index 6ad2f96dde8c..61b1f2eb19ce 100644 --- a/drivers/spi/spi-qpic-snand.c +++ b/drivers/spi/spi-qpic-snand.c @@ -52,6 +52,7 @@ #define SPINAND_READ_CACHE 0x0b #define SPINAND_READ 0x13 #define SPINAND_READ_QUAD 0xeb +#define SPINAND_READ_MACRONIX 0x6b #define SPINAND_ERASE 0xd8 #define SPINAND_WRITE_EN 0x06 #define SPINAND_PROGRAM_EXECUTE 0x10 @@ -1036,6 +1037,7 @@ static int qcom_spi_cmd_mapping(struct qcom_nand_controller *snandc, case SPINAND_READ: case SPINAND_READ_QUAD: case SPINAND_READ_CACHE: + case SPINAND_READ_MACRONIX: if (snandc->qspi->raw_rw) { *cmd = (PAGE_ACC | LAST_PAGE | transfer_mode | SPI_WP | SPI_HOLD | OP_PAGE_READ); From 356d5869bc33abda94e3dc01eea2293363230efb Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Fri, 7 Aug 2026 17:29:30 +0700 Subject: [PATCH 119/132] spi: mtk-nor: Propagate errors from optional IRQ lookup platform_get_irq_optional() returns a positive IRQ number on success or a negative error code on failure. For an optional IRQ, -ENXIO indicates that no IRQ is available, while other errors should be propagated. Propagate errors such as -EPROBE_DEFER and -EINVAL instead of continuing probe without the IRQ. Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260807102932.45785-1-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- drivers/spi/spi-mtk-nor.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/spi/spi-mtk-nor.c b/drivers/spi/spi-mtk-nor.c index 5e1fdbb40ffb..e80d1de03556 100644 --- a/drivers/spi/spi-mtk-nor.c +++ b/drivers/spi/spi-mtk-nor.c @@ -895,6 +895,10 @@ static int mtk_nor_probe(struct platform_device *pdev) irq = platform_get_irq_optional(pdev, 0); if (irq < 0) { + if (irq != -ENXIO) { + ret = irq; + goto err_disable_clk; + } dev_warn(sp->dev, "IRQ not available."); } else { ret = devm_request_irq(sp->dev, irq, mtk_nor_irq_handler, 0, @@ -928,6 +932,7 @@ static int mtk_nor_probe(struct platform_device *pdev) pm_runtime_set_suspended(&pdev->dev); pm_runtime_dont_use_autosuspend(&pdev->dev); +err_disable_clk: mtk_nor_disable_clk(sp); return ret; From 560fbcf4720b6d671597f271911fb6b60787738e Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Fri, 7 Aug 2026 17:29:31 +0700 Subject: [PATCH 120/132] spi: mtk-nor: Propagate errors from IRQ request Treat a failure from devm_request_irq() as a probe error instead of continuing without an IRQ after only reporting a warning. Return the error through the existing error path to ensure the driver does not continue with an unsuccessfully requested IRQ. Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260807102932.45785-2-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- drivers/spi/spi-mtk-nor.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/spi/spi-mtk-nor.c b/drivers/spi/spi-mtk-nor.c index e80d1de03556..63f5139176c8 100644 --- a/drivers/spi/spi-mtk-nor.c +++ b/drivers/spi/spi-mtk-nor.c @@ -905,6 +905,7 @@ static int mtk_nor_probe(struct platform_device *pdev) pdev->name, sp); if (ret < 0) { dev_warn(sp->dev, "failed to request IRQ."); + goto err_disable_clk; } else { init_completion(&sp->op_done); sp->has_irq = true; From 923e41ed59511cffe98357c7d58d0294a1c157ee Mon Sep 17 00:00:00 2001 From: Felix Gu Date: Sat, 8 Aug 2026 01:41:43 +0800 Subject: [PATCH 121/132] spi: img-spfi: don't disable runtime PM on DMA deferred probe When dma_request_chan() returns -EPROBE_DEFER, the error path jumps to disable_pm and calls pm_runtime_disable() even though pm_runtime_enable() was never called, leaving disable_depth unbalanced and the device permanently PM-disabled. Route the defer path through free_dma to skip pm_runtime_disable(). Fixes: 6bfbf4d0aa0c ("spi: img-spfi: Use dma_request_chan() instead dma_request_slave_channel()") Signed-off-by: Felix Gu Link: https://patch.msgid.link/20260808-spfi-v1-1-6bc4345be430@gmail.com Signed-off-by: Mark Brown --- drivers/spi/spi-img-spfi.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/spi/spi-img-spfi.c b/drivers/spi/spi-img-spfi.c index 19c8250ddb64..76dcb283d122 100644 --- a/drivers/spi/spi-img-spfi.c +++ b/drivers/spi/spi-img-spfi.c @@ -611,7 +611,7 @@ static int img_spfi_probe(struct platform_device *pdev) ret = PTR_ERR(spfi->tx_ch); spfi->tx_ch = NULL; if (ret == -EPROBE_DEFER) - goto disable_pm; + goto free_dma; } spfi->rx_ch = dma_request_chan(spfi->dev, "rx"); @@ -619,7 +619,7 @@ static int img_spfi_probe(struct platform_device *pdev) ret = PTR_ERR(spfi->rx_ch); spfi->rx_ch = NULL; if (ret == -EPROBE_DEFER) - goto disable_pm; + goto free_dma; } if (!spfi->tx_ch || !spfi->rx_ch) { @@ -647,6 +647,7 @@ static int img_spfi_probe(struct platform_device *pdev) disable_pm: pm_runtime_disable(spfi->dev); +free_dma: if (spfi->rx_ch) dma_release_channel(spfi->rx_ch); if (spfi->tx_ch) From 11dfeb73d1f9cdf8fc493251c8616f5bc338acad Mon Sep 17 00:00:00 2001 From: Liang Hao Date: Fri, 14 Aug 2026 19:42:35 +0800 Subject: [PATCH 122/132] spi: dw: Remove shadowed dws in dw_spi_setup() The controller private data is already fetched at the start of dw_spi_setup(). Drop the redundant inner declaration that shadows it. Signed-off-by: Liang Hao Link: https://patch.msgid.link/20260814114235.31281-1-haohlliang@gmail.com Signed-off-by: Mark Brown --- drivers/spi/spi-dw-core.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/spi/spi-dw-core.c b/drivers/spi/spi-dw-core.c index dfce2fa20adf..206d3f9dd83d 100644 --- a/drivers/spi/spi-dw-core.c +++ b/drivers/spi/spi-dw-core.c @@ -1098,7 +1098,6 @@ static int dw_spi_setup(struct spi_device *spi) /* Only alloc on first setup */ chip = spi_get_ctldata(spi); if (!chip) { - struct dw_spi *dws = spi_controller_get_devdata(spi->controller); u32 rx_sample_dly_ns; chip = kzalloc_obj(*chip); From dc72e9e8cd19848d9ab286feef44d3d87b9f974a Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Thu, 13 Aug 2026 10:00:21 +0200 Subject: [PATCH 123/132] 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 124/132] 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 125/132] 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 126/132] 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 127/132] 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), From 122970a4a6f3ebaceeb994878044699fd5bcbebb Mon Sep 17 00:00:00 2001 From: Jerome Brunet Date: Wed, 12 Aug 2026 23:02:02 +0200 Subject: [PATCH 128/132] spi: dt-bindings: sun6i: Add compatibles for A733's SPI controllers The A733 has six SPI controllers with the same IP as the A523. Like the A523, one of them (spi1) also supports DBI mode. Add SoC specific compatible strings, falling back to the A523 ones. Signed-off-by: Jerome Brunet Link: https://patch.msgid.link/20260812-allwinner-a733-spi-support-v2-1-937bff0fa78e@baylibre.com Signed-off-by: Mark Brown --- .../devicetree/bindings/spi/allwinner,sun6i-a31-spi.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Documentation/devicetree/bindings/spi/allwinner,sun6i-a31-spi.yaml b/Documentation/devicetree/bindings/spi/allwinner,sun6i-a31-spi.yaml index 6af4ff233158..14c2f318dd04 100644 --- a/Documentation/devicetree/bindings/spi/allwinner,sun6i-a31-spi.yaml +++ b/Documentation/devicetree/bindings/spi/allwinner,sun6i-a31-spi.yaml @@ -34,6 +34,12 @@ properties: - const: allwinner,sun50i-r329-spi-dbi - const: allwinner,sun50i-r329-spi - items: + - enum: + - allwinner,sun55i-a523-spi-dbi + - allwinner,sun60i-a733-spi + - const: allwinner,sun55i-a523-spi + - items: + - const: allwinner,sun60i-a733-spi-dbi - const: allwinner,sun55i-a523-spi-dbi - const: allwinner,sun55i-a523-spi From 367cea239fc93094e5c16a72724800e0358f5c46 Mon Sep 17 00:00:00 2001 From: Honghui Jiang Date: Fri, 14 Aug 2026 11:14:15 +0800 Subject: [PATCH 129/132] spi: Fix DMA mapping ownership on partial map failure If RX mapping fails after TX mapping succeeds, __spi_map_msg() unmaps TX but leaves tx_sg_mapped set. If TX mapping fails on a later transfer, mappings created for earlier transfers remain active. In both cases, cur_{tx,rx}_dma_dev have not yet been updated because they are assigned only after every transfer has been mapped. The subsequent spi_unmap_msg() may therefore unmap the TX mapping again or release earlier mappings using a NULL or stale device. Using a NULL device can trigger an oops. An empty SG table does not prevent the NULL dereference because dma_unmap_sg_attrs() accesses the device before checking the entry count. Publish both mapping devices before mapping starts and unwind all failures through __spi_unmap_msg(). This clears the mapping flags and releases each mapping once with the device that created it. Publishing the devices before the loop also refreshes them when no transfer needs mapping. No mapping flag is set in that case, so current users do not use the pointers as mapping owners. Fixes: e289df82344f ("spi: Rework per message DMA mapped flag to be per transfer") Cc: stable@vger.kernel.org Signed-off-by: Honghui Jiang Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/20260814031419.43378-2-jiang_hh2019@163.com Signed-off-by: Mark Brown --- drivers/spi/spi.c | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c index 07c572e1df5d..0e86dc954713 100644 --- a/drivers/spi/spi.c +++ b/drivers/spi/spi.c @@ -1418,6 +1418,8 @@ void spi_unmap_buf(struct spi_controller *ctlr, struct device *dev, spi_unmap_buf_attrs(ctlr, dev, sgt, dir, 0); } +static int __spi_unmap_msg(struct spi_controller *ctlr, struct spi_message *msg); + static int __spi_map_msg(struct spi_controller *ctlr, struct spi_message *msg) { struct device *tx_dev, *rx_dev; @@ -1441,7 +1443,13 @@ static int __spi_map_msg(struct spi_controller *ctlr, struct spi_message *msg) else rx_dev = ctlr->dev.parent; - ret = -ENOMSG; + /* + * Store the devices before mapping so partial failures can be unwound + * with the device that created each mapping. + */ + ctlr->cur_tx_dma_dev = tx_dev; + ctlr->cur_rx_dma_dev = rx_dev; + list_for_each_entry(xfer, &msg->transfers, transfer_list) { /* The sync is done before each transfer. */ unsigned long attrs = DMA_ATTR_SKIP_CPU_SYNC; @@ -1454,8 +1462,8 @@ static int __spi_map_msg(struct spi_controller *ctlr, struct spi_message *msg) (void *)xfer->tx_buf, xfer->len, DMA_TO_DEVICE, attrs); - if (ret != 0) - return ret; + if (ret) + goto unwind; xfer->tx_sg_mapped = true; } @@ -1464,25 +1472,19 @@ static int __spi_map_msg(struct spi_controller *ctlr, struct spi_message *msg) ret = spi_map_buf_attrs(ctlr, rx_dev, &xfer->rx_sg, xfer->rx_buf, xfer->len, DMA_FROM_DEVICE, attrs); - if (ret != 0) { - spi_unmap_buf_attrs(ctlr, tx_dev, - &xfer->tx_sg, DMA_TO_DEVICE, - attrs); - - return ret; - } + if (ret) + goto unwind; xfer->rx_sg_mapped = true; } } - /* No transfer has been mapped, bail out with success */ - if (ret) - return 0; - - ctlr->cur_rx_dma_dev = rx_dev; - ctlr->cur_tx_dma_dev = tx_dev; return 0; + +unwind: + __spi_unmap_msg(ctlr, msg); + + return ret; } static int __spi_unmap_msg(struct spi_controller *ctlr, struct spi_message *msg) From b82b2dfc93d3c7729250e1107b7121775f5dfd40 Mon Sep 17 00:00:00 2001 From: Honghui Jiang Date: Fri, 14 Aug 2026 11:14:16 +0800 Subject: [PATCH 130/132] spi: Move __spi_unmap_msg() before __spi_map_msg() Move __spi_unmap_msg() above __spi_map_msg() so the mapping error path can call it without a forward declaration. This is a code-only relocation with no functional change. Suggested-by: Andy Shevchenko Signed-off-by: Honghui Jiang Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/20260814031419.43378-3-jiang_hh2019@163.com Signed-off-by: Mark Brown --- drivers/spi/spi.c | 48 +++++++++++++++++++++++------------------------ 1 file changed, 23 insertions(+), 25 deletions(-) diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c index 0e86dc954713..bfb5ac93c8ee 100644 --- a/drivers/spi/spi.c +++ b/drivers/spi/spi.c @@ -1418,7 +1418,29 @@ void spi_unmap_buf(struct spi_controller *ctlr, struct device *dev, spi_unmap_buf_attrs(ctlr, dev, sgt, dir, 0); } -static int __spi_unmap_msg(struct spi_controller *ctlr, struct spi_message *msg); +static int __spi_unmap_msg(struct spi_controller *ctlr, struct spi_message *msg) +{ + struct device *rx_dev = ctlr->cur_rx_dma_dev; + struct device *tx_dev = ctlr->cur_tx_dma_dev; + struct spi_transfer *xfer; + + list_for_each_entry(xfer, &msg->transfers, transfer_list) { + /* The sync has already been done after each transfer. */ + unsigned long attrs = DMA_ATTR_SKIP_CPU_SYNC; + + if (xfer->rx_sg_mapped) + spi_unmap_buf_attrs(ctlr, rx_dev, &xfer->rx_sg, + DMA_FROM_DEVICE, attrs); + xfer->rx_sg_mapped = false; + + if (xfer->tx_sg_mapped) + spi_unmap_buf_attrs(ctlr, tx_dev, &xfer->tx_sg, + DMA_TO_DEVICE, attrs); + xfer->tx_sg_mapped = false; + } + + return 0; +} static int __spi_map_msg(struct spi_controller *ctlr, struct spi_message *msg) { @@ -1487,30 +1509,6 @@ static int __spi_map_msg(struct spi_controller *ctlr, struct spi_message *msg) return ret; } -static int __spi_unmap_msg(struct spi_controller *ctlr, struct spi_message *msg) -{ - struct device *rx_dev = ctlr->cur_rx_dma_dev; - struct device *tx_dev = ctlr->cur_tx_dma_dev; - struct spi_transfer *xfer; - - list_for_each_entry(xfer, &msg->transfers, transfer_list) { - /* The sync has already been done after each transfer. */ - unsigned long attrs = DMA_ATTR_SKIP_CPU_SYNC; - - if (xfer->rx_sg_mapped) - spi_unmap_buf_attrs(ctlr, rx_dev, &xfer->rx_sg, - DMA_FROM_DEVICE, attrs); - xfer->rx_sg_mapped = false; - - if (xfer->tx_sg_mapped) - spi_unmap_buf_attrs(ctlr, tx_dev, &xfer->tx_sg, - DMA_TO_DEVICE, attrs); - xfer->tx_sg_mapped = false; - } - - return 0; -} - static void spi_dma_sync_for_device(struct spi_controller *ctlr, struct spi_transfer *xfer) { From af6aaacd42f74e2139391f9853d770d61ac430ce Mon Sep 17 00:00:00 2001 From: Honghui Jiang Date: Fri, 14 Aug 2026 11:14:17 +0800 Subject: [PATCH 131/132] spi: Clear current DMA devices when unmapping a message The current DMA device pointers remain set after a message has been unmapped. Existing users either check the corresponding mapped flag or access the pointers before finalizing the message, but retaining stale device pointers is fragile. Clear both pointers in spi_unmap_msg() after the internal unmap completes. Keep them intact in __spi_unmap_msg(), since that helper is also used during partial-map unwind and the in-message DMA-to-PIO fallback, before processing of the current message is complete. Suggested-by: Andy Shevchenko Signed-off-by: Honghui Jiang Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/20260814031419.43378-4-jiang_hh2019@163.com Signed-off-by: Mark Brown --- drivers/spi/spi.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c index bfb5ac93c8ee..ebda5855bba4 100644 --- a/drivers/spi/spi.c +++ b/drivers/spi/spi.c @@ -1560,6 +1560,7 @@ static inline int spi_unmap_msg(struct spi_controller *ctlr, struct spi_message *msg) { struct spi_transfer *xfer; + int ret; list_for_each_entry(xfer, &msg->transfers, transfer_list) { /* @@ -1572,7 +1573,12 @@ static inline int spi_unmap_msg(struct spi_controller *ctlr, xfer->rx_buf = NULL; } - return __spi_unmap_msg(ctlr, msg); + ret = __spi_unmap_msg(ctlr, msg); + + ctlr->cur_rx_dma_dev = NULL; + ctlr->cur_tx_dma_dev = NULL; + + return ret; } static int spi_map_msg(struct spi_controller *ctlr, struct spi_message *msg) From 9b81a87c5244bc139357460a262d4536226ba41d Mon Sep 17 00:00:00 2001 From: Honghui Jiang Date: Fri, 14 Aug 2026 11:14:18 +0800 Subject: [PATCH 132/132] spi: Add KUnit coverage for DMA mapping error paths Add KUnit tests for the __spi_map_msg() error paths. The tests verify that a later TX or RX mapping failure clears the mapping state of earlier transfers and leaves cur_{tx,rx}_dma_dev identifying the current mapping device. A zero-length transfer causes sg_alloc_table() to return -EINVAL, providing deterministic failure injection without test hooks. Additional cases cover successful map/unmap and a message which requires no mapping. Build the DMA suite as a separate translation unit, exposing the two internal mapping helpers only for KUnit through the local internal header. Enable SPI in the default and all-tests KUnit configurations so the suite is exercised there. Signed-off-by: Honghui Jiang Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/20260814031419.43378-5-jiang_hh2019@163.com Signed-off-by: Mark Brown --- drivers/spi/.kunitconfig | 4 + drivers/spi/Kconfig | 13 + drivers/spi/Makefile | 1 + drivers/spi/internals.h | 9 +- drivers/spi/spi.c | 9 +- drivers/spi/tests/Makefile | 3 + drivers/spi/tests/spi-dma-kunit.c | 259 +++++++++++++++++++ tools/testing/kunit/configs/all_tests.config | 1 + tools/testing/kunit/configs/default.config | 1 + 9 files changed, 296 insertions(+), 4 deletions(-) create mode 100644 drivers/spi/.kunitconfig create mode 100644 drivers/spi/tests/Makefile create mode 100644 drivers/spi/tests/spi-dma-kunit.c diff --git a/drivers/spi/.kunitconfig b/drivers/spi/.kunitconfig new file mode 100644 index 000000000000..07fa092c8fd0 --- /dev/null +++ b/drivers/spi/.kunitconfig @@ -0,0 +1,4 @@ +CONFIG_KUNIT=y +CONFIG_SPI=y +CONFIG_SPI_MASTER=y +CONFIG_SPI_DMA_KUNIT_TEST=y diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index 3826fe83000c..a0f37e2df716 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -1370,6 +1370,19 @@ config SPI_SPIDEV help This supports user mode SPI protocol drivers. +config SPI_DMA_KUNIT_TEST + tristate "KUnit tests for SPI core DMA mapping" if !KUNIT_ALL_TESTS + depends on KUNIT && HAS_DMA + default KUNIT_ALL_TESTS + help + Build KUnit tests for SPI core DMA mapping. The tests exercise + partial TX and RX mapping failures, verify that the mapping state is + unwound, and check that the current DMA devices identify the owner of + those mappings when the error is returned. They also cover a + successful mapping and a message which requires no mapping. + + If unsure say N. + config SPI_LOOPBACK_TEST tristate "spi loopback test framework support" depends on m diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index cb94935524d5..a18814fbde93 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -13,6 +13,7 @@ obj-$(CONFIG_SPI_MUX) += spi-mux.o obj-$(CONFIG_SPI_OFFLOAD) += spi-offload.o obj-$(CONFIG_SPI_SPIDEV) += spidev.o obj-$(CONFIG_SPI_LOOPBACK_TEST) += spi-loopback-test.o +obj-$(CONFIG_SPI_DMA_KUNIT_TEST) += tests/ # SPI master controller drivers (bus) obj-$(CONFIG_SPI_AIROHA_SNFI) += spi-airoha-snfi.o diff --git a/drivers/spi/internals.h b/drivers/spi/internals.h index 1f459b895891..c56c190b02e9 100644 --- a/drivers/spi/internals.h +++ b/drivers/spi/internals.h @@ -5,8 +5,8 @@ * * Author: Boris Brezillon * - * Helpers needed by the spi or spi-mem logic. Should not be used outside of - * spi-mem.c and spi.c. + * Helpers needed by the SPI core and its tests. Should not be used outside + * drivers/spi/. */ #ifndef __LINUX_SPI_INTERNALS_H @@ -20,6 +20,11 @@ void spi_flush_queue(struct spi_controller *ctrl); #ifdef CONFIG_HAS_DMA +#if IS_ENABLED(CONFIG_KUNIT) +int __spi_map_msg(struct spi_controller *ctlr, struct spi_message *msg); +int __spi_unmap_msg(struct spi_controller *ctlr, struct spi_message *msg); +#endif + int spi_map_buf(struct spi_controller *ctlr, struct device *dev, struct sg_table *sgt, void *buf, size_t len, enum dma_data_direction dir); diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c index ebda5855bba4..5b5b3bc5f0d8 100644 --- a/drivers/spi/spi.c +++ b/drivers/spi/spi.c @@ -4,6 +4,7 @@ // Copyright (C) 2005 David Brownell // Copyright (C) 2008 Secret Lab Technologies Ltd. +#include #include #include #include @@ -1418,7 +1419,8 @@ void spi_unmap_buf(struct spi_controller *ctlr, struct device *dev, spi_unmap_buf_attrs(ctlr, dev, sgt, dir, 0); } -static int __spi_unmap_msg(struct spi_controller *ctlr, struct spi_message *msg) +VISIBLE_IF_KUNIT +int __spi_unmap_msg(struct spi_controller *ctlr, struct spi_message *msg) { struct device *rx_dev = ctlr->cur_rx_dma_dev; struct device *tx_dev = ctlr->cur_tx_dma_dev; @@ -1441,8 +1443,10 @@ static int __spi_unmap_msg(struct spi_controller *ctlr, struct spi_message *msg) return 0; } +EXPORT_SYMBOL_IF_KUNIT(__spi_unmap_msg); -static int __spi_map_msg(struct spi_controller *ctlr, struct spi_message *msg) +VISIBLE_IF_KUNIT +int __spi_map_msg(struct spi_controller *ctlr, struct spi_message *msg) { struct device *tx_dev, *rx_dev; struct spi_transfer *xfer; @@ -1508,6 +1512,7 @@ static int __spi_map_msg(struct spi_controller *ctlr, struct spi_message *msg) return ret; } +EXPORT_SYMBOL_IF_KUNIT(__spi_map_msg); static void spi_dma_sync_for_device(struct spi_controller *ctlr, struct spi_transfer *xfer) diff --git a/drivers/spi/tests/Makefile b/drivers/spi/tests/Makefile new file mode 100644 index 000000000000..26689e0cb28a --- /dev/null +++ b/drivers/spi/tests/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0-only + +obj-$(CONFIG_SPI_DMA_KUNIT_TEST) += spi-dma-kunit.o diff --git a/drivers/spi/tests/spi-dma-kunit.c b/drivers/spi/tests/spi-dma-kunit.c new file mode 100644 index 000000000000..ee5476ff3f06 --- /dev/null +++ b/drivers/spi/tests/spi-dma-kunit.c @@ -0,0 +1,259 @@ +// SPDX-License-Identifier: GPL-2.0 +// KUnit tests for the SPI core DMA mapping error paths. +// +// A mapping error must clear all SG tables and *_sg_mapped flags while +// cur_{tx,rx}_dma_dev identify the devices used for the attempted mapping. +// Zero-length transfers make sg_alloc_table() fail with -EINVAL, providing +// deterministic failure injection without test hooks. + +#include +#include +#include +#include +#include + +#include "../internals.h" + +MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING"); + +#define SPI_DMA_TEST_LEN 256 +#define SPI_DMA_TEST_XFERS 2 + +struct spi_dma_test_ctx { + struct spi_controller *ctlr; + struct spi_device *spi; + struct device *dma_dev; + struct device *stale_dma_dev; + struct spi_transfer xfer[SPI_DMA_TEST_XFERS]; + struct spi_message msg; + void *buf[SPI_DMA_TEST_XFERS * 2]; +}; + +static bool spi_dma_test_can_dma(struct spi_controller *ctlr, + struct spi_device *spi, + struct spi_transfer *xfer) +{ + /* Opt every transfer into the core DMA mapping path. */ + return true; +} + +/* + * A bare controller is sufficient because the mapping helpers do not + * dereference ctlr->dev. With dma_tx and dma_rx unset, both directions use + * dma_map_dev, so the controller need not be registered. + */ +static struct spi_dma_test_ctx *spi_dma_test_ctx_new(struct kunit *test) +{ + struct spi_dma_test_ctx *ctx; + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); + + ctx->dma_dev = kunit_device_register(test, "spi-dma-error-path"); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx->dma_dev); + ctx->stale_dma_dev = + kunit_device_register(test, "spi-dma-stale-device"); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx->stale_dma_dev); + + /* Keep both devices valid if an assertion aborts the test. */ + KUNIT_ASSERT_EQ(test, 0, + dma_coerce_mask_and_coherent(ctx->dma_dev, + DMA_BIT_MASK(64))); + KUNIT_ASSERT_EQ(test, 0, + dma_coerce_mask_and_coherent(ctx->stale_dma_dev, + DMA_BIT_MASK(64))); + + ctx->ctlr = kunit_kzalloc(test, sizeof(*ctx->ctlr), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx->ctlr); + + ctx->spi = kunit_kzalloc(test, sizeof(*ctx->spi), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx->spi); + + ctx->ctlr->can_dma = spi_dma_test_can_dma; + ctx->ctlr->dma_map_dev = ctx->dma_dev; + /* Normally initialized by spi_register_controller(). */ + ctx->ctlr->max_dma_len = INT_MAX; + + ctx->spi->controller = ctx->ctlr; + spi_message_init(&ctx->msg); + ctx->msg.spi = ctx->spi; + + return ctx; +} + +static void *spi_dma_test_buf(struct kunit *test, struct spi_dma_test_ctx *ctx, + unsigned int slot) +{ + KUNIT_ASSERT_LT(test, slot, ARRAY_SIZE(ctx->buf)); + + ctx->buf[slot] = kunit_kzalloc(test, SPI_DMA_TEST_LEN, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx->buf[slot]); + + return ctx->buf[slot]; +} + +/* + * Emulate DMA devices retained from an earlier message. Using valid devices + * also lets the unfixed path reach the assertions instead of dereferencing + * NULL during cleanup. + */ +static void spi_dma_test_pin_stale_dma_devs(struct spi_dma_test_ctx *ctx) +{ + ctx->ctlr->cur_tx_dma_dev = ctx->stale_dma_dev; + ctx->ctlr->cur_rx_dma_dev = ctx->stale_dma_dev; +} + +static void spi_dma_test_assert_dma_devs_published(struct kunit *test, + struct spi_dma_test_ctx *ctx) +{ + KUNIT_ASSERT_PTR_EQ(test, ctx->ctlr->cur_tx_dma_dev, ctx->dma_dev); + KUNIT_ASSERT_PTR_EQ(test, ctx->ctlr->cur_rx_dma_dev, ctx->dma_dev); +} + +static void spi_dma_test_assert_nothing_mapped(struct kunit *test, + struct spi_dma_test_ctx *ctx, + unsigned int nr_xfers) +{ + unsigned int i; + + for (i = 0; i < nr_xfers; i++) { + KUNIT_ASSERT_FALSE_MSG(test, ctx->xfer[i].tx_sg_mapped, + "xfer[%u] still claims a TX mapping after __spi_map_msg() failed", + i); + KUNIT_ASSERT_FALSE_MSG(test, ctx->xfer[i].rx_sg_mapped, + "xfer[%u] still claims an RX mapping after __spi_map_msg() failed", + i); + KUNIT_EXPECT_NULL(test, ctx->xfer[i].tx_sg.sgl); + KUNIT_EXPECT_EQ(test, ctx->xfer[i].tx_sg.orig_nents, 0U); + KUNIT_EXPECT_EQ(test, ctx->xfer[i].tx_sg.nents, 0U); + KUNIT_EXPECT_NULL(test, ctx->xfer[i].rx_sg.sgl); + KUNIT_EXPECT_EQ(test, ctx->xfer[i].rx_sg.orig_nents, 0U); + KUNIT_EXPECT_EQ(test, ctx->xfer[i].rx_sg.nents, 0U); + } +} + +/* + * xfer0 maps TX and RX; zero-length xfer1 then fails its TX mapping. + * The failure must unwind xfer0 and update cur_*_dma_dev. + */ +static void spi_dma_later_tx_fail_rolls_back_earlier(struct kunit *test) +{ + struct spi_dma_test_ctx *ctx = spi_dma_test_ctx_new(test); + int ret; + + ctx->xfer[0].tx_buf = spi_dma_test_buf(test, ctx, 0); + ctx->xfer[0].rx_buf = spi_dma_test_buf(test, ctx, 1); + ctx->xfer[0].len = SPI_DMA_TEST_LEN; + + ctx->xfer[1].tx_buf = spi_dma_test_buf(test, ctx, 2); + ctx->xfer[1].rx_buf = NULL; + ctx->xfer[1].len = 0; /* forces -EINVAL */ + + spi_message_add_tail(&ctx->xfer[0], &ctx->msg); + spi_message_add_tail(&ctx->xfer[1], &ctx->msg); + + spi_dma_test_pin_stale_dma_devs(ctx); + + ret = __spi_map_msg(ctx->ctlr, &ctx->msg); + KUNIT_ASSERT_EQ(test, ret, -EINVAL); + + spi_dma_test_assert_dma_devs_published(test, ctx); + spi_dma_test_assert_nothing_mapped(test, ctx, SPI_DMA_TEST_XFERS); + + KUNIT_EXPECT_EQ(test, 0, __spi_unmap_msg(ctx->ctlr, &ctx->msg)); +} + +/* + * xfer0 maps TX and RX; zero-length RX-only xfer1 then fails. + * The failure must unwind xfer0 without leaving either mapping flag set. + */ +static void spi_dma_later_rx_fail_rolls_back_earlier(struct kunit *test) +{ + struct spi_dma_test_ctx *ctx = spi_dma_test_ctx_new(test); + int ret; + + ctx->xfer[0].tx_buf = spi_dma_test_buf(test, ctx, 0); + ctx->xfer[0].rx_buf = spi_dma_test_buf(test, ctx, 1); + ctx->xfer[0].len = SPI_DMA_TEST_LEN; + + ctx->xfer[1].tx_buf = NULL; + ctx->xfer[1].rx_buf = spi_dma_test_buf(test, ctx, 2); + ctx->xfer[1].len = 0; /* forces -EINVAL */ + + spi_message_add_tail(&ctx->xfer[0], &ctx->msg); + spi_message_add_tail(&ctx->xfer[1], &ctx->msg); + + spi_dma_test_pin_stale_dma_devs(ctx); + + ret = __spi_map_msg(ctx->ctlr, &ctx->msg); + KUNIT_ASSERT_EQ(test, ret, -EINVAL); + + spi_dma_test_assert_dma_devs_published(test, ctx); + spi_dma_test_assert_nothing_mapped(test, ctx, SPI_DMA_TEST_XFERS); + + KUNIT_EXPECT_EQ(test, 0, __spi_unmap_msg(ctx->ctlr, &ctx->msg)); +} + +/* Ensure the error unwind does not affect successful mappings. */ +static void spi_dma_map_success_publishes_dma_devs(struct kunit *test) +{ + struct spi_dma_test_ctx *ctx = spi_dma_test_ctx_new(test); + int ret; + + ctx->xfer[0].tx_buf = spi_dma_test_buf(test, ctx, 0); + ctx->xfer[0].rx_buf = spi_dma_test_buf(test, ctx, 1); + ctx->xfer[0].len = SPI_DMA_TEST_LEN; + + spi_message_add_tail(&ctx->xfer[0], &ctx->msg); + + ret = __spi_map_msg(ctx->ctlr, &ctx->msg); + KUNIT_ASSERT_EQ(test, ret, 0); + + KUNIT_EXPECT_TRUE(test, ctx->xfer[0].tx_sg_mapped); + KUNIT_EXPECT_TRUE(test, ctx->xfer[0].rx_sg_mapped); + KUNIT_EXPECT_PTR_EQ(test, ctx->ctlr->cur_tx_dma_dev, ctx->dma_dev); + KUNIT_EXPECT_PTR_EQ(test, ctx->ctlr->cur_rx_dma_dev, ctx->dma_dev); + + KUNIT_EXPECT_EQ(test, 0, __spi_unmap_msg(ctx->ctlr, &ctx->msg)); + + KUNIT_EXPECT_FALSE(test, ctx->xfer[0].tx_sg_mapped); + KUNIT_EXPECT_FALSE(test, ctx->xfer[0].rx_sg_mapped); + KUNIT_EXPECT_NULL(test, ctx->xfer[0].tx_sg.sgl); + KUNIT_EXPECT_NULL(test, ctx->xfer[0].rx_sg.sgl); +} + +/* A transfer without buffers requires no DMA mapping. */ +static void spi_dma_map_nothing_is_success(struct kunit *test) +{ + struct spi_dma_test_ctx *ctx = spi_dma_test_ctx_new(test); + int ret; + + ctx->xfer[0].tx_buf = NULL; + ctx->xfer[0].rx_buf = NULL; + ctx->xfer[0].len = SPI_DMA_TEST_LEN; + + spi_message_add_tail(&ctx->xfer[0], &ctx->msg); + + ret = __spi_map_msg(ctx->ctlr, &ctx->msg); + KUNIT_EXPECT_EQ(test, ret, 0); + + spi_dma_test_assert_nothing_mapped(test, ctx, 1); +} + +static struct kunit_case spi_dma_error_path_cases[] = { + KUNIT_CASE(spi_dma_later_tx_fail_rolls_back_earlier), + KUNIT_CASE(spi_dma_later_rx_fail_rolls_back_earlier), + KUNIT_CASE(spi_dma_map_success_publishes_dma_devs), + KUNIT_CASE(spi_dma_map_nothing_is_success), + {} +}; + +static struct kunit_suite spi_dma_error_path_suite = { + .name = "spi_dma", + .test_cases = spi_dma_error_path_cases, +}; + +kunit_test_suite(spi_dma_error_path_suite); + +MODULE_DESCRIPTION("KUnit tests for SPI core DMA mapping"); +MODULE_LICENSE("GPL"); diff --git a/tools/testing/kunit/configs/all_tests.config b/tools/testing/kunit/configs/all_tests.config index bccc2c77196d..7bdcbdec2cab 100644 --- a/tools/testing/kunit/configs/all_tests.config +++ b/tools/testing/kunit/configs/all_tests.config @@ -21,6 +21,7 @@ CONFIG_VFAT_FS=y CONFIG_PCI=y CONFIG_USB4=y CONFIG_I2C=y +CONFIG_SPI=y CONFIG_NET=y CONFIG_MCTP=y diff --git a/tools/testing/kunit/configs/default.config b/tools/testing/kunit/configs/default.config index e67af7b9f1bb..2f24147c938c 100644 --- a/tools/testing/kunit/configs/default.config +++ b/tools/testing/kunit/configs/default.config @@ -1,3 +1,4 @@ CONFIG_KUNIT=y CONFIG_KUNIT_EXAMPLE_TEST=y CONFIG_KUNIT_ALL_TESTS=y +CONFIG_SPI=y