From b34c325539ca9e473a754685b3ca866efe21bea1 Mon Sep 17 00:00:00 2001 From: Francesco Valla Date: Sun, 30 Aug 2026 15:48:35 +0200 Subject: [PATCH 001/136] spi: virtio: drop duplicate completion init The completion iused for SPI transfers is initialized twice in the same function, without it being used in between. Drop the redundant initialization. Signed-off-by: Francesco Valla Link: https://patch.msgid.link/20260830-virtio-spi-fix-v1-1-62f486f4acdc@valla.it Signed-off-by: Mark Brown --- drivers/spi/spi-virtio.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/spi/spi-virtio.c b/drivers/spi/spi-virtio.c index 2256dfec5407..87ce32b6d133 100644 --- a/drivers/spi/spi-virtio.c +++ b/drivers/spi/spi-virtio.c @@ -162,8 +162,6 @@ static int virtio_spi_transfer_one(struct spi_controller *ctrl, if (!spi_req) return -ENOMEM; - init_completion(&spi_req->completion); - th = &spi_req->transfer_head; /* Fill struct spi_transfer_head */ From dd007297551f20874124f6a703aa2f98ffd72084 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Fri, 21 Aug 2026 12:54:52 +0200 Subject: [PATCH 002/136] spi: Fix tab and space mixture in Kconfig There are a couple of sections which indented using spaces and not tabs. Fix them. Signed-off-by: Andy Shevchenko Link: https://patch.msgid.link/20260821105541.1432348-2-andriy.shevchenko@linux.intel.com Signed-off-by: Mark Brown --- drivers/spi/Kconfig | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index a0f37e2df716..93c3d9e1416a 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -388,14 +388,14 @@ config SPI_DW_MMIO endif config SPI_DLN2 - tristate "Diolan DLN-2 USB SPI adapter" - depends on MFD_DLN2 - help - If you say yes to this option, support will be included for Diolan - DLN2, a USB to SPI interface. + tristate "Diolan DLN-2 USB SPI adapter" + depends on MFD_DLN2 + help + If you say yes to this option, support will be included for Diolan + DLN2, a USB to SPI interface. - This driver can also be built as a module. If so, the module - will be called spi-dln2. + This driver can also be built as a module. If so, the module + will be called spi-dln2. config SPI_EP93XX tristate "Cirrus Logic EP93xx SPI controller" @@ -475,7 +475,7 @@ config SPI_NXP_FLEXSPI supports the high-level SPI memory interface. config SPI_NXP_XSPI - tristate "NXP xSPI controller" + tristate "NXP xSPI controller" depends on ARCH_MXC || COMPILE_TEST depends on HAS_IOMEM help From 4895d7068723e6a2e575603a5764b4db07041cc2 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Fri, 21 Aug 2026 12:54:53 +0200 Subject: [PATCH 003/136] spi: Drop redundant dependency on SPI_MASTER The sections defined under 'if SPI_MASTER' already imply that the SPI_MASTER is selected. Drop redundant dependencies. Signed-off-by: Andy Shevchenko Link: https://patch.msgid.link/20260821105541.1432348-3-andriy.shevchenko@linux.intel.com Signed-off-by: Mark Brown --- drivers/spi/Kconfig | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index 93c3d9e1416a..f53c0ece5d96 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -63,7 +63,6 @@ comment "SPI Master Controller Drivers" config SPI_AIROHA_SNFI tristate "Airoha SPI NAND Flash Interface" depends on ARCH_AIROHA || COMPILE_TEST - depends on SPI_MASTER select REGMAP_MMIO help This enables support for SPI-NAND mode on the Airoha NAND @@ -333,7 +332,7 @@ config SPI_CADENCE_XSPI config SPI_CH341 tristate "CH341 USB2SPI adapter" - depends on SPI_MASTER && USB + depends on USB help Enables the SPI controller on the CH341a USB to serial chip @@ -722,7 +721,6 @@ config SPI_MESON_SPIFC config SPI_MICROCHIP_CORE_QSPI tristate "Microchip FPGA QSPI controllers" - depends on SPI_MASTER help This enables the QSPI driver for Microchip FPGA QSPI controllers. Say Y or M here if you want to use the QSPI controllers on @@ -731,7 +729,6 @@ config SPI_MICROCHIP_CORE_QSPI config SPI_MICROCHIP_CORE_SPI tristate "Microchip FPGA CoreSPI controller" - depends on SPI_MASTER help This enables the SPI driver for Microchip FPGA CoreSPI controller. Say Y or M here if you want to use the "soft" controllers on @@ -887,7 +884,6 @@ config SPI_PL022 config SPI_POLARFIRE_SOC tristate "Microchip FPGA SPI controllers" - depends on SPI_MASTER depends on ARCH_MICROCHIP || COMPILE_TEST help This enables the SPI driver for Microchip FPGA SPI controllers. @@ -950,7 +946,7 @@ config SPI_ROCKCHIP_SFC config SPI_RB4XX tristate "Mikrotik RB4XX SPI master" - depends on SPI_MASTER && (ATH79 || COMPILE_TEST) + depends on ATH79 || COMPILE_TEST depends on OF help SPI controller driver for the Mikrotik RB4xx series boards. @@ -1191,7 +1187,6 @@ config SPI_SYNQUACER config SPI_MXIC tristate "Macronix MX25F0A SPI controller" - depends on SPI_MASTER imply MTD_NAND_ECC_MXIC help This selects the Macronix MX25F0A SPI controller driver. @@ -1270,7 +1265,7 @@ config SPI_UNIPHIER config SPI_VIRTIO tristate "Virtio SPI Controller" - depends on SPI_MASTER && VIRTIO + depends on VIRTIO help If you say yes to this option, support will be included for the virtio SPI controller driver. The hardware can be emulated by any device model @@ -1339,7 +1334,7 @@ config SPI_ZYNQMP_GQSPI config SPI_AMD tristate "AMD SPI controller" depends on PCI - depends on SPI_MASTER || X86 || COMPILE_TEST + depends on X86 || COMPILE_TEST depends on SPI_MEM help Enables SPI controller driver for AMD SoC. From d696b9109d2315fc5f664766628337489a2b828f Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Fri, 21 Aug 2026 12:54:54 +0200 Subject: [PATCH 004/136] spi: Fix the section ordering in accordance with the comment The comments in all sections tell that the list of the sections should be alphabetically ordered. With time this went apart, mostly for Freescale entries. So, put the things into order again. Signed-off-by: Andy Shevchenko Link: https://patch.msgid.link/20260821105541.1432348-4-andriy.shevchenko@linux.intel.com Signed-off-by: Mark Brown --- drivers/spi/Kconfig | 339 ++++++++++++++++++++++---------------------- 1 file changed, 169 insertions(+), 170 deletions(-) diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index f53c0ece5d96..357cebef9917 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -91,6 +91,14 @@ config SPI_ALTERA_DFL Altera SPI master controller. The SPI master is connected to a SPI slave to Avalon bridge in a Intel MAX BMC. +config SPI_AMD + tristate "AMD SPI controller" + depends on PCI + depends on X86 || COMPILE_TEST + depends on SPI_MEM + help + Enables SPI controller driver for AMD SoC. + config SPI_AMLOGIC_SPIFC_A1 tristate "Amlogic A1 SPIFC controller" depends on ARCH_MESON || COMPILE_TEST @@ -135,6 +143,13 @@ config SPI_AR934X This enables support for the SPI controller present on the Qualcomm Atheros AR934X/QCA95XX SoCs. +config SPI_ARMADA_3700 + tristate "Marvell Armada 3700 SPI Controller" + depends on (ARCH_MVEBU && OF) || COMPILE_TEST + help + This enables support for the SPI controller present on the + Marvell Armada 3700 SoCs. + config SPI_ATCSPI200 tristate "Andes ATCSPI200 SPI controller" depends on ARCH_ANDES || COMPILE_TEST @@ -152,13 +167,6 @@ config SPI_ATH79 This enables support for the SPI controller present on the Atheros AR71XX/AR724X/AR913X SoCs. -config SPI_ARMADA_3700 - tristate "Marvell Armada 3700 SPI Controller" - depends on (ARCH_MVEBU && OF) || COMPILE_TEST - help - This enables support for the SPI controller present on the - Marvell Armada 3700 SoCs. - config SPI_ASPEED_SMC tristate "Aspeed flash controllers in SPI mode" depends on ARCH_ASPEED || COMPILE_TEST @@ -419,6 +427,22 @@ config SPI_FSI This enables support for the driver for FSI bus attached SPI controllers. +config SPI_FSL_DSPI + tristate "Freescale DSPI controller" + select REGMAP_MMIO + depends on ARCH_MXC || ARCH_NXP || M5441x || COMPILE_TEST + help + This enables support for the Freescale DSPI controller in master + mode. S32, VF610, LS1021A and ColdFire platforms uses the controller. + +config SPI_FSL_ESPI + tristate "Freescale eSPI controller" + depends on FSL_SOC + help + This enables using the Freescale eSPI controllers in master mode. + From MPC8536, 85xx platform uses the controller, and all P10xx, + P20xx, P30xx,P40xx, P50xx uses this controller. + config SPI_FSL_LPSPI tristate "Freescale i.MX LPSPI controller" depends on ARCH_MXC || COMPILE_TEST @@ -437,6 +461,26 @@ config SPI_FSL_QUADSPI This controller does not support generic SPI messages. It only supports the high-level SPI memory interface. +config SPI_FSL_LIB + tristate + depends on OF + +config SPI_FSL_CPM + tristate + depends on FSL_SOC + +config SPI_FSL_SPI + tristate "Freescale SPI controller and Aeroflex Gaisler GRLIB SPI controller" + depends on OF + select SPI_FSL_LIB + select SPI_FSL_CPM if FSL_SOC + help + This enables using the Freescale SPI controllers in master mode. + MPC83xx platform uses the controller in cpu mode or CPM/QE mode. + MPC8569 uses the controller in QE mode, MPC8610 in cpu mode. + This also enables using the Aeroflex Gaisler GRLIB SPI controller in + master mode. + config SPI_GXP tristate "GXP SPI driver" depends on ARCH_HPE || COMPILE_TEST @@ -462,27 +506,6 @@ config SPI_HISI_SFC_V3XX This enables support for HiSilicon v3xx SPI NOR flash controller found in hi16xx chipsets. -config SPI_NXP_FLEXSPI - tristate "NXP Flex SPI controller" - depends on ARCH_LAYERSCAPE || ARCH_MXC || COMPILE_TEST - depends on HAS_IOMEM - help - This enables support for the Flex SPI controller in master mode. - Up to four slave devices can be connected on two buses with two - chipselects each. - This controller does not support generic SPI messages and only - supports the high-level SPI memory interface. - -config SPI_NXP_XSPI - tristate "NXP xSPI controller" - depends on ARCH_MXC || COMPILE_TEST - depends on HAS_IOMEM - help - This enables support for the xSPI controller. Up to two devices - can be connected to one host. - This controller does not support generic SPI messages and only - supports the high-level SPI memory interface. - config SPI_GPIO tristate "GPIO-based bitbanging SPI Master" depends on GPIOLIB || COMPILE_TEST @@ -580,6 +603,25 @@ config SPI_KSPI2 This driver can also be built as a module. If so, the module will be called spi-kspi2. +config SPI_LANTIQ_SSC + tristate "Lantiq SSC SPI controller" + depends on LANTIQ || X86 || COMPILE_TEST + help + This driver supports the Lantiq SSC SPI controller in master + mode. This controller is found on Intel (former Lantiq) SoCs like + the Danube, Falcon, xRX200, xRX300, Lightning Mountain. + +config SPI_LJCA + tristate "Intel La Jolla Cove Adapter SPI support" + depends on USB_LJCA + default USB_LJCA + help + Select this option to enable SPI driver for the Intel + La Jolla Cove Adapter (LJCA) board. + + This driver can also be built as a module. If so, the module + will be called spi-ljca. + config SPI_LM70_LLP tristate "Parallel port adapter for LM70 eval board (DEVELOPMENT)" depends on PARPORT @@ -625,74 +667,6 @@ config SPI_LP8841_RTC Say N here unless you plan to run the kernel on an ICP DAS LP-8x4x industrial computer. -config SPI_MPC52xx - tristate "Freescale MPC52xx SPI (non-PSC) controller support" - depends on PPC_MPC52xx - help - This drivers supports the MPC52xx SPI controller in master SPI - mode. - -config SPI_MPC52xx_PSC - tristate "Freescale MPC52xx PSC SPI controller" - depends on PPC_MPC52xx - help - This enables using the Freescale MPC52xx Programmable Serial - Controller in master SPI mode. - -config SPI_MPC512x_PSC - tristate "Freescale MPC512x PSC SPI controller" - depends on PPC_MPC512x - help - This enables using the Freescale MPC5121 Programmable Serial - Controller in SPI master mode. - -config SPI_FSL_LIB - tristate - depends on OF - -config SPI_FSL_CPM - tristate - depends on FSL_SOC - -config SPI_FSL_SPI - tristate "Freescale SPI controller and Aeroflex Gaisler GRLIB SPI controller" - depends on OF - select SPI_FSL_LIB - select SPI_FSL_CPM if FSL_SOC - help - This enables using the Freescale SPI controllers in master mode. - MPC83xx platform uses the controller in cpu mode or CPM/QE mode. - MPC8569 uses the controller in QE mode, MPC8610 in cpu mode. - This also enables using the Aeroflex Gaisler GRLIB SPI controller in - master mode. - -config SPI_FSL_DSPI - tristate "Freescale DSPI controller" - select REGMAP_MMIO - depends on ARCH_MXC || ARCH_NXP || M5441x || COMPILE_TEST - help - This enables support for the Freescale DSPI controller in master - mode. S32, VF610, LS1021A and ColdFire platforms uses the controller. - -config SPI_FSL_ESPI - tristate "Freescale eSPI controller" - depends on FSL_SOC - help - This enables using the Freescale eSPI controllers in master mode. - From MPC8536, 85xx platform uses the controller, and all P10xx, - P20xx, P30xx,P40xx, P50xx uses this controller. - -config SPI_LJCA - tristate "Intel La Jolla Cove Adapter SPI support" - depends on USB_LJCA - default USB_LJCA - help - Select this option to enable SPI driver for the Intel - La Jolla Cove Adapter (LJCA) board. - - 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 @@ -735,6 +709,27 @@ config SPI_MICROCHIP_CORE_SPI PolarFire SoC. If built as a module, it will be called spi-microchip-core-spi. +config SPI_MPC52xx + tristate "Freescale MPC52xx SPI (non-PSC) controller support" + depends on PPC_MPC52xx + help + This drivers supports the MPC52xx SPI controller in master SPI + mode. + +config SPI_MPC52xx_PSC + tristate "Freescale MPC52xx PSC SPI controller" + depends on PPC_MPC52xx + help + This enables using the Freescale MPC52xx Programmable Serial + Controller in master SPI mode. + +config SPI_MPC512x_PSC + tristate "Freescale MPC512x PSC SPI controller" + depends on PPC_MPC512x + help + This enables using the Freescale MPC5121 Programmable Serial + Controller in SPI master mode. + config SPI_MT65XX tristate "MediaTek SPI controller" depends on ARCH_MEDIATEK || COMPILE_TEST @@ -770,6 +765,19 @@ config SPI_MTK_SNFI is implemented as a SPI-MEM controller with pipelined ECC capability. +config SPI_MXIC + tristate "Macronix MX25F0A SPI controller" + imply MTD_NAND_ECC_MXIC + help + This selects the Macronix MX25F0A SPI controller driver. + +config SPI_MXS + tristate "Freescale MXS SPI controller" + depends on ARCH_MXS + select STMP_DEVICE + help + SPI driver for Freescale MXS devices. + config SPI_WPCM_FIU tristate "Nuvoton WPCM450 Flash Interface Unit" depends on ARCH_NPCM || COMPILE_TEST @@ -798,13 +806,26 @@ config SPI_NPCM_PSPI This driver provides support for Nuvoton NPCM BMC Peripheral SPI controller in master mode. -config SPI_LANTIQ_SSC - tristate "Lantiq SSC SPI controller" - depends on LANTIQ || X86 || COMPILE_TEST +config SPI_NXP_FLEXSPI + tristate "NXP Flex SPI controller" + depends on ARCH_LAYERSCAPE || ARCH_MXC || COMPILE_TEST + depends on HAS_IOMEM help - This driver supports the Lantiq SSC SPI controller in master - mode. This controller is found on Intel (former Lantiq) SoCs like - the Danube, Falcon, xRX200, xRX300, Lightning Mountain. + This enables support for the Flex SPI controller in master mode. + Up to four slave devices can be connected on two buses with two + chipselects each. + This controller does not support generic SPI messages and only + supports the high-level SPI memory interface. + +config SPI_NXP_XSPI + tristate "NXP xSPI controller" + depends on ARCH_MXC || COMPILE_TEST + depends on HAS_IOMEM + help + This enables support for the xSPI controller. Up to two devices + can be connected to one host. + This controller does not support generic SPI messages and only + supports the high-level SPI memory interface. config SPI_OC_TINY tristate "OpenCores tiny SPI" @@ -835,14 +856,6 @@ config SPI_OMAP24XX SPI master controller for OMAP24XX and later Multichannel SPI (McSPI) modules. -config SPI_TI_QSPI - tristate "DRA7xxx QSPI controller support" - depends on ARCH_OMAP2PLUS || COMPILE_TEST - help - QSPI master controller for DRA7xxx used for flash devices. - This device supports single, dual and quad read support, while - it only supports single write mode. - config SPI_ORION tristate "Orion SPI master" depends on PLAT_ORION || ARCH_MVEBU || COMPILE_TEST @@ -1031,12 +1044,14 @@ config SPI_SC18IS602 help SPI driver for NXP SC18IS602/602B/603 I2C to SPI bridge. -config SPI_SH_MSIOF - tristate "SuperH MSIOF SPI controller" - depends on HAVE_CLK - depends on ARCH_SHMOBILE || ARCH_RENESAS || COMPILE_TEST +config SPI_SG2044_NOR + tristate "SG2044 SPI NOR Controller" + depends on ARCH_SOPHGO || COMPILE_TEST help - SPI driver for SuperH and SH Mobile MSIOF blocks. + This enables support for the SG2044 SPI NOR controller, + which supports Dual/Quad read and write operations while + also supporting 3Byte address devices and 4Byte address + devices. config SPI_SH tristate "SuperH SPI controller" @@ -1044,6 +1059,19 @@ config SPI_SH help SPI driver for SuperH SPI blocks. +config SPI_SH_HSPI + tristate "SuperH HSPI controller" + depends on ARCH_RENESAS || COMPILE_TEST + help + SPI driver for SuperH HSPI blocks. + +config SPI_SH_MSIOF + tristate "SuperH MSIOF SPI controller" + depends on HAVE_CLK + depends on ARCH_SHMOBILE || ARCH_RENESAS || COMPILE_TEST + help + SPI driver for SuperH and SH Mobile MSIOF blocks. + config SPI_SH_SCI tristate "SuperH SCI SPI controller" depends on SUPERH @@ -1051,28 +1079,12 @@ config SPI_SH_SCI help SPI driver for SuperH SCI blocks. -config SPI_SH_HSPI - tristate "SuperH HSPI controller" - depends on ARCH_RENESAS || COMPILE_TEST - help - SPI driver for SuperH HSPI blocks. - config SPI_SIFIVE tristate "SiFive SPI controller" depends on HAS_IOMEM help This exposes the SPI controller IP from SiFive. -config SPI_SLAVE_MT27XX - tristate "MediaTek SPI slave device" - depends on ARCH_MEDIATEK || COMPILE_TEST - depends on SPI_SLAVE - help - This selects the MediaTek(R) SPI slave device driver. - If you want to use MediaTek(R) SPI slave interface, - say Y or M here.If you are not sure, say N. - SPI slave drivers for Mediatek MT27XX series ARM SoCs. - config SPI_SN_F_OSPI tristate "Socionext F_OSPI SPI flash controller" depends on OF && HAS_IOMEM @@ -1082,15 +1094,6 @@ config SPI_SN_F_OSPI for connecting an SPI Flash memory over up to 8-bit wide bus. It supports indirect access mode only. -config SPI_SG2044_NOR - tristate "SG2044 SPI NOR Controller" - depends on ARCH_SOPHGO || COMPILE_TEST - help - This enables support for the SG2044 SPI NOR controller, - which supports Dual/Quad read and write operations while - also supporting 3Byte address devices and 4Byte address - devices. - config SPI_SPACEMIT_K1 tristate "K1 SPI Controller" depends on ARCH_SPACEMIT || COMPILE_TEST @@ -1185,19 +1188,6 @@ config SPI_SYNQUACER It also supports the new dual-bit and quad-bit SPI protocol. -config SPI_MXIC - tristate "Macronix MX25F0A SPI controller" - imply MTD_NAND_ECC_MXIC - help - This selects the Macronix MX25F0A SPI controller driver. - -config SPI_MXS - tristate "Freescale MXS SPI controller" - depends on ARCH_MXS - select STMP_DEVICE - help - SPI driver for Freescale MXS devices. - config SPI_TEGRA210_QUAD tristate "NVIDIA Tegra QSPI Controller" depends on ARCH_TEGRA || COMPILE_TEST @@ -1239,6 +1229,23 @@ config SPI_THUNDERX SPI host driver for the hardware found on Cavium ThunderX SOCs. +config SPI_TI_QSPI + tristate "DRA7xxx QSPI controller support" + depends on ARCH_OMAP2PLUS || COMPILE_TEST + help + QSPI master controller for DRA7xxx used for flash devices. + This device supports single, dual and quad read support, while + it only supports single write mode. + +config SPI_TLE62X0 + tristate "Infineon TLE62X0 (for power switching)" + depends on SYSFS + help + SPI driver for Infineon TLE62X0 series line driver chips, + such as the TLE6220, TLE6230 and TLE6240. This provides a + sysfs interface, with each line presented as a kind of GPIO + exposing both switch control and diagnostic feedback. + config SPI_TOPCLIFF_PCH tristate "Intel EG20T PCH/LAPIS Semicon IOH(ML7213/ML7223/ML7831) SPI" depends on PCI && (X86_32 || MIPS || COMPILE_TEST) @@ -1331,14 +1338,6 @@ config SPI_ZYNQMP_GQSPI Enables Xilinx GQSPI controller driver for Zynq UltraScale+ MPSoC. This controller only supports SPI memory interface. -config SPI_AMD - tristate "AMD SPI controller" - depends on PCI - depends on X86 || COMPILE_TEST - depends on SPI_MEM - help - Enables SPI controller driver for AMD SoC. - # # Add new SPI master controllers in alphabetical order above this line # @@ -1387,15 +1386,6 @@ config SPI_LOOPBACK_TEST primarily used for development of spi_master drivers and to detect regressions -config SPI_TLE62X0 - tristate "Infineon TLE62X0 (for power switching)" - depends on SYSFS - help - SPI driver for Infineon TLE62X0 series line driver chips, - such as the TLE6220, TLE6230 and TLE6240. This provides a - sysfs interface, with each line presented as a kind of GPIO - exposing both switch control and diagnostic feedback. - # # Add new SPI protocol masters in alphabetical order above this line # @@ -1426,6 +1416,15 @@ config SPI_SLAVE_SYSTEM_CONTROL SPI slave handler to allow remote control of system reboot, power off, halt, and suspend. +config SPI_SLAVE_MT27XX + tristate "MediaTek SPI slave device" + depends on ARCH_MEDIATEK || COMPILE_TEST + help + This selects the MediaTek(R) SPI slave device driver. + If you want to use MediaTek(R) SPI slave interface, + say Y or M here.If you are not sure, say N. + SPI slave drivers for Mediatek MT27XX series ARM SoCs. + endif # SPI_SLAVE config SPI_DYNAMIC From 152867998da9f331d6235d76aae1c7e1650708a9 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Sun, 30 Aug 2026 22:01:33 +0800 Subject: [PATCH 005/136] spi: sh-msiof: propagate setup runtime-PM errors sh_msiof_spi_setup() ignores pm_runtime_get_sync() before programming native chip-select registers and marking the configuration initialized. Use the checked runtime-PM helper and return failure before register access. Fixes: 7ff0b53c4051 ("spi: sh-msiof: Avoid writing to registers from spi_master.setup()") Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260830140133.24156-1-pengpeng@iscas.ac.cn Signed-off-by: Mark Brown --- drivers/spi/spi-sh-msiof.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/spi/spi-sh-msiof.c b/drivers/spi/spi-sh-msiof.c index 1aeab7ec0bc8..4c425ec62776 100644 --- a/drivers/spi/spi-sh-msiof.c +++ b/drivers/spi/spi-sh-msiof.c @@ -453,6 +453,7 @@ static int sh_msiof_spi_setup(struct spi_device *spi) struct sh_msiof_spi_priv *p = spi_controller_get_devdata(spi->controller); u32 clr, set, tmp; + int ret; if (spi_get_csgpiod(spi, 0) || spi_controller_is_target(p->ctlr)) return 0; @@ -468,7 +469,9 @@ static int sh_msiof_spi_setup(struct spi_device *spi) clr |= SIMDR1_SYNCAC; else set |= SIMDR1_SYNCAC; - pm_runtime_get_sync(&p->pdev->dev); + ret = pm_runtime_resume_and_get(&p->pdev->dev); + if (ret < 0) + return ret; tmp = sh_msiof_read(p, SITMDR1) & ~clr; sh_msiof_write(p, SITMDR1, tmp | set | SIMDR1_TRMD | SITMDR1_PCON); tmp = sh_msiof_read(p, SIRMDR1) & ~clr; From 7fe2098d612b35fc0aaa7a550b0adca326fedcee Mon Sep 17 00:00:00 2001 From: Andrew Gaylard Date: Thu, 20 Aug 2026 14:58:35 +0200 Subject: [PATCH 006/136] spi: sunplus: handle signal interruption in transfer wait wait_for_completion_interruptible_timeout() returns -ERESTARTSYS when interrupted by a signal, 0 on timeout, and positive on success. The previous check was: if (!wait_for_completion_interruptible_timeout(...)) SIGKILL caused the interrupted path to fall through as if the transfer succeeded. The loop then re-entered mutex_lock() on the next iteration, which is TASK_UNINTERRUPTIBLE. The process could not be killed while blocked there. Check ret <= 0 and return -EINTR for the interrupted case so the process can exit promptly on SIGKILL. Signed-off-by: Andrew Gaylard Link: https://patch.msgid.link/20260820125835.1584270-1-ag@ffroot.co.za Signed-off-by: Mark Brown --- drivers/spi/spi-sunplus-sp7021.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/drivers/spi/spi-sunplus-sp7021.c b/drivers/spi/spi-sunplus-sp7021.c index f16fcda187dd..54431c363bbc 100644 --- a/drivers/spi/spi-sunplus-sp7021.c +++ b/drivers/spi/spi-sunplus-sp7021.c @@ -337,10 +337,15 @@ static int sp7021_spi_host_transfer_one(struct spi_controller *ctlr, struct spi_ SP7021_SPI_START_FD; writel(reg_temp, pspim->m_base + SP7021_SPI_STATUS_REG); - if (!wait_for_completion_interruptible_timeout(&pspim->isr_done, timeout)) { - dev_err(&spi->dev, "wait_for_completion err\n"); - mutex_unlock(&pspim->buf_lock); - return -ETIMEDOUT; + { + long ret = wait_for_completion_interruptible_timeout( + &pspim->isr_done, timeout); + if (ret <= 0) { + dev_err(&spi->dev, ret == 0 ? "SPI transfer timeout\n" + : "SPI transfer interrupted\n"); + mutex_unlock(&pspim->buf_lock); + return ret == 0 ? -ETIMEDOUT : -EINTR; + } } reg_temp = readl(pspim->m_base + SP7021_SPI_STATUS_REG); From ed07cdb2b386d53d1ba4abbb4154bcec7bd9488a Mon Sep 17 00:00:00 2001 From: Viken Dadhaniya Date: Mon, 24 Aug 2026 15:43:58 +0530 Subject: [PATCH 007/136] spi: qcom-geni: rename setup_fifo_params() to setup_spi_params() and make it void The function always returned 0 and had no error paths, so change its return type to void. Drop the now-dead ret variable and error check in spi_geni_prepare_message(). setup_fifo_params() is called for both GENI_SE_FIFO and GENI_SE_DMA modes, so the "fifo" in the name is misleading. Rename it to setup_spi_params() to better reflect its purpose of configuring SPI mode parameters (CS, CPHA, CPOL, loopback, LSB-first). No functional change. Signed-off-by: Viken Dadhaniya Reviewed-by: Konrad Dybcio Reviewed-by: Mukesh Kumar Savaliya Link: https://patch.msgid.link/20260824-spi-qcom-geni-cleanup-setup-fifo-params-v1-1-bdf98ae62953@oss.qualcomm.com Signed-off-by: Mark Brown --- drivers/spi/spi-geni-qcom.c | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/drivers/spi/spi-geni-qcom.c b/drivers/spi/spi-geni-qcom.c index 6c57f8309a3b..05abaf18a0f4 100644 --- a/drivers/spi/spi-geni-qcom.c +++ b/drivers/spi/spi-geni-qcom.c @@ -363,8 +363,8 @@ static int geni_spi_set_clock_and_bw(struct geni_se *se, return 0; } -static int setup_fifo_params(struct spi_device *spi_slv, - struct spi_controller *spi) +static void setup_spi_params(struct spi_device *spi_slv, + struct spi_controller *spi) { struct spi_geni_master *mas = spi_controller_get_devdata(spi); struct geni_se *se = &mas->se; @@ -390,8 +390,6 @@ static int setup_fifo_params(struct spi_device *spi_slv, trace_geni_spi_setup_params(mas->dev, chipselect, spi_slv->mode, mode_changed, cs_changed); - - return 0; } static void @@ -554,17 +552,14 @@ static int spi_geni_prepare_message(struct spi_controller *spi, struct spi_message *spi_msg) { struct spi_geni_master *mas = spi_controller_get_devdata(spi); - int ret; switch (mas->cur_xfer_mode) { case GENI_SE_FIFO: case GENI_SE_DMA: if (spi_geni_is_abort_still_pending(mas)) return -EBUSY; - ret = setup_fifo_params(spi_msg->spi, spi); - if (ret) - dev_err(mas->dev, "Couldn't select mode %d\n", ret); - return ret; + setup_spi_params(spi_msg->spi, spi); + return 0; case GENI_GPI_DMA: /* nothing to do for GPI DMA */ @@ -700,7 +695,7 @@ static int spi_geni_init(struct spi_geni_master *mas) case 0: mas->cur_xfer_mode = GENI_SE_FIFO; geni_se_select_mode(se, GENI_SE_FIFO); - /* setup_fifo_params assumes that these registers start with a zero value */ + /* setup_spi_params assumes that these registers start with a zero value */ writel(0, se->base + SE_SPI_LOOPBACK); writel(0, se->base + SE_SPI_DEMUX_SEL); writel(0, se->base + SE_SPI_CPHA); From 5f01cb141169fe422c42279f7d502479b1ff9fd2 Mon Sep 17 00:00:00 2001 From: Felix Gu Date: Wed, 26 Aug 2026 01:08:40 +0800 Subject: [PATCH 008/136] spi: omap2-mcspi: Remove unbalanced pm_runtime_put_sync() calls pm_runtime_put_sync() is called in the probe error path and in remove when nothing holds a runtime PM reference anymore, so it underflows the usage count. Fixes: 0e6f357a5deb ("spi: omap2-mcspi: Fix PM regression with deferred probe for pm_runtime_reinit") Signed-off-by: Felix Gu Link: https://patch.msgid.link/20260826-mcspi-v1-1-0a8dd7f6dd56@gmail.com Signed-off-by: Mark Brown --- drivers/spi/spi-omap2-mcspi.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/spi/spi-omap2-mcspi.c b/drivers/spi/spi-omap2-mcspi.c index a3355147f5de..82c8b91e504e 100644 --- a/drivers/spi/spi-omap2-mcspi.c +++ b/drivers/spi/spi-omap2-mcspi.c @@ -1595,7 +1595,6 @@ static int omap2_mcspi_probe(struct platform_device *pdev) err_disable_rpm: pm_runtime_dont_use_autosuspend(&pdev->dev); - pm_runtime_put_sync(&pdev->dev); pm_runtime_disable(&pdev->dev); err_release_dma: omap2_mcspi_release_dma(ctlr); @@ -1613,7 +1612,6 @@ static void omap2_mcspi_remove(struct platform_device *pdev) omap2_mcspi_release_dma(ctlr); pm_runtime_dont_use_autosuspend(mcspi->dev); - pm_runtime_put_sync(mcspi->dev); pm_runtime_disable(&pdev->dev); } From 3958b39fa5b76d692ed548ce6c7c563f465ff8fb Mon Sep 17 00:00:00 2001 From: Praveen Talari Date: Tue, 18 Aug 2026 18:58:07 +0530 Subject: [PATCH 009/136] spi: qcom-geni: Add shutdown callback to quiesce hardware on reboot During system reboot, an active SPI transfer can leave the GENI Serial Engine in an indeterminate state. On VM-based platforms, if a DMA transfer is in progress when the VM is shut down, the SMMU can raise context faults as the DMA engine continues to access IOVAs that have already been invalidated during VM teardown. Add a shutdown callback to suspend the SPI controller and abort any in-progress transfer, ensuring the DMA engine is idle and all IOVA mappings are retired before the system resets. Signed-off-by: Praveen Talari Link: https://patch.msgid.link/20260818-add-shutdown-and-panic-notifier-for-spi-v3-1-8b62c4bc2d21@oss.qualcomm.com Signed-off-by: Mark Brown --- drivers/spi/spi-geni-qcom.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/drivers/spi/spi-geni-qcom.c b/drivers/spi/spi-geni-qcom.c index 6c57f8309a3b..c65c1788325d 100644 --- a/drivers/spi/spi-geni-qcom.c +++ b/drivers/spi/spi-geni-qcom.c @@ -1066,6 +1066,13 @@ static int spi_geni_target_abort(struct spi_controller *spi) return 0; } +static void spi_geni_shutdown(struct platform_device *pdev) +{ + struct spi_controller *spi = platform_get_drvdata(pdev); + + spi_controller_suspend(spi); +} + static int spi_geni_probe(struct platform_device *pdev) { int ret, irq; @@ -1241,7 +1248,8 @@ static const struct of_device_id spi_geni_dt_match[] = { MODULE_DEVICE_TABLE(of, spi_geni_dt_match); static struct platform_driver spi_geni_driver = { - .probe = spi_geni_probe, + .probe = spi_geni_probe, + .shutdown = spi_geni_shutdown, .driver = { .name = "geni_spi", .pm = pm_ptr(&spi_geni_pm_ops), From b7b192e58f353424aa26bef5da88b2a3cb20049e Mon Sep 17 00:00:00 2001 From: Praveen Talari Date: Tue, 18 Aug 2026 18:58:08 +0530 Subject: [PATCH 010/136] spi: qcom-geni: Add panic notifier to cancel and reset DMA during panic When a VM crashes with an active SPI DMA transfer in progress, the SMMU raises context faults as the DMA engine continues to access IOVAs that are invalidated when the VM's memory context is torn down. These faults can affect other VMs sharing the same SMMU instance and obscure the root cause of the crash. Register a panic notifier that cancels (or aborts, if cancel doesn't complete) the in-flight command and resets the TX/RX DMA FSMs, so the DMA engine stops issuing transactions against invalidated IOVAs before the system halts. For GPI DMA mode, the DMA channels are terminated directly via dmaengine_terminate_async(). The notifier bails out early if the device is not runtime-active or if there's no active GENI command, avoiding unnecessary register accesses while the SE is clock-gated or idle. Since panic notifiers run with IRQs and preemption disabled, completion-based waits used by the regular error-handling path (handle_se_timeout()) cannot be reused here. Instead, the relevant status registers are polled directly with readl_poll_timeout_atomic(), which is safe to call in this context. Signed-off-by: Praveen Talari Link: https://patch.msgid.link/20260818-add-shutdown-and-panic-notifier-for-spi-v3-2-8b62c4bc2d21@oss.qualcomm.com Signed-off-by: Mark Brown --- drivers/spi/spi-geni-qcom.c | 68 +++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/drivers/spi/spi-geni-qcom.c b/drivers/spi/spi-geni-qcom.c index c65c1788325d..f272bd0cb640 100644 --- a/drivers/spi/spi-geni-qcom.c +++ b/drivers/spi/spi-geni-qcom.c @@ -12,8 +12,10 @@ #include #include #include +#include #include #include +#include #include #include #include @@ -115,6 +117,7 @@ struct spi_geni_master { struct dma_chan *rx; int cur_xfer_mode; const struct geni_spi_desc *dev_data; + struct notifier_block panic_nb; }; static void spi_slv_setup(struct spi_geni_master *mas) @@ -1073,6 +1076,62 @@ static void spi_geni_shutdown(struct platform_device *pdev) spi_controller_suspend(spi); } +static int spi_geni_panic_notifier(struct notifier_block *nb, + unsigned long action, void *data) +{ + struct spi_geni_master *mas = container_of(nb, struct spi_geni_master, panic_nb); + struct spi_controller *spi = dev_get_drvdata(mas->dev); + struct geni_se *se = &mas->se; + u32 val; + + if (!pm_runtime_active(mas->dev)) + return NOTIFY_OK; + + if (mas->cur_xfer_mode == GENI_GPI_DMA) { + dmaengine_terminate_async(mas->tx); + dmaengine_terminate_async(mas->rx); + return NOTIFY_OK; + } + + if (!(readl_relaxed(se->base + SE_GENI_STATUS) & M_GENI_CMD_ACTIVE)) + return NOTIFY_OK; + + if (!spi->target) { + geni_se_cancel_m_cmd(se); + if (!readl_poll_timeout_atomic(se->base + SE_GENI_M_IRQ_STATUS, val, + val & M_CMD_CANCEL_EN, 10, 50000)) { + writel_relaxed(M_CMD_CANCEL_EN, se->base + SE_GENI_M_IRQ_CLEAR); + return NOTIFY_OK; + } + } + + geni_se_abort_m_cmd(se); + if (!readl_poll_timeout_atomic(se->base + SE_GENI_M_IRQ_STATUS, val, + val & M_CMD_ABORT_EN, 10, 50000)) + writel_relaxed(M_CMD_ABORT_EN, se->base + SE_GENI_M_IRQ_CLEAR); + + if (mas->cur_xfer_mode == GENI_SE_DMA) { + writel_relaxed(1, se->base + SE_DMA_TX_FSM_RST); + readl_poll_timeout_atomic(se->base + SE_DMA_TX_IRQ_STAT, val, + val & TX_RESET_DONE, 10, 50000); + writel_relaxed(val, se->base + SE_DMA_TX_IRQ_CLR); + + writel_relaxed(1, se->base + SE_DMA_RX_FSM_RST); + readl_poll_timeout_atomic(se->base + SE_DMA_RX_IRQ_STAT, val, + val & RX_RESET_DONE, 10, 50000); + writel_relaxed(val, se->base + SE_DMA_RX_IRQ_CLR); + } + + return NOTIFY_OK; +} + +static void spi_geni_unregister_notifiers(void *data) +{ + struct spi_geni_master *mas = data; + + atomic_notifier_chain_unregister(&panic_notifier_list, &mas->panic_nb); +} + static int spi_geni_probe(struct platform_device *pdev) { int ret, irq; @@ -1161,6 +1220,15 @@ static int spi_geni_probe(struct platform_device *pdev) if (ret) return ret; + mas->panic_nb.notifier_call = spi_geni_panic_notifier; + ret = atomic_notifier_chain_register(&panic_notifier_list, &mas->panic_nb); + if (ret) + return ret; + + ret = devm_add_action_or_reset(dev, spi_geni_unregister_notifiers, mas); + if (ret) + return ret; + return devm_spi_register_controller(dev, spi); } From 29359ee126bdb8222eb1b74c32dc63ab1f921584 Mon Sep 17 00:00:00 2001 From: Xianwei Zhao Date: Fri, 31 Jul 2026 09:08:25 +0000 Subject: [PATCH 011/136] spi: dt-bindings: amlogic: spisg: Document A9-specific properties Document the Amlogic A9 SPISG controller and its additional Device Tree properties. Add the "amlogic,a9-spisg" compatible string and define the A9-specific "amlogic,mo-idle-output" property, which configure the MOSI idle output level. Reviewed-by: Krzysztof Kozlowski Signed-off-by: Xianwei Zhao Link: https://patch.msgid.link/20260731-a9-spisg-v3-1-a15da3f70029@amlogic.com Signed-off-by: Mark Brown --- .../bindings/spi/amlogic,a4-spisg.yaml | 30 ++++++++++++++++--- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/Documentation/devicetree/bindings/spi/amlogic,a4-spisg.yaml b/Documentation/devicetree/bindings/spi/amlogic,a4-spisg.yaml index 9bfb8089f7ea..427d8883009c 100644 --- a/Documentation/devicetree/bindings/spi/amlogic,a4-spisg.yaml +++ b/Documentation/devicetree/bindings/spi/amlogic,a4-spisg.yaml @@ -11,12 +11,11 @@ maintainers: - Xianwei Zhao - Sunny Luo -allOf: - - $ref: spi-controller.yaml# - properties: compatible: - const: amlogic,a4-spisg + enum: + - amlogic,a4-spisg + - amlogic,a9-spisg reg: maxItems: 1 @@ -35,6 +34,16 @@ properties: resets: maxItems: 1 + amlogic,mo-idle-output: + description: | + Controls the MOSI output level when the controller is idle. + 0 - Drive MOSI low + 1 - Drive MOSI high + 2 - Follow the last transmitted bit + $ref: /schemas/types.yaml#/definitions/uint32 + enum: [0, 1, 2] + default: 0 + required: - compatible - reg @@ -42,6 +51,19 @@ required: - clocks - clock-names +allOf: + - $ref: spi-controller.yaml# + - if: + not: + properties: + compatible: + contains: + enum: + - amlogic,a9-spisg + then: + properties: + amlogic,mo-idle-output: false + unevaluatedProperties: false examples: From 754d2bbd92f5ecef0eb4f5b1ac7b1417320a56ca Mon Sep 17 00:00:00 2001 From: Xianwei Zhao Date: Fri, 31 Jul 2026 09:08:26 +0000 Subject: [PATCH 012/136] spi: amlogic: spisg: Fix transfer size limit and timeout handling The CFG_BLOCK_NUM field can encode at most 20 bits, so reduce SPISG_BLOCK_MAX to 0xfffff to avoid programming a zero-length transfer. Perform the delay calculation in 64-bit arithmetic to avoid overflow when converting nanoseconds to SPI clock cycles. Stop the controller on transfer timeout by clearing the descriptor list register before returning an error. Fixes: cef9991e04ae ("spi: Add Amlogic SPISG driver") Signed-off-by: Xianwei Zhao Link: https://patch.msgid.link/20260731-a9-spisg-v3-2-a15da3f70029@amlogic.com Signed-off-by: Mark Brown --- drivers/spi/spi-amlogic-spisg.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/drivers/spi/spi-amlogic-spisg.c b/drivers/spi/spi-amlogic-spisg.c index 9049a87e9d0f..58dc6fedd8bc 100644 --- a/drivers/spi/spi-amlogic-spisg.c +++ b/drivers/spi/spi-amlogic-spisg.c @@ -95,7 +95,7 @@ #define SPISG_MAX_REG 0x40 -#define SPISG_BLOCK_MAX 0x100000 +#define SPISG_BLOCK_MAX 0xFFFFF #define SPISG_OP_MODE_WRITE_CMD 0 #define SPISG_OP_MODE_READ_STS 1 @@ -175,7 +175,7 @@ static int spi_delay_to_sclk(u32 slck_speed_hz, struct spi_delay *delay) if (ns < 0) return 0; - return DIV_ROUND_UP_ULL(slck_speed_hz * ns, NSEC_PER_SEC); + return DIV_ROUND_UP_ULL((u64)slck_speed_hz * ns, NSEC_PER_SEC); } static inline u32 aml_spisg_sem_down_read(struct spisg_device *spisg) @@ -565,10 +565,13 @@ static int aml_spisg_transfer_one_message(struct spi_controller *ctlr, aml_spisg_pending(spisg, descs_paddr, false, true); if (wait_for_completion_timeout(&spisg->completion, spi_controller_is_target(spisg->controller) ? - MAX_SCHEDULE_TIMEOUT : msecs_to_jiffies(ms))) + MAX_SCHEDULE_TIMEOUT : msecs_to_jiffies(ms))) { ret = spisg->status ? -EIO : 0; - else + } else { + /* stop transfer */ + regmap_write(spisg->map, SPISG_REG_DESC_LIST_H, 0); ret = -ETIMEDOUT; + } dma_unmap_single(dev, descs_paddr, descs_len, DMA_TO_DEVICE); end: From 253dfb1839c6bc357aebe8aac4f1b4dcc20ee322 Mon Sep 17 00:00:00 2001 From: Sunny Luo Date: Fri, 31 Jul 2026 09:08:27 +0000 Subject: [PATCH 013/136] spi: amlogic: spisg: Fix the incorrect keep_ss of the last descriptor The driver currently unconditionally clears CFG_KEEP_SS on the last descriptor, causing the last transfer's cs_change setting to be ignored. Record the cs_change value of the last SPI transfer and use it to program CFG_KEEP_SS on the final descriptor. When a null descriptor is inserted to implement the cs-hold delay, keep CFG_KEEP_SS set on the preceding transfer descriptor and apply the recorded value to the final descriptor instead. This ensures the controller handles chip select correctly for the last transfer regardless of whether a cs-hold delay is required. Fixes: cef9991e04ae ("spi: Add Amlogic SPISG driver") Signed-off-by: Sunny Luo Signed-off-by: Xianwei Zhao Link: https://patch.msgid.link/20260731-a9-spisg-v3-3-a15da3f70029@amlogic.com Signed-off-by: Mark Brown --- drivers/spi/spi-amlogic-spisg.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/drivers/spi/spi-amlogic-spisg.c b/drivers/spi/spi-amlogic-spisg.c index 58dc6fedd8bc..6bca2379e4ee 100644 --- a/drivers/spi/spi-amlogic-spisg.c +++ b/drivers/spi/spi-amlogic-spisg.c @@ -489,6 +489,7 @@ static int aml_spisg_transfer_one_message(struct spi_controller *ctlr, struct spisg_descriptor_extra *exdescs, *exdesc; dma_addr_t descs_paddr; int desc_num = 1, descs_len; + bool last_xfer_keep_ss = false; u32 cs_hold_in_sclk = 0; int ret = -EIO; @@ -529,9 +530,11 @@ static int aml_spisg_transfer_one_message(struct spi_controller *ctlr, spi_delay_to_sclk(xfer->effective_speed_hz, &msg->spi->cs_setup)); /* calculate cs-hold delay with the last xfer speed */ - if (list_is_last(&xfer->transfer_list, &msg->transfers)) + if (list_is_last(&xfer->transfer_list, &msg->transfers)) { cs_hold_in_sclk = spi_delay_to_sclk(xfer->effective_speed_hz, &msg->spi->cs_hold); + last_xfer_keep_ss = xfer->cs_change; + } desc++; exdesc++; @@ -539,13 +542,17 @@ static int aml_spisg_transfer_one_message(struct spi_controller *ctlr, xfer->effective_speed_hz); } - if (cs_hold_in_sclk) + if (cs_hold_in_sclk) { /* additional null-descriptor to achieve the cs-hold delay */ aml_spisg_setup_null_desc(spisg, desc, cs_hold_in_sclk); - else desc--; + desc->cfg_bus |= FIELD_PREP(CFG_KEEP_SS, 1); + desc++; + } else { + desc--; + } - desc->cfg_bus |= FIELD_PREP(CFG_KEEP_SS, 0); + FIELD_MODIFY(CFG_KEEP_SS, &desc->cfg_bus, last_xfer_keep_ss); desc->cfg_start |= FIELD_PREP(CFG_EOC, 1); /* some tolerances */ From b552119acd63658bbfd86ee55ac802df4c9ab3f1 Mon Sep 17 00:00:00 2001 From: Xianwei Zhao Date: Fri, 31 Jul 2026 09:08:28 +0000 Subject: [PATCH 014/136] spi: amlogic: spisg: Add support for A9 controller features The Amlogic A9 SPISG controller extends the A4 controller with additional configuration options, including: - Extended CS setup timing - Hardware-controlled CS hold timing - MOSI idle output configuration - Configurable word delay Add SoC-specific capability data and configure these features when they are supported by the underlying hardware while keeping compatibility with existing A4 controllers. Signed-off-by: Xianwei Zhao Link: https://patch.msgid.link/20260731-a9-spisg-v3-4-a15da3f70029@amlogic.com Signed-off-by: Mark Brown --- drivers/spi/spi-amlogic-spisg.c | 75 ++++++++++++++++++++++++++++++--- 1 file changed, 68 insertions(+), 7 deletions(-) diff --git a/drivers/spi/spi-amlogic-spisg.c b/drivers/spi/spi-amlogic-spisg.c index 6bca2379e4ee..5ace2c927da0 100644 --- a/drivers/spi/spi-amlogic-spisg.c +++ b/drivers/spi/spi-amlogic-spisg.c @@ -37,6 +37,12 @@ #define CFG_HW_POS BIT(6) /* start on vsync falling */ #define CFG_HW_NEG BIT(7) +#define CFG_WORD_GAP GENMASK(9, 8) +#define CFG_MO_IDLE_OUTPUT GENMASK(11, 10) +/* cs hold time in pclk */ +#define CFG_CS_HOLD GENMASK(26, 12) +/* high 4 bits of cs setup time in sclk */ +#define CFG_CS_SETUP_EXTEND GENMASK(30, 27) #define SPISG_REG_CFG_START 0x08 #define CFG_BLOCK_NUM GENMASK(19, 0) @@ -143,6 +149,13 @@ struct spisg_descriptor_extra { int rx_ccsg_len; }; +struct aml_spisg_data { + bool mo_idle_output_ctrl; + bool word_gap_ctrl; + bool cs_hold_ctrl; + bool cs_setup_extend_ctrl; +}; + struct spisg_device { struct spi_controller *controller; struct platform_device *pdev; @@ -152,6 +165,7 @@ struct spisg_device { struct clk *sclk; struct clk_div_table *tbl; struct completion completion; + const struct aml_spisg_data *data; u32 status; u32 speed_hz; u32 effective_speed_hz; @@ -424,7 +438,8 @@ static void aml_spisg_setup_null_desc(struct spisg_device *spisg, static void aml_spisg_pending(struct spisg_device *spisg, dma_addr_t desc_paddr, bool trig, - bool irq_en) + bool irq_en, + u32 delay) { u32 desc_l, desc_h, cfg_spi, irq_enable; @@ -437,6 +452,13 @@ static void aml_spisg_pending(struct spisg_device *spisg, #endif cfg_spi = spisg->cfg_spi; + + if (spisg->data && spisg->data->word_gap_ctrl) { + if (delay > 3) + delay = 3; + cfg_spi |= FIELD_PREP(CFG_WORD_GAP, delay); + } + if (trig) cfg_spi |= CFG_HW_POS; else @@ -483,6 +505,7 @@ static int aml_spisg_transfer_one_message(struct spi_controller *ctlr, { struct spisg_device *spisg = spi_controller_get_devdata(ctlr); struct device *dev = &spisg->pdev->dev; + const struct aml_spisg_data *data = spisg->data; unsigned long long ms = 0; struct spi_transfer *xfer; struct spisg_descriptor *descs, *desc; @@ -491,6 +514,7 @@ static int aml_spisg_transfer_one_message(struct spi_controller *ctlr, int desc_num = 1, descs_len; bool last_xfer_keep_ss = false; u32 cs_hold_in_sclk = 0; + u32 val, delay = 0; int ret = -EIO; if (!aml_spisg_sem_down_read(spisg)) { @@ -524,10 +548,20 @@ static int aml_spisg_transfer_one_message(struct spi_controller *ctlr, goto end; } - /* calculate cs-setup delay with the first xfer speed */ - if (list_is_first(&xfer->transfer_list, &msg->transfers)) - desc->cfg_bus |= FIELD_PREP(CFG_CS_SETUP, - spi_delay_to_sclk(xfer->effective_speed_hz, &msg->spi->cs_setup)); + /* calculate cs-setup delay with the first xfer speed and word dealy*/ + if (list_is_first(&xfer->transfer_list, &msg->transfers)) { + val = spi_delay_to_sclk(xfer->effective_speed_hz, &msg->spi->cs_setup); + if (data && data->cs_setup_extend_ctrl) { + val = min_t(u32, 0xFF, val); + desc->cfg_bus |= FIELD_PREP(CFG_CS_SETUP, val & 0xF); + FIELD_MODIFY(CFG_CS_SETUP_EXTEND, &spisg->cfg_spi, val >> 4); + } else { + val = min_t(u32, 0xF, val); + desc->cfg_bus |= FIELD_PREP(CFG_CS_SETUP, val); + } + + delay = spi_delay_to_sclk(xfer->effective_speed_hz, &msg->spi->word_delay); + } /* calculate cs-hold delay with the last xfer speed */ if (list_is_last(&xfer->transfer_list, &msg->transfers)) { @@ -542,7 +576,13 @@ static int aml_spisg_transfer_one_message(struct spi_controller *ctlr, xfer->effective_speed_hz); } - if (cs_hold_in_sclk) { + if (data && data->cs_hold_ctrl) { + cs_hold_in_sclk = cs_hold_in_sclk ? : 1; + val = cs_hold_in_sclk * (FIELD_GET(CFG_CLK_DIV, spisg->cfg_bus) + 1); + val = min_t(u32, 0x7FFF, val); + FIELD_MODIFY(CFG_CS_HOLD, &spisg->cfg_spi, val); + desc--; + } else if (cs_hold_in_sclk) { /* additional null-descriptor to achieve the cs-hold delay */ aml_spisg_setup_null_desc(spisg, desc, cs_hold_in_sclk); desc--; @@ -569,7 +609,7 @@ static int aml_spisg_transfer_one_message(struct spi_controller *ctlr, } reinit_completion(&spisg->completion); - aml_spisg_pending(spisg, descs_paddr, false, true); + aml_spisg_pending(spisg, descs_paddr, false, true, delay); if (wait_for_completion_timeout(&spisg->completion, spi_controller_is_target(spisg->controller) ? MAX_SCHEDULE_TIMEOUT : msecs_to_jiffies(ms))) { @@ -725,6 +765,7 @@ static int aml_spisg_probe(struct platform_device *pdev) struct spisg_device *spisg; struct device *dev = &pdev->dev; void __iomem *base; + u32 val = 0; int ret, irq; const struct regmap_config aml_regmap_config = { @@ -743,6 +784,7 @@ static int aml_spisg_probe(struct platform_device *pdev) spisg = spi_controller_get_devdata(ctlr); spisg->controller = ctlr; + spisg->data = (struct aml_spisg_data *)of_device_get_match_data(dev); spisg->pdev = pdev; platform_set_drvdata(pdev, spisg); @@ -773,6 +815,14 @@ static int aml_spisg_probe(struct platform_device *pdev) spisg->cfg_spi = FIELD_PREP(CFG_SFLASH_WP, 1) | FIELD_PREP(CFG_SFLASH_HD, 1); + + if (spisg->data && spisg->data->mo_idle_output_ctrl) { + if (!of_property_read_u32(dev->of_node, "amlogic,mo-idle-output", &val)) + spisg->cfg_spi |= FIELD_PREP(CFG_MO_IDLE_OUTPUT, val); + else + spisg->cfg_spi |= FIELD_PREP(CFG_MO_IDLE_OUTPUT, 0); + } + if (spi_controller_is_target(ctlr)) { spisg->cfg_spi |= FIELD_PREP(CFG_SLAVE_EN, 1); spisg->cfg_bus = FIELD_PREP(CFG_TX_TUNING, 0xf); @@ -859,6 +909,13 @@ static int spisg_resume_runtime(struct device *dev) return 0; } +static const struct aml_spisg_data a9_spisg_data = { + .mo_idle_output_ctrl = true, + .word_gap_ctrl = true, + .cs_hold_ctrl = true, + .cs_setup_extend_ctrl = true, +}; + static const struct dev_pm_ops amlogic_spisg_pm_ops = { .runtime_suspend = spisg_suspend_runtime, .runtime_resume = spisg_resume_runtime, @@ -868,6 +925,10 @@ static const struct of_device_id amlogic_spisg_of_match[] = { { .compatible = "amlogic,a4-spisg", }, + { + .compatible = "amlogic,a9-spisg", + .data = &a9_spisg_data, + }, { /* sentinel */ } }; From bb308b27016c9df54433474cdae6eacc32429b4a Mon Sep 17 00:00:00 2001 From: Felix Gu Date: Fri, 4 Sep 2026 01:17:33 +0800 Subject: [PATCH 015/136] spi: ingenic: release tx DMA channel when rx request fails When the rx channel request fails, the driver carries on without DMA but the tx channel it already acquired stayed claimed until devm teardown. Release it on the error path so it goes straight back to the DMA engine for other users. Signed-off-by: Felix Gu Link: https://patch.msgid.link/20260904-ingenic-v1-1-06218181a4f8@gmail.com Signed-off-by: Mark Brown --- drivers/spi/spi-ingenic.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/spi/spi-ingenic.c b/drivers/spi/spi-ingenic.c index adcf85bccbcc..7ce4fe965cac 100644 --- a/drivers/spi/spi-ingenic.c +++ b/drivers/spi/spi-ingenic.c @@ -354,8 +354,11 @@ static int spi_ingenic_request_dma(struct spi_controller *ctlr, ctlr->dma_tx = chan; chan = dma_request_chan(dev, "rx"); - if (IS_ERR(chan)) + if (IS_ERR(chan)) { + dma_release_channel(ctlr->dma_tx); + ctlr->dma_tx = NULL; return PTR_ERR(chan); + } ctlr->dma_rx = chan; ctlr->can_dma = spi_ingenic_can_dma; From 8992169e7ac6b216f881cb373bb2c6bff0c6162a Mon Sep 17 00:00:00 2001 From: Li Youhong Date: Tue, 1 Sep 2026 10:51:29 +0800 Subject: [PATCH 016/136] spi: ma35d1-qspi: assert reset on probe error paths nuvoton_qspi_probe() pulses the exclusive reset and leaves it deasserted for normal operation. Later probe failures returned without re-asserting the reset, leaving the controller out of reset after a failed probe. Assert the reset on those error paths. Fixes: 78b16af159ae ("spi: ma35d1-qspi: Add Nuvoton MA35D1 QSPI controller support") Signed-off-by: Li Youhong Link: https://patch.msgid.link/20260901025129.359960-1-dayou5941@163.com Signed-off-by: Mark Brown --- drivers/spi/spi-ma35d1-qspi.c | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/drivers/spi/spi-ma35d1-qspi.c b/drivers/spi/spi-ma35d1-qspi.c index 7f938d0c6f1f..cf731fa2f7e2 100644 --- a/drivers/spi/spi-ma35d1-qspi.c +++ b/drivers/spi/spi-ma35d1-qspi.c @@ -633,12 +633,16 @@ static int nuvoton_qspi_probe(struct platform_device *pdev) 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 (ret && ret != -EINVAL) { + ret = dev_err_probe(dev, ret, "failed to read num-cs\n"); + goto err_assert; + } - if (!num_cs || num_cs > NUVOTON_QSPI_MAX_NUM_CS) - return dev_err_probe(dev, -EINVAL, "invalid num-cs %u\n", + if (!num_cs || num_cs > NUVOTON_QSPI_MAX_NUM_CS) { + ret = dev_err_probe(dev, -EINVAL, "invalid num-cs %u\n", num_cs); + goto err_assert; + } ctlr->num_chipselect = num_cs; ctlr->max_transfer_size = nuvoton_qspi_max_transfer_size; @@ -655,14 +659,20 @@ static int nuvoton_qspi_probe(struct platform_device *pdev) ret = nuvoton_qspi_hw_init(qspi); if (ret) - return ret; + goto err_assert; ret = devm_spi_register_controller(dev, ctlr); - if (ret) - return dev_err_probe(dev, ret, - "failed to register spi controller\n"); + if (ret) { + ret = dev_err_probe(dev, ret, + "failed to register spi controller\n"); + goto err_assert; + } return 0; + +err_assert: + reset_control_assert(rst); + return ret; } static const struct of_device_id nuvoton_qspi_of_match[] = { From 4cee1ed13e2687f1a9fec7dd50e31eb179506d75 Mon Sep 17 00:00:00 2001 From: Felix Gu Date: Fri, 4 Sep 2026 23:51:29 +0800 Subject: [PATCH 017/136] spi: mtk-nor: fix runtime PM usage count leak in probe error path mtk_nor_probe() takes a runtime PM reference with pm_runtime_get_noresume() before registering the controller, but the error path never drops it. Balance the get with pm_runtime_put_noidle() in the error path. Fixes: 3bfd9103c7af ("spi: spi-mtk-nor: Add power management support") Signed-off-by: Felix Gu Link: https://patch.msgid.link/20260904-mtk-nor-v1-1-cefdca098f83@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 63f5139176c8..6b6e55d68048 100644 --- a/drivers/spi/spi-mtk-nor.c +++ b/drivers/spi/spi-mtk-nor.c @@ -929,6 +929,7 @@ static int mtk_nor_probe(struct platform_device *pdev) return 0; err_probe: + pm_runtime_put_noidle(&pdev->dev); pm_runtime_disable(&pdev->dev); pm_runtime_set_suspended(&pdev->dev); pm_runtime_dont_use_autosuspend(&pdev->dev); From 86f4ab98e716fea99c604a427e676c44f781c185 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 18:15:10 +0200 Subject: [PATCH 018/136] spi: spi-mem: Enable DTR transfers using the standard API Most spi-mem operations today go through controllers implementing the spi-mem API. But it is also totally possible to use any standard SPI controller to operate these memories. If the controllers support DTR, there is no reason to prevent this feature from being used. Extend spi_mem_exec_op()'s fallback to the standard SPI API, by filling the transfer DTR information. Doing so also requires checking the dtr_caps flag, of course. Signed-off-by: Miquel Raynal Link: https://patch.msgid.link/20260904-perso-ma35d1-master-v1-1-b6936e8c6fa1@bootlin.com Signed-off-by: Mark Brown --- drivers/spi/spi-mem.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/spi/spi-mem.c b/drivers/spi/spi-mem.c index 5f973ebfb8b6..94d122be1c93 100644 --- a/drivers/spi/spi-mem.c +++ b/drivers/spi/spi-mem.c @@ -172,7 +172,7 @@ bool spi_mem_default_supports_op(struct spi_mem *mem, op->cmd.dtr || op->addr.dtr || op->dummy.dtr || op->data.dtr; if (op_is_dtr) { - if (!spi_mem_controller_is_capable(ctlr, dtr)) + if (!spi_mem_controller_is_capable(ctlr, dtr) && !ctlr->dtr_caps) return false; if (op->data.swap16 && !spi_mem_controller_is_capable(ctlr, swap16)) @@ -461,6 +461,7 @@ int spi_mem_exec_op(struct spi_mem *mem, const struct spi_mem_op *op) xfers[xferpos].len = op->cmd.nbytes; xfers[xferpos].tx_nbits = op->cmd.buswidth; xfers[xferpos].speed_hz = op->max_freq; + xfers[xferpos].dtr_mode = op->cmd.dtr; spi_message_add_tail(&xfers[xferpos], &msg); xferpos++; totalxferlen++; @@ -476,6 +477,7 @@ int spi_mem_exec_op(struct spi_mem *mem, const struct spi_mem_op *op) xfers[xferpos].len = op->addr.nbytes; xfers[xferpos].tx_nbits = op->addr.buswidth; xfers[xferpos].speed_hz = op->max_freq; + xfers[xferpos].dtr_mode = op->addr.dtr; spi_message_add_tail(&xfers[xferpos], &msg); xferpos++; totalxferlen += op->addr.nbytes; @@ -488,6 +490,7 @@ int spi_mem_exec_op(struct spi_mem *mem, const struct spi_mem_op *op) xfers[xferpos].tx_nbits = op->dummy.buswidth; xfers[xferpos].dummy_data = 1; xfers[xferpos].speed_hz = op->max_freq; + xfers[xferpos].dtr_mode = op->dummy.dtr; spi_message_add_tail(&xfers[xferpos], &msg); xferpos++; totalxferlen += op->dummy.nbytes; @@ -504,6 +507,7 @@ int spi_mem_exec_op(struct spi_mem *mem, const struct spi_mem_op *op) xfers[xferpos].len = op->data.nbytes; xfers[xferpos].speed_hz = op->max_freq; + xfers[xferpos].dtr_mode = op->data.dtr; spi_message_add_tail(&xfers[xferpos], &msg); xferpos++; totalxferlen += op->data.nbytes; From 2448f25cc839739fe576d31d85c7d0df4844bb3a Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 18:15:11 +0200 Subject: [PATCH 019/136] spi: ma35d1-qspi: Allow DTR operations with the regular SPI API The feature was implemented but not actually enabled for regular SPI operations (as opposed to spi-mem operations). Make sure the missing capability is actually set. Reported-by: Mark Brown Closes: https://lore.kernel.org/all/09c3928f-4b58-4ac6-8e1a-84dfe3ff6b92@sirena.org.uk/ Fixes: 15e9362f6190 ("spi: ma35d1-qspi: Add DTR support") Signed-off-by: Miquel Raynal Link: https://patch.msgid.link/20260904-perso-ma35d1-master-v1-2-b6936e8c6fa1@bootlin.com Signed-off-by: Mark Brown --- drivers/spi/spi-ma35d1-qspi.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/spi/spi-ma35d1-qspi.c b/drivers/spi/spi-ma35d1-qspi.c index 7f938d0c6f1f..ee3427640f3a 100644 --- a/drivers/spi/spi-ma35d1-qspi.c +++ b/drivers/spi/spi-ma35d1-qspi.c @@ -645,6 +645,7 @@ static int nuvoton_qspi_probe(struct platform_device *pdev) ctlr->max_message_size = nuvoton_qspi_max_message_size; ctlr->mem_ops = &nuvoton_qspi_mem_ops; ctlr->mem_caps = &nuvoton_qspi_mem_caps; + ctlr->dtr_caps = true; ctlr->set_cs = nuvoton_qspi_set_cs; ctlr->transfer_one = nuvoton_qspi_transfer_one; ctlr->bits_per_word_mask = SPI_BPW_MASK(8); From 98477036fc35472834ccfee018fdc99d2d21b073 Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Fri, 4 Sep 2026 16:34:16 +0530 Subject: [PATCH 020/136] spi: fix typos in comments Fix typos in comments, reported by scripts/checkpatch.pl using the misspelling list in scripts/spelling.txt. Only touches comments, no code changes. Assisted-by: Cursor:claude-opus-5 Signed-off-by: Hemanth Selam Link: https://patch.msgid.link/20260904110420.13707-1-hemanth.selam@gmail.com Signed-off-by: Mark Brown --- drivers/spi/spi-bcm63xx.c | 2 +- drivers/spi/spi-imx.c | 2 +- drivers/spi/spi-omap2-mcspi.c | 2 +- drivers/spi/spi-pic32.c | 2 +- drivers/spi/spi-s3c64xx.c | 2 +- drivers/spi/spi-stm32.c | 2 +- drivers/spi/spi-sun6i.c | 2 +- drivers/spi/spi-xilinx.c | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/drivers/spi/spi-bcm63xx.c b/drivers/spi/spi-bcm63xx.c index 1d8e257bad30..3f349c1db581 100644 --- a/drivers/spi/spi-bcm63xx.c +++ b/drivers/spi/spi-bcm63xx.c @@ -413,7 +413,7 @@ static irqreturn_t bcm63xx_spi_interrupt(int irq, void *dev_id) struct bcm63xx_spi *bs = spi_controller_get_devdata(host); u8 intr; - /* Read interupts and clear them immediately */ + /* Read interrupts and clear them immediately */ intr = bcm_spi_readb(bs, SPI_INT_STATUS); bcm_spi_writeb(bs, SPI_INTR_CLEAR_ALL, SPI_INT_STATUS); bcm_spi_writeb(bs, 0, SPI_INT_MASK); diff --git a/drivers/spi/spi-imx.c b/drivers/spi/spi-imx.c index 79a6c1a60b0a..6f9686df0486 100644 --- a/drivers/spi/spi-imx.c +++ b/drivers/spi/spi-imx.c @@ -673,7 +673,7 @@ static int mx51_ecspi_prepare_message(struct spi_imx_data *spi_imx, * propagate into the hardware. It takes exactly one tick of the * SCLK clock, but we will wait two SCLK clock just to be sure. The * effect of the delay it takes for the hardware to apply changes - * is noticable if the SCLK clock run very slow. In such a case, if + * is noticeable if the SCLK clock run very slow. In such a case, if * the polarity of SCLK should be inverted, the GPIO ChipSelect might * be asserted before the SCLK polarity changes, which would disrupt * the SPI communication as the device on the other end would consider diff --git a/drivers/spi/spi-omap2-mcspi.c b/drivers/spi/spi-omap2-mcspi.c index 82c8b91e504e..ace6886a75c3 100644 --- a/drivers/spi/spi-omap2-mcspi.c +++ b/drivers/spi/spi-omap2-mcspi.c @@ -1244,7 +1244,7 @@ static int omap2_mcspi_transfer_one(struct spi_controller *ctlr, omap2_mcspi_set_fifo(spi, t, 0); out: - /* Restore defaults if they were overriden */ + /* Restore defaults if they were overridden */ if (par_override) { par_override = 0; status = omap2_mcspi_setup_transfer(spi, NULL); diff --git a/drivers/spi/spi-pic32.c b/drivers/spi/spi-pic32.c index 972128271e4b..8363bc873b03 100644 --- a/drivers/spi/spi-pic32.c +++ b/drivers/spi/spi-pic32.c @@ -53,7 +53,7 @@ struct pic32_spi_regs { #define TX_FIFO_ALL_EMPTY 0 /* completely empty */ #define TX_FIFO_EMPTY 1 /* empty */ #define TX_FIFO_HALF_EMPTY 2 /* empty by half or more */ -#define TX_FIFO_NOT_FULL 3 /* atleast one empty */ +#define TX_FIFO_NOT_FULL 3 /* at least one empty */ #define CTRL_MSTEN BIT(5) /* enable master mode */ #define CTRL_CKP BIT(6) /* active low */ diff --git a/drivers/spi/spi-s3c64xx.c b/drivers/spi/spi-s3c64xx.c index 8d4120cf9df1..3546414dfc70 100644 --- a/drivers/spi/spi-s3c64xx.c +++ b/drivers/spi/spi-s3c64xx.c @@ -661,7 +661,7 @@ static int s3c64xx_wait_for_pio(struct s3c64xx_spi_driver_data *sdd, * loops = length / max fifo size (calculated by using the * fifo mask). * For any size less than the fifo size the below code is - * executed atleast once. + * executed at least once. */ loops = xfer->len / sdd->fifo_depth; buf = xfer->rx_buf; diff --git a/drivers/spi/spi-stm32.c b/drivers/spi/spi-stm32.c index be88e62075af..5f5c43ae5096 100644 --- a/drivers/spi/spi-stm32.c +++ b/drivers/spi/spi-stm32.c @@ -515,7 +515,7 @@ static int stm32h7_spi_get_bpw_mask(struct stm32_spi *spi) /* * The most significant bit at DSIZE bit field is reserved when the - * maximum data size of periperal instances is limited to 16-bit + * maximum data size of peripheral instances is limited to 16-bit */ stm32_spi_set_bits(spi, STM32H7_SPI_CFG1, STM32H7_SPI_CFG1_DSIZE); diff --git a/drivers/spi/spi-sun6i.c b/drivers/spi/spi-sun6i.c index 4631e9c7ca1d..6b7a05dd0de8 100644 --- a/drivers/spi/spi-sun6i.c +++ b/drivers/spi/spi-sun6i.c @@ -386,7 +386,7 @@ static int sun6i_spi_transfer_one(struct spi_controller *host, * SPI_CLK = MOD_CLK / (2 ^ cdr) * Or we can use CDR2, which is calculated with the formula: * SPI_CLK = MOD_CLK / (2 * (cdr + 1)) - * Wether we use the former or the latter is set through the + * Whether we use the former or the latter is set through the * DRS bit. * * First try CDR2, and if we can't reach the expected diff --git a/drivers/spi/spi-xilinx.c b/drivers/spi/spi-xilinx.c index dc1e968659e1..df4ea99a5427 100644 --- a/drivers/spi/spi-xilinx.c +++ b/drivers/spi/spi-xilinx.c @@ -460,7 +460,7 @@ static int xilinx_spi_probe(struct platform_device *pdev) host->num_chipselect = num_cs; /* - * Detect endianess on the IP via loop bit in CR. Detection + * Detect endianness on the IP via loop bit in CR. Detection * must be done before reset is sent because incorrect reset * value generates error interrupt. * Setup little endian helper functions first and try to use them From 23ea08ca607c2be0d4445dce7942a5049efb1451 Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Sun, 6 Sep 2026 17:57:50 -0700 Subject: [PATCH 021/136] spi: orion: yield to scheduler in transfer wait loop orion_spi_wait_till_ready() busy-waits in a tight udelay(1) loop, up to 2000 iterations, and is called per byte from the polled, byte-at-a-time transfer path. On SoCs such as the Armada 388 (e.g. SolidRun Helios4), which also run SATA over the shared internal MBus fabric, this stalls the CPU for the whole transfer and delays servicing of SATA interrupts. Under sustained activity this can cause SATA timeouts and link resets (sometimes renegotiating down to SATA II, 3 Gbps). Add cond_resched() to the wait loop so the scheduler can run pending IRQs between polls. This is a no-op at runtime unless the kernel is built with CONFIG_PREEMPT enabled, where it lets other peripheral interrupts be serviced during SPI transfers. Built with LLVM=1 ARCH=powerpc; passes checkpatch --strict. Assisted-by: opencode:big-pickle Signed-off-by: Rosen Penev Link: https://patch.msgid.link/20260907005750.230103-1-rosenp@gmail.com Signed-off-by: Mark Brown --- drivers/spi/spi-orion.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/spi/spi-orion.c b/drivers/spi/spi-orion.c index 26a9f268b2d3..b8882229c054 100644 --- a/drivers/spi/spi-orion.c +++ b/drivers/spi/spi-orion.c @@ -370,7 +370,16 @@ static inline int orion_spi_wait_till_ready(struct orion_spi *orion_spi) if (readl(spi_reg(orion_spi, ORION_SPI_INT_CAUSE_REG))) return 1; + /* + * This is a polled, byte-at-a-time transfer loop. Each + * iteration busy-waits in a tight udelay() loop, which can + * starve other time-sensitive peripherals (e.g. SATA) of CPU + * time and interfere with them if they run on this SoC. + * Yield to the scheduler between polls so pending IRQs can + * be serviced. + */ udelay(1); + cond_resched(); } return -1; From e95dac34dd5763c125dc597a159d72922b4f8fbb Mon Sep 17 00:00:00 2001 From: Felix Gu Date: Mon, 7 Sep 2026 20:35:45 +0800 Subject: [PATCH 022/136] spi: ar934x: Convert to devm_spi_register_controller() Switch to devm_spi_register_controller() and drop the unneeded .remove callback and dev_set_drvdata(). Signed-off-by: Felix Gu Link: https://patch.msgid.link/20260907-ar934x-v1-1-71327eb482bc@gmail.com Signed-off-by: Mark Brown --- drivers/spi/spi-ar934x.c | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/drivers/spi/spi-ar934x.c b/drivers/spi/spi-ar934x.c index 2210186feab8..01e51a30591d 100644 --- a/drivers/spi/spi-ar934x.c +++ b/drivers/spi/spi-ar934x.c @@ -197,23 +197,13 @@ static int ar934x_spi_probe(struct platform_device *pdev) SPI_BPW_MASK(16) | SPI_BPW_MASK(8); ctlr->num_chipselect = 3; - dev_set_drvdata(&pdev->dev, ctlr); - sp = spi_controller_get_devdata(ctlr); sp->base = base; sp->clk = clk; sp->clk_freq = clk_get_rate(clk); sp->ctlr = ctlr; - return spi_register_controller(ctlr); -} - -static void ar934x_spi_remove(struct platform_device *pdev) -{ - struct spi_controller *ctlr; - - ctlr = dev_get_drvdata(&pdev->dev); - spi_unregister_controller(ctlr); + return devm_spi_register_controller(&pdev->dev, ctlr); } static struct platform_driver ar934x_spi_driver = { @@ -222,7 +212,6 @@ static struct platform_driver ar934x_spi_driver = { .of_match_table = ar934x_spi_match, }, .probe = ar934x_spi_probe, - .remove = ar934x_spi_remove, }; module_platform_driver(ar934x_spi_driver); From e329b94b1e47d1a73706bd09380bf0989fbcd4a8 Mon Sep 17 00:00:00 2001 From: Vishwaroop A Date: Thu, 13 Aug 2026 20:00:25 +0000 Subject: [PATCH 023/136] spi: tegra210-quad: Convert to hard IRQ with high-priority workqueue Threaded IRQ handlers can be delayed by the scheduler on heavily loaded systems, causing wait_for_completion_timeout() to expire before the handler runs and producing false transfer timeouts. On GB200 with TPM hwrng traffic running alongside a NCCL multicast workload this shows up as a WARN in tegra_qspi_transfer_one_message() even though the hardware has already signalled QSPI_RDY. irq_thread() runs SCHED_FIFO but set_cpus_allowed_ptr()s to the IRQ affinity mask (typically a single CPU). When that CPU is saturated by non-preemptible kernel work on the same interrupt line (softirqs, spinlock contention, network RX processing), the FIFO priority alone does not help - there is nothing at lower priority to preempt. The bottom half sits on the runqueue for milliseconds and occasionally seconds. Convert to a hard IRQ handler that schedules work on a WQ_HIGHPRI | WQ_UNBOUND workqueue: - The hard IRQ handler runs outside process-scheduler control - it can still be delayed by higher-priority IRQ handling or local IRQ-disabled / non-preemptible sections, but not by CFS or RT-userspace backpressure. tegra_qspi_isr() captures FIFO / trans status and masks the controller IRQ synchronously with the hardware event, so the subsequent timeout classification (added in the following patch) always sees the true state. - The workqueue worker runs SCHED_NORMAL with HIGHPRI_NICE_LEVEL (nice -20). A real-time SCHED_FIFO userspace task will preempt it where it would not have preempted the old irq_thread; that is a real trade-off. In exchange, WQ_UNBOUND lets the worker migrate off the interrupt-taking CPU that the threaded IRQ could not leave, which is the actual failure mode observed in the field. The following patch (small-PIO fastpath) further removes the worker from the latency-sensitive TPM path entirely. The work handler only touches QSPI MMIO when curr_xfer is non-NULL. curr_xfer is cleared only after the transfer thread has processed the completion, and while it is set the transfer thread is blocked in wait_for_completion_timeout() with the SPI core's runtime PM reference held, so the clocks are guaranteed on. The ISR returns IRQ_HANDLED unconditionally. Tegra QSPI has a dedicated, non-shared GIC SPI line on every SoC that uses this driver, so any spurious / late IRQ (for example after the timeout path has cleared curr_xfer) must still be acked and re-masked here; otherwise the level-triggered line could stay asserted and trip the kernel spurious-IRQ detector into disabling the line ("nobody cared, try to disable"). The lock-free curr_xfer NULL check lets the ISR bail without touching FIFO / status when there is no transfer to drive forward. handle_dma_based_xfer() snapshots curr_xfer under the spinlock at function entry and bails immediately when the timeout path has already cleared it. This avoids waiting up to QSPI_DMA_TIMEOUT on a DMA completion that belongs to a transfer the synchronous path has already torn down, and keeps the subsequent dma_unmap / FIFO-drain operations consistent with the transfer that actually started. Resources are allocated and torn down manually so that remove() can stop the controller, free the IRQ (preventing new work from being queued), then destroy the workqueue (which drains any already-queued work while the clocks are still on) before runtime PM is disabled. Signed-off-by: Vishwaroop A Link: https://patch.msgid.link/20260813200027.2711863-2-va@nvidia.com Signed-off-by: Mark Brown --- drivers/spi/spi-tegra210-quad.c | 165 ++++++++++++++++++++++---------- 1 file changed, 115 insertions(+), 50 deletions(-) diff --git a/drivers/spi/spi-tegra210-quad.c b/drivers/spi/spi-tegra210-quad.c index 8ede864c3d3c..7c09a1fe0d41 100644 --- a/drivers/spi/spi-tegra210-quad.c +++ b/drivers/spi/spi-tegra210-quad.c @@ -191,6 +191,8 @@ struct tegra_qspi { void __iomem *base; phys_addr_t phys; unsigned int irq; + struct work_struct irq_work; + struct workqueue_struct *wq; u32 cur_speed; unsigned int cur_pos; @@ -1232,9 +1234,9 @@ static int tegra_qspi_combined_seq_xfer(struct tegra_qspi *tqspi, if (ret == 0) { /* - * Check if hardware completed the transfer - * even though interrupt was lost or delayed. - * If so, process the completion and continue. + * Check if hardware completed the transfer even though + * workqueue was delayed. If so, process completion and + * continue. */ ret = tegra_qspi_handle_timeout(tqspi); if (ret < 0) { @@ -1351,8 +1353,8 @@ static int tegra_qspi_non_combined_seq_xfer(struct tegra_qspi *tqspi, if (ret == 0) { /* * Check if hardware completed the transfer even though - * interrupt was lost or delayed. If so, process the - * completion and continue. + * workqueue was delayed. If so, process completion and + * continue. */ ret = tegra_qspi_handle_timeout(tqspi); if (ret < 0) { @@ -1506,6 +1508,19 @@ static irqreturn_t handle_dma_based_xfer(struct tegra_qspi *tqspi) long wait_status; int num_errors = 0; + /* + * Snapshot curr_xfer under the lock before the (potentially long) + * DMA waits below. The timeout path can clear tqspi->curr_xfer + * concurrently; using the local copy keeps the subsequent dma_unmap + * and FIFO-drain steps consistent with the transfer that actually + * started, and lets us bail safely if cleanup already happened. + */ + spin_lock_irqsave(&tqspi->lock, flags); + t = tqspi->curr_xfer; + spin_unlock_irqrestore(&tqspi->lock, flags); + if (!t) + return IRQ_HANDLED; + if (tqspi->cur_direction & DATA_DIR_TX) { if (tqspi->tx_status) { if (tqspi->tx_dma_chan) @@ -1539,12 +1554,6 @@ static irqreturn_t handle_dma_based_xfer(struct tegra_qspi *tqspi) } spin_lock_irqsave(&tqspi->lock, flags); - t = tqspi->curr_xfer; - - if (!t) { - spin_unlock_irqrestore(&tqspi->lock, flags); - return IRQ_HANDLED; - } if (num_errors) { tegra_qspi_dma_unmap_xfer(tqspi, t); @@ -1581,46 +1590,38 @@ static irqreturn_t handle_dma_based_xfer(struct tegra_qspi *tqspi) return IRQ_HANDLED; } -static irqreturn_t tegra_qspi_isr_thread(int irq, void *context_data) +/** + * tegra_qspi_work_handler - Workqueue handler for interrupt bottom-half + * @work: work_struct embedded in tegra_qspi + * + * Runs in process context and can sleep (needed for DMA completion waits). + * Runs on any CPU in the WQ_UNBOUND pool, so the bottom half can migrate off + * the interrupt-taking CPU that the previous threaded IRQ pinned to + * (irq_thread() calls set_cpus_allowed_ptr() with the IRQ affinity mask). + * + * The hard IRQ handler has already: + * - Verified this is our interrupt (QSPI_RDY was set) + * - Cached FIFO status in tqspi->status_reg + * - Parsed tx_status / rx_status from FIFO status + * - Masked further interrupts + */ +static void tegra_qspi_work_handler(struct work_struct *work) { - struct tegra_qspi *tqspi = context_data; + struct tegra_qspi *tqspi = container_of(work, struct tegra_qspi, irq_work); unsigned long flags; - u32 status; - /* - * Read transfer status to check if interrupt was triggered by transfer - * completion - */ - status = tegra_qspi_readl(tqspi, QSPI_TRANS_STATUS); - - /* - * Occasionally the IRQ thread takes a long time to wake up (usually - * when the CPU that it's running on is excessively busy) and we have - * already reached the timeout before and cleaned up the timed out - * transfer. Avoid any processing in that case and bail out early. - * - * If no transfer is in progress, check if this was a real interrupt - * that the timeout handler already processed, or a spurious one. - */ spin_lock_irqsave(&tqspi->lock, flags); + + /* + * The timeout path can clear curr_xfer between the ISR queuing + * this work and the worker actually running, so re-check under + * the lock and bail if there is nothing to do. + */ if (!tqspi->curr_xfer) { spin_unlock_irqrestore(&tqspi->lock, flags); - /* Spurious interrupt - transfer not ready */ - if (!(status & QSPI_RDY)) - return IRQ_NONE; - /* Real interrupt, already handled by timeout path */ - return IRQ_HANDLED; + return; } - tqspi->status_reg = tegra_qspi_readl(tqspi, QSPI_FIFO_STATUS); - - if (tqspi->cur_direction & DATA_DIR_TX) - tqspi->tx_status = tqspi->status_reg & (QSPI_TX_FIFO_UNF | QSPI_TX_FIFO_OVF); - - if (tqspi->cur_direction & DATA_DIR_RX) - tqspi->rx_status = tqspi->status_reg & (QSPI_RX_FIFO_OVF | QSPI_RX_FIFO_UNF); - - tegra_qspi_mask_clear_irq(tqspi); spin_unlock_irqrestore(&tqspi->lock, flags); /* @@ -1630,9 +1631,55 @@ static irqreturn_t tegra_qspi_isr_thread(int irq, void *context_data) * cannot be done while holding spinlock. */ if (!tqspi->is_curr_dma_xfer) - return handle_cpu_based_xfer(tqspi); + handle_cpu_based_xfer(tqspi); + else + handle_dma_based_xfer(tqspi); +} - return handle_dma_based_xfer(tqspi); +/** + * tegra_qspi_isr - Hard IRQ handler + * @irq: IRQ number + * @context_data: QSPI controller instance + * + * Runs in hard IRQ context with minimal latency. Cannot sleep. + * + * Tegra QSPI uses a dedicated, non-shared GIC SPI line on every SoC that + * uses this driver. The handler always returns IRQ_HANDLED and always + * acknowledges/re-masks the controller IRQ, so the level-triggered line + * cannot stay asserted and trip the kernel spurious-IRQ detector into + * disabling the line. On a stray IRQ where curr_xfer is NULL (e.g. the + * timeout path has already torn the transfer down) the FIFO/status + * processing and bottom-half scheduling are skipped because there is no + * transfer to drive forward. + * + * Return: IRQ_HANDLED. + */ +static irqreturn_t tegra_qspi_isr(int irq, void *context_data) +{ + struct tegra_qspi *tqspi = context_data; + + if (!READ_ONCE(tqspi->curr_xfer)) { + tegra_qspi_mask_clear_irq(tqspi); + return IRQ_HANDLED; + } + + spin_lock(&tqspi->lock); + tqspi->status_reg = tegra_qspi_readl(tqspi, QSPI_FIFO_STATUS); + tegra_qspi_mask_clear_irq(tqspi); + + if (tqspi->cur_direction & DATA_DIR_TX) + tqspi->tx_status = tqspi->status_reg & + (QSPI_TX_FIFO_UNF | QSPI_TX_FIFO_OVF); + + if (tqspi->cur_direction & DATA_DIR_RX) + tqspi->rx_status = tqspi->status_reg & + (QSPI_RX_FIFO_OVF | QSPI_RX_FIFO_UNF); + + spin_unlock(&tqspi->lock); + + queue_work(tqspi->wq, &tqspi->irq_work); + + return IRQ_HANDLED; } static struct tegra_qspi_soc_data tegra210_qspi_soc_data = { @@ -1800,12 +1847,21 @@ static int tegra_qspi_probe(struct platform_device *pdev) pm_runtime_put_autosuspend(&pdev->dev); - ret = request_threaded_irq(tqspi->irq, NULL, - tegra_qspi_isr_thread, IRQF_ONESHOT, - dev_name(&pdev->dev), tqspi); + tqspi->wq = alloc_workqueue("%s", WQ_HIGHPRI | WQ_UNBOUND, 0, + dev_name(&pdev->dev)); + if (!tqspi->wq) { + dev_err(&pdev->dev, "failed to allocate workqueue\n"); + ret = -ENOMEM; + goto exit_pm_disable; + } + + INIT_WORK(&tqspi->irq_work, tegra_qspi_work_handler); + + ret = request_irq(tqspi->irq, tegra_qspi_isr, 0, + dev_name(&pdev->dev), tqspi); if (ret < 0) { dev_err(&pdev->dev, "failed to request IRQ#%u: %d\n", tqspi->irq, ret); - goto exit_pm_disable; + goto exit_destroy_wq; } ret = spi_register_controller(host); @@ -1817,7 +1873,9 @@ static int tegra_qspi_probe(struct platform_device *pdev) return 0; exit_free_irq: - free_irq(qspi_irq, tqspi); + free_irq(tqspi->irq, tqspi); +exit_destroy_wq: + destroy_workqueue(tqspi->wq); exit_pm_disable: pm_runtime_dont_use_autosuspend(&pdev->dev); pm_runtime_force_suspend(&pdev->dev); @@ -1830,8 +1888,15 @@ static void tegra_qspi_remove(struct platform_device *pdev) struct spi_controller *host = platform_get_drvdata(pdev); struct tegra_qspi *tqspi = spi_controller_get_devdata(host); + /* + * Tear down in reverse order of probe() so that the controller stops + * accepting transfers before the IRQ is released, no new work can be + * queued after the IRQ is freed, and any work already queued is + * drained while the clocks are still running. + */ spi_unregister_controller(host); free_irq(tqspi->irq, tqspi); + destroy_workqueue(tqspi->wq); pm_runtime_dont_use_autosuspend(&pdev->dev); pm_runtime_force_suspend(&pdev->dev); tegra_qspi_deinit_dma(tqspi); From 1ff82c13ea9475abc51eebfb83ee54bc75c08900 Mon Sep 17 00:00:00 2001 From: Vishwaroop A Date: Thu, 13 Aug 2026 20:00:26 +0000 Subject: [PATCH 024/136] spi: tegra210-quad: Cache TRANS_STATUS in ISR for timeout handler On heavily loaded systems the workqueue bottom half can be delayed long enough for wait_for_completion_timeout() to expire before the ISR's queued work actually runs. Reading QSPI_TRANS_STATUS directly from the controller in the timeout handler races with both the workqueue handler and the controller itself, and can mis-classify a transfer that genuinely timed out as having "completed". Cache the controller status captured by the hard IRQ before it is acked, and let the timeout handler consume that cache: - tegra_qspi_isr() reads QSPI_FIFO_STATUS and QSPI_TRANS_STATUS, derives tx_status / rx_status, publishes them via WRITE_ONCE(), and then publishes the trans_status cache via smp_store_release() *before* masking and acking the controller IRQ. Publish-before-clear is required so that a timeout handler that fell back to a live QSPI_TRANS_STATUS read (because it saw the cache still zero on another CPU) also sees the hardware RDY bit that has not been cleared yet. - tegra_qspi_handle_timeout() consumes trans_status with a paired smp_load_acquire() and a cache-live-cache retry pattern. If the initial cache load returns zero, the handler reads the live QSPI_TRANS_STATUS register; if that also returns zero it retries the cache once more. That closes the interleaving where an ISR publishes trans_status with release semantics and then W1Cs the hardware between the timeout handler's cache load and its live load, otherwise leaving the timeout handler with cache = 0 and HW = 0 (a false timeout on a transfer that has in fact just completed). - tegra_qspi_setup_transfer_one() and both tegra_qspi_start_{cpu,dma}_based_transfer() paths clear the cache with smp_store_release() under the spinlock before unmasking the IRQ for the new chunk, so a stale RDY bit from a previous chunk of a multi-chunk transfer cannot fool the handler. Serialise handle_timeout with the workqueue and the ISR unconditionally. Every expired wait_for_completion_timeout() enters the recovery state: publish recovery_in_progress under tqspi->lock, mask the controller IRQ, synchronize_irq() to drain any in-flight hard IRQ (including the small-PIO fastpath), and cancel_work_sync() to drain the workqueue. This holds regardless of whether the hardware finished, because a genuine hardware timeout still races the caller's dma_stop() + device_reset() + curr_xfer clear against a delayed ISR or worker that arrives immediately after the status sample. Classifying the timeout as -ETIMEDOUT only *after* serialisation gives the caller a stable state to clean up. Snapshot the live FIFO error status *before* entering recovery when the ISR cache is empty and the live QSPI_TRANS_STATUS shows RDY (the lost-IRQ path). tegra_qspi_mask_clear_irq() W1Cs QSPI_TRANS_STATUS and the QSPI_FIFO_STATUS error bits, so a lost-IRQ recovery that called it first would erase the very error state the manual handler downstream needs to see. Capturing the snapshot before the mask and publishing it into tqspi->{status_reg,tx_status,rx_status} after the drain keeps the manual final-chunk handler operating on fresh error data rather than stale fields from an earlier ISR run. cancel_work_sync() cancels a pending worker without executing it and waits for a currently running one to finish. The recovery_in_progress guard is checked inside tegra_qspi_isr() under the same tqspi->lock as its queue_work() and small-PIO fastpath dispatch decisions, so no new bottom-half work is enqueued once we publish the flag. synchronize_irq() closes the window where an ISR observed recovery_in_progress == false, released the lock, and is about to call queue_work(): we wait for that ISR to finish before draining the workqueue, so its queued work is caught by cancel_work_sync(). After the drain, re-check the cache once more (the drained worker may have published a completion status the entry snapshot did not observe). If try_wait_for_completion() reports the whole transfer completed, return success. Restrict the manual fallback that invokes handle_{cpu,dma}_based_xfer() from process context to the last chunk of a transfer. On an intermediate chunk of a multi-chunk DMA transfer the work handler may have processed the current chunk and armed the next chunk (unmasked the IRQ and kicked HW) before cancel_work_sync() returned; running another handler here would let the caller's seq_xfer clear curr_xfer and finalise the message while the DMA engine is still moving the next chunk into the client buffer. Return -ETIMEDOUT in that case and let the caller's existing dma_stop() + reset() path clean up. Before returning, re-mask the controller IRQ and synchronize_irq() one more time. The drained bottom half may have unmasked the IRQ when arming a subsequent chunk; without the re-mask a lingering RDY IRQ that arrives after this function returns could invoke the ISR and queue a worker after recovery_in_progress has been cleared, racing the caller's cleanup of curr_xfer. Signed-off-by: Vishwaroop A Link: https://patch.msgid.link/20260813200027.2711863-3-va@nvidia.com Signed-off-by: Mark Brown --- drivers/spi/spi-tegra210-quad.c | 296 ++++++++++++++++++++++++++++---- 1 file changed, 267 insertions(+), 29 deletions(-) diff --git a/drivers/spi/spi-tegra210-quad.c b/drivers/spi/spi-tegra210-quad.c index 7c09a1fe0d41..c242f56a09fd 100644 --- a/drivers/spi/spi-tegra210-quad.c +++ b/drivers/spi/spi-tegra210-quad.c @@ -193,6 +193,13 @@ struct tegra_qspi { unsigned int irq; struct work_struct irq_work; struct workqueue_struct *wq; + /* + * Set by tegra_qspi_handle_timeout() while it drains the bottom + * half so tegra_qspi_isr() suppresses new queue_work() calls + * that would otherwise race the recovery path or the caller's + * cleanup of curr_xfer. + */ + bool recovery_in_progress; u32 cur_speed; unsigned int cur_pos; @@ -214,6 +221,7 @@ struct tegra_qspi { u32 tx_status; u32 rx_status; u32 status_reg; + u32 trans_status; bool is_packed; bool use_dma; @@ -624,6 +632,17 @@ static int tegra_qspi_start_dma_based_transfer(struct tegra_qspi *tqspi, struct val = QSPI_DMA_BLK_SET(tqspi->curr_dma_words - 1); tegra_qspi_writel(tqspi, val, QSPI_DMA_BLK); + /* + * Reset the cached transfer status before unmasking the IRQ for + * this chunk. The cache must represent only the IRQ for THIS + * chunk; a stale RDY from the previous chunk of a multi-chunk + * transfer would otherwise mislead tegra_qspi_handle_timeout() + * into a false-positive recovery while the new chunk is still in + * flight. Pairs with smp_load_acquire() in + * tegra_qspi_handle_timeout(). The new chunk's IRQ cannot fire + * until QSPI_DMA_CTL is written below. + */ + smp_store_release(&tqspi->trans_status, 0); tegra_qspi_unmask_irq(tqspi); if (tqspi->is_packed) @@ -736,6 +755,16 @@ static int tegra_qspi_start_cpu_based_transfer(struct tegra_qspi *qspi, struct s val = QSPI_DMA_BLK_SET(cur_words - 1); tegra_qspi_writel(qspi, val, QSPI_DMA_BLK); + /* + * Reset the cached transfer status before unmasking the IRQ for + * this chunk so the cache represents only the IRQ for THIS chunk; + * a stale RDY from the previous chunk would otherwise mislead + * tegra_qspi_handle_timeout() into a false-positive recovery + * while the new chunk is still in flight. Pairs with + * smp_load_acquire() in tegra_qspi_handle_timeout(). The new + * chunk's IRQ cannot fire until QSPI_COMMAND1 is written below. + */ + smp_store_release(&qspi->trans_status, 0); tegra_qspi_unmask_irq(qspi); qspi->is_curr_dma_xfer = false; @@ -861,6 +890,13 @@ static u32 tegra_qspi_setup_transfer_one(struct spi_device *spi, struct spi_tran tqspi->cur_rx_pos = 0; tqspi->cur_tx_pos = 0; tqspi->curr_xfer = t; + /* + * Pairs with smp_load_acquire() in tegra_qspi_handle_timeout(). + * Clearing the cached trans_status before unmasking the IRQ for + * the new transfer prevents a stale RDY bit from the previous + * transfer fooling the timeout handler into a false recovery. + */ + smp_store_release(&tqspi->trans_status, 0); spin_unlock_irqrestore(&tqspi->lock, flags); if (is_first_of_msg) { @@ -1067,40 +1103,206 @@ static irqreturn_t handle_dma_based_xfer(struct tegra_qspi *tqspi); * tegra_qspi_handle_timeout - Handle transfer timeout with hardware check * @tqspi: QSPI controller instance * - * When a timeout occurs but hardware has completed the transfer (interrupt - * was lost or delayed), manually trigger transfer completion processing. - * This avoids failing transfers that actually succeeded. + * When wait_for_completion_timeout() expires the hardware may still have + * finished the current chunk. Drain the pending bottom half and, if the + * whole transfer really did complete during the drain, consume the + * completion and report success. * - * Returns: 0 if transfer was completed, -ETIMEDOUT if real timeout + * When the bottom half advanced the transfer by only one chunk of a + * multi-chunk DMA/PIO transfer without signalling xfer_completion, a + * fallback that ran handle_{cpu,dma}_based_xfer() here would race with + * the DMA engine already moving the next chunk into the client buffer + * (spi_finalize_current_message() would then release the buffer while + * the controller is still writing memory). Fake completion is therefore + * only attempted when the current chunk is the last chunk of the + * transfer; multi-chunk continuation timeouts return -ETIMEDOUT and + * let the caller reset the controller. + * + * Returns: 0 if the transfer completed, -ETIMEDOUT otherwise. */ static int tegra_qspi_handle_timeout(struct tegra_qspi *tqspi) { + struct spi_transfer *t; + unsigned long flags; + bool is_last_chunk; + bool lost_irq_snapshot = false; irqreturn_t ret; - u32 status; - - /* Check if hardware actually completed the transfer */ - status = tegra_qspi_readl(tqspi, QSPI_TRANS_STATUS); - if (!(status & QSPI_RDY)) - return -ETIMEDOUT; + int retval; + u32 status, refreshed; + u32 lost_fifo_status = 0; + u32 lost_tx_status = 0; + u32 lost_rx_status = 0; /* - * Hardware completed but interrupt was lost/delayed. Manually - * process the completion by calling the appropriate handler. + * Snapshot both the ISR cache and (if the cache is empty) the + * live status registers BEFORE entering recovery. The recovery + * path calls tegra_qspi_mask_clear_irq() below, which performs + * W1Cs on QSPI_TRANS_STATUS and on the QSPI_FIFO_STATUS error + * bits: a lost-IRQ recovery must capture the current FIFO error + * state before the mask erases it. + * + * Cache-live-cache retry: if the initial cache load returns zero + * we fall back to a live QSPI_TRANS_STATUS read, and if that also + * returns zero we retry the cache once more. That closes the + * interleaving where an ISR on another CPU publishes trans_status + * with release semantics and then W1Cs the hardware between our + * cache load and our live load: the second cache load observes + * the now-visible release and we correctly classify the transfer + * as complete rather than reporting a false timeout. + * + * The trans_status cache is reset to zero in + * tegra_qspi_start_{cpu,dma}_based_transfer() before unmasking + * the IRQ for every chunk, so a stale RDY from the previous + * chunk of a multi-chunk transfer cannot survive into this + * check. */ + status = smp_load_acquire(&tqspi->trans_status); + if (!status) { + status = tegra_qspi_readl(tqspi, QSPI_TRANS_STATUS); + if (!status) { + /* Retry cache; pairs with release in ISR post-store. */ + status = smp_load_acquire(&tqspi->trans_status); + } else { + /* + * Live register shows RDY but the ISR cache is + * empty: either the ISR ran and cleared HW between + * our two loads (the cache retry above would have + * observed it, so we would not be here), or the IRQ + * was genuinely lost. Snapshot the live FIFO error + * status now so tegra_qspi_mask_clear_irq() below + * does not W1C it away before the manual handler + * downstream can see it. + */ + lost_fifo_status = tegra_qspi_readl(tqspi, + QSPI_FIFO_STATUS); + lost_tx_status = lost_fifo_status & + (QSPI_TX_FIFO_UNF | QSPI_TX_FIFO_OVF); + lost_rx_status = lost_fifo_status & + (QSPI_RX_FIFO_OVF | QSPI_RX_FIFO_UNF); + lost_irq_snapshot = true; + } + } + + /* + * Enter recovery unconditionally. Every expired + * wait_for_completion_timeout() must serialise against a delayed + * ISR or worker before the caller runs dma_stop() + + * device_reset() + curr_xfer clear: publishing + * recovery_in_progress under tqspi->lock, masking the controller + * IRQ, calling synchronize_irq() to drain any in-flight ISR + * (including the small-PIO hard-IRQ fastpath), and finally + * cancel_work_sync() to drain the workqueue gives us that + * serialisation regardless of whether the hardware finished. A + * genuine hardware timeout still ends up as -ETIMEDOUT further + * down, but only after ISR and workqueue activity are quiesced. + * + * cancel_work_sync() cancels a pending worker without executing + * it and waits for a currently running one to finish; the + * recovery_in_progress guard checked inside tegra_qspi_isr() + * under tqspi->lock is atomic with its queue_work() and small-PIO + * fastpath dispatch decisions, so no new bottom-half work is + * enqueued once we publish the flag. + * + * tegra_qspi_mask_clear_irq() is idempotent: its read-modify-write + * of QSPI_INTR_MASK and W1C of QSPI_TRANS_STATUS / FIFO error + * status all tolerate a double-write, so it is safe whether or + * not the ISR has already run for this transfer. + */ + spin_lock_irqsave(&tqspi->lock, flags); + WRITE_ONCE(tqspi->recovery_in_progress, true); + spin_unlock_irqrestore(&tqspi->lock, flags); + + tegra_qspi_mask_clear_irq(tqspi); + synchronize_irq(tqspi->irq); + cancel_work_sync(&tqspi->irq_work); + + if (try_wait_for_completion(&tqspi->xfer_completion)) { + retval = 0; + goto out; + } + + /* + * Re-check the cache after the drain: the worker we just drained + * may have published a completion status the entry snapshot did + * not observe (for example the ISR fired on another CPU after we + * loaded the cache but before we masked). + */ + refreshed = smp_load_acquire(&tqspi->trans_status); + if (refreshed) + status = refreshed; + + if (!(status & QSPI_RDY)) { + retval = -ETIMEDOUT; + goto out; + } + + /* + * If the ISR never ran (lost IRQ path) publish the FIFO error + * snapshot we captured before mask_clear_irq() so the manual + * handler downstream has fresh error state rather than stale + * fields from a previous chunk's ISR. + */ + if (lost_irq_snapshot) { + WRITE_ONCE(tqspi->status_reg, lost_fifo_status); + WRITE_ONCE(tqspi->tx_status, lost_tx_status); + WRITE_ONCE(tqspi->rx_status, lost_rx_status); + } + + /* + * The bottom half did not signal full completion. Either the work + * ran and advanced the transfer by one chunk (possibly arming the + * next chunk of a multi-chunk transfer), or it was cancelled + * before it could run, or the current chunk really did not + * complete. Only fake completion when the current chunk is the + * last chunk of the transfer; otherwise the DMA engine may still + * be moving the next chunk into memory, and returning 0 here would + * let seq_xfer clear curr_xfer and finalise the message while the + * hardware is still writing. + * + * The last-chunk arithmetic mirrors tegra_qspi_start_cpu_based_ + * transfer(), which uses cur_pos + curr_dma_words * bytes_per_word + * >= t->len to set is_last_pio_chunk before arming the IRQ. + */ + spin_lock_irqsave(&tqspi->lock, flags); + t = tqspi->curr_xfer; + if (!t) { + /* CPU-path handler already cleared curr_xfer */ + spin_unlock_irqrestore(&tqspi->lock, flags); + retval = 0; + goto out; + } + is_last_chunk = (tqspi->cur_pos + + tqspi->curr_dma_words * tqspi->bytes_per_word) >= t->len; + spin_unlock_irqrestore(&tqspi->lock, flags); + + if (!is_last_chunk) { + retval = -ETIMEDOUT; + goto out; + } + dev_warn_ratelimited(tqspi->dev, "QSPI interrupt timeout, but transfer complete\n"); - /* Clear the transfer status */ - status = tegra_qspi_readl(tqspi, QSPI_TRANS_STATUS); - tegra_qspi_writel(tqspi, status, QSPI_TRANS_STATUS); - - /* Manually trigger completion handler */ - if (!tqspi->is_curr_dma_xfer) + if (!READ_ONCE(tqspi->is_curr_dma_xfer)) ret = handle_cpu_based_xfer(tqspi); else ret = handle_dma_based_xfer(tqspi); - return (ret == IRQ_HANDLED) ? 0 : -EIO; + retval = (ret == IRQ_HANDLED) ? 0 : -EIO; + +out: + /* + * The drained bottom half may have unmasked the controller IRQ + * to arm the next chunk of a multi-chunk transfer. Re-mask and + * synchronize before clearing recovery_in_progress so that no + * lingering ISR can queue fresh work behind the caller's back + * (the caller's dma_stop() + device_reset() + curr_xfer clear + * runs immediately after we return on the error path). + */ + tegra_qspi_mask_clear_irq(tqspi); + synchronize_irq(tqspi->irq); + WRITE_ONCE(tqspi->recovery_in_progress, false); + return retval; } static u32 tegra_qspi_cmd_config(bool is_ddr, u8 bus_width, u8 len) @@ -1613,9 +1815,12 @@ static void tegra_qspi_work_handler(struct work_struct *work) spin_lock_irqsave(&tqspi->lock, flags); /* - * The timeout path can clear curr_xfer between the ISR queuing - * this work and the worker actually running, so re-check under - * the lock and bail if there is nothing to do. + * tegra_qspi_handle_timeout() sets recovery_in_progress under + * tqspi->lock and then calls cancel_work_sync(), so any running + * worker is drained and tegra_qspi_isr() cannot enqueue a new + * one while recovery runs. The curr_xfer NULL check catches the + * case where the timeout path already tore the transfer down + * before this work got a chance to run. */ if (!tqspi->curr_xfer) { spin_unlock_irqrestore(&tqspi->lock, flags); @@ -1657,6 +1862,7 @@ static void tegra_qspi_work_handler(struct work_struct *work) static irqreturn_t tegra_qspi_isr(int irq, void *context_data) { struct tegra_qspi *tqspi = context_data; + u32 status_reg, trans_status; if (!READ_ONCE(tqspi->curr_xfer)) { tegra_qspi_mask_clear_irq(tqspi); @@ -1664,21 +1870,53 @@ static irqreturn_t tegra_qspi_isr(int irq, void *context_data) } spin_lock(&tqspi->lock); - tqspi->status_reg = tegra_qspi_readl(tqspi, QSPI_FIFO_STATUS); - tegra_qspi_mask_clear_irq(tqspi); + status_reg = tegra_qspi_readl(tqspi, QSPI_FIFO_STATUS); + trans_status = tegra_qspi_readl(tqspi, QSPI_TRANS_STATUS); if (tqspi->cur_direction & DATA_DIR_TX) - tqspi->tx_status = tqspi->status_reg & - (QSPI_TX_FIFO_UNF | QSPI_TX_FIFO_OVF); + WRITE_ONCE(tqspi->tx_status, + status_reg & (QSPI_TX_FIFO_UNF | QSPI_TX_FIFO_OVF)); if (tqspi->cur_direction & DATA_DIR_RX) - tqspi->rx_status = tqspi->status_reg & - (QSPI_RX_FIFO_OVF | QSPI_RX_FIFO_UNF); + WRITE_ONCE(tqspi->rx_status, + status_reg & (QSPI_RX_FIFO_OVF | QSPI_RX_FIFO_UNF)); - spin_unlock(&tqspi->lock); + WRITE_ONCE(tqspi->status_reg, status_reg); + /* + * Publish trans_status with release semantics before we clear + * the hardware status in tegra_qspi_mask_clear_irq() below. That + * ordering matters for the lock-free cache read in + * tegra_qspi_handle_timeout(): if the timeout path sees the + * released trans_status it also observes the matching status_reg + * / tx_status / rx_status; if it does not yet see the released + * value it falls back to a live QSPI_TRANS_STATUS read, and that + * live read still returns QSPI_RDY because we have not cleared + * the register yet. Reversing this order would open a window + * where the cache is still zero but the hardware bit has already + * been cleared, making the fallback report a false timeout. + */ + smp_store_release(&tqspi->trans_status, trans_status); + + tegra_qspi_mask_clear_irq(tqspi); + + /* + * If tegra_qspi_handle_timeout() is draining the bottom half, + * skip queueing new work. The flag is set under tqspi->lock and + * queue_work() below happens while we still hold the lock, so + * the guard is atomic with the queue decision. Any ISR that had + * already passed this check is drained by the synchronize_irq() + * call that tegra_qspi_handle_timeout() issues after publishing + * the flag. + */ + if (READ_ONCE(tqspi->recovery_in_progress)) { + spin_unlock(&tqspi->lock); + return IRQ_HANDLED; + } queue_work(tqspi->wq, &tqspi->irq_work); + spin_unlock(&tqspi->lock); + return IRQ_HANDLED; } From 252ee24aff97cd0a14c192c200753487ed88ee24 Mon Sep 17 00:00:00 2001 From: Vishwaroop A Date: Thu, 13 Aug 2026 20:00:27 +0000 Subject: [PATCH 025/136] spi: tegra210-quad: Process small PIO transfers in hard IRQ context On heavily loaded systems, workqueue scheduling delays can exceed transfer timeouts even for high-priority queues, causing false timeouts for latency-sensitive devices like TPM despite hardware completing in microseconds. Process small PIO transfers (those that complete the whole spi_transfer in a single chunk) directly in hard IRQ context instead of deferring to the workqueue. This reduces completion latency from 1000ms+ to microseconds and matches the pattern used by other SPI drivers. To avoid touching the spi_transfer object from hard IRQ context (which would race with the synchronous teardown path that clears curr_xfer on timeout), tegra_qspi_start_cpu_based_transfer() caches the "this PIO chunk completes the whole transfer" decision into a scalar tqspi->is_last_pio_chunk *before* unmasking the IRQ. The hard-IRQ fastpath consumes that scalar with READ_ONCE() and never dereferences curr_xfer or any spi_transfer fields. Multi-chunk PIO transfers are intentionally kept on the workqueue (only the final chunk sets the flag) so the fastpath can never recurse into tegra_qspi_start_cpu_based_transfer() from hard IRQ context, and DMA transfers always go through the workqueue because their completion path sleeps on the DMA engine. The fastpath also gates on the per-IRQ tx_status / rx_status locals being zero, because handle_cpu_based_xfer()'s error path calls tegra_qspi_reset() -> device_reset(), which can sleep and must not run from hard IRQ context. is_curr_dma_xfer and is_last_pio_chunk are written from process context (the transfer-start functions) and read lock-free from the hard IRQ handler and the workqueue handler, so the writes use WRITE_ONCE() and the reads use READ_ONCE() to prevent compiler tearing and silence KCSAN data-race warnings. Signed-off-by: Vishwaroop A Link: https://patch.msgid.link/20260813200027.2711863-4-va@nvidia.com Signed-off-by: Mark Brown --- drivers/spi/spi-tegra210-quad.c | 88 +++++++++++++++++++++++++++++---- 1 file changed, 79 insertions(+), 9 deletions(-) diff --git a/drivers/spi/spi-tegra210-quad.c b/drivers/spi/spi-tegra210-quad.c index c242f56a09fd..d8cca3aa276b 100644 --- a/drivers/spi/spi-tegra210-quad.c +++ b/drivers/spi/spi-tegra210-quad.c @@ -214,6 +214,18 @@ struct tegra_qspi { unsigned int dma_buf_size; unsigned int max_buf_size; bool is_curr_dma_xfer; + /* + * Cached "this PIO chunk completes the whole transfer" decision, + * computed by tegra_qspi_start_cpu_based_transfer() before it + * unmasks the IRQ. Used by the hard IRQ small-PIO fastpath in + * place of dereferencing curr_xfer->len, so the ISR cannot touch + * the spi_transfer object even on a late IRQ that races with the + * synchronous teardown path. Multi-chunk PIO transfers always go + * through the workqueue (this flag is only set on the final + * chunk), so the fastpath cannot recurse into + * tegra_qspi_start_cpu_based_transfer() from hard IRQ context. + */ + bool is_last_pio_chunk; struct completion rx_dma_complete; struct completion tx_dma_complete; @@ -734,7 +746,13 @@ static int tegra_qspi_start_dma_based_transfer(struct tegra_qspi *tqspi, struct tegra_qspi_writel(tqspi, tqspi->command1_reg, QSPI_COMMAND1); - tqspi->is_curr_dma_xfer = true; + /* + * WRITE_ONCE() pairs with READ_ONCE() in tegra_qspi_isr() and + * tegra_qspi_work_handler(); the flag is read lock-free across + * the hard-IRQ / process-context boundary so the annotation + * prevents compiler tearing and silences KCSAN. + */ + WRITE_ONCE(tqspi->is_curr_dma_xfer, true); tqspi->dma_control_reg = val; val |= QSPI_DMA_EN; tegra_qspi_writel(tqspi, val, QSPI_DMA_CTL); @@ -755,6 +773,20 @@ static int tegra_qspi_start_cpu_based_transfer(struct tegra_qspi *qspi, struct s val = QSPI_DMA_BLK_SET(cur_words - 1); tegra_qspi_writel(qspi, val, QSPI_DMA_BLK); + /* + * Snapshot whether this PIO chunk completes the whole transfer + * before unmasking the IRQ, so the hard IRQ small-PIO fastpath + * can decide whether to drain inline without dereferencing the + * spi_transfer object. cur_pos / curr_dma_words / bytes_per_word + * are stable here: they are written by + * tegra_qspi_calculate_curr_xfer_param() earlier in this code + * path. The IRQ cannot fire until the QSPI_COMMAND1 write below + * kicks the transfer off, so this store happens-before any ISR + * that observes the unmask. + */ + WRITE_ONCE(qspi->is_last_pio_chunk, + qspi->cur_pos + qspi->curr_dma_words * qspi->bytes_per_word >= t->len); + /* * Reset the cached transfer status before unmasking the IRQ for * this chunk so the cache represents only the IRQ for THIS chunk; @@ -767,7 +799,7 @@ static int tegra_qspi_start_cpu_based_transfer(struct tegra_qspi *qspi, struct s smp_store_release(&qspi->trans_status, 0); tegra_qspi_unmask_irq(qspi); - qspi->is_curr_dma_xfer = false; + WRITE_ONCE(qspi->is_curr_dma_xfer, false); val = qspi->command1_reg; val |= QSPI_PIO; tegra_qspi_writel(qspi, val, QSPI_COMMAND1); @@ -1835,7 +1867,7 @@ static void tegra_qspi_work_handler(struct work_struct *work) * DMA handler also needs to sleep in wait_for_completion_*(), which * cannot be done while holding spinlock. */ - if (!tqspi->is_curr_dma_xfer) + if (!READ_ONCE(tqspi->is_curr_dma_xfer)) handle_cpu_based_xfer(tqspi); else handle_dma_based_xfer(tqspi); @@ -1863,6 +1895,7 @@ static irqreturn_t tegra_qspi_isr(int irq, void *context_data) { struct tegra_qspi *tqspi = context_data; u32 status_reg, trans_status; + u32 tx_status = 0, rx_status = 0; if (!READ_ONCE(tqspi->curr_xfer)) { tegra_qspi_mask_clear_irq(tqspi); @@ -1873,13 +1906,15 @@ static irqreturn_t tegra_qspi_isr(int irq, void *context_data) status_reg = tegra_qspi_readl(tqspi, QSPI_FIFO_STATUS); trans_status = tegra_qspi_readl(tqspi, QSPI_TRANS_STATUS); - if (tqspi->cur_direction & DATA_DIR_TX) - WRITE_ONCE(tqspi->tx_status, - status_reg & (QSPI_TX_FIFO_UNF | QSPI_TX_FIFO_OVF)); + if (tqspi->cur_direction & DATA_DIR_TX) { + tx_status = status_reg & (QSPI_TX_FIFO_UNF | QSPI_TX_FIFO_OVF); + WRITE_ONCE(tqspi->tx_status, tx_status); + } - if (tqspi->cur_direction & DATA_DIR_RX) - WRITE_ONCE(tqspi->rx_status, - status_reg & (QSPI_RX_FIFO_OVF | QSPI_RX_FIFO_UNF)); + if (tqspi->cur_direction & DATA_DIR_RX) { + rx_status = status_reg & (QSPI_RX_FIFO_OVF | QSPI_RX_FIFO_UNF); + WRITE_ONCE(tqspi->rx_status, rx_status); + } WRITE_ONCE(tqspi->status_reg, status_reg); /* @@ -1913,6 +1948,41 @@ static irqreturn_t tegra_qspi_isr(int irq, void *context_data) return IRQ_HANDLED; } + /* + * Small-PIO fastpath: drain the FIFO inline only when this chunk + * completes the entire outstanding transfer and no error bit was + * latched, to avoid workqueue scheduling latency for TPM-style + * short reads. + * + * The "last chunk" decision is computed and cached as a scalar by + * tegra_qspi_start_cpu_based_transfer() before it unmasks the IRQ, + * so the hard-IRQ fastpath never dereferences the spi_transfer + * pointer here. That keeps the ISR safe against any teardown race + * where the synchronous path could clear curr_xfer concurrently. + * + * The fastpath dispatch decision is made while still holding + * tqspi->lock, so the recovery_in_progress guard above covers it + * atomically with queue_work() below: an ISR that reaches the + * fastpath cannot race a tegra_qspi_handle_timeout() that + * subsequently observes recovery_in_progress == true, because + * that path calls synchronize_irq() before proceeding. We drop + * the lock before calling handle_cpu_based_xfer() so it can take + * tqspi->lock internally without deadlocking. + * + * Multi-chunk PIO continuation stays on the workqueue so that + * tegra_qspi_start_cpu_based_transfer() can re-arm the IRQ from + * process context. DMA transfers also stay on the workqueue + * because their completion path sleeps on the DMA engine. + * tegra_qspi_handle_error() -> device_reset() can sleep, so the + * fastpath only runs when both status words are clean. + */ + if (!READ_ONCE(tqspi->is_curr_dma_xfer) && + READ_ONCE(tqspi->is_last_pio_chunk) && + !tx_status && !rx_status) { + spin_unlock(&tqspi->lock); + return handle_cpu_based_xfer(tqspi); + } + queue_work(tqspi->wq, &tqspi->irq_work); spin_unlock(&tqspi->lock); From c595dcbce20f55c98cd9b0003c26db3787e0ddee Mon Sep 17 00:00:00 2001 From: Li Youhong Date: Mon, 31 Aug 2026 17:45:53 +0800 Subject: [PATCH 026/136] spi: amlogic-spisg: check clk_prepare_enable() return value The driver ignored clk_prepare_enable() failures for sclk during probe and for core/sclk during runtime resume. Propagate the errors and, on resume, disable core if enabling sclk fails, so probe/resume do not continue with clocks disabled or report success falsely. Fixes: cef9991e04ae ("spi: Add Amlogic SPISG driver") Signed-off-by: Li Youhong Link: https://patch.msgid.link/20260831094553.2247003-1-dayou5941@163.com Signed-off-by: Mark Brown --- drivers/spi/spi-amlogic-spisg.c | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/drivers/spi/spi-amlogic-spisg.c b/drivers/spi/spi-amlogic-spisg.c index 5ace2c927da0..1b00f34e9e93 100644 --- a/drivers/spi/spi-amlogic-spisg.c +++ b/drivers/spi/spi-amlogic-spisg.c @@ -754,7 +754,9 @@ static int aml_spisg_clk_init(struct spisg_device *spisg, void __iomem *base) return PTR_ERR(spisg->sclk); } - clk_prepare_enable(spisg->sclk); + ret = clk_prepare_enable(spisg->sclk); + if (ret) + return ret; return 0; } @@ -901,9 +903,18 @@ static int spisg_suspend_runtime(struct device *dev) static int spisg_resume_runtime(struct device *dev) { struct spisg_device *spisg = dev_get_drvdata(dev); + int ret; + + ret = clk_prepare_enable(spisg->core); + if (ret) + return ret; + + ret = clk_prepare_enable(spisg->sclk); + if (ret) { + clk_disable_unprepare(spisg->core); + return ret; + } - clk_prepare_enable(spisg->core); - clk_prepare_enable(spisg->sclk); pinctrl_pm_select_default_state(&spisg->pdev->dev); return 0; From 34e32e9ade58e32daf933974c9b697d066fc5aaa Mon Sep 17 00:00:00 2001 From: Jiawen Liu <1298662399@qq.com> Date: Tue, 18 Aug 2026 17:05:50 +0400 Subject: [PATCH 027/136] spi: rockchip-sfc: disable runtime PM in remove rockchip_sfc_probe enables runtime PM and leaves the device active with a usage count held by pm_runtime_get_noresume(). The remove callback only disables the clocks and does not disable runtime PM, leaving the device in an inconsistent state and leaking the runtime PM reference. Add the missing runtime PM teardown in rockchip_sfc_remove to balance the probe's enable and get_noresume calls. Signed-off-by: jiawen <1298662399@qq.com> Link: https://patch.msgid.link/tencent_DD16C0EEDFEF4ACAFAB5A23EC8235D462707@qq.com Signed-off-by: Mark Brown --- drivers/spi/spi-rockchip-sfc.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/spi/spi-rockchip-sfc.c b/drivers/spi/spi-rockchip-sfc.c index 662a994da60b..43e91b49bb1d 100644 --- a/drivers/spi/spi-rockchip-sfc.c +++ b/drivers/spi/spi-rockchip-sfc.c @@ -743,6 +743,11 @@ static void rockchip_sfc_remove(struct platform_device *pdev) struct spi_controller *host = sfc->host; spi_unregister_controller(host); + pm_runtime_get_sync(&pdev->dev); + pm_runtime_put_noidle(&pdev->dev); + pm_runtime_disable(&pdev->dev); + pm_runtime_set_suspended(&pdev->dev); + pm_runtime_dont_use_autosuspend(&pdev->dev); dma_unmap_single(&pdev->dev, sfc->dma_buffer, sfc->max_iosize, DMA_BIDIRECTIONAL); free_pages((unsigned long)sfc->buffer, get_order(sfc->max_iosize)); From 44d39535cd4f7343284336c7288018db2a167bde Mon Sep 17 00:00:00 2001 From: Gabor Juhos Date: Tue, 8 Sep 2026 22:23:55 +0200 Subject: [PATCH 028/136] spi: spi-qpic-snand: remove interim 'dev_data' variable from qcom_spi_probe() The dev_data variable in the qcom_spi_probe() function is only used to temporarily store a pointer of the device specific data before that value gets assigned to 'snandc->props'. Remove the interim variable and use 'snandc->props' directly instead in order to simplify the code. No functional changes. Signed-off-by: Gabor Juhos Link: https://patch.msgid.link/20260908-qpic-snand-drop-dev_data-var-v1-1-147d3fab6c48@gmail.com Signed-off-by: Mark Brown --- drivers/spi/spi-qpic-snand.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/drivers/spi/spi-qpic-snand.c b/drivers/spi/spi-qpic-snand.c index 61b1f2eb19ce..9eafaea74f6b 100644 --- a/drivers/spi/spi-qpic-snand.c +++ b/drivers/spi/spi-qpic-snand.c @@ -1585,7 +1585,6 @@ static int qcom_spi_probe(struct platform_device *pdev) struct qpic_spi_nand *qspi; struct qpic_ecc *ecc; struct resource *res; - const void *dev_data; int ret; ecc = devm_kzalloc(dev, sizeof(*ecc), GFP_KERNEL); @@ -1613,14 +1612,12 @@ static int qcom_spi_probe(struct platform_device *pdev) snandc->qspi->ctlr = ctlr; snandc->qspi->ecc = ecc; - dev_data = of_device_get_match_data(dev); - if (!dev_data) { + snandc->props = of_device_get_match_data(dev); + if (!snandc->props) { dev_err(&pdev->dev, "failed to get device data\n"); return -ENODEV; } - snandc->props = dev_data; - snandc->core_clk = devm_clk_get_enabled(dev, "core"); if (IS_ERR(snandc->core_clk)) return PTR_ERR(snandc->core_clk); From 6ca4474f5a362c502182d737589942bbd282330d Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Thu, 10 Sep 2026 05:32:19 +0200 Subject: [PATCH 029/136] spi: docs: Use the multi-lane mode constants in examples The multi-lane documentation names SPI_MULTI_BUS_MODE_* constants, including in the transfer initializers. The interface introduced with the document uses SPI_MULTI_LANE_MODE_*, so the examples refer to undefined macros. Use the interface's constants in the descriptions and examples. Fixes: 05c3bd745bb0 ("spi: Documentation: add page on multi-lane support") Assisted-by: LLM Signed-off-by: Karl Mehltretter Reviewed-by: Randy Dunlap Link: https://patch.msgid.link/20260910033219.15996-1-kmehltretter@gmail.com Signed-off-by: Mark Brown --- Documentation/spi/multiple-data-lanes.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Documentation/spi/multiple-data-lanes.rst b/Documentation/spi/multiple-data-lanes.rst index 69cb532d052f..802aece35d56 100644 --- a/Documentation/spi/multiple-data-lanes.rst +++ b/Documentation/spi/multiple-data-lanes.rst @@ -127,7 +127,7 @@ field to indicate which mode they want to use for a given transfer. The possible values for this field have the following semantics: -- :c:macro:`SPI_MULTI_BUS_MODE_SINGLE`: Only use the first lane. Other lanes are +- :c:macro:`SPI_MULTI_LANE_MODE_SINGLE`: Only use the first lane. Other lanes are ignored. This means that it is operating just like a conventional SPI peripheral. This is the default, so it does not need to be explicitly set. @@ -149,7 +149,7 @@ The possible values for this field have the following semantics: ---------- ---------------- ---------- SDO 0 0-0-0-1-0-0-0-1 SDI 0 -- :c:macro:`SPI_MULTI_BUS_MODE_MIRROR`: Send a single data word over all of the +- :c:macro:`SPI_MULTI_LANE_MODE_MIRROR`: Send a single data word over all of the lanes at the same time. This only makes sense for writes and not for reads. @@ -160,7 +160,7 @@ The possible values for this field have the following semantics: struct spi_transfer xfer = { .tx_buf = tx_buf, .len = 1, - .multi_lane_mode = SPI_MULTI_BUS_MODE_MIRROR, + .multi_lane_mode = SPI_MULTI_LANE_MODE_MIRROR, }; spi_sync_transfer(spi, &xfer, 1); @@ -172,7 +172,7 @@ The possible values for this field have the following semantics: SDO 0 0-0-0-1-0-0-0-1 SDI 0 SDO 1 0-0-0-1-0-0-0-1 SDI 1 -- :c:macro:`SPI_MULTI_BUS_MODE_STRIPE`: Send or receive two different data words +- :c:macro:`SPI_MULTI_LANE_MODE_STRIPE`: Send or receive two different data words at the same time, one on each lane. This means that the buffer needs to be sized to hold data for all lanes. Data is interleaved in the buffer, with the first word corresponding to lane 0, the second to lane 1, and so on. @@ -185,7 +185,7 @@ The possible values for this field have the following semantics: struct spi_transfer xfer = { .rx_buf = rx_buf, .len = 2, - .multi_lane_mode = SPI_MULTI_BUS_MODE_STRIPE, + .multi_lane_mode = SPI_MULTI_LANE_MODE_STRIPE, }; spi_sync_transfer(spi, &xfer, 1); From 3742867ce96539100ed5f4c754f04273d8aa00d7 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sat, 12 Sep 2026 01:02:11 +0200 Subject: [PATCH 030/136] spi: Fix the return value in spi_new_ancillary_device() kernel-doc spi_new_ancillary_device() returns the new struct spi_device, or an ERR_PTR() on failure, but its kernel-doc says "0 on success; negative errno on failure", which was never true. Describe the pointer. Fixes: 0c79378c0199 ("spi: add ancillary device support") Assisted-by: LLM Signed-off-by: Karl Mehltretter Link: https://patch.msgid.link/20260911230211.11755-1-kmehltretter@gmail.com Signed-off-by: Mark Brown --- drivers/spi/spi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c index 5b5b3bc5f0d8..172141650b64 100644 --- a/drivers/spi/spi.c +++ b/drivers/spi/spi.c @@ -2893,7 +2893,7 @@ static void of_register_spi_devices(struct spi_controller *ctlr) { } * * This may only be called from main SPI device's probe routine. * - * Return: 0 on success; negative errno on failure + * Return: the new device on success; an ERR_PTR() on failure */ struct spi_device *spi_new_ancillary_device(struct spi_device *spi, u8 chip_select) From 4b258adc049d5c52e9e316eec2102ca821516cd1 Mon Sep 17 00:00:00 2001 From: Gabor Juhos Date: Tue, 8 Sep 2026 21:53:16 +0200 Subject: [PATCH 031/136] spi: spi-qpic-snand: remove unnecessary cast to '__le32 *' Remove an unnecessary cast to '__le32 *' in the qcom_spi_io_op() function. The 'reg_read_buf' member of the 'qcom_nand_controller' structure is defined as '__le32 *' already, so the cast is not needed. Signed-off-by: Gabor Juhos Link: https://patch.msgid.link/20260908-qpic-snand-unnecessary-cast-v1-1-26df1cdd4bb6@gmail.com Signed-off-by: Mark Brown --- drivers/spi/spi-qpic-snand.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/spi/spi-qpic-snand.c b/drivers/spi/spi-qpic-snand.c index 9eafaea74f6b..cfcf51f4b249 100644 --- a/drivers/spi/spi-qpic-snand.c +++ b/drivers/spi/spi-qpic-snand.c @@ -1485,7 +1485,7 @@ static int qcom_spi_io_op(struct qcom_nand_controller *snandc, const struct spi_ if (copy_ftr) { qcom_nandc_dev_to_mem(snandc, true); - val = le32_to_cpu(*(__le32 *)snandc->reg_read_buf); + val = le32_to_cpu(*snandc->reg_read_buf); val >>= 8; memcpy(op->data.buf.in, &val, snandc->buf_count); From 26f42ca8becd01877e762916865a168c1b6c4ff4 Mon Sep 17 00:00:00 2001 From: Francesco Valla Date: Tue, 15 Sep 2026 22:24:36 +0200 Subject: [PATCH 032/136] spi: virtio: fix max frequency setting The maximum transfer frequency is read from the virtio config space but never propagated to the SPI framework logic. Fix this behavior and drop the useless setting copy from the driver's private data. Fixes: f98cabe3f6cf ("SPI: Add virtio SPI driver") Signed-off-by: Francesco Valla Link: https://patch.msgid.link/20260915-virtio-spi-fix2-v1-1-7a474cb1b13b@valla.it Signed-off-by: Mark Brown --- drivers/spi/spi-virtio.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/spi/spi-virtio.c b/drivers/spi/spi-virtio.c index 3e181bd8bc94..96cdaf1b3697 100644 --- a/drivers/spi/spi-virtio.c +++ b/drivers/spi/spi-virtio.c @@ -33,8 +33,6 @@ struct virtio_spi_priv { struct virtqueue *vq; /* Copy of config space mode_func_supported */ u32 mode_func_supported; - /* Copy of config space max_freq_hz */ - u32 max_freq_hz; }; static void virtio_spi_msg_done(struct virtqueue *vq) @@ -304,7 +302,7 @@ static void virtio_spi_read_config(struct virtio_device *vdev) virtio_cread32(vdev, offsetof(struct virtio_spi_config, bits_per_word_mask)); - priv->max_freq_hz = + ctrl->max_speed_hz = virtio_cread32(vdev, offsetof(struct virtio_spi_config, max_freq_hz)); } From 62b223cf4334c646bbb0b842ca0ff13c5cd0e875 Mon Sep 17 00:00:00 2001 From: Francesco Valla Date: Tue, 15 Sep 2026 22:24:37 +0200 Subject: [PATCH 033/136] spi: virtio: drop unused field from private data The mode_func_supported field is read from the config space into the driver's private data, but then never used outside of the function it is read in. Drop the variable from the private data and parse it from the stack instead. Signed-off-by: Francesco Valla Link: https://patch.msgid.link/20260915-virtio-spi-fix2-v1-2-7a474cb1b13b@valla.it Signed-off-by: Mark Brown --- drivers/spi/spi-virtio.c | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/drivers/spi/spi-virtio.c b/drivers/spi/spi-virtio.c index 96cdaf1b3697..cdee3a78979b 100644 --- a/drivers/spi/spi-virtio.c +++ b/drivers/spi/spi-virtio.c @@ -31,8 +31,6 @@ struct virtio_spi_priv { struct virtio_device *vdev; /* Pointer to the virtqueue */ struct virtqueue *vq; - /* Copy of config space mode_func_supported */ - u32 mode_func_supported; }; static void virtio_spi_msg_done(struct virtqueue *vq) @@ -256,7 +254,7 @@ static int virtio_spi_transfer_one(struct spi_controller *ctrl, static void virtio_spi_read_config(struct virtio_device *vdev) { struct spi_controller *ctrl = dev_get_drvdata(&vdev->dev); - struct virtio_spi_priv *priv = vdev->priv; + u32 mode_func_supported; u8 cs_max_number; u8 tx_nbits_supported; u8 rx_nbits_supported; @@ -266,18 +264,18 @@ static void virtio_spi_read_config(struct virtio_device *vdev) ctrl->num_chipselect = cs_max_number; /* Set the mode bits which are understood by this driver */ - priv->mode_func_supported = + mode_func_supported = virtio_cread32(vdev, offsetof(struct virtio_spi_config, mode_func_supported)); - ctrl->mode_bits = priv->mode_func_supported & + ctrl->mode_bits = mode_func_supported & (VIRTIO_SPI_CS_HIGH | VIRTIO_SPI_MODE_LSB_FIRST); - if (priv->mode_func_supported & VIRTIO_SPI_MF_SUPPORT_CPHA_1) + if (mode_func_supported & VIRTIO_SPI_MF_SUPPORT_CPHA_1) ctrl->mode_bits |= VIRTIO_SPI_CPHA; - if (priv->mode_func_supported & VIRTIO_SPI_MF_SUPPORT_CPOL_1) + if (mode_func_supported & VIRTIO_SPI_MF_SUPPORT_CPOL_1) ctrl->mode_bits |= VIRTIO_SPI_CPOL; - if (priv->mode_func_supported & VIRTIO_SPI_MF_SUPPORT_LSB_FIRST) + if (mode_func_supported & VIRTIO_SPI_MF_SUPPORT_LSB_FIRST) ctrl->mode_bits |= SPI_LSB_FIRST; - if (priv->mode_func_supported & VIRTIO_SPI_MF_SUPPORT_LOOPBACK) + if (mode_func_supported & VIRTIO_SPI_MF_SUPPORT_LOOPBACK) ctrl->mode_bits |= SPI_LOOP; tx_nbits_supported = virtio_cread8(vdev, offsetof(struct virtio_spi_config, From f94c9b68bb5fdbef9f841da493d5305ff634ebb5 Mon Sep 17 00:00:00 2001 From: Johan Alvarado Date: Fri, 11 Sep 2026 13:44:15 -0500 Subject: [PATCH 034/136] spi: spi-qpic-snand: publish the ECC context to snandc->qspi qcom_spi_ooblayout_ecc() and qcom_spi_ooblayout_free() read the ECC configuration through snandc->qspi->ecc. qcom_spi_probe() points it at a zeroed scratch struct and only qcom_spi_ecc_prepare_io_req_pipelined(), which runs on page I/O, ever updates it. qcom_spi_ecc_init_ctx_pipelined() installs the ooblayout but does not publish the context it just allocated, and qcom_spi_ecc_cleanup_ctx_pipelined() frees that context without clearing the pointer. spinand_init() calls mtd_ooblayout_count_freebytes() right after the ECC context is created and before any page I/O, so the ooblayout always runs against a pointer that does not describe the current context: - On a first probe it reads the zeroed struct from qcom_spi_probe(), so steps, bytes and bbm_size are 0. The count then returns 0 rather than an error, so the probe continues with mtd->oobavail set to 0. - On a probe retry it reads the ecc_cfg the previous attempt freed. A retry is easy to hit. On IPQ5018 with the qcom,smem-part parser the partition parse returns -EPROBE_DEFER until SMEM has probed, so the first spi-nand probe defers. It defers inside mtd_device_parse_register(), after mtd_otp_nvmem_add() has already read the factory OTP - that read goes through prepare_io_req and leaves snandc->qspi->ecc pointing at the context that spinand_cleanup() then frees. The second probe allocates a new context, never publishes it, and computes the OOB layout from the freed one. Once the slab has been reused, qecc->steps holds garbage and oobregion->length = qecc->steps * 4; goes negative. qcom_spi_ooblayout_free() only reports -ERANGE for section 1 and later, so mtd_ooblayout_count_bytes() sums the regions and returns that negative length as the byte count. The -512 below is steps * 4 with steps == -128. It is a byte count that happens to collide with -ERESTARTSYS, not an error the driver returned. spinand_init() takes it as an error, and because it is not -EPROBE_DEFER the driver core never retries and the NAND never appears: spi-nand spi0.0: ESMT SPI NAND was found. spi-nand spi0.0: probe with driver spi-nand failed with error -512 UBI error: cannot open mtd rootfs, error -2 Waiting for root device /dev/ubiblock0_1... On a Mercusys MR80X (IPQ5018, ESMT F50D1G41LB) about half of the boots failed to mount the rootfs, the outcome depending on whether the freed memory had been overwritten yet. Publish the context when it is created and clear the pointer when it is destroyed. Clearing leaves snandc->qspi->ecc NULL after cleanup, which is safe: the mtd is unregistered before cleanup_ctx runs, so no ooblayout callback can follow. Fixes: 7304d1909080 ("spi: spi-qpic: add driver for QCOM SPI NAND flash Interface") Cc: stable@vger.kernel.org Tested-by: Gabor Juhos Signed-off-by: Johan Alvarado Link: https://patch.msgid.link/20260911184416.109790-2-contact@c127.dev Signed-off-by: Mark Brown --- drivers/spi/spi-qpic-snand.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/spi/spi-qpic-snand.c b/drivers/spi/spi-qpic-snand.c index cfcf51f4b249..e96a2bdbb69d 100644 --- a/drivers/spi/spi-qpic-snand.c +++ b/drivers/spi/spi-qpic-snand.c @@ -411,6 +411,8 @@ static int qcom_spi_ecc_init_ctx_pipelined(struct nand_device *nand) dev_dbg(snandc->dev, "ECC strength: %u bits per %u bytes\n", ecc_cfg->strength, ecc_cfg->step_size); + snandc->qspi->ecc = ecc_cfg; + return 0; err_free_ecc_cfg: @@ -427,6 +429,7 @@ static void qcom_spi_ecc_cleanup_ctx_pipelined(struct nand_device *nand) kfree(snandc->qspi->oob_buf); snandc->qspi->oob_buf = NULL; + snandc->qspi->ecc = NULL; kfree(ecc_cfg); } From 8d691164547832f43289a7d5f3450738ead98c2d Mon Sep 17 00:00:00 2001 From: Johan Alvarado Date: Fri, 11 Sep 2026 13:44:16 -0500 Subject: [PATCH 035/136] spi: spi-qpic-snand: drop the redundant ECC context handling qcom_spi_ecc_init_ctx_pipelined() now publishes the ECC context to snandc->qspi->ecc, so the assignment in qcom_spi_ecc_prepare_io_req_pipelined() repeats what the pointer already holds, and the zeroed struct qpic_ecc that qcom_spi_probe() allocates is never read. The pointer is non-NULL only between context creation and destruction, and every reader runs inside that window. The ooblayout callbacks are installed by init_ctx. The page read, write and program helpers run only when prepare_io_req has set page_rw or oob_rw. qcom_spi_block_erase() runs only while the mtd is registered, which happens after init_ctx and ends before cleanup_ctx. The controller drives a single chip select, so the per-controller pointer and the per-chip context cannot disagree. Remove both. No functional change. Suggested-by: Gabor Juhos Signed-off-by: Johan Alvarado Tested-by: Gabor Juhos Link: https://patch.msgid.link/20260911184416.109790-3-contact@c127.dev Signed-off-by: Mark Brown --- drivers/spi/spi-qpic-snand.c | 8 -------- 1 file changed, 8 deletions(-) diff --git a/drivers/spi/spi-qpic-snand.c b/drivers/spi/spi-qpic-snand.c index e96a2bdbb69d..c3cdc43daa21 100644 --- a/drivers/spi/spi-qpic-snand.c +++ b/drivers/spi/spi-qpic-snand.c @@ -437,9 +437,7 @@ static int qcom_spi_ecc_prepare_io_req_pipelined(struct nand_device *nand, struct nand_page_io_req *req) { struct qcom_nand_controller *snandc = nand_to_qcom_snand(nand); - struct qpic_ecc *ecc_cfg = nand_to_ecc_ctx(nand); - snandc->qspi->ecc = ecc_cfg; snandc->qspi->raw_rw = false; snandc->qspi->oob_rw = false; snandc->qspi->page_rw = false; @@ -1586,14 +1584,9 @@ static int qcom_spi_probe(struct platform_device *pdev) struct spi_controller *ctlr; struct qcom_nand_controller *snandc; struct qpic_spi_nand *qspi; - struct qpic_ecc *ecc; struct resource *res; int ret; - ecc = devm_kzalloc(dev, sizeof(*ecc), GFP_KERNEL); - if (!ecc) - return -ENOMEM; - qspi = devm_kzalloc(dev, sizeof(*qspi), GFP_KERNEL); if (!qspi) return -ENOMEM; @@ -1613,7 +1606,6 @@ static int qcom_spi_probe(struct platform_device *pdev) snandc->dev = dev; snandc->qspi = qspi; snandc->qspi->ctlr = ctlr; - snandc->qspi->ecc = ecc; snandc->props = of_device_get_match_data(dev); if (!snandc->props) { From 648e3c436032b675b339662ea9c0edd9221add76 Mon Sep 17 00:00:00 2001 From: Felix Gu Date: Wed, 2 Sep 2026 22:48:09 +0800 Subject: [PATCH 036/136] spi: mxic: fix ps_clk leak in runtime resume mxic_spi_runtime_resume() enables ps_clk before calling mxic_spi_clk_enable(). If the latter fails, ps_clk was not disabled. Unwind ps_clk with clk_disable_unprepare() before returning the error. Fixes: b942d80b0a39 ("spi: Add MXIC controller driver") Signed-off-by: Felix Gu Link: https://patch.msgid.link/20260902-mxic-v1-1-76806325826d@gmail.com Signed-off-by: Mark Brown --- drivers/spi/spi-mxic.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/spi/spi-mxic.c b/drivers/spi/spi-mxic.c index e3c85e5191e3..481edd4b35cb 100644 --- a/drivers/spi/spi-mxic.c +++ b/drivers/spi/spi-mxic.c @@ -744,7 +744,13 @@ static int mxic_spi_runtime_resume(struct device *dev) return ret; } - return mxic_spi_clk_enable(mxic); + ret = mxic_spi_clk_enable(mxic); + if (ret) { + clk_disable_unprepare(mxic->ps_clk); + return ret; + } + + return 0; } static const struct dev_pm_ops mxic_spi_dev_pm_ops = { From 001cdc434aa89d1e6c7ea3e5e118167e02dffe20 Mon Sep 17 00:00:00 2001 From: Gabor Juhos Date: Fri, 18 Sep 2026 21:53:08 +0200 Subject: [PATCH 037/136] spi: spi-qpic-snand: add qcom_spi_config_page_read() helper Move the register write sequence needed for configuring a page read into a separate function to reduce code duplication. No functional changes. Signed-off-by: Gabor Juhos Link: https://patch.msgid.link/20260918-qpic-snand-config_page_read-v1-1-a686c4657857@gmail.com Signed-off-by: Mark Brown --- drivers/spi/spi-qpic-snand.c | 45 ++++++++++++------------------------ 1 file changed, 15 insertions(+), 30 deletions(-) diff --git a/drivers/spi/spi-qpic-snand.c b/drivers/spi/spi-qpic-snand.c index 9e626ffbe63a..aaa40753d274 100644 --- a/drivers/spi/spi-qpic-snand.c +++ b/drivers/spi/spi-qpic-snand.c @@ -501,6 +501,17 @@ static void qcom_spi_set_read_loc(struct qcom_nand_controller *snandc, int cw, i read_size, is_last_read_loc); } +static void qcom_spi_config_page_read(struct qcom_nand_controller *snandc) +{ + qcom_write_reg_dma(snandc, &snandc->regs->addr0, NAND_ADDR0, 2, 0); + qcom_write_reg_dma(snandc, &snandc->regs->cfg0, NAND_DEV0_CFG0, 3, 0); + qcom_write_reg_dma(snandc, &snandc->regs->erased_cw_detect_cfg_clr, + NAND_ERASED_CW_DETECT_CFG, 1, 0); + qcom_write_reg_dma(snandc, &snandc->regs->erased_cw_detect_cfg_set, + NAND_ERASED_CW_DETECT_CFG, 1, + NAND_ERASED_CW_SET | NAND_BAM_NEXT_SGL); +} + static void qcom_spi_config_cw_read(struct qcom_nand_controller *snandc, bool use_ecc, int cw) { @@ -564,13 +575,7 @@ static void qcom_spi_config_single_cw_page_read(struct qcom_nand_controller *sna __le32 *reg = &snandc->regs->read_location0; int num_cw = snandc->qspi->num_cw; - qcom_write_reg_dma(snandc, &snandc->regs->addr0, NAND_ADDR0, 2, 0); - qcom_write_reg_dma(snandc, &snandc->regs->cfg0, NAND_DEV0_CFG0, 3, 0); - qcom_write_reg_dma(snandc, &snandc->regs->erased_cw_detect_cfg_clr, - NAND_ERASED_CW_DETECT_CFG, 1, 0); - qcom_write_reg_dma(snandc, &snandc->regs->erased_cw_detect_cfg_set, - NAND_ERASED_CW_DETECT_CFG, 1, - NAND_ERASED_CW_SET | NAND_BAM_NEXT_SGL); + qcom_spi_config_page_read(snandc); if (cw == (num_cw - 1)) { reg = &snandc->regs->read_location_last0; @@ -763,14 +768,7 @@ static int qcom_spi_read_cw_raw(struct qcom_nand_controller *snandc, u8 *data_bu snandc->regs->exec = cpu_to_le32(1); qcom_spi_set_read_loc(snandc, raw_cw, 0, 0, ecc_cfg->cw_size, 1); - - qcom_write_reg_dma(snandc, &snandc->regs->addr0, NAND_ADDR0, 2, 0); - qcom_write_reg_dma(snandc, &snandc->regs->cfg0, NAND_DEV0_CFG0, 3, 0); - qcom_write_reg_dma(snandc, &snandc->regs->erased_cw_detect_cfg_clr, - NAND_ERASED_CW_DETECT_CFG, 1, 0); - qcom_write_reg_dma(snandc, &snandc->regs->erased_cw_detect_cfg_set, - NAND_ERASED_CW_DETECT_CFG, 1, - NAND_ERASED_CW_SET | NAND_BAM_NEXT_SGL); + qcom_spi_config_page_read(snandc); data_size1 = mtd->writesize - ecc_cfg->cw_size * (num_cw - 1); oob_size1 = ecc_cfg->bbm_size; @@ -875,14 +873,7 @@ static int qcom_spi_read_page_ecc(struct qcom_nand_controller *snandc, snandc->regs->exec = cpu_to_le32(1); qcom_clear_bam_transaction(snandc); - - qcom_write_reg_dma(snandc, &snandc->regs->addr0, NAND_ADDR0, 2, 0); - qcom_write_reg_dma(snandc, &snandc->regs->cfg0, NAND_DEV0_CFG0, 3, 0); - qcom_write_reg_dma(snandc, &snandc->regs->erased_cw_detect_cfg_clr, - NAND_ERASED_CW_DETECT_CFG, 1, 0); - qcom_write_reg_dma(snandc, &snandc->regs->erased_cw_detect_cfg_set, - NAND_ERASED_CW_DETECT_CFG, 1, - NAND_ERASED_CW_SET | NAND_BAM_NEXT_SGL); + qcom_spi_config_page_read(snandc); for (i = 0; i < num_cw; i++) { int data_size, oob_size; @@ -963,13 +954,7 @@ static int qcom_spi_read_page_oob(struct qcom_nand_controller *snandc, snandc->regs->ecc_bch_cfg = cpu_to_le32(ecc_bch_cfg); snandc->regs->exec = cpu_to_le32(1); - qcom_write_reg_dma(snandc, &snandc->regs->addr0, NAND_ADDR0, 2, 0); - qcom_write_reg_dma(snandc, &snandc->regs->cfg0, NAND_DEV0_CFG0, 3, 0); - qcom_write_reg_dma(snandc, &snandc->regs->erased_cw_detect_cfg_clr, - NAND_ERASED_CW_DETECT_CFG, 1, 0); - qcom_write_reg_dma(snandc, &snandc->regs->erased_cw_detect_cfg_set, - NAND_ERASED_CW_DETECT_CFG, 1, - NAND_ERASED_CW_SET | NAND_BAM_NEXT_SGL); + qcom_spi_config_page_read(snandc); for (i = 0; i < num_cw; i++) { int data_size, oob_size; From 3c82e199d411af3a0fff00ca3c54baaf04abbbb3 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Sun, 20 Sep 2026 21:47:13 -0700 Subject: [PATCH 038/136] spi: spi-master: use literal blocks for commands Use literal blocks for console I/O to separate it from the surrounding text. Otherwise the lines run together, without any clear break before or after the commands. Signed-off-by: Randy Dunlap Link: https://patch.msgid.link/20260921044713.93122-1-rdunlap@infradead.org Signed-off-by: Mark Brown --- Documentation/ABI/testing/sysfs-class-spi-master | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Documentation/ABI/testing/sysfs-class-spi-master b/Documentation/ABI/testing/sysfs-class-spi-master index 0a524fcd96d2..4e1e8a7ca83c 100644 --- a/Documentation/ABI/testing/sysfs-class-spi-master +++ b/Documentation/ABI/testing/sysfs-class-spi-master @@ -17,7 +17,8 @@ Description: (WO) Instantiate a new SPI device on bus B, where B with delete_device; platform and DT devices are not affected. - Example: + 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 @@ -30,5 +31,6 @@ 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: + Example:: + # echo 0 > /sys/class/spi_master/spi0/delete_device From 2c68b2403acd1cbf0b19c71dab9858cd2ca6bbb5 Mon Sep 17 00:00:00 2001 From: Frieder Schrempf Date: Mon, 21 Sep 2026 12:16:44 +0200 Subject: [PATCH 039/136] spi: dt-bindings: Clarify what rx-sample-delay-ns describes The property is documented as an offset from the controller's default sampling time. That says what the value does, but not what it is meant to describe, and it leaves room for two readings: a delay that belongs to the board, or a stand-in for the peripheral's own clock-to-output-valid time, which has no other expression in a device tree today. The first reading is the original one. The property was introduced for Rockchip in commit 76b17e6e4923 ("spi/rockchip: Add device tree property to configure Rx Sample Delay") to deal with "boards with high-capacitance SPI lines", where "the controller samples the Rx data line too early". The current wording arrived later, in commit 5ce78f4456a9 ("dt-bindings: snps, dw-apb-ssi: Add sparx5 support, plus rx-sample-delay-ns property"), where it described a DesignWare register and was qualified as such. That qualification was dropped when the property was moved to the generic schema in commit b658be56e867 ("spi: dt-bindings: Move 'rx-sample-delay-ns' to spi-peripheral-props.yaml"), leaving a register description standing in for a definition. The distinction matters because the two compose. A board delay is specific to one design, while a datasheet timing parameter is the same wherever the chip is soldered, so a mechanism that derives the chip side from the chip would add to a value that already accounts for it. Spell out that the property describes the board. Nothing changes in what the value means or in how existing device trees are interpreted. Assisted-by: Claude:claude-opus-5 Signed-off-by: Frieder Schrempf Link: https://patch.msgid.link/20260921-spi-sample-delay-cleanup-v2-1-b460d572fd32@kontron.de Signed-off-by: Mark Brown --- .../devicetree/bindings/spi/spi-peripheral-props.yaml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Documentation/devicetree/bindings/spi/spi-peripheral-props.yaml b/Documentation/devicetree/bindings/spi/spi-peripheral-props.yaml index 880a9f624566..afcf9c41c058 100644 --- a/Documentation/devicetree/bindings/spi/spi-peripheral-props.yaml +++ b/Documentation/devicetree/bindings/spi/spi-peripheral-props.yaml @@ -91,6 +91,14 @@ properties: The delay from the default sample time before the actual sample of the rxd input signal occurs. + This describes the board rather than the peripheral, namely the extra + delay this particular design needs, for example because of the flight time + of the clock and data signals between controller and peripheral. + + Timing parameters of the peripheral itself, such as its + clock-to-output-valid time, are the same on every board using that chip + and should be described with the chip. + spi-tx-bus-width: description: Bus width to the SPI bus used for write transfers. From 631a5633638136add14406d5bc18ca113492cd77 Mon Sep 17 00:00:00 2001 From: Frieder Schrempf Date: Mon, 21 Sep 2026 12:16:45 +0200 Subject: [PATCH 040/136] spi: Parse the rx-sample-delay-ns peripheral property in the core "rx-sample-delay-ns" is documented in spi-peripheral-props.yaml as a generic SPI peripheral property, but the core never looks at it. Only spi-dw reads it from the peripheral node as the binding describes, and any other driver wanting to act on it would have to duplicate that. Add spi_device.rx_sample_delay_ns and parse the property once, in __spi_add_device(), which covers every way a device can be instantiated and runs before ->setup(). Use device_property_read_u32() rather than adding this to of_spi_parse_dt(). spi-dw already uses the fwnode accessor, so the property works today on ACPI and software node platforms such as the ones served by spi-dw-pci, and parsing it only in the device tree path would quietly drop it there. No functional change, since nothing reads the new field yet. Assisted-by: Claude:claude-opus-5 Signed-off-by: Frieder Schrempf Link: https://patch.msgid.link/20260921-spi-sample-delay-cleanup-v2-2-b460d572fd32@kontron.de Signed-off-by: Mark Brown --- drivers/spi/spi.c | 9 +++++++++ include/linux/spi/spi.h | 9 +++++++++ 2 files changed, 18 insertions(+) diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c index 5b5b3bc5f0d8..9e3c24ed8f9f 100644 --- a/drivers/spi/spi.c +++ b/drivers/spi/spi.c @@ -929,6 +929,15 @@ static int __spi_add_device(struct spi_device *spi, struct spi_device *parent) } } + /* + * Peripheral properties the core handles on behalf of controller + * drivers are parsed here, rather than in the firmware specific + * instantiation paths, so that device tree, ACPI and software nodes + * are covered alike, and early enough for ->setup() to act on them. + */ + device_property_read_u32(&spi->dev, "rx-sample-delay-ns", + &spi->rx_sample_delay_ns); + /* * Drivers may modify this initial i/o setup, but will * normally rely on the device being setup. Devices diff --git a/include/linux/spi/spi.h b/include/linux/spi/spi.h index 88d17fce02dc..fb4baa0d3398 100644 --- a/include/linux/spi/spi.h +++ b/include/linux/spi/spi.h @@ -169,6 +169,12 @@ extern void spi_transfer_cs_change_delay_exec(struct spi_message *msg, * @cs_inactive: delay to be introduced by the controller after CS is * deasserted. If @cs_change_delay is used from @spi_transfer, then the * two delays will be added up. + * @rx_sample_delay_ns: Delay in nanoseconds by which the controller should + * postpone sampling the incoming data, relative to the sampling point it + * uses by default. Describes the board rather than the device, namely the + * flight time of the clock and data signals between controller and + * device, and comes from the "rx-sample-delay-ns" property. Zero when the + * property is absent. * @chip_select: Array of physical chipselect, spi->chipselect[i] gives * the corresponding physical CS for logical CS i. * @num_chipselect: Number of physical chipselects used. @@ -235,6 +241,9 @@ struct spi_device { struct spi_delay cs_hold; struct spi_delay cs_inactive; + /* Additional delay before the incoming data is sampled, in ns */ + u32 rx_sample_delay_ns; + u8 chip_select[SPI_DEVICE_CS_CNT_MAX]; u8 num_chipselect; From 38d4b42e4a2af5066b602867042c10c699bd08ad Mon Sep 17 00:00:00 2001 From: Frieder Schrempf Date: Mon, 21 Sep 2026 12:16:46 +0200 Subject: [PATCH 041/136] spi: dw: Use the rx-sample-delay-ns value parsed by the core The core now parses the "rx-sample-delay-ns" peripheral property into spi_device.rx_sample_delay_ns, so drop the private copy of that parsing. The controller-wide default is deliberately left alone. It is read from the *controller* node into dws->def_rx_sample_dly_ns, which is a different node and none of the core's business when it parses properties of a peripheral. One corner case changes: the peripheral value is now treated as unset when it is zero, whereas before an absent property could be told apart from an explicit "rx-sample-delay-ns = <0>", the latter overriding a non-zero controller default with no delay at all. There are no in-tree users affected by this and it is very unlikely for out-of-tree users to set rx-sample-delay-ns on the controller level and then revert it to 0 at the chip level. We should abandon this configuration that was probably never meant to work like this and doesn't make sense. Assisted-by: Claude:claude-opus-5 Signed-off-by: Frieder Schrempf Link: https://patch.msgid.link/20260921-spi-sample-delay-cleanup-v2-3-b460d572fd32@kontron.de Signed-off-by: Mark Brown --- drivers/spi/spi-dw-core.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/drivers/spi/spi-dw-core.c b/drivers/spi/spi-dw-core.c index 206d3f9dd83d..b86b607cc817 100644 --- a/drivers/spi/spi-dw-core.c +++ b/drivers/spi/spi-dw-core.c @@ -1104,12 +1104,13 @@ static int dw_spi_setup(struct spi_device *spi) if (!chip) return -ENOMEM; spi_set_ctldata(spi, chip); - /* Get specific / default rx-sample-delay */ - if (device_property_read_u32(&spi->dev, - "rx-sample-delay-ns", - &rx_sample_dly_ns) != 0) - /* Use default controller value */ - rx_sample_dly_ns = dws->def_rx_sample_dly_ns; + /* + * Use the per-device value the core parsed from the peripheral + * node, and fall back to the controller-wide default when the + * device does not ask for a delay of its own. + */ + rx_sample_dly_ns = spi->rx_sample_delay_ns ?: + dws->def_rx_sample_dly_ns; chip->rx_sample_dly = DIV_ROUND_CLOSEST(rx_sample_dly_ns, NSEC_PER_SEC / dws->max_freq); From 1f82e6956faacc6e0c1b402838ba188e19af5a3c Mon Sep 17 00:00:00 2001 From: Jonas Jelonek Date: Mon, 21 Sep 2026 21:31:45 +0000 Subject: [PATCH 042/136] spi: realtek-rtl: add support for second CS The initial driver assumed that the controller's second chip select was vestigial because none of the devices examined at the time used it. This does not hold for all hardware: there are devices with a peripheral connected to CS1. Since the controller only advertises one chip select and keeps CS1 permanently deasserted, those peripherals cannot be used. Advertise both native chip selects and select the appropriate CSBx bit in ->set_cs(). Build the initial SFCSR control value from zero. The datasheet specifies CHIP_SEL for MMIO mode only, so keep it clear for this PIO driver and set only the two active-low CSB bits to leave both lines deasserted. Rename the callback argument from "active" to "level" while touching this code. ->set_cs() receives the physical line level, not a logical assertion state: after applying SPI_CS_HIGH, the core passes false to drive the line low and true to drive it high. The CSBx bits directly encode that level, and the native outputs are active-low, so clear the selected bit for a low/asserted line and set it for a high/deasserted line. Signed-off-by: Jonas Jelonek Link: https://patch.msgid.link/20260921213145.671282-1-jonas@jonasjelonek.de Signed-off-by: Mark Brown --- drivers/spi/spi-realtek-rtl.c | 36 +++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/drivers/spi/spi-realtek-rtl.c b/drivers/spi/spi-realtek-rtl.c index e1c40ff2e49d..7b250b447abf 100644 --- a/drivers/spi/spi-realtek-rtl.c +++ b/drivers/spi/spi-realtek-rtl.c @@ -18,7 +18,6 @@ struct rtspi { #define RTL_SPI_SFCSR_CSB0 BIT(31) #define RTL_SPI_SFCSR_CSB1 BIT(30) #define RTL_SPI_SFCSR_RDY BIT(27) -#define RTL_SPI_SFCSR_CS BIT(24) #define RTL_SPI_SFCSR_LEN_MASK ~(0x03 << 28) #define RTL_SPI_SFCSR_LEN1 (0x00 << 28) #define RTL_SPI_SFCSR_LEN4 (0x03 << 28) @@ -29,17 +28,31 @@ struct rtspi { #define REG(x) (rtspi->base + x) -static void rt_set_cs(struct spi_device *spi, bool active) +static void rt_set_cs(struct spi_device *spi, bool level) { struct rtspi *rtspi = spi_controller_get_devdata(spi->controller); - u32 value; + unsigned int cs = spi_get_chipselect(spi, 0); + u32 cs_mask, value; + + switch (cs) { + case 0: + cs_mask = RTL_SPI_SFCSR_CSB0; + break; + case 1: + cs_mask = RTL_SPI_SFCSR_CSB1; + break; + default: + return; + } - /* CS0 bit is active low */ value = __raw_readl(REG(RTL_SPI_SFCSR)); - if (active) - value |= RTL_SPI_SFCSR_CSB0; + + /* CSBx is active low */ + if (level) + value |= cs_mask; else - value &= ~RTL_SPI_SFCSR_CSB0; + value &= ~cs_mask; + __raw_writel(value, REG(RTL_SPI_SFCSR)); } @@ -138,11 +151,9 @@ static void init_hw(struct rtspi *rtspi) value |= RTL_SPI_SFCR_RBO | RTL_SPI_SFCR_WBO; __raw_writel(value, REG(RTL_SPI_SFCR)); - 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; + /* CHIP_SEL is only used in MMIO mode; CSB0/CSB1 are active low. */ + value = 0; + value |= RTL_SPI_SFCSR_CSB0 | RTL_SPI_SFCSR_CSB1; __raw_writel(value, REG(RTL_SPI_SFCSR)); } @@ -171,6 +182,7 @@ static int realtek_rtl_spi_probe(struct platform_device *pdev) ctrl->flags = SPI_CONTROLLER_HALF_DUPLEX; ctrl->set_cs = rt_set_cs; ctrl->transfer_one = transfer_one; + ctrl->num_chipselect = 2; err = devm_spi_register_controller(&pdev->dev, ctrl); if (err) { From 51e04054e2994ccbf79a2596853e89d539956659 Mon Sep 17 00:00:00 2001 From: Thanh Quan Date: Tue, 22 Sep 2026 10:27:00 +0200 Subject: [PATCH 043/136] spi: dt-bindings: renesas,sh-msiof: Add support for R-Car X5H Document support for Clock-Synchronized Serial Interfaces with FIFO (MSIOF) in the Renesas R-Car X5H (R8A78000) SoC, which is the first member of the R-Car Gen5 family. On R-Car X5H, MSIOF modules are wired to three DMACs, hence increase the maximum number of DMA channels to six. Signed-off-by: Thanh Quan Signed-off-by: Geert Uytterhoeven Reviewed-by: Wolfram Sang Link: https://patch.msgid.link/b8f4648eb1151b75d41e561c6b2adbd9bc4c0782.1790065451.git.geert+renesas@glider.be Signed-off-by: Mark Brown --- .../devicetree/bindings/spi/renesas,sh-msiof.yaml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Documentation/devicetree/bindings/spi/renesas,sh-msiof.yaml b/Documentation/devicetree/bindings/spi/renesas,sh-msiof.yaml index e0c7047ae8ad..41e423a42654 100644 --- a/Documentation/devicetree/bindings/spi/renesas,sh-msiof.yaml +++ b/Documentation/devicetree/bindings/spi/renesas,sh-msiof.yaml @@ -54,6 +54,11 @@ properties: - renesas,msiof-r8a779h0 # R-Car V4M - const: renesas,rcar-gen4-msiof # generic R-Car Gen4 # compatible device + - items: + - enum: + - renesas,msiof-r8a78000 # R-Car X5H + - const: renesas,rcar-gen5-msiof # generic R-Car Gen5 + # compatible device - items: - const: renesas,sh-msiof # deprecated @@ -100,11 +105,11 @@ properties: dmas: minItems: 2 - maxItems: 4 + maxItems: 6 dma-names: minItems: 2 - maxItems: 4 + maxItems: 6 items: enum: [ tx, rx ] From 8157bb8232a46e3596d396735bfbc3572ea823f5 Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Tue, 22 Sep 2026 10:27:01 +0200 Subject: [PATCH 044/136] spi: sh-msiof: Add support for R-Car Gen5 The Clock-Synchronized Serial Interfaces with FIFO (MSIOF) on R-Car Gen5 have FIFOs with 256 entries, just like on R-Car Gen4. Signed-off-by: Geert Uytterhoeven Reviewed-by: Wolfram Sang Link: https://patch.msgid.link/a254e27481716a07a49b10db31523fcaa3114afb.1790065451.git.geert+renesas@glider.be Signed-off-by: Mark Brown --- drivers/spi/spi-sh-msiof.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/spi/spi-sh-msiof.c b/drivers/spi/spi-sh-msiof.c index 1aeab7ec0bc8..f493df5df9e4 100644 --- a/drivers/spi/spi-sh-msiof.c +++ b/drivers/spi/spi-sh-msiof.c @@ -1013,6 +1013,7 @@ static const struct of_device_id sh_msiof_match[] __maybe_unused = { { .compatible = "renesas,msiof-r8a779a0", .data = &rcar_gen3_data }, { .compatible = "renesas,msiof-r8a779f0", .data = &rcar_gen3_data }, { .compatible = "renesas,rcar-gen4-msiof", .data = &rcar_gen4_data }, + { .compatible = "renesas,rcar-gen5-msiof", .data = &rcar_gen4_data }, { .compatible = "renesas,sh-msiof", .data = &sh_data }, /* Deprecated */ { /* sentinel */ } }; From cb5a15943d7d9c78cc5bef86ee080010c1029c70 Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Wed, 16 Sep 2026 23:30:55 +0800 Subject: [PATCH 045/136] spi: use dmaengine_get_dma_device() for DMA mapping Replace chan->device->dev with dmaengine_get_dma_device(chan) in the SPI core (spi.c) and SPI mem (spi-mem.c) DMA mapping helpers so that DMA mapping goes through the correct device when the DMA controller has per-channel IOMMU domains (chan_dma_dev=true). Assisted-by: Claude:claude-opus-4-6 Signed-off-by: Peng Fan Reviewed-by: Frank Li Link: https://patch.msgid.link/20260916-spi-dma-v1-1-2b30657d373d@nxp.com Signed-off-by: Mark Brown --- drivers/spi/spi-mem.c | 8 ++++---- drivers/spi/spi.c | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/spi/spi-mem.c b/drivers/spi/spi-mem.c index 94d122be1c93..b94680b60af9 100644 --- a/drivers/spi/spi-mem.c +++ b/drivers/spi/spi-mem.c @@ -46,9 +46,9 @@ int spi_controller_dma_map_mem_op_data(struct spi_controller *ctlr, return -EINVAL; if (op->data.dir == SPI_MEM_DATA_OUT && ctlr->dma_tx) - dmadev = ctlr->dma_tx->device->dev; + dmadev = dmaengine_get_dma_device(ctlr->dma_tx); else if (op->data.dir == SPI_MEM_DATA_IN && ctlr->dma_rx) - dmadev = ctlr->dma_rx->device->dev; + dmadev = dmaengine_get_dma_device(ctlr->dma_rx); else dmadev = ctlr->dev.parent; @@ -92,9 +92,9 @@ void spi_controller_dma_unmap_mem_op_data(struct spi_controller *ctlr, return; if (op->data.dir == SPI_MEM_DATA_OUT && ctlr->dma_tx) - dmadev = ctlr->dma_tx->device->dev; + dmadev = dmaengine_get_dma_device(ctlr->dma_tx); else if (op->data.dir == SPI_MEM_DATA_IN && ctlr->dma_rx) - dmadev = ctlr->dma_rx->device->dev; + dmadev = dmaengine_get_dma_device(ctlr->dma_rx); else dmadev = ctlr->dev.parent; diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c index 6bd6478f766e..015d93680e5d 100644 --- a/drivers/spi/spi.c +++ b/drivers/spi/spi.c @@ -1465,14 +1465,14 @@ int __spi_map_msg(struct spi_controller *ctlr, struct spi_message *msg) return 0; if (ctlr->dma_tx) - tx_dev = ctlr->dma_tx->device->dev; + tx_dev = dmaengine_get_dma_device(ctlr->dma_tx); else if (ctlr->dma_map_dev) tx_dev = ctlr->dma_map_dev; else tx_dev = ctlr->dev.parent; if (ctlr->dma_rx) - rx_dev = ctlr->dma_rx->device->dev; + rx_dev = dmaengine_get_dma_device(ctlr->dma_rx); else if (ctlr->dma_map_dev) rx_dev = ctlr->dma_map_dev; else From 2f06fa7a4150debe9c89c13a1d6ee13ffd881271 Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Mon, 17 Aug 2026 23:14:03 +0800 Subject: [PATCH 046/136] spi: dw: use DW_SPI_ISR directly The DW_SPI_ISR register reports the masked interrupts, no need to mask again. Signed-off-by: Jisheng Zhang Link: https://patch.msgid.link/20260817151404.28324-2-jszhang@kernel.org Signed-off-by: Mark Brown --- drivers/spi/spi-dw-core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/spi/spi-dw-core.c b/drivers/spi/spi-dw-core.c index b86b607cc817..ed6e688cfafb 100644 --- a/drivers/spi/spi-dw-core.c +++ b/drivers/spi/spi-dw-core.c @@ -275,7 +275,7 @@ static irqreturn_t dw_spi_irq(int irq, void *dev_id) { struct spi_controller *ctlr = dev_id; struct dw_spi *dws = spi_controller_get_devdata(ctlr); - u16 irq_status = dw_readl(dws, DW_SPI_ISR) & DW_SPI_INT_MASK; + u16 irq_status = dw_readl(dws, DW_SPI_ISR); if (!irq_status) return IRQ_NONE; From 88ca7a587f737ff717565063518e0a555e110f07 Mon Sep 17 00:00:00 2001 From: Jonas Rebmann Date: Thu, 24 Sep 2026 15:43:51 +0200 Subject: [PATCH 047/136] spi: spidev_test: include tools/include Add tools/include to include paths to allow using macro helpers. Use ARRAY_SIZE() from tools/include/linux/kernel.h instead of maintaining a copy. Remove the obsolete comment with cross-compile instructions from the header since spidev_test is integrated with the tools buildsystem since commit f325b73dc4db ("spi: tools: move to tools buildsystem"). Signed-off-by: Jonas Rebmann Link: https://patch.msgid.link/20260924-spi-sun4i-spidev_test-v7-1-126e31a07fdc@pengutronix.de Signed-off-by: Mark Brown --- tools/spi/Makefile | 2 +- tools/spi/spidev_test.c | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/tools/spi/Makefile b/tools/spi/Makefile index 7fccd245a535..8cf21ff8cd03 100644 --- a/tools/spi/Makefile +++ b/tools/spi/Makefile @@ -12,7 +12,7 @@ endif # (this improves performance and avoids hard-to-debug behaviour); MAKEFLAGS += -r -CFLAGS += -O2 -Wall -g -D_GNU_SOURCE -I$(OUTPUT)include +CFLAGS += -O2 -Wall -g -D_GNU_SOURCE -I$(OUTPUT)include -I$(srctree)/tools/include ALL_TARGETS := spidev_test spidev_fdx ALL_PROGRAMS := $(patsubst %,$(OUTPUT)%,$(ALL_TARGETS)) diff --git a/tools/spi/spidev_test.c b/tools/spi/spidev_test.c index f2135d619a0b..bd5ee7a6ac82 100644 --- a/tools/spi/spidev_test.c +++ b/tools/spi/spidev_test.c @@ -4,8 +4,6 @@ * * Copyright (c) 2007 MontaVista Software, Inc. * Copyright (c) 2007 Anton Vorontsov - * - * Cross-compile with cross-gcc -I/path/to/cross-kernel/include */ #include @@ -22,8 +20,7 @@ #include #include #include - -#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) +#include static void pabort(const char *s) { From a9e74a4c991126e263e2984fca327267ade5e224 Mon Sep 17 00:00:00 2001 From: Jonas Rebmann Date: Thu, 24 Sep 2026 15:43:52 +0200 Subject: [PATCH 048/136] spi: spidev_test: clarify usage for --size By default, spidev_test transmits the predetermined message default_tx. Given the --size parameter, the given number of random bytes are transmitted instead. Make all the input options (random bytes, string, file) mutually exclusive. Clarify the help text for --size. While at it, make the -p help text consistently lowercase. Signed-off-by: Jonas Rebmann Link: https://patch.msgid.link/20260924-spi-sun4i-spidev_test-v7-2-126e31a07fdc@pengutronix.de Signed-off-by: Mark Brown --- tools/spi/spidev_test.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/tools/spi/spidev_test.c b/tools/spi/spidev_test.c index bd5ee7a6ac82..0bd6ae419cbf 100644 --- a/tools/spi/spidev_test.c +++ b/tools/spi/spidev_test.c @@ -44,6 +44,7 @@ static int verbose; static int transfer_size; static int iterations; static int interval = 5; /* interval in seconds for showing transfer rate */ +static int input_choices; static uint8_t default_tx[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, @@ -191,8 +192,8 @@ static void print_usage(const char *prog) "data:\n" " -i --input input data from a file (e.g. \"test.bin\")\n" " -o --output output data to a file (e.g. \"results.bin\")\n" - " -p Send data (e.g. \"1234\\xde\\xad\")\n" - " -S --size transfer size\n" + " -p send data (e.g. \"1234\\xde\\xad\")\n" + " -S --size transfer the given number of random bytes\n" " -I --iter iterations\n" "additional parameters:\n" " -b --bpw bits per word\n" @@ -262,6 +263,7 @@ static void parse_opts(int argc, char *argv[]) break; case 'i': input_file = optarg; + input_choices++; break; case 'o': output_file = optarg; @@ -304,6 +306,7 @@ static void parse_opts(int argc, char *argv[]) break; case 'p': input_tx = optarg; + input_choices++; break; case '2': mode |= SPI_TX_DUAL; @@ -316,6 +319,7 @@ static void parse_opts(int argc, char *argv[]) break; case 'S': transfer_size = atoi(optarg); + input_choices++; break; case 'I': iterations = atoi(optarg); @@ -446,8 +450,9 @@ int main(int argc, char *argv[]) parse_opts(argc, argv); - if (input_tx && input_file) - pabort("only one of -p and --input may be selected"); + if (input_choices > 1) + pabort("at most one of -S (--size), -p, -i (--input) may be selected, " + "and each may be specified only once"); fd = open(device, O_RDWR); if (fd < 0) From df79e49bd7d77121a0396a094676568de79b3f6c Mon Sep 17 00:00:00 2001 From: Jonas Rebmann Date: Thu, 24 Sep 2026 15:43:53 +0200 Subject: [PATCH 049/136] spi: spidev_test: make size argument mandatory for --size When the argument to --size is omitted, abort with a message instead of transmitting default_tx[]. Signed-off-by: Jonas Rebmann Link: https://patch.msgid.link/20260924-spi-sun4i-spidev_test-v7-3-126e31a07fdc@pengutronix.de Signed-off-by: Mark Brown --- tools/spi/spidev_test.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tools/spi/spidev_test.c b/tools/spi/spidev_test.c index 0bd6ae419cbf..36292d868d69 100644 --- a/tools/spi/spidev_test.c +++ b/tools/spi/spidev_test.c @@ -41,9 +41,10 @@ static uint32_t speed = 500000; static uint16_t delay; static uint16_t word_delay; static int verbose; -static int transfer_size; +static int transfer_size = -1; static int iterations; static int interval = 5; /* interval in seconds for showing transfer rate */ +static int random_input; static int input_choices; static uint8_t default_tx[] = { @@ -319,6 +320,7 @@ static void parse_opts(int argc, char *argv[]) break; case 'S': transfer_size = atoi(optarg); + random_input = 1; input_choices++; break; case 'I': @@ -454,6 +456,9 @@ int main(int argc, char *argv[]) pabort("at most one of -S (--size), -p, -i (--input) may be selected, " "and each may be specified only once"); + if (random_input && transfer_size < 0) + pabort("a size argument is mandatory for -S (--size)"); + fd = open(device, O_RDWR); if (fd < 0) pabort("can't open device"); @@ -509,7 +514,7 @@ int main(int argc, char *argv[]) transfer_escaped_string(fd, input_tx); else if (input_file) transfer_file(fd, input_file); - else if (transfer_size) { + else if (transfer_size >= 0) { struct timespec last_stat; clock_gettime(CLOCK_MONOTONIC, &last_stat); From bd511c42d290807a04a325d584d65b98fd6721d7 Mon Sep 17 00:00:00 2001 From: Jonas Rebmann Date: Thu, 24 Sep 2026 15:43:54 +0200 Subject: [PATCH 050/136] spi: spidev_test: allow zero-length transfers SPI drivers may in fact handle zero-length transfers. The behavior so far was that such transfers would be executed but then result in the "can't send spi message" error message because the spi_sync() call returning 0 would be treated as an error while really it simply indicates that zero bytes were successfully transferred in a message. Don't treat a return value of zero as indication of an error. Update hex_dump() to work with empty buffers now printing e.g. TX | __ || Now including a trailing newline. Signed-off-by: Jonas Rebmann Link: https://patch.msgid.link/20260924-spi-sun4i-spidev_test-v7-4-126e31a07fdc@pengutronix.de Signed-off-by: Mark Brown --- tools/spi/spidev_test.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tools/spi/spidev_test.c b/tools/spi/spidev_test.c index 36292d868d69..908ed038889b 100644 --- a/tools/spi/spidev_test.c +++ b/tools/spi/spidev_test.c @@ -68,6 +68,10 @@ static void hex_dump(const void *src, size_t length, size_t line_size, unsigned char c; printf("%s | ", prefix); + + if (length == 0) + printf("__ ||\n"); + while (length-- > 0) { printf("%02X ", *address++); if (!(++i % line_size) || (length == 0 && i % line_size)) { @@ -149,7 +153,7 @@ static void transfer(int fd, uint8_t const *tx, uint8_t const *rx, size_t len) } ret = ioctl(fd, SPI_IOC_MESSAGE(1), &tr); - if (ret < 1) + if (ret < 0) pabort("can't send spi message"); if (verbose) From 683d7fac9b0ff7aa8f0ff2ccfa0f4bbba99ee273 Mon Sep 17 00:00:00 2001 From: Jonas Rebmann Date: Thu, 24 Sep 2026 15:43:55 +0200 Subject: [PATCH 051/136] spi: spidev_test: abort when -I is selected without -S -I (--iter) for transferring multiple iterations of the selected input currently only has an effect when transferring random data using -S (--size). Error out if -I is used without -S to avoid confusion. Signed-off-by: Jonas Rebmann Link: https://patch.msgid.link/20260924-spi-sun4i-spidev_test-v7-5-126e31a07fdc@pengutronix.de Signed-off-by: Mark Brown --- tools/spi/spidev_test.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tools/spi/spidev_test.c b/tools/spi/spidev_test.c index 908ed038889b..97d09bf4dafb 100644 --- a/tools/spi/spidev_test.c +++ b/tools/spi/spidev_test.c @@ -463,6 +463,9 @@ int main(int argc, char *argv[]) if (random_input && transfer_size < 0) pabort("a size argument is mandatory for -S (--size)"); + if (iterations && transfer_size < 0) + pabort("-I (--iter) is only implemented for -S (--size)"); + fd = open(device, O_RDWR); if (fd < 0) pabort("can't open device"); From 2d736e05dbcd1294ed621fe5d4b6d06ef0c259be Mon Sep 17 00:00:00 2001 From: Jonas Rebmann Date: Thu, 24 Sep 2026 15:43:56 +0200 Subject: [PATCH 052/136] spi: spidev_test: always compare loopback data In loopback mode, spidev_test verifies that the received data matches the sent data. However this test was only performed when random data was transferred using the -S switch. Always perform the test by moving it from transfer_buf() to transfer(). Signed-off-by: Jonas Rebmann Link: https://patch.msgid.link/20260924-spi-sun4i-spidev_test-v7-6-126e31a07fdc@pengutronix.de Signed-off-by: Mark Brown --- tools/spi/spidev_test.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/tools/spi/spidev_test.c b/tools/spi/spidev_test.c index 97d09bf4dafb..e46eaef4d2ab 100644 --- a/tools/spi/spidev_test.c +++ b/tools/spi/spidev_test.c @@ -173,6 +173,15 @@ static void transfer(int fd, uint8_t const *tx, uint8_t const *rx, size_t len) if (verbose) hex_dump(rx, len, 32, "RX"); + + if (mode & SPI_LOOP) { + if (memcmp(tx, rx, len)) { + fprintf(stderr, "transfer error !\n"); + hex_dump(tx, len, 32, "TX"); + hex_dump(rx, len, 32, "RX"); + exit(1); + } + } } static void print_usage(const char *prog) @@ -435,15 +444,6 @@ static void transfer_buf(int fd, int len) _write_count += len; _read_count += len; - if (mode & SPI_LOOP) { - if (memcmp(tx, rx, len)) { - fprintf(stderr, "transfer error !\n"); - hex_dump(tx, len, 32, "TX"); - hex_dump(rx, len, 32, "RX"); - exit(1); - } - } - free(rx); free(tx); } From a47226866b8ae4c3a63802b668cdb99f4a76b151 Mon Sep 17 00:00:00 2001 From: Marc Kleine-Budde Date: Thu, 24 Sep 2026 15:43:57 +0200 Subject: [PATCH 053/136] spi: spidev_test: add compare mode Check whether the received data correspond to the transferred data but without enabling loopback mode in the controller. This is for testing with TX physically bridged to RX which is useful if a loopback mode is unavailable or insufficient. Signed-off-by: Marc Kleine-Budde Signed-off-by: Jonas Rebmann Link: https://patch.msgid.link/20260924-spi-sun4i-spidev_test-v7-7-126e31a07fdc@pengutronix.de Signed-off-by: Mark Brown --- tools/spi/spidev_test.c | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/tools/spi/spidev_test.c b/tools/spi/spidev_test.c index e46eaef4d2ab..7b343803903e 100644 --- a/tools/spi/spidev_test.c +++ b/tools/spi/spidev_test.c @@ -44,6 +44,7 @@ static int verbose; static int transfer_size = -1; static int iterations; static int interval = 5; /* interval in seconds for showing transfer rate */ +static int compare; static int random_input; static int input_choices; @@ -174,7 +175,7 @@ static void transfer(int fd, uint8_t const *tx, uint8_t const *rx, size_t len) if (verbose) hex_dump(rx, len, 32, "RX"); - if (mode & SPI_LOOP) { + if (compare || mode & SPI_LOOP) { if (memcmp(tx, rx, len)) { fprintf(stderr, "transfer error !\n"); hex_dump(tx, len, 32, "TX"); @@ -186,13 +187,14 @@ static void transfer(int fd, uint8_t const *tx, uint8_t const *rx, size_t len) static void print_usage(const char *prog) { - printf("Usage: %s [-2348CDFHILMNORSZbdilopsvw]\n", prog); + printf("Usage: %s [-2348CDFHILMNORSZbcdilopsvw]\n", prog); puts("general device settings:\n" " -D --device device to use (default /dev/spidev1.1)\n" " -s --speed max speed (Hz)\n" " -d --delay delay (usec)\n" " -w --word-delay word delay (usec)\n" " -l --loop loopback\n" + " -c --compare compare RX'ed and TX'ed data\n" "spi mode:\n" " -H --cpha clock phase\n" " -O --cpol clock polarity\n" @@ -230,6 +232,7 @@ static void parse_opts(int argc, char *argv[]) { "delay", 1, 0, 'd' }, { "word-delay", 1, 0, 'w' }, { "loop", 0, 0, 'l' }, + { "compare", 0, 0, 'c' }, { "cpha", 0, 0, 'H' }, { "cpol", 0, 0, 'O' }, { "rx-cpha-flip", 0, 0, 'F' }, @@ -253,7 +256,7 @@ static void parse_opts(int argc, char *argv[]) }; int c; - c = getopt_long(argc, argv, "D:s:d:w:b:i:o:lHOLC3ZFMNR248p:vS:I:", + c = getopt_long(argc, argv, "D:s:d:w:b:i:o:lcHOLC3ZFMNR248p:vS:I:", lopts, NULL); if (c == -1) @@ -285,6 +288,9 @@ static void parse_opts(int argc, char *argv[]) case 'l': mode |= SPI_LOOP; break; + case 'c': + compare = 1; + break; case 'H': mode |= SPI_CPHA; break; @@ -466,6 +472,9 @@ int main(int argc, char *argv[]) if (iterations && transfer_size < 0) pabort("-I (--iter) is only implemented for -S (--size)"); + if (compare && mode & (SPI_TX_OCTAL | SPI_TX_QUAD | SPI_TX_DUAL)) + pabort("-c (--compare) conflicts with -2 (--dual), -4 (--quad) or -8 (--octal)"); + fd = open(device, O_RDWR); if (fd < 0) pabort("can't open device"); From d2b908c75a8103efbcefe9c6efa290c58c8fe849 Mon Sep 17 00:00:00 2001 From: Marc Kleine-Budde Date: Thu, 24 Sep 2026 15:43:58 +0200 Subject: [PATCH 054/136] spi: spidev_test: allow disabling rx or tx buffers Allow not providing rx or tx buffers. This is useful to check if drivers that don't use SPI_CONTROLLER_MUST_RX (or -TX respectively) handle their operations correctly without a buffer. Signed-off-by: Marc Kleine-Budde Signed-off-by: Jonas Rebmann Link: https://patch.msgid.link/20260924-spi-sun4i-spidev_test-v7-8-126e31a07fdc@pengutronix.de Signed-off-by: Mark Brown --- tools/spi/spidev_test.c | 84 ++++++++++++++++++++++++++++------------- 1 file changed, 57 insertions(+), 27 deletions(-) diff --git a/tools/spi/spidev_test.c b/tools/spi/spidev_test.c index 7b343803903e..b02a6384d27b 100644 --- a/tools/spi/spidev_test.c +++ b/tools/spi/spidev_test.c @@ -45,6 +45,7 @@ static int transfer_size = -1; static int iterations; static int interval = 5; /* interval in seconds for showing transfer rate */ static int compare; +static int do_tx = 1, do_rx = 1; static int random_input; static int input_choices; @@ -157,7 +158,7 @@ static void transfer(int fd, uint8_t const *tx, uint8_t const *rx, size_t len) if (ret < 0) pabort("can't send spi message"); - if (verbose) + if (verbose && tx) hex_dump(tx, len, 32, "TX"); if (output_file) { @@ -172,10 +173,10 @@ static void transfer(int fd, uint8_t const *tx, uint8_t const *rx, size_t len) close(out_fd); } - if (verbose) + if (verbose && rx) hex_dump(rx, len, 32, "RX"); - if (compare || mode & SPI_LOOP) { + if (tx && rx && (compare || mode & SPI_LOOP)) { if (memcmp(tx, rx, len)) { fprintf(stderr, "transfer error !\n"); hex_dump(tx, len, 32, "TX"); @@ -187,7 +188,7 @@ static void transfer(int fd, uint8_t const *tx, uint8_t const *rx, size_t len) static void print_usage(const char *prog) { - printf("Usage: %s [-2348CDFHILMNORSZbcdilopsvw]\n", prog); + printf("Usage: %s [-2348CDFHILMNORSZbcdiloprstvw]\n", prog); puts("general device settings:\n" " -D --device device to use (default /dev/spidev1.1)\n" " -s --speed max speed (Hz)\n" @@ -195,6 +196,8 @@ static void print_usage(const char *prog) " -w --word-delay word delay (usec)\n" " -l --loop loopback\n" " -c --compare compare RX'ed and TX'ed data\n" + " -t --no-tx don't send data\n" + " -r --no-rx don't receive data\n" "spi mode:\n" " -H --cpha clock phase\n" " -O --cpol clock polarity\n" @@ -233,6 +236,8 @@ static void parse_opts(int argc, char *argv[]) { "word-delay", 1, 0, 'w' }, { "loop", 0, 0, 'l' }, { "compare", 0, 0, 'c' }, + { "no-tx", 0, 0, 't' }, + { "no-rx", 0, 0, 'r' }, { "cpha", 0, 0, 'H' }, { "cpol", 0, 0, 'O' }, { "rx-cpha-flip", 0, 0, 'F' }, @@ -256,7 +261,7 @@ static void parse_opts(int argc, char *argv[]) }; int c; - c = getopt_long(argc, argv, "D:s:d:w:b:i:o:lcHOLC3ZFMNR248p:vS:I:", + c = getopt_long(argc, argv, "D:s:d:w:b:i:o:lctrHOLC3ZFMNR248p:vS:I:", lopts, NULL); if (c == -1) @@ -291,6 +296,12 @@ static void parse_opts(int argc, char *argv[]) case 'c': compare = 1; break; + case 't': + do_tx = 0; + break; + case 'r': + do_rx = 0; + break; case 'H': mode |= SPI_CPHA; break; @@ -363,15 +374,17 @@ static void transfer_escaped_string(int fd, char *str) { size_t size = strlen(str); uint8_t *tx; - uint8_t *rx; + uint8_t *rx = NULL; tx = malloc(size); if (!tx) pabort("can't allocate tx buffer"); - rx = malloc(size); - if (!rx) - pabort("can't allocate rx buffer"); + if (do_rx) { + rx = malloc(size); + if (!rx) + pabort("can't allocate rx buffer"); + } size = unescape((char *)tx, str, size); transfer(fd, tx, rx, size); @@ -385,7 +398,7 @@ static void transfer_file(int fd, char *filename) struct stat sb; int tx_fd; uint8_t *tx; - uint8_t *rx; + uint8_t *rx = NULL; if (stat(filename, &sb) == -1) pabort("can't stat input file"); @@ -398,9 +411,12 @@ static void transfer_file(int fd, char *filename) if (!tx) pabort("can't allocate tx buffer"); - rx = malloc(sb.st_size); - if (!rx) - pabort("can't allocate rx buffer"); + + if (do_rx) { + rx = malloc(sb.st_size); + if (!rx) + pabort("can't allocate rx buffer"); + } bytes = read(tx_fd, tx, sb.st_size); if (bytes != sb.st_size) @@ -431,24 +447,29 @@ static void show_transfer_rate(void) static void transfer_buf(int fd, int len) { - uint8_t *tx; - uint8_t *rx; + uint8_t *tx = NULL; + uint8_t *rx = NULL; int i; - tx = malloc(len); - if (!tx) - pabort("can't allocate tx buffer"); - for (i = 0; i < len; i++) - tx[i] = random(); + if (do_tx) { + tx = malloc(len); + if (!tx) + pabort("can't allocate tx buffer"); + for (i = 0; i < len; i++) + tx[i] = random(); + } - rx = malloc(len); - if (!rx) - pabort("can't allocate rx buffer"); + if (do_rx) { + rx = malloc(len); + if (!rx) + pabort("can't allocate rx buffer"); + } transfer(fd, tx, rx, len); - - _write_count += len; - _read_count += len; + if (do_tx) + _write_count += len; + if (do_rx) + _read_count += len; free(rx); free(tx); @@ -472,6 +493,15 @@ int main(int argc, char *argv[]) if (iterations && transfer_size < 0) pabort("-I (--iter) is only implemented for -S (--size)"); + if (compare && (!do_tx || !do_rx)) + pabort("-c (--compare) conflicts with -t (--no-tx) or -r (--no-rx)"); + + if (!do_rx && output_file) + pabort("-r (--no-rx) conflicts with -o (--output)"); + + if (!do_tx && transfer_size < 0) + pabort("-t (--no-tx) is only implemented for -S (--size)"); + if (compare && mode & (SPI_TX_OCTAL | SPI_TX_QUAD | SPI_TX_DUAL)) pabort("-c (--compare) conflicts with -2 (--dual), -4 (--quad) or -8 (--octal)"); @@ -549,7 +579,7 @@ int main(int argc, char *argv[]) printf("total: tx %.1fKB, rx %.1fKB\n", _write_count/1024.0, _read_count/1024.0); } else - transfer(fd, default_tx, default_rx, sizeof(default_tx)); + transfer(fd, default_tx, do_rx ? default_rx : NULL, sizeof(default_tx)); close(fd); From 951a60af6c0baa36d6bed82cf3f7e8b754426ea7 Mon Sep 17 00:00:00 2001 From: Marc Kleine-Budde Date: Thu, 24 Sep 2026 15:43:59 +0200 Subject: [PATCH 055/136] spi: spidev_test: don't send 0x0 or 0xff Particularly when using compare mode, if the controller fails to transfer any data, asserting on a read of 0x00 or 0xff may lead to a false negative test, indicating a byte was successfully transferred when the values simply originate from the pull-up or pull-down of RX. Signed-off-by: Marc Kleine-Budde Signed-off-by: Jonas Rebmann Link: https://patch.msgid.link/20260924-spi-sun4i-spidev_test-v7-9-126e31a07fdc@pengutronix.de Signed-off-by: Mark Brown --- tools/spi/spidev_test.c | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/tools/spi/spidev_test.c b/tools/spi/spidev_test.c index b02a6384d27b..7ca427c27f07 100644 --- a/tools/spi/spidev_test.c +++ b/tools/spi/spidev_test.c @@ -45,6 +45,7 @@ static int transfer_size = -1; static int iterations; static int interval = 5; /* interval in seconds for showing transfer rate */ static int compare; +static int nonzero; static int do_tx = 1, do_rx = 1; static int random_input; static int input_choices; @@ -188,7 +189,7 @@ static void transfer(int fd, uint8_t const *tx, uint8_t const *rx, size_t len) static void print_usage(const char *prog) { - printf("Usage: %s [-2348CDFHILMNORSZbcdiloprstvw]\n", prog); + printf("Usage: %s [-2348CDFHILMNORSZbcdiloprstvwz]\n", prog); puts("general device settings:\n" " -D --device device to use (default /dev/spidev1.1)\n" " -s --speed max speed (Hz)\n" @@ -212,6 +213,7 @@ static void print_usage(const char *prog) " -i --input input data from a file (e.g. \"test.bin\")\n" " -o --output output data to a file (e.g. \"results.bin\")\n" " -p send data (e.g. \"1234\\xde\\xad\")\n" + " -z --nonzero don't send 0x00 or 0xff bytes\n" " -S --size transfer the given number of random bytes\n" " -I --iter iterations\n" "additional parameters:\n" @@ -249,6 +251,7 @@ static void parse_opts(int argc, char *argv[]) { "input", 1, 0, 'i' }, { "output", 1, 0, 'o' }, { "size", 1, 0, 'S' }, + { "nonzero", 0, 0, 'z' }, { "iter", 1, 0, 'I' }, { "bpw", 1, 0, 'b' }, { "lsb", 0, 0, 'L' }, @@ -261,7 +264,7 @@ static void parse_opts(int argc, char *argv[]) }; int c; - c = getopt_long(argc, argv, "D:s:d:w:b:i:o:lctrHOLC3ZFMNR248p:vS:I:", + c = getopt_long(argc, argv, "D:s:d:w:b:i:o:lctrHOLC3ZFMNR248p:vS:zI:", lopts, NULL); if (c == -1) @@ -353,6 +356,9 @@ static void parse_opts(int argc, char *argv[]) random_input = 1; input_choices++; break; + case 'z': + nonzero = 1; + break; case 'I': iterations = atoi(optarg); break; @@ -455,8 +461,11 @@ static void transfer_buf(int fd, int len) tx = malloc(len); if (!tx) pabort("can't allocate tx buffer"); - for (i = 0; i < len; i++) - tx[i] = random(); + for (i = 0; i < len; i++) { + do + tx[i] = random(); + while (nonzero && (tx[i] == 0x0 || tx[i] == 0xff)); + } } if (do_rx) { @@ -493,6 +502,9 @@ int main(int argc, char *argv[]) if (iterations && transfer_size < 0) pabort("-I (--iter) is only implemented for -S (--size)"); + if (nonzero && transfer_size < 0) + pabort("-z (--nonzero) is only implemented for -S (--size)"); + if (compare && (!do_tx || !do_rx)) pabort("-c (--compare) conflicts with -t (--no-tx) or -r (--no-rx)"); From c807c71d21eb77862fc497899720d28d5a149830 Mon Sep 17 00:00:00 2001 From: Jonas Rebmann Date: Thu, 24 Sep 2026 15:44:00 +0200 Subject: [PATCH 056/136] spi: spidev_test: send predictable data Introduce a flag to test on a predictable byte sequence instead of random bytes. This is useful when comparing multiple runs with the same option set e.g. with an oscilloscope. Signed-off-by: Marc Kleine-Budde Signed-off-by: Jonas Rebmann Link: https://patch.msgid.link/20260924-spi-sun4i-spidev_test-v7-10-126e31a07fdc@pengutronix.de Signed-off-by: Mark Brown --- tools/spi/spidev_test.c | 38 +++++++++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/tools/spi/spidev_test.c b/tools/spi/spidev_test.c index 7ca427c27f07..a58680b6288a 100644 --- a/tools/spi/spidev_test.c +++ b/tools/spi/spidev_test.c @@ -48,6 +48,7 @@ static int compare; static int nonzero; static int do_tx = 1, do_rx = 1; static int random_input; +static int predictable; static int input_choices; static uint8_t default_tx[] = { @@ -189,7 +190,7 @@ static void transfer(int fd, uint8_t const *tx, uint8_t const *rx, size_t len) static void print_usage(const char *prog) { - printf("Usage: %s [-2348CDFHILMNORSZbcdiloprstvwz]\n", prog); + printf("Usage: %s [-2348CDFHILMNOPRSZbcdiloprstvwz]\n", prog); puts("general device settings:\n" " -D --device device to use (default /dev/spidev1.1)\n" " -s --speed max speed (Hz)\n" @@ -214,6 +215,7 @@ static void print_usage(const char *prog) " -o --output output data to a file (e.g. \"results.bin\")\n" " -p send data (e.g. \"1234\\xde\\xad\")\n" " -z --nonzero don't send 0x00 or 0xff bytes\n" + " -P --predictable transfer the given number of sequential bytes\n" " -S --size transfer the given number of random bytes\n" " -I --iter iterations\n" "additional parameters:\n" @@ -259,12 +261,13 @@ static void parse_opts(int argc, char *argv[]) { "no-cs", 0, 0, 'N' }, { "ready", 0, 0, 'R' }, { "mosi-idle-low", 0, 0, 'M' }, + { "predictable", 1, 0, 'P' }, { "verbose", 0, 0, 'v' }, { NULL, 0, 0, 0 }, }; int c; - c = getopt_long(argc, argv, "D:s:d:w:b:i:o:lctrHOLC3ZFMNR248p:vS:zI:", + c = getopt_long(argc, argv, "D:s:d:w:b:i:o:lctrHOLC3ZFMNR248p:P:vS:zI:", lopts, NULL); if (c == -1) @@ -342,6 +345,11 @@ static void parse_opts(int argc, char *argv[]) input_tx = optarg; input_choices++; break; + case 'P': + transfer_size = atoi(optarg); + predictable = 1; + input_choices++; + break; case '2': mode |= SPI_TX_DUAL; break; @@ -462,9 +470,17 @@ static void transfer_buf(int fd, int len) if (!tx) pabort("can't allocate tx buffer"); for (i = 0; i < len; i++) { - do - tx[i] = random(); - while (nonzero && (tx[i] == 0x0 || tx[i] == 0xff)); + if (predictable) { + int v = i - iterations; + + if (nonzero) + v = ((v % 254) + 254) % 254 + 1; + tx[i] = (char)v; + } else { + do { + tx[i] = random(); + } while (nonzero && (tx[i] == 0x0 || tx[i] == 0xff)); + } } } @@ -493,17 +509,17 @@ int main(int argc, char *argv[]) parse_opts(argc, argv); if (input_choices > 1) - pabort("at most one of -S (--size), -p, -i (--input) may be selected, " + pabort("at most one of -S (--size), -p, -i (--input), -P (--predictable) may be selected, " "and each may be specified only once"); - if (random_input && transfer_size < 0) - pabort("a size argument is mandatory for -S (--size)"); + if ((random_input || predictable) && transfer_size < 0) + pabort("a size argument is mandatory for -S (--size) and -P (--predictable)"); if (iterations && transfer_size < 0) - pabort("-I (--iter) is only implemented for -S (--size)"); + pabort("-I (--iter) is only implemented for -S (--size) and -P (--predictable)"); if (nonzero && transfer_size < 0) - pabort("-z (--nonzero) is only implemented for -S (--size)"); + pabort("-z (--nonzero) is only implemented for -S (--size) and -P (--predictable)"); if (compare && (!do_tx || !do_rx)) pabort("-c (--compare) conflicts with -t (--no-tx) or -r (--no-rx)"); @@ -512,7 +528,7 @@ int main(int argc, char *argv[]) pabort("-r (--no-rx) conflicts with -o (--output)"); if (!do_tx && transfer_size < 0) - pabort("-t (--no-tx) is only implemented for -S (--size)"); + pabort("-t (--no-tx) is only implemented for -S (--size) and -P (--predictable)"); if (compare && mode & (SPI_TX_OCTAL | SPI_TX_QUAD | SPI_TX_DUAL)) pabort("-c (--compare) conflicts with -2 (--dual), -4 (--quad) or -8 (--octal)"); From 31ce698070aa1e177f8e608d7fb92045fb01275c Mon Sep 17 00:00:00 2001 From: Marc Kleine-Budde Date: Thu, 24 Sep 2026 15:44:01 +0200 Subject: [PATCH 057/136] spi: spidev_test: add option to split message into multiple transfers SPI_IOC_MESSAGE() submits a single SPI message containing one or more transfers. Introduce an option to allow splitting the message into multiple transfers. Signed-off-by: Marc Kleine-Budde Signed-off-by: Jonas Rebmann Link: https://patch.msgid.link/20260924-spi-sun4i-spidev_test-v7-11-126e31a07fdc@pengutronix.de Signed-off-by: Mark Brown --- tools/spi/spidev_test.c | 111 +++++++++++++++++++++++++++++----------- 1 file changed, 81 insertions(+), 30 deletions(-) diff --git a/tools/spi/spidev_test.c b/tools/spi/spidev_test.c index a58680b6288a..ac24d051835f 100644 --- a/tools/spi/spidev_test.c +++ b/tools/spi/spidev_test.c @@ -42,6 +42,7 @@ static uint16_t delay; static uint16_t word_delay; static int verbose; static int transfer_size = -1; +static int transfers = 1; static int iterations; static int interval = 5; /* interval in seconds for showing transfer rate */ static int compare; @@ -123,40 +124,79 @@ static int unescape(char *_dst, char *_src, size_t len) return ret; } -static void transfer(int fd, uint8_t const *tx, uint8_t const *rx, size_t len) +static void transfer(int fd, uint8_t const * const tx, uint8_t const * const rx, size_t len) { int ret; int out_fd; - struct spi_ioc_transfer tr = { - .tx_buf = (unsigned long)tx, - .rx_buf = (unsigned long)rx, - .len = len, - .delay_usecs = delay, - .word_delay_usecs = word_delay, - .speed_hz = speed, - .bits_per_word = bits, - }; + size_t bytes_per_word = DIV_ROUND_UP(bits, 8); + int effective_transfers; + struct spi_ioc_transfer *tr; + size_t len_per_transfer; + const uint8_t *tx_buf = tx; + const uint8_t *rx_buf = rx; + size_t rem; - if (mode & SPI_TX_OCTAL) - tr.tx_nbits = 8; - else if (mode & SPI_TX_QUAD) - tr.tx_nbits = 4; - else if (mode & SPI_TX_DUAL) - tr.tx_nbits = 2; - if (mode & SPI_RX_OCTAL) - tr.rx_nbits = 8; - else if (mode & SPI_RX_QUAD) - tr.rx_nbits = 4; - else if (mode & SPI_RX_DUAL) - tr.rx_nbits = 2; - if (!(mode & SPI_LOOP)) { - if (mode & (SPI_TX_OCTAL | SPI_TX_QUAD | SPI_TX_DUAL)) - tr.rx_buf = 0; - else if (mode & (SPI_RX_OCTAL | SPI_RX_QUAD | SPI_RX_DUAL)) - tr.tx_buf = 0; + if (len) { + effective_transfers = min_t(size_t, transfers, DIV_ROUND_UP(len, bytes_per_word)); + len_per_transfer = roundup(DIV_ROUND_UP(len, effective_transfers), bytes_per_word); + } else { + /* zero-length transfers may be handled by the driver, deliver as ordered */ + effective_transfers = transfers; + len_per_transfer = 0; } - ret = ioctl(fd, SPI_IOC_MESSAGE(1), &tr); + if (SPI_MSGSIZE(effective_transfers) == 0) + pabort("too many transfers, SPI_IOC_MESSAGE size limit exceeded"); + + tr = calloc(effective_transfers, sizeof(*tr)); + if (!tr) + pabort("can't allocate transfer array"); + + rem = len; + + for (int i = 0; i < effective_transfers; i++) { + size_t n = min(rem, len_per_transfer); + + tr[i] = (struct spi_ioc_transfer) { + .tx_buf = (unsigned long)tx_buf, + .rx_buf = (unsigned long)rx_buf, + .len = n, + .delay_usecs = delay, + .word_delay_usecs = word_delay, + .speed_hz = speed, + .bits_per_word = bits, + }; + + if (tx_buf) + tx_buf += n; + + if (rx_buf) + rx_buf += n; + + rem -= n; + + if (mode & SPI_TX_OCTAL) + tr[i].tx_nbits = 8; + else if (mode & SPI_TX_QUAD) + tr[i].tx_nbits = 4; + else if (mode & SPI_TX_DUAL) + tr[i].tx_nbits = 2; + if (mode & SPI_RX_OCTAL) + tr[i].rx_nbits = 8; + else if (mode & SPI_RX_QUAD) + tr[i].rx_nbits = 4; + else if (mode & SPI_RX_DUAL) + tr[i].rx_nbits = 2; + if (!(mode & SPI_LOOP)) { + if (mode & (SPI_TX_OCTAL | SPI_TX_QUAD | SPI_TX_DUAL)) + tr[i].rx_buf = 0; + else if (mode & (SPI_RX_OCTAL | SPI_RX_QUAD | SPI_RX_DUAL)) + tr[i].tx_buf = 0; + } + } + + ret = ioctl(fd, SPI_IOC_MESSAGE(effective_transfers), tr); + free(tr); if (ret < 0) pabort("can't send spi message"); @@ -190,7 +230,7 @@ static void transfer(int fd, uint8_t const *tx, uint8_t const *rx, size_t len) static void print_usage(const char *prog) { - printf("Usage: %s [-2348CDFHILMNOPRSZbcdiloprstvwz]\n", prog); + printf("Usage: %s [-2348CDFHILMNOPRSTZbcdiloprstvwz]\n", prog); puts("general device settings:\n" " -D --device device to use (default /dev/spidev1.1)\n" " -s --speed max speed (Hz)\n" @@ -225,6 +265,7 @@ static void print_usage(const char *prog) " -N --no-cs no chip select\n" " -R --ready slave pulls low to pause\n" " -M --mosi-idle-low leave mosi line low when idle\n" + " -T --transfers number of transfers\n" "misc:\n" " -v --verbose Verbose (show tx buffer)\n"); exit(1); @@ -262,12 +303,13 @@ static void parse_opts(int argc, char *argv[]) { "ready", 0, 0, 'R' }, { "mosi-idle-low", 0, 0, 'M' }, { "predictable", 1, 0, 'P' }, + { "transfers", 1, 0, 'T' }, { "verbose", 0, 0, 'v' }, { NULL, 0, 0, 0 }, }; int c; - c = getopt_long(argc, argv, "D:s:d:w:b:i:o:lctrHOLC3ZFMNR248p:P:vS:zI:", + c = getopt_long(argc, argv, "D:s:d:w:b:i:o:lctrHOLC3ZFMNR248p:P:T:vS:zI:", lopts, NULL); if (c == -1) @@ -332,6 +374,9 @@ static void parse_opts(int argc, char *argv[]) case 'M': mode |= SPI_MOSI_IDLE_LOW; break; + case 'T': + transfers = atoi(optarg); + break; case 'N': mode |= SPI_NO_CS; break; @@ -533,6 +578,12 @@ int main(int argc, char *argv[]) if (compare && mode & (SPI_TX_OCTAL | SPI_TX_QUAD | SPI_TX_DUAL)) pabort("-c (--compare) conflicts with -2 (--dual), -4 (--quad) or -8 (--octal)"); + if (transfers < 1) + pabort("-T (--transfers) must be 1 or above"); + + if (bits < 1) + pabort("-b (--bpw) must be 1 or above"); + fd = open(device, O_RDWR); if (fd < 0) pabort("can't open device"); From a73c71e1f25a4f7d6266a6c1cc5e3c903b52cc6a Mon Sep 17 00:00:00 2001 From: Marc Kleine-Budde Date: Thu, 24 Sep 2026 15:44:02 +0200 Subject: [PATCH 058/136] spi: spidev_test: print TX on error If sending an spi message fails, dump its first up to 256 bytes to the console. Signed-off-by: Marc Kleine-Budde Signed-off-by: Jonas Rebmann Link: https://patch.msgid.link/20260924-spi-sun4i-spidev_test-v7-12-126e31a07fdc@pengutronix.de Signed-off-by: Mark Brown --- tools/spi/spidev_test.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/tools/spi/spidev_test.c b/tools/spi/spidev_test.c index ac24d051835f..61951ed1c908 100644 --- a/tools/spi/spidev_test.c +++ b/tools/spi/spidev_test.c @@ -197,8 +197,18 @@ static void transfer(int fd, uint8_t const * const tx, uint8_t const * const rx, ret = ioctl(fd, SPI_IOC_MESSAGE(effective_transfers), tr); free(tr); - if (ret < 0) + if (ret < 0) { + const size_t dump_len = min_t(size_t, len, 256); + int saved_errno = errno; + + if (tx) { + hex_dump(tx, dump_len, 32, "TX"); + if (len > dump_len) + printf("... (%zu more bytes)\n", len - dump_len); + } + errno = saved_errno; pabort("can't send spi message"); + } if (verbose && tx) hex_dump(tx, len, 32, "TX"); From c79bbeadabc07e9a4c68ca16a3744a2264d6fbae Mon Sep 17 00:00:00 2001 From: Jonas Rebmann Date: Thu, 24 Sep 2026 15:44:03 +0200 Subject: [PATCH 059/136] spi: spidev_test: rewrite unescape() to stay in bounds unescape() could read the source buffer out of bounds for inputs like "\\x" and could write the destination buffer out of bounds due to lack of checking against the len parameter. For better readability and consistency, rewrite it as a simplified version of unescape_string() in string_helpers.h. This includes rearranging src and dst parameters. Signed-off-by: Jonas Rebmann Link: https://patch.msgid.link/20260924-spi-sun4i-spidev_test-v7-13-126e31a07fdc@pengutronix.de Signed-off-by: Mark Brown --- tools/spi/spidev_test.c | 38 +++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/tools/spi/spidev_test.c b/tools/spi/spidev_test.c index 61951ed1c908..5bd3159f61a7 100644 --- a/tools/spi/spidev_test.c +++ b/tools/spi/spidev_test.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -100,28 +101,31 @@ static void hex_dump(const void *src, size_t length, size_t line_size, * Unescape - process hexadecimal escape character * converts shell input "\x23" -> 0x23 */ -static int unescape(char *_dst, char *_src, size_t len) +static int unescape(char *src, char *dst, size_t size) { - int ret = 0; - int match; - char *src = _src; - char *dst = _dst; + char *out = dst; unsigned int ch; - while (*src) { - if (*src == '\\' && *(src+1) == 'x') { - match = sscanf(src + 2, "%2x", &ch); - if (!match) - pabort("malformed input string"); + while (*src && size--) { + if (src[0] == '\\' && src[1] != '\0') { + src++; - src += 4; - *dst++ = (unsigned char)ch; - } else { - *dst++ = *src++; + if (src[0] == 'x' && + isxdigit((unsigned char)src[1]) && + isxdigit((unsigned char)src[2]) && + sscanf(&src[1], "%2x", &ch)) { + *out++ = (unsigned char)ch; + src += 3; + continue; + } + + *out++ = '\\'; + if (!size--) + break; } - ret++; + *out++ = *src++; } - return ret; + return out - dst; } static void transfer(int fd, uint8_t const * const tx, uint8_t const * const rx, size_t len) @@ -455,7 +459,7 @@ static void transfer_escaped_string(int fd, char *str) pabort("can't allocate rx buffer"); } - size = unescape((char *)tx, str, size); + size = unescape(str, (char *)tx, size); transfer(fd, tx, rx, size); free(rx); free(tx); From 2a252105e03411e9a5fed78ad4a99e3b6dc98afd Mon Sep 17 00:00:00 2001 From: Ziyang Huang Date: Mon, 17 Aug 2026 23:15:02 +0800 Subject: [PATCH 060/136] spi: spi-qpic-snand: reject when no enough OOB space Print a clear error when the flash doesn't have enough space, instand of return -EBADMSG silently in read or write functions. ECC4 requires 64B OOB space. ECC8 requires 80B OOB space. Signed-off-by: Ziyang Huang Link: https://patch.msgid.link/SEYPR01MB5882463D860A9670617D5834C9A72@SEYPR01MB5882.apcprd01.prod.exchangelabs.com Signed-off-by: Mark Brown --- drivers/spi/spi-qpic-snand.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/spi/spi-qpic-snand.c b/drivers/spi/spi-qpic-snand.c index aaa40753d274..9d626cee3d17 100644 --- a/drivers/spi/spi-qpic-snand.c +++ b/drivers/spi/spi-qpic-snand.c @@ -343,6 +343,15 @@ static int qcom_spi_ecc_init_ctx_pipelined(struct nand_device *nand) ecc_cfg->cw_size = ecc_cfg->cw_data + ecc_cfg->bytes; bad_block_byte = mtd->writesize - ecc_cfg->cw_size * (cwperpage - 1) + 1; + if ((ecc_cfg->cw_size * cwperpage) > (mtd->writesize + mtd->oobsize)) { + dev_err(snandc->dev, + "Current ECC settings require %d bytes, but the flash only has %d+%d bytes.\n", + (ecc_cfg->cw_size * cwperpage), mtd->writesize, + mtd->oobsize); + ret = -EINVAL; + goto err_free_ecc_cfg; + } + mtd_set_ooblayout(mtd, &qcom_spi_ooblayout); /* From 926f9c44a76fe9e722180d0804c607c6cf1ebbb4 Mon Sep 17 00:00:00 2001 From: Myeonghun Pak Date: Sat, 12 Sep 2026 15:00:56 -0400 Subject: [PATCH 061/136] spi: rockchip-sfc: skip DMA cleanup in PIO mode With rockchip,sfc-no-dma, probe skips allocating and mapping the transfer buffer. However, controller registration failure and driver removal still call dma_unmap_single() for that nonexistent mapping. Guard the DMA mapping and buffer cleanup with use_dma. Keep the existing cleanup for allocation and mapping failures on the DMA path. This issue was identified during our ongoing static-analysis research while reviewing kernel code. Fixes: ee795e82e101 ("spi: rockchip-sfc: Fix DMA-API usage") Assisted-by: OpenAI:GPT-5.6 Co-developed-by: Ijae Kim Signed-off-by: Ijae Kim Signed-off-by: Myeonghun Pak Link: https://patch.msgid.link/20260912190056.42192-1-mhun512@gmail.com Signed-off-by: Mark Brown --- drivers/spi/spi-rockchip-sfc.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/drivers/spi/spi-rockchip-sfc.c b/drivers/spi/spi-rockchip-sfc.c index 43e91b49bb1d..a85a260ba95f 100644 --- a/drivers/spi/spi-rockchip-sfc.c +++ b/drivers/spi/spi-rockchip-sfc.c @@ -719,10 +719,12 @@ static int rockchip_sfc_probe(struct platform_device *pdev) return 0; err_register: - dma_unmap_single(dev, sfc->dma_buffer, sfc->max_iosize, - DMA_BIDIRECTIONAL); + if (sfc->use_dma) + dma_unmap_single(dev, sfc->dma_buffer, sfc->max_iosize, + DMA_BIDIRECTIONAL); err_dma_map: - free_pages((unsigned long)sfc->buffer, get_order(sfc->max_iosize)); + if (sfc->use_dma) + free_pages((unsigned long)sfc->buffer, get_order(sfc->max_iosize)); err_dma: pm_runtime_get_sync(dev); pm_runtime_put_noidle(dev); @@ -748,9 +750,11 @@ static void rockchip_sfc_remove(struct platform_device *pdev) pm_runtime_disable(&pdev->dev); pm_runtime_set_suspended(&pdev->dev); pm_runtime_dont_use_autosuspend(&pdev->dev); - dma_unmap_single(&pdev->dev, sfc->dma_buffer, sfc->max_iosize, - DMA_BIDIRECTIONAL); - free_pages((unsigned long)sfc->buffer, get_order(sfc->max_iosize)); + if (sfc->use_dma) { + dma_unmap_single(&pdev->dev, sfc->dma_buffer, sfc->max_iosize, + DMA_BIDIRECTIONAL); + free_pages((unsigned long)sfc->buffer, get_order(sfc->max_iosize)); + } clk_disable_unprepare(sfc->clk); clk_disable_unprepare(sfc->hclk); From 7f7400bd60c3ccaf823bb7a245da67d27e2face5 Mon Sep 17 00:00:00 2001 From: Gabor Juhos Date: Sun, 27 Sep 2026 16:11:22 +0200 Subject: [PATCH 062/136] spi: spi-qpic-snand: drop ecceng_to_qspi() macro The ecceng_to_qspi() macro is only used within the nand_to_qcom_snand() function. Drop the macro and use the respective container_of() macro call directly in the function instead to simplify the code. No functional changes. Signed-off-by: Gabor Juhos Link: https://patch.msgid.link/20260927-qpic-snand-drop-ecceng_to_qspi-v1-1-993e9ec1c2d4@gmail.com Signed-off-by: Mark Brown --- drivers/spi/spi-qpic-snand.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/spi/spi-qpic-snand.c b/drivers/spi/spi-qpic-snand.c index 9d626cee3d17..40ec6a3769bc 100644 --- a/drivers/spi/spi-qpic-snand.c +++ b/drivers/spi/spi-qpic-snand.c @@ -63,7 +63,6 @@ #define ACC_FEATURE 0xe #define BAD_BLOCK_MARKER_SIZE 0x2 #define OOB_BUF_SIZE 128 -#define ecceng_to_qspi(eng) container_of(eng, struct qpic_spi_nand, ecc_eng) struct snandc_read_status { __le32 snandc_flash; @@ -168,8 +167,9 @@ static void qcom_spi_set_read_loc_last(struct qcom_nand_controller *snandc, static struct qcom_nand_controller *nand_to_qcom_snand(struct nand_device *nand) { struct nand_ecc_engine *eng = nand->ecc.engine; - struct qpic_spi_nand *qspi = ecceng_to_qspi(eng); + struct qpic_spi_nand *qspi; + qspi = container_of(eng, struct qpic_spi_nand, ecc_eng); return qspi->snandc; } From 2ec5d38037b849340e6312ada3d54b522b648c9b Mon Sep 17 00:00:00 2001 From: Xianwei Zhao Date: Thu, 24 Sep 2026 05:51:08 +0000 Subject: [PATCH 063/136] spi: dt-bindings: amlogic,meson-gx-spicc: add T7 compatible Add the Amlogic T7 SPICC compatible to the device tree binding. The T7 controller is compatible with the existing G12A SPICC implementation, so require amlogic,meson-g12a-spicc as a fallback compatible for T7 devices. Signed-off-by: Xianwei Zhao Link: https://patch.msgid.link/20260924-t7-node-new-v2-1-319d3c19b4b2@amlogic.com Signed-off-by: Mark Brown --- .../bindings/spi/amlogic,meson-gx-spicc.yaml | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/Documentation/devicetree/bindings/spi/amlogic,meson-gx-spicc.yaml b/Documentation/devicetree/bindings/spi/amlogic,meson-gx-spicc.yaml index 4e28e6e9d8e0..f0ba15402a4c 100644 --- a/Documentation/devicetree/bindings/spi/amlogic,meson-gx-spicc.yaml +++ b/Documentation/devicetree/bindings/spi/amlogic,meson-gx-spicc.yaml @@ -16,10 +16,15 @@ description: | properties: compatible: - enum: - - amlogic,meson-gx-spicc # SPICC controller on Amlogic GX and compatible SoCs - - amlogic,meson-axg-spicc # SPICC controller on Amlogic AXG and compatible SoCs - - amlogic,meson-g12a-spicc # SPICC controller on Amlogic G12A and compatible SoCs + oneOf: + - items: + - enum: + - amlogic,t7-spicc + - const: amlogic,meson-g12a-spicc + - enum: + - amlogic,meson-gx-spicc # SPICC controller on Amlogic GX and compatible SoCs + - amlogic,meson-axg-spicc # SPICC controller on Amlogic AXG and compatible SoCs + - amlogic,meson-g12a-spicc # SPICC controller on Amlogic G12A and compatible SoCs interrupts: maxItems: 1 From 7b85114d47f693b709b2e8e22e44cdf58c04b111 Mon Sep 17 00:00:00 2001 From: Ovidiu Panait Date: Mon, 21 Sep 2026 19:59:43 +0000 Subject: [PATCH 064/136] spi: rspi: Fix DMA mode when the DMA controller is built as a module On the Renesas RZ/G2L platform, the RSPI driver falls back to PIO mode instead of using DMA when the DMA controller driver is built as a module (CONFIG_RZ_DMAC=m): renesas_spi 1004b000.spi: dma_request_slave_channel_compat failed renesas_spi 1004b000.spi: DMA not available, using PIO The driver requests DMA channels through dma_request_slave_channel_compat() which returns either a valid pointer or NULL and cannot handle -EPROBE_DEFER. If the DMA controller has not probed yet, the driver falls back to PIO directly. Fix this by replacing the dma_request_slave_channel_compat() call with a regular dma_request_chan() call. dma_request_slave_channel_compat() is always passed a NULL fn_param in all code paths, so it actually behaves like a plain dma_request_chan() call. Fixes: e825b8dd2b36 ("spi: rspi: Add DT support to DMA setup") Signed-off-by: Ovidiu Panait Link: https://patch.msgid.link/20260921195943.107247-1-ovidiu.panait.rb@renesas.com Signed-off-by: Mark Brown --- drivers/spi/spi-rspi.c | 49 ++++++++++++++++-------------------------- 1 file changed, 19 insertions(+), 30 deletions(-) diff --git a/drivers/spi/spi-rspi.c b/drivers/spi/spi-rspi.c index 38df676774ee..c61edd1c1353 100644 --- a/drivers/spi/spi-rspi.c +++ b/drivers/spi/spi-rspi.c @@ -22,7 +22,6 @@ #include #include #include -#include #include #include @@ -1091,24 +1090,15 @@ static irqreturn_t rspi_irq_tx(int irq, void *_sr) static struct dma_chan *rspi_request_dma_chan(struct device *dev, enum dma_transfer_direction dir, - unsigned int id, dma_addr_t port_addr) { - dma_cap_mask_t mask; struct dma_chan *chan; struct dma_slave_config cfg; int ret; - dma_cap_zero(mask); - dma_cap_set(DMA_SLAVE, mask); - - chan = dma_request_slave_channel_compat(mask, shdma_chan_filter, - (void *)(unsigned long)id, dev, - dir == DMA_MEM_TO_DEV ? "tx" : "rx"); - if (!chan) { - dev_warn(dev, "dma_request_slave_channel_compat failed\n"); - return NULL; - } + chan = dma_request_chan(dev, dir == DMA_MEM_TO_DEV ? "tx" : "rx"); + if (IS_ERR(chan)) + return chan; memset(&cfg, 0, sizeof(cfg)); cfg.dst_addr = port_addr + RSPI_SPDR; @@ -1121,7 +1111,7 @@ static struct dma_chan *rspi_request_dma_chan(struct device *dev, if (ret) { dev_warn(dev, "dmaengine_slave_config failed %d\n", ret); dma_release_channel(chan); - return NULL; + return ERR_PTR(ret); } return chan; @@ -1130,29 +1120,23 @@ static struct dma_chan *rspi_request_dma_chan(struct device *dev, static int rspi_request_dma(struct device *dev, struct spi_controller *ctlr, const struct resource *res) { - unsigned int dma_tx_id, dma_rx_id; + struct dma_chan *chan; - if (dev->of_node) { - /* In the OF case we will get the slave IDs from the DT */ - dma_tx_id = 0; - dma_rx_id = 0; - } else { - /* The driver assumes no error. */ + if (!dev->of_node) return 0; - } - ctlr->dma_tx = rspi_request_dma_chan(dev, DMA_MEM_TO_DEV, dma_tx_id, - res->start); - if (!ctlr->dma_tx) - return -ENODEV; + chan = rspi_request_dma_chan(dev, DMA_MEM_TO_DEV, res->start); + if (IS_ERR(chan)) + return PTR_ERR(chan); + ctlr->dma_tx = chan; - ctlr->dma_rx = rspi_request_dma_chan(dev, DMA_DEV_TO_MEM, dma_rx_id, - res->start); - if (!ctlr->dma_rx) { + chan = rspi_request_dma_chan(dev, DMA_DEV_TO_MEM, res->start); + if (IS_ERR(chan)) { dma_release_channel(ctlr->dma_tx); ctlr->dma_tx = NULL; - return -ENODEV; + return PTR_ERR(chan); } + ctlr->dma_rx = chan; ctlr->can_dma = rspi_can_dma; dev_info(dev, "DMA available"); @@ -1355,6 +1339,11 @@ static int rspi_probe(struct platform_device *pdev) } ret = rspi_request_dma(&pdev->dev, ctlr, res); + if (ret == -EPROBE_DEFER) { + dev_err_probe(&pdev->dev, ret, + "failed to request DMA channels\n"); + goto error2; + } if (ret < 0) dev_warn(&pdev->dev, "DMA not available, using PIO\n"); From 8db44cca03df066186d6220bfad9fa482d7cdc13 Mon Sep 17 00:00:00 2001 From: Lucas Costa <2000.costalucas@gmail.com> Date: Fri, 25 Sep 2026 16:30:08 -0300 Subject: [PATCH 065/136] spi: bcm2835: fix device_node reference leak in bcm2835_spi_setup() of_find_compatible_node() returns a device_node with its reference count incremented, but bcm2835_spi_setup() only uses the return value to test for a matching node in the loop and never releases it. Declare the node with a scope-based __free(device_node) cleanup so the reference is dropped automatically when leaving the loop body, matching the existing __free(kfree) usage in the same function. Fixes: e19c1272c80a ("spi: bcm2835: Restore native CS probing when pinctrl-bcm2835 is absent") Cc: stable@vger.kernel.org Signed-off-by: Lucas Costa <2000.costalucas@gmail.com> Reviewed-by: Bartosz Golaszewski Link: https://patch.msgid.link/20260925193008.127314-1-2000.costalucas@gmail.com Signed-off-by: Mark Brown --- drivers/spi/spi-bcm2835.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/spi/spi-bcm2835.c b/drivers/spi/spi-bcm2835.c index 8f8715809c7c..23606967f7d1 100644 --- a/drivers/spi/spi-bcm2835.c +++ b/drivers/spi/spi-bcm2835.c @@ -1297,7 +1297,9 @@ static int bcm2835_spi_setup(struct spi_device *spi) } for (i = 0; i < ARRAY_SIZE(pinctrl_compats); i++) { - if (of_find_compatible_node(NULL, NULL, pinctrl_compats[i])) + struct device_node *np __free(device_node) = + of_find_compatible_node(NULL, NULL, pinctrl_compats[i]); + if (np) break; } From f3c0a2b557ea87cbd802b278783c32484a063ded Mon Sep 17 00:00:00 2001 From: Gabor Juhos Date: Tue, 22 Sep 2026 19:50:57 +0200 Subject: [PATCH 066/136] spi: spi-qpic-snand: fix read location register usage in qcom_spi_config_cw_read() While configuring a codeword read, the com_spi_config_cw_read() function unconditionally schedules writing of the regular read location registers. However these registers should be written only if the actual codeword is not the last one. Change the function to schedule writing of the registers corresponding to the acual codeword only. While at it, also drop the single-use 'reg' and 'num_cw' local variables and use the respective values directly in order to make the code easier to understand. Fixes: 7304d1909080 ("spi: spi-qpic: add driver for QCOM SPI NAND flash Interface") Signed-off-by: Gabor Juhos Link: https://patch.msgid.link/20260922-qpic-snand-fix-cw_read-readloc-v1-1-67f6819fb3d7@gmail.com Signed-off-by: Mark Brown --- drivers/spi/spi-qpic-snand.c | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/drivers/spi/spi-qpic-snand.c b/drivers/spi/spi-qpic-snand.c index 40ec6a3769bc..edfac4866a96 100644 --- a/drivers/spi/spi-qpic-snand.c +++ b/drivers/spi/spi-qpic-snand.c @@ -524,15 +524,14 @@ static void qcom_spi_config_page_read(struct qcom_nand_controller *snandc) static void qcom_spi_config_cw_read(struct qcom_nand_controller *snandc, bool use_ecc, int cw) { - __le32 *reg = &snandc->regs->read_location0; - int num_cw = snandc->qspi->num_cw; - - qcom_write_reg_dma(snandc, reg, NAND_READ_LOCATION_0, 4, NAND_BAM_NEXT_SGL); - if (cw == (num_cw - 1)) { - reg = &snandc->regs->read_location_last0; - qcom_write_reg_dma(snandc, reg, NAND_READ_LOCATION_LAST_CW_0, 4, + if (cw == (snandc->qspi->num_cw - 1)) + qcom_write_reg_dma(snandc, &snandc->regs->read_location_last0, + NAND_READ_LOCATION_LAST_CW_0, 4, + NAND_BAM_NEXT_SGL); + else + qcom_write_reg_dma(snandc, &snandc->regs->read_location0, + NAND_READ_LOCATION_0, 4, NAND_BAM_NEXT_SGL); - } qcom_write_reg_dma(snandc, &snandc->regs->cmd, NAND_FLASH_CMD, 1, NAND_BAM_NEXT_SGL); qcom_write_reg_dma(snandc, &snandc->regs->exec, NAND_EXEC_CMD, 1, NAND_BAM_NEXT_SGL); From cecb86a37ac0bdd69edc2b70c160b436ec6bea16 Mon Sep 17 00:00:00 2001 From: Udaya Kiran Challa Date: Tue, 25 Aug 2026 21:52:57 +0530 Subject: [PATCH 067/136] spi: dt-bindings: microchip,pic32mzda-spi: Convert to DT schema Convert Microchip PIC32 SPI controller devicetree binding from legacy text format to DT schema. Reviewed-by: Krzysztof Kozlowski Signed-off-by: Udaya Kiran Challa Link: https://patch.msgid.link/20260825162257.77042-1-challauday369@gmail.com Signed-off-by: Mark Brown --- .../bindings/spi/microchip,pic32mzda-spi.yaml | 82 +++++++++++++++++++ .../bindings/spi/microchip,spi-pic32.txt | 34 -------- 2 files changed, 82 insertions(+), 34 deletions(-) create mode 100644 Documentation/devicetree/bindings/spi/microchip,pic32mzda-spi.yaml delete mode 100644 Documentation/devicetree/bindings/spi/microchip,spi-pic32.txt diff --git a/Documentation/devicetree/bindings/spi/microchip,pic32mzda-spi.yaml b/Documentation/devicetree/bindings/spi/microchip,pic32mzda-spi.yaml new file mode 100644 index 000000000000..4669b521fa1f --- /dev/null +++ b/Documentation/devicetree/bindings/spi/microchip,pic32mzda-spi.yaml @@ -0,0 +1,82 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/spi/microchip,pic32mzda-spi.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Microchip PIC32MZDA SPI Controller + +maintainers: + - Thomas Bogendoerfer + +allOf: + - $ref: spi-controller.yaml# + +properties: + compatible: + const: microchip,pic32mzda-spi + + reg: + maxItems: 1 + + interrupts: + items: + - description: Fault interrupt + - description: Receive interrupt + - description: Transmit interrupt + + interrupt-names: + items: + - const: fault + - const: rx + - const: tx + + clocks: + maxItems: 1 + + clock-names: + items: + - const: mck0 + + cs-gpios: + maxItems: 1 + + dmas: + items: + - description: RX DMA channel + - description: TX DMA channel + + dma-names: + items: + - const: spi-rx + - const: spi-tx + +required: + - compatible + - reg + - interrupts + - interrupt-names + - clocks + - clock-names + - cs-gpios + +unevaluatedProperties: false + +examples: + - | + #include + #include + + spi@1f821000 { + compatible = "microchip,pic32mzda-spi"; + reg = <0x1f821000 0x200>; + interrupts = <109 IRQ_TYPE_LEVEL_HIGH>, + <110 IRQ_TYPE_LEVEL_HIGH>, + <111 IRQ_TYPE_LEVEL_HIGH>; + interrupt-names = "fault", "rx", "tx"; + clocks = <&PBCLK2>; + clock-names = "mck0"; + cs-gpios = <&gpio3 4 GPIO_ACTIVE_LOW>; + dmas = <&dma 134>, <&dma 135>; + dma-names = "spi-rx", "spi-tx"; + }; diff --git a/Documentation/devicetree/bindings/spi/microchip,spi-pic32.txt b/Documentation/devicetree/bindings/spi/microchip,spi-pic32.txt deleted file mode 100644 index 79de379f4dc0..000000000000 --- a/Documentation/devicetree/bindings/spi/microchip,spi-pic32.txt +++ /dev/null @@ -1,34 +0,0 @@ -Microchip PIC32 SPI Master controller - -Required properties: -- compatible: Should be "microchip,pic32mzda-spi". -- reg: Address and length of register space for the device. -- interrupts: Should contain all three spi interrupts in sequence - of , , . -- interrupt-names: Should be "fault", "rx", "tx" in order. -- clocks: Phandle of the clock generating SPI clock on the bus. -- clock-names: Should be "mck0". -- cs-gpios: Specifies the gpio pins to be used for chipselects. - See: Documentation/devicetree/bindings/spi/spi-bus.txt - -Optional properties: -- dmas: Two or more DMA channel specifiers following the convention outlined - in Documentation/devicetree/bindings/dma/dma.txt -- dma-names: Names for the dma channels. There must be at least one channel - named "spi-tx" for transmit and named "spi-rx" for receive. - -Example: - -spi1: spi@1f821000 { - compatible = "microchip,pic32mzda-spi"; - reg = <0x1f821000 0x200>; - interrupts = <109 IRQ_TYPE_LEVEL_HIGH>, - <110 IRQ_TYPE_LEVEL_HIGH>, - <111 IRQ_TYPE_LEVEL_HIGH>; - interrupt-names = "fault", "rx", "tx"; - clocks = <&PBCLK2>; - clock-names = "mck0"; - cs-gpios = <&gpio3 4 GPIO_ACTIVE_LOW>; - dmas = <&dma 134>, <&dma 135>; - dma-names = "spi-rx", "spi-tx"; -}; From 1f07c6fecb38c2839bc71bb4f8b6637684fdd33e Mon Sep 17 00:00:00 2001 From: Gabor Juhos Date: Tue, 29 Sep 2026 13:04:00 +0200 Subject: [PATCH 068/136] spi: spi-qpic-snand: remove 'ecc_buf_cfg' member of struct 'qpic_ecc' The 'ecc_buf_cfg' member of struct 'qpic_ecc' is used to store a configuration value for the QPIC_EBI2_ECC_BUF_CFG register. However that register is obsolete on the QPIC v2 controller the driver is written for and programming of the register has been removed in commit 930a7312c946 ("spi: spi-qpic-snand: avoid writing QPIC_EBI2_ECC_BUF_CFG register"). Since thaen, the value stored in 'ecc_buf_cfg' has no real use, so drop the member from the structure. Also remove the remaining code which uses that. Signed-off-by: Gabor Juhos Link: https://patch.msgid.link/20260929-qpic-snand-drop-ecc_buf_cfg-v1-1-c9de93142815@gmail.com Signed-off-by: Mark Brown --- drivers/spi/spi-qpic-snand.c | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/drivers/spi/spi-qpic-snand.c b/drivers/spi/spi-qpic-snand.c index edfac4866a96..00072c44db7a 100644 --- a/drivers/spi/spi-qpic-snand.c +++ b/drivers/spi/spi-qpic-snand.c @@ -97,7 +97,6 @@ struct qpic_ecc { u32 cfg1; u32 cfg0_raw; u32 cfg1_raw; - u32 ecc_buf_cfg; u32 ecc_bch_cfg; bool bch_enabled; }; @@ -407,8 +406,6 @@ static int qcom_spi_ecc_init_ctx_pipelined(struct nand_device *nand) FIELD_PREP(ECC_MODE_MASK, ecc_cfg->ecc_mode) | FIELD_PREP(ECC_PARITY_SIZE_BYTES_BCH_MASK, ecc_cfg->ecc_bytes_hw); - ecc_cfg->ecc_buf_cfg = FIELD_PREP(NUM_STEPS_MASK, 0x203); - conf->step_size = ecc_cfg->step_size; conf->strength = ecc_cfg->strength; @@ -1198,13 +1195,12 @@ static int qcom_spi_program_ecc(struct qcom_nand_controller *snandc, u8 *data_buf = NULL, *oob_buf = NULL; int i, ret; int num_cw = snandc->qspi->num_cw; - u32 cfg0, cfg1, ecc_bch_cfg, ecc_buf_cfg; + u32 cfg0, cfg1, ecc_bch_cfg; cfg0 = (ecc_cfg->cfg0 & ~CW_PER_PAGE_MASK) | FIELD_PREP(CW_PER_PAGE_MASK, num_cw - 1); cfg1 = ecc_cfg->cfg1; ecc_bch_cfg = ecc_cfg->ecc_bch_cfg; - ecc_buf_cfg = ecc_cfg->ecc_buf_cfg; if (snandc->qspi->data_buf) data_buf = snandc->qspi->data_buf; @@ -1222,7 +1218,6 @@ static int qcom_spi_program_ecc(struct qcom_nand_controller *snandc, snandc->regs->cfg0 = cpu_to_le32(cfg0); snandc->regs->cfg1 = cpu_to_le32(cfg1); snandc->regs->ecc_bch_cfg = cpu_to_le32(ecc_bch_cfg); - snandc->regs->ecc_buf_cfg = cpu_to_le32(ecc_buf_cfg); snandc->regs->exec = cpu_to_le32(1); qcom_spi_config_page_write(snandc); @@ -1275,13 +1270,12 @@ static int qcom_spi_program_oob(struct qcom_nand_controller *snandc, u8 *oob_buf = NULL; int ret, col, data_size, oob_size; int num_cw = snandc->qspi->num_cw; - u32 cfg0, cfg1, ecc_bch_cfg, ecc_buf_cfg; + u32 cfg0, cfg1, ecc_bch_cfg; cfg0 = (ecc_cfg->cfg0 & ~CW_PER_PAGE_MASK) | FIELD_PREP(CW_PER_PAGE_MASK, 0); cfg1 = ecc_cfg->cfg1; ecc_bch_cfg = ecc_cfg->ecc_bch_cfg; - ecc_buf_cfg = ecc_cfg->ecc_buf_cfg; col = ecc_cfg->cw_size * (num_cw - 1); @@ -1297,7 +1291,6 @@ static int qcom_spi_program_oob(struct qcom_nand_controller *snandc, snandc->regs->cfg0 = cpu_to_le32(cfg0); snandc->regs->cfg1 = cpu_to_le32(cfg1); snandc->regs->ecc_bch_cfg = cpu_to_le32(ecc_bch_cfg); - snandc->regs->ecc_buf_cfg = cpu_to_le32(ecc_buf_cfg); snandc->regs->exec = cpu_to_le32(1); /* calculate the data and oob size for the last codeword/step */ From 69338914b1d98d2c0dcd6e9617c56907a1b3e2db Mon Sep 17 00:00:00 2001 From: Frank Li Date: Tue, 29 Sep 2026 15:06:20 -0400 Subject: [PATCH 069/136] spi: ingenic: remove dmaengine_desc_free() dmaengine_desc_free() is designed for DMA_CTRL_REUSE. If DMA_CTRL_REUSE is not set, dmaengine_desc_free() will return -EPERM. if (!dmaengine_desc_test_reuse(desc)) return -EPERM; So calling dmaengine_desc_free() does nothing. dmaengine_terminate_(a)sync() will free all already allocated descriptors. Signed-off-by: Frank Li Link: https://patch.msgid.link/20260929190621.3516424-1-Frank.Li@oss.nxp.com Signed-off-by: Mark Brown --- drivers/spi/spi-ingenic.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/drivers/spi/spi-ingenic.c b/drivers/spi/spi-ingenic.c index 7ce4fe965cac..a0a047510611 100644 --- a/drivers/spi/spi-ingenic.c +++ b/drivers/spi/spi-ingenic.c @@ -164,10 +164,8 @@ spi_ingenic_prepare_dma(struct spi_controller *ctlr, struct dma_chan *chan, cookie = dmaengine_submit(desc); ret = dma_submit_error(cookie); - if (ret) { - dmaengine_desc_free(desc); + if (ret) return ERR_PTR(ret); - } return desc; } @@ -186,7 +184,6 @@ static int spi_ingenic_dma_tx(struct spi_controller *ctlr, &xfer->tx_sg, DMA_MEM_TO_DEV, bits); if (IS_ERR(tx_desc)) { dmaengine_terminate_async(ctlr->dma_rx); - dmaengine_desc_free(rx_desc); return PTR_ERR(tx_desc); } From cf0f217a52d04af0be2e695475b8b4b392ae508c Mon Sep 17 00:00:00 2001 From: Jyan Chou Date: Wed, 30 Sep 2026 14:59:44 +0800 Subject: [PATCH 070/136] spi: dt-bindings: Add Realtek RTD1625 SPI NOR support Add DT binding schema for Realtek RTD1625 SPI NOR Flash Controller Signed-off-by: Jyan Chou Co-developed-by: Yu-Chun Lin Signed-off-by: Yu-Chun Lin Acked-by: Conor Dooley Link: https://patch.msgid.link/20260930065945.88008-2-eleanor.lin@realtek.com Signed-off-by: Mark Brown --- .../bindings/spi/realtek,rtd1625-nor.yaml | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 Documentation/devicetree/bindings/spi/realtek,rtd1625-nor.yaml diff --git a/Documentation/devicetree/bindings/spi/realtek,rtd1625-nor.yaml b/Documentation/devicetree/bindings/spi/realtek,rtd1625-nor.yaml new file mode 100644 index 000000000000..014ddfe0f421 --- /dev/null +++ b/Documentation/devicetree/bindings/spi/realtek,rtd1625-nor.yaml @@ -0,0 +1,65 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/spi/realtek,rtd1625-nor.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Realtek RTD1625 SPI NOR Flash Controller + +maintainers: + - Jyan Chou + - Yu-Chun Lin + +properties: + compatible: + enum: + - realtek,rtd1625-nor + + reg: + maxItems: 3 + + reg-names: + items: + - const: ctrl + - const: dma + - const: dirmap + + clocks: + maxItems: 1 + + resets: + maxItems: 1 + + "#address-cells": + const: 1 + + "#size-cells": + const: 0 + +required: + - compatible + - reg + - reg-names + - clocks + - resets + - "#address-cells" + - "#size-cells" + +allOf: + - $ref: /schemas/spi/spi-controller.yaml# + +unevaluatedProperties: false + +examples: + - | + spi@14b000 { + compatible = "realtek,rtd1625-nor"; + reg = <0x14b000 0x50>, <0x14bf00 0x30>, <0x88100000 0x2000000>; + reg-names = "ctrl", "dma", "dirmap"; + clocks = <&cc 18>; + resets = <&cc 26>; + pinctrl-0 = <&spi_pins>; + pinctrl-names = "default"; + #address-cells = <1>; + #size-cells = <0>; + }; From aca8cbecdd2e9fcc54540228be78197913bbea7d Mon Sep 17 00:00:00 2001 From: Jyan Chou Date: Wed, 30 Sep 2026 14:59:45 +0800 Subject: [PATCH 071/136] spi: spi-mem: Add Realtek SPI NOR flash controller driver Add a spi-mem driver for the SPI NOR Flash Controller (SFC) found on Realtek DHC SoC. SFC supports Dual I/O. Implement the exec_op API for regular control commands and the dirmap API for hardware-accelerated read/write operations. The controller is described by three register ranges. The first one (ctrl) holds the control registers. The second one (dma) holds the DMA engine registers, used to transfer the payload between the flash and main memory. The third range is the memory aperture (dirmap) that maps the flash contents. Signed-off-by: Jyan Chou Co-developed-by: Yu-Chun Lin Signed-off-by: Yu-Chun Lin Link: https://patch.msgid.link/20260930065945.88008-3-eleanor.lin@realtek.com Signed-off-by: Mark Brown --- MAINTAINERS | 6 + drivers/spi/Kconfig | 10 + drivers/spi/Makefile | 1 + drivers/spi/spi-rtk-nor.c | 624 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 641 insertions(+) create mode 100644 drivers/spi/spi-rtk-nor.c diff --git a/MAINTAINERS b/MAINTAINERS index 360977678f70..a9cec9cfdf1e 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -23023,6 +23023,12 @@ S: Maintained F: Documentation/devicetree/bindings/spi/realtek,rtl9301-snand.yaml F: drivers/spi/spi-realtek-rtl-snand.c +REALTEK SPI-NOR +M: Jyan Chou +M: Yu-Chun Lin +S: Supported +F: drivers/spi/spi-rtk-nor.c + REALTEK SYSTIMER DRIVER M: Hao-Wen Ting S: Maintained diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index a0f37e2df716..99ed19a99d75 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -967,6 +967,16 @@ config SPI_RSPI help SPI driver for Renesas RSPI and QSPI blocks. +config SPI_RTK_NOR + tristate "Realtek SPI-NOR flash controller" + depends on ARCH_REALTEK || COMPILE_TEST + depends on SPI_MEM + help + This enables support for the SPI NOR Flash Controller found on + Realtek DHC SoCs. The controller supports single and dual I/O + transfers and offloads the payload to an integrated DMA engine. + Say Y or M here if you are building a kernel for a Realtek DHC SoC + config SPI_RZV2H_RSPI tristate "Renesas RZ/V2H RSPI controller" depends on ARCH_RENESAS || COMPILE_TEST diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index a18814fbde93..37c6eec9b541 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -133,6 +133,7 @@ obj-$(CONFIG_MACH_REALTEK_RTL) += spi-realtek-rtl.o obj-$(CONFIG_SPI_REALTEK_SNAND) += spi-realtek-rtl-snand.o obj-$(CONFIG_SPI_RPCIF) += spi-rpc-if.o obj-$(CONFIG_SPI_RSPI) += spi-rspi.o +obj-$(CONFIG_SPI_RTK_NOR) += spi-rtk-nor.o obj-$(CONFIG_SPI_RZV2H_RSPI) += spi-rzv2h-rspi.o obj-$(CONFIG_SPI_RZV2M_CSI) += spi-rzv2m-csi.o obj-$(CONFIG_SPI_S3C64XX) += spi-s3c64xx.o diff --git a/drivers/spi/spi-rtk-nor.c b/drivers/spi/spi-rtk-nor.c new file mode 100644 index 000000000000..7f56346506b3 --- /dev/null +++ b/drivers/spi/spi-rtk-nor.c @@ -0,0 +1,624 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Realtek SPI Nor Flash Controller Driver (SFC) +// +// Copyright (c) 2024-2026 Realtek Semiconductor Corp. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SFC_OPCODE 0x00 +#define DUAL_MODE_EN BIT(9) + +#define SFC_CTL 0x04 +#define RW_DATAEN BIT(4) +#define ADDR_EN BIT(3) + +#define SFC_SCK 0x08 +#define FDIV_MASK GENMASK(7, 0) + +#define SFC_CE 0x0c +#define DESLT_TIME 0x1a +#define DESLT_TIME_SHIFT 16 +#define PH_CNT 0x13 +#define PH_CNT_SHIFT 8 +#define PL_CNT 0x7 + +#define WT_PROM_DONE BIT(8) + +#define SFC_POS_LATCH 0x14 +#define FALLING_EDGE_EN 0x0 + +#define SFC_WAIT_WR 0x18 +#define SFC_EN_WR 0x1c +#define WT_PROM_EN BIT(8) + +#define SFC_ADR_FOUR_BYTE_EN 0x28 + +#define MD_FDMA_DDR_SADDR 0x0c +#define MD_FDMA_FL_SADDR 0x10 + +#define MD_FDMA_CTRL2 0x14 +#define MAX_XFER_DMA_LEN (BIT(26) | BIT(27)) +#define MAX_XFER_256 BIT(26) +#define DMA_TO_FLASH BIT(25) + +#define MD_FDMA_CTRL1 0x18 +#define DMA_W_EN_START BIT(3) +#define DMA_END BIT(2) +#define DMA_W_EN BIT(1) +#define DMA_START BIT(0) + +#define MD_FDMA_DDR_SADDR1 0x20 + +#define SFC_DMA_TIMEOUT 20000 +#define SFC_DMA_MAX_LEN 0x100 + +#define SFC_CTL_DMYCNT_MASK GENMASK(31, 24) +#define SFC_CTL_DMYCNT_SHIFT 24 + +#define DMA_HIGH_BITS_MASK 0x7 + +#define RTK_SPI_OP_RDSR 0x05 +#define RTK_SPI_OP_WREN 0x06 +#define RTK_SPI_OP_EN4B 0xb7 +#define RTK_SPI_OP_EX4B 0xe9 + +#define SFC_AUTOSUSPEND_TIMEOUT 2000 + +struct rtk_spi_host { + struct device *dev; + struct clk *clk; + struct reset_control *rstc; + void __iomem *regbase; + void __iomem *iobase; + void __iomem *mdbase; + void *buffer; + bool is_4byte; + dma_addr_t dma_buffer; + resource_size_t flash_phys_base; + size_t dirmap_size; +}; + +static u32 rtk_spi_calc_dummy_cycles(const struct spi_mem_op *op) +{ + if (!op->dummy.nbytes) + return 0; + + return (op->dummy.nbytes * 8) / op->dummy.buswidth; +} + +static void rtk_spi_read_mode(struct rtk_spi_host *host, const struct spi_mem_op *op) +{ + u32 opcode = op->cmd.opcode; + u32 dummy_cycles, val; + + if (op->data.buswidth == 2) + opcode |= DUAL_MODE_EN; + + writel(opcode, host->regbase + SFC_OPCODE); + + val = readl(host->regbase + SFC_CTL); + val |= RW_DATAEN | ADDR_EN; + + dummy_cycles = rtk_spi_calc_dummy_cycles(op); + + val &= ~SFC_CTL_DMYCNT_MASK; + val |= (dummy_cycles << SFC_CTL_DMYCNT_SHIFT) & SFC_CTL_DMYCNT_MASK; + writel(val, host->regbase + SFC_CTL); + + readl(host->iobase); +} + +static void rtk_spi_write_mode(struct rtk_spi_host *host, + const struct spi_mem_op *op) +{ + u32 opcode = op->cmd.opcode; + u32 val; + + writel(opcode, host->regbase + SFC_OPCODE); + + val = readl(host->regbase + SFC_CTL); + val |= RW_DATAEN | ADDR_EN; + writel(val, host->regbase + SFC_CTL); +} + +static void rtk_spi_enable_auto_write(struct rtk_spi_host *host) +{ + u32 val; + + val = WT_PROM_DONE | RTK_SPI_OP_RDSR; + writel(val, host->regbase + SFC_WAIT_WR); + + val = WT_PROM_EN | RTK_SPI_OP_WREN; + writel(val, host->regbase + SFC_EN_WR); +} + +static void rtk_spi_disable_auto_write(struct rtk_spi_host *host) +{ + writel(RTK_SPI_OP_RDSR, host->regbase + SFC_WAIT_WR); + writel(RTK_SPI_OP_WREN, host->regbase + SFC_EN_WR); +} + +static int rtk_spi_byte_transfer(struct rtk_spi_host *host, loff_t offset, + size_t len, unsigned char *buf, bool is_read) +{ + if (offset + len > host->dirmap_size) { + dev_err(host->dev, "PIO transfer out of bounds\n"); + return -EINVAL; + } + + if (is_read) + memcpy_fromio(buf, host->iobase + offset, len); + else + memcpy_toio(host->iobase + offset, buf, len); + + return 0; +} + +static void rtk_spi_init(struct rtk_spi_host *host) +{ + u32 val; + + val = readl(host->regbase + SFC_SCK); + val &= ~FDIV_MASK; + val |= (3 << 0) & FDIV_MASK; + writel(val, host->regbase + SFC_SCK); + + val = (DESLT_TIME << DESLT_TIME_SHIFT) | (PH_CNT << PH_CNT_SHIFT) | PL_CNT; + writel(val, host->regbase + SFC_CE); + + writel(FALLING_EDGE_EN, host->regbase + SFC_POS_LATCH); + writel(RTK_SPI_OP_RDSR, host->regbase + SFC_WAIT_WR); + writel(RTK_SPI_OP_WREN, host->regbase + SFC_EN_WR); + + writel(host->is_4byte ? 0x1 : 0x0, host->regbase + SFC_ADR_FOUR_BYTE_EN); +} + +static int rtk_spi_command_read(struct rtk_spi_host *host, const struct spi_mem_op *op) +{ + size_t len = op->data.nbytes; + loff_t offset = op->addr.val; + u8 opcode = op->cmd.opcode; + u32 dummy_cycles, val; + + writel(opcode, host->regbase + SFC_OPCODE); + + val = readl(host->regbase + SFC_CTL); + val &= ~(SFC_CTL_DMYCNT_MASK | ADDR_EN); + val |= RW_DATAEN; + + if (op->addr.nbytes > 0) + val |= ADDR_EN; + + dummy_cycles = rtk_spi_calc_dummy_cycles(op); + val |= (dummy_cycles << SFC_CTL_DMYCNT_SHIFT) & SFC_CTL_DMYCNT_MASK; + writel(val, host->regbase + SFC_CTL); + + return rtk_spi_byte_transfer(host, offset, len, op->data.buf.in, true); +} + +static int rtk_spi_do_write_and_cmds(struct rtk_spi_host *host, const struct spi_mem_op *op) +{ + u8 opcode = op->cmd.opcode; + u32 ctl_val = 0; + + writel(opcode, host->regbase + SFC_OPCODE); + + if (op->data.nbytes > 0) + ctl_val |= RW_DATAEN; + + if (op->addr.nbytes > 0) + ctl_val |= ADDR_EN; + + writel(ctl_val, host->regbase + SFC_CTL); + + if (op->data.nbytes > 0) { + const u8 *buf = op->data.buf.out; + + if (op->addr.val + op->data.nbytes > host->dirmap_size) + return -EINVAL; + + writeb(*buf, host->iobase + op->addr.val); + } else { + if (op->addr.val >= host->dirmap_size) + return -EINVAL; + + readb(host->iobase + op->addr.val); + } + + return 0; +} + +static int rtk_spi_dma_transfer(struct rtk_spi_host *host, loff_t offset, + size_t len, bool is_read) +{ + u64 dma_buffer, timeout_us = SFC_DMA_TIMEOUT * 100; + u32 flash_phys_addr, val; + int ret; + + if (offset + len > host->dirmap_size) { + dev_err(host->dev, "DMA transfer out of bounds\n"); + return -EINVAL; + } + + writel(DMA_W_EN_START | DMA_W_EN, host->mdbase + MD_FDMA_CTRL1); + + dma_buffer = host->dma_buffer; + + /* Setup MD DDR address and flash address */ + writel(lower_32_bits(dma_buffer), host->mdbase + MD_FDMA_DDR_SADDR); + writel(upper_32_bits(dma_buffer) & DMA_HIGH_BITS_MASK, + host->mdbase + MD_FDMA_DDR_SADDR1); + + /* MD_FDMA_FL_SADDR is a 32-bit hardware register */ + flash_phys_addr = lower_32_bits(host->flash_phys_base + offset); + writel(flash_phys_addr, host->mdbase + MD_FDMA_FL_SADDR); + + if (is_read) + val = MAX_XFER_DMA_LEN | len; + else + val = DMA_TO_FLASH | MAX_XFER_256 | len; + + writel(val, host->mdbase + MD_FDMA_CTRL2); + + writel(DMA_W_EN | DMA_START, host->mdbase + MD_FDMA_CTRL1); + udelay(1); + + ret = readl_poll_timeout(host->mdbase + MD_FDMA_CTRL1, val, + !(val & DMA_START), 100, timeout_us); + if (ret) { + dev_err(host->dev, "DMA transfer timed out\n"); + val = readl(host->mdbase + MD_FDMA_CTRL1); + val |= DMA_END | DMA_W_EN_START; + writel(val, host->mdbase + MD_FDMA_CTRL1); + + if (readl_poll_timeout(host->mdbase + MD_FDMA_CTRL1, val, + !(val & DMA_END), 10, 1000)) { + dev_err(host->dev, "Failed to stop DMA gracefully\n"); + } + + return ret; + } + + return 0; +} + +static int rtk_spi_exec_op(struct spi_mem *mem, const struct spi_mem_op *op) +{ + struct rtk_spi_host *host = spi_controller_get_devdata(mem->spi->controller); + u32 opcode = op->cmd.opcode; + + /* + * Hardware Workaround: + * The controller's auto-mode engine requires SFC_ADR_FOUR_BYTE_EN to be + * explicitly updated when the flash enters or exits 4-byte mode via control + * commands. + */ + if ((op->addr.nbytes == 4 || opcode == RTK_SPI_OP_EN4B) && !host->is_4byte) { + host->is_4byte = true; + writel(0x1, host->regbase + SFC_ADR_FOUR_BYTE_EN); + } else if ((op->addr.nbytes == 3 || opcode == RTK_SPI_OP_EX4B) && host->is_4byte) { + host->is_4byte = false; + writel(0x0, host->regbase + SFC_ADR_FOUR_BYTE_EN); + } + + if (op->data.dir == SPI_MEM_DATA_IN) + return rtk_spi_command_read(host, op); + + return rtk_spi_do_write_and_cmds(host, op); +} + +static bool rtk_spi_supports_op(struct spi_mem *mem, + const struct spi_mem_op *op) +{ + if (op->cmd.buswidth != 1) + return false; + + if (op->cmd.dtr || op->addr.dtr || op->data.dtr) + return false; + + if (op->addr.nbytes != 0) { + if (op->addr.buswidth > 1) + return false; + if (op->addr.nbytes < 3 || op->addr.nbytes > 4) + return false; + } + + /* + * The data window is access-triggered: each CPU access to host->iobase + * generates one complete SPI transaction. Consecutive byte accesses + * therefore cannot be merged into a single transaction, so + * multi-byte stateless commands are not supported by this controller. + */ + if (!op->addr.nbytes && op->data.nbytes > 1) + return -EOPNOTSUPP; + + if (op->dummy.nbytes != 0) { + if (op->dummy.buswidth > 1 || op->dummy.nbytes > 7) + return false; + } + + if (op->data.nbytes != 0 && op->data.buswidth > 2) + return false; + + return spi_mem_default_supports_op(mem, op); +} + +static int rtk_spi_dirmap_create(struct spi_mem_dirmap_desc *desc) +{ + const struct spi_mem_op *op = desc->info.op_tmpl; + + if (op->data.dir == SPI_MEM_DATA_IN && op->addr.nbytes != 3 && op->addr.nbytes != 4) + return -EOPNOTSUPP; + + if (op->data.dir != SPI_MEM_DATA_IN && op->data.dir != SPI_MEM_DATA_OUT) + return -EOPNOTSUPP; + + return 0; +} + +static ssize_t rtk_spi_dirmap_read(struct spi_mem_dirmap_desc *desc, + u64 offs, size_t len, void *buf) +{ + struct rtk_spi_host *host = spi_controller_get_devdata(desc->mem->spi->controller); + const struct spi_mem_op *op = desc->info.op_tmpl; + loff_t addr = desc->info.offset + offs; + size_t chunk_len; + int ret; + + /* + * Handle unaligned address bytes at the beginning of the read operation. + * The hardware requires 4-byte alignment for DMA transfers. + */ + if (addr & 0x3) { + rtk_spi_read_mode(host, op); + chunk_len = min_t(size_t, 4 - (addr & 0x3), len); + ret = rtk_spi_byte_transfer(host, addr, chunk_len, buf, true); + if (ret) + return ret; + + return chunk_len; + } + + rtk_spi_read_mode(host, op); + chunk_len = min_t(size_t, len, SFC_DMA_MAX_LEN); + ret = rtk_spi_dma_transfer(host, addr, chunk_len, true); + if (ret) { + dev_err(host->dev, "DMA read transfer failed: %d\n", ret); + return ret; + } + + memcpy(buf, host->buffer, chunk_len); + + return chunk_len; +} + +static ssize_t rtk_spi_dirmap_write(struct spi_mem_dirmap_desc *desc, + u64 offs, size_t len, const void *buf) +{ + struct rtk_spi_host *host = spi_controller_get_devdata(desc->mem->spi->controller); + const struct spi_mem_op *op = desc->info.op_tmpl; + loff_t addr = desc->info.offset + offs; + size_t chunk_len; + int ret = 0; + + rtk_spi_enable_auto_write(host); + rtk_spi_write_mode(host, op); + + /* + * Handle unaligned address bytes at the beginning of the write operation. + * The hardware requires 4-byte alignment for DMA transfers. + */ + if (addr & 0x3) { + chunk_len = min_t(size_t, 4 - (addr & 0x3), len); + ret = rtk_spi_byte_transfer(host, addr, chunk_len, (u8 *)buf, false); + + goto out; + } + + chunk_len = min_t(size_t, len, SFC_DMA_MAX_LEN); + + memcpy(host->buffer, buf, chunk_len); + + ret = rtk_spi_dma_transfer(host, addr, chunk_len, false); + if (ret) + dev_err(host->dev, "DMA write transfer failed: %d\n", ret); + +out: + rtk_spi_disable_auto_write(host); + + return ret < 0 ? ret : chunk_len; +} + +static const struct spi_controller_mem_ops rtk_spi_mem_ops = { + .supports_op = rtk_spi_supports_op, + .exec_op = rtk_spi_exec_op, + .dirmap_create = rtk_spi_dirmap_create, + .dirmap_read = rtk_spi_dirmap_read, + .dirmap_write = rtk_spi_dirmap_write, +}; + +static int rtk_spi_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct spi_controller *ctrl; + struct rtk_spi_host *host; + struct resource *res; + int ret; + + ctrl = devm_spi_alloc_host(dev, sizeof(*host)); + if (!ctrl) + return -ENOMEM; + + platform_set_drvdata(pdev, ctrl); + host = spi_controller_get_devdata(ctrl); + host->dev = dev; + + host->clk = devm_clk_get(dev, NULL); + if (IS_ERR(host->clk)) + return PTR_ERR(host->clk); + + host->rstc = devm_reset_control_get_optional_exclusive(dev, NULL); + if (IS_ERR(host->rstc)) + return PTR_ERR(host->rstc); + + ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(35)); + if (ret) { + dev_err(dev, "Failed to set dma mask\n"); + return ret; + } + + host->buffer = dmam_alloc_coherent(dev, SFC_DMA_MAX_LEN, + &host->dma_buffer, GFP_KERNEL); + if (!host->buffer) + return -ENOMEM; + + host->regbase = devm_platform_ioremap_resource_byname(pdev, "ctrl"); + if (IS_ERR(host->regbase)) + return PTR_ERR(host->regbase); + + host->mdbase = devm_platform_ioremap_resource_byname(pdev, "dma"); + if (IS_ERR(host->mdbase)) + return PTR_ERR(host->mdbase); + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dirmap"); + host->iobase = devm_ioremap_resource(dev, res); + if (IS_ERR(host->iobase)) + return PTR_ERR(host->iobase); + + host->dirmap_size = resource_size(res); + host->flash_phys_base = res->start; + host->is_4byte = false; + + ret = clk_prepare_enable(host->clk); + if (ret) + return ret; + + ret = reset_control_assert(host->rstc); + usleep_range(10, 20); + ret = reset_control_deassert(host->rstc); + + rtk_spi_init(host); + + pm_runtime_set_autosuspend_delay(dev, SFC_AUTOSUSPEND_TIMEOUT); + pm_runtime_use_autosuspend(dev); + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + pm_runtime_get_noresume(dev); + + ctrl->mode_bits = SPI_RX_DUAL | SPI_TX_DUAL; + ctrl->bus_num = -1; + ctrl->mem_ops = &rtk_spi_mem_ops; + ctrl->num_chipselect = 1; + ctrl->auto_runtime_pm = true; + + ret = spi_register_controller(ctrl); + if (ret < 0) { + dev_err(dev, "failed to register controller\n"); + goto err_pm_disable; + } + + pm_runtime_put_autosuspend(dev); + + return 0; + +err_pm_disable: + pm_runtime_put_noidle(dev); + pm_runtime_disable(dev); + pm_runtime_set_suspended(dev); + pm_runtime_dont_use_autosuspend(dev); + reset_control_assert(host->rstc); + clk_disable_unprepare(host->clk); + + return ret; +} + +static void rtk_spi_remove(struct platform_device *pdev) +{ + struct spi_controller *ctrl = platform_get_drvdata(pdev); + struct rtk_spi_host *host = spi_controller_get_devdata(ctrl); + struct device *dev = &pdev->dev; + + spi_unregister_controller(ctrl); + + if (pm_runtime_resume_and_get(dev) < 0) + dev_warn(dev, "Failed to resume device during remove\n"); + + pm_runtime_put_noidle(dev); + pm_runtime_disable(dev); + pm_runtime_set_suspended(dev); + pm_runtime_dont_use_autosuspend(dev); + reset_control_assert(host->rstc); + clk_disable_unprepare(host->clk); +} + +static const struct of_device_id rtk_spi_dt_ids[] = { + { .compatible = "realtek,rtd1625-nor" }, + { /* sentinel */ } +}; + +MODULE_DEVICE_TABLE(of, rtk_spi_dt_ids); + +static int rtk_spi_runtime_suspend(struct device *dev) +{ + struct spi_controller *ctlr = dev_get_drvdata(dev); + struct rtk_spi_host *host = spi_controller_get_devdata(ctlr); + + reset_control_assert(host->rstc); + + clk_disable_unprepare(host->clk); + + return 0; +} + +static int rtk_spi_runtime_resume(struct device *dev) +{ + struct spi_controller *ctlr = dev_get_drvdata(dev); + struct rtk_spi_host *host = spi_controller_get_devdata(ctlr); + int ret; + + ret = clk_prepare_enable(host->clk); + if (ret < 0) { + dev_err(dev, "clk_prepare_enable failed: %d\n", ret); + return ret; + } + + reset_control_assert(host->rstc); + usleep_range(10, 20); + reset_control_deassert(host->rstc); + + rtk_spi_init(host); + + return 0; +} + +static DEFINE_RUNTIME_DEV_PM_OPS(rtk_spi_pm_ops, rtk_spi_runtime_suspend, + rtk_spi_runtime_resume, NULL); + +static struct platform_driver rtk_spi_driver = { + .driver = { + .name = "rtk-spi-nor", + .of_match_table = rtk_spi_dt_ids, + .pm = pm_ptr(&rtk_spi_pm_ops), + }, + .probe = rtk_spi_probe, + .remove = rtk_spi_remove, +}; +module_platform_driver(rtk_spi_driver); + +MODULE_DESCRIPTION("Realtek SPI Nor Controller Driver"); +MODULE_AUTHOR("Jyan Chou "); +MODULE_LICENSE("GPL"); From 52efc89ccf664dcfa331fbdf2f5c8d3acd45a863 Mon Sep 17 00:00:00 2001 From: Gabor Juhos Date: Thu, 24 Sep 2026 19:02:56 +0200 Subject: [PATCH 072/136] spi: spi-qpic-snand: set 'qpic_version2' flag The kerneldoc of 'struct qcom_nandc_props' says this about the 'qpic_version2' member of the structure: "flag to indicate QPIC IP version 2" Since the SNAND controller is based on QPIC v2, set the flag in the device specific properties. Fixes: 7304d1909080 ("spi: spi-qpic: add driver for QCOM SPI NAND flash Interface") Signed-off-by: Gabor Juhos Link: https://patch.msgid.link/20260924-qpic-snand-set-qpic_version2-v1-1-a0a9a3e0d06a@gmail.com Signed-off-by: Mark Brown --- drivers/spi/spi-qpic-snand.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/spi/spi-qpic-snand.c b/drivers/spi/spi-qpic-snand.c index 00072c44db7a..001e4bd8bb4b 100644 --- a/drivers/spi/spi-qpic-snand.c +++ b/drivers/spi/spi-qpic-snand.c @@ -1677,6 +1677,7 @@ static void qcom_spi_remove(struct platform_device *pdev) static const struct qcom_nandc_props ipq9574_snandc_props = { .dev_cmd_reg_start = 0x7000, .bam_offset = 0x30000, + .qpic_version2 = true, .supports_bam = true, }; From 5c99cb15169d6336c446d079f803e1b13916960f Mon Sep 17 00:00:00 2001 From: Changhuang Liang Date: Wed, 30 Sep 2026 00:15:33 -0700 Subject: [PATCH 073/136] spi: dw: Convert the Tx done delay from SCK cycles to time Currently the delay in dw_spi_dma_wait_tx_done() is expressed in SCK cycles, which requires passing a valid struct spi_transfer pointer. This does not work for enhanced SPI memory operations, which are not described by a spi_transfer. Compute the delay in ns or us based on an explicit bus speed instead, so that the helper can also be used for those transfers. Signed-off-by: Changhuang Liang Link: https://patch.msgid.link/20260930071535.7947-2-changhuang.liang@starfivetech.com Signed-off-by: Mark Brown --- drivers/spi/spi-dw-dma.c | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/drivers/spi/spi-dw-dma.c b/drivers/spi/spi-dw-dma.c index f7d848fec9ab..d0b1a03682e6 100644 --- a/drivers/spi/spi-dw-dma.c +++ b/drivers/spi/spi-dw-dma.c @@ -286,19 +286,27 @@ static inline bool dw_spi_dma_tx_busy(struct dw_spi *dws) (DW_SPI_SR_BUSY | DW_SPI_SR_TF_EMPT)) != DW_SPI_SR_TF_EMPT; } -static int dw_spi_dma_wait_tx_done(struct dw_spi *dws, - struct spi_transfer *xfer) +static int dw_spi_dma_wait_tx_done(struct dw_spi *dws, u32 speed_hz) { int retry = DW_SPI_WAIT_RETRIES; struct spi_delay delay; + unsigned long ns, us; u32 nents; nents = dw_readl(dws, DW_SPI_TXFLR); - delay.unit = SPI_DELAY_UNIT_SCK; - delay.value = nents * dws->n_bytes * BITS_PER_BYTE; + ns = DIV_ROUND_UP(NSEC_PER_SEC, speed_hz) * nents * + dws->n_bytes * BITS_PER_BYTE; + if (ns <= NSEC_PER_USEC) { + delay.unit = SPI_DELAY_UNIT_NSECS; + delay.value = ns; + } else { + us = DIV_ROUND_UP(ns, NSEC_PER_USEC); + delay.unit = SPI_DELAY_UNIT_USECS; + delay.value = clamp_val(us, 0, USHRT_MAX); + } while (dw_spi_dma_tx_busy(dws) && retry--) - spi_delay_exec(&delay, xfer); + spi_delay_exec(&delay, NULL); if (retry < 0) { dev_err(&dws->ctlr->dev, "Tx hanged up\n"); @@ -659,7 +667,7 @@ static int dw_spi_dma_transfer(struct dw_spi *dws, struct spi_transfer *xfer) return ret; if (dws->ctlr->cur_msg->status == -EINPROGRESS) { - ret = dw_spi_dma_wait_tx_done(dws, xfer); + ret = dw_spi_dma_wait_tx_done(dws, xfer->effective_speed_hz); if (ret) return ret; } From 929b82c85fc10e18804c7743587c5ac84e07310b Mon Sep 17 00:00:00 2001 From: Changhuang Liang Date: Wed, 30 Sep 2026 00:15:34 -0700 Subject: [PATCH 074/136] spi: dw: Support DMA with a single channel Some platforms, such as StarFive JHB100, provide only one DMA channel for the DW SPI controller. dw_spi_dma_init_generic() requires both the Tx and Rx channels, so DMA is disabled entirely on such platforms. Request both channels at probe time and decide what is usable from the return values. If the first (Rx) channel request fails, fall back to non-DMA transfers as before. If only the second (Tx) channel request fails, keep the Rx channel. Each successfully requested channel is counted in dws->dma_nr_chans. -EPROBE_DEFER on the second channel is still propagated. Full-duplex standard SPI transfers need both channels, so only set ctlr->can_dma and SPI_CONTROLLER_MUST_TX when two channels are available. A single channel can still serve half-duplex transfers, which will be used by the enhanced SPI memory operations in a follow-up patch. Make dw_spi_dma_caps_init() and dw_spi_dma_maxburst_init() cope with a missing channel, and set ctlr->dma_tx/dma_rx only after the capability check has passed so that they never point to a released channel. Signed-off-by: Changhuang Liang Link: https://patch.msgid.link/20260930071535.7947-3-changhuang.liang@starfivetech.com Signed-off-by: Mark Brown --- drivers/spi/spi-dw-core.c | 3 ++ drivers/spi/spi-dw-dma.c | 88 ++++++++++++++++++++++++++++----------- drivers/spi/spi-dw.h | 1 + 3 files changed, 67 insertions(+), 25 deletions(-) diff --git a/drivers/spi/spi-dw-core.c b/drivers/spi/spi-dw-core.c index 206d3f9dd83d..6889f4446ecb 100644 --- a/drivers/spi/spi-dw-core.c +++ b/drivers/spi/spi-dw-core.c @@ -1360,6 +1360,9 @@ int dw_spi_add_controller(struct device *dev, struct dw_spi *dws) goto err_free_irq; } else if (ret) { dev_warn(dev, "DMA init failed\n"); + } else if (dws->dma_nr_chans == 1) { + /* Full-duplex DMA transfers need both channels */ + dev_info(dev, "Single DMA channel, full-duplex DMA disabled\n"); } else { ctlr->can_dma = dws->dma_ops->can_dma; ctlr->flags |= SPI_CONTROLLER_MUST_TX; diff --git a/drivers/spi/spi-dw-dma.c b/drivers/spi/spi-dw-dma.c index d0b1a03682e6..76d7814f0dcf 100644 --- a/drivers/spi/spi-dw-dma.c +++ b/drivers/spi/spi-dw-dma.c @@ -42,14 +42,19 @@ static void dw_spi_dma_maxburst_init(struct dw_spi *dws) def_burst = dws->fifo_len / 2; - ret = dma_get_slave_caps(dws->rxchan, &caps); - if (!ret && caps.max_burst) - max_burst = caps.max_burst; - else - max_burst = DW_SPI_RX_BURST_LEVEL; + if (dws->rxchan) { + ret = dma_get_slave_caps(dws->rxchan, &caps); + if (!ret && caps.max_burst) + max_burst = caps.max_burst; + else + max_burst = DW_SPI_RX_BURST_LEVEL; - dws->rxburst = min(max_burst, def_burst); - dw_writel(dws, DW_SPI_DMARDLR, dws->rxburst - 1); + dws->rxburst = min(max_burst, def_burst); + dw_writel(dws, DW_SPI_DMARDLR, dws->rxburst - 1); + } + + if (!dws->txchan) + return; ret = dma_get_slave_caps(dws->txchan, &caps); if (!ret && caps.max_burst) @@ -74,20 +79,39 @@ static void dw_spi_dma_maxburst_init(struct dw_spi *dws) static int dw_spi_dma_caps_init(struct dw_spi *dws) { - struct dma_slave_caps tx, rx; + struct dma_slave_caps tx = {}, rx = {}; int ret; - ret = dma_get_slave_caps(dws->txchan, &tx); - if (ret) - return ret; + if (dws->txchan) { + ret = dma_get_slave_caps(dws->txchan, &tx); + if (ret) + return ret; - ret = dma_get_slave_caps(dws->rxchan, &rx); - if (ret) - return ret; + if (!(tx.directions & BIT(DMA_MEM_TO_DEV))) + return -ENXIO; + } - if (!(tx.directions & BIT(DMA_MEM_TO_DEV) && - rx.directions & BIT(DMA_DEV_TO_MEM))) - return -ENXIO; + if (dws->rxchan) { + ret = dma_get_slave_caps(dws->rxchan, &rx); + if (ret) + return ret; + + if (!(rx.directions & BIT(DMA_DEV_TO_MEM))) + return -ENXIO; + } + + /* With a single channel only one direction is available at a time */ + if (!dws->rxchan) { + dws->dma_sg_burst = tx.max_sg_burst; + dws->dma_addr_widths = tx.dst_addr_widths; + return 0; + } + + if (!dws->txchan) { + dws->dma_sg_burst = rx.max_sg_burst; + dws->dma_addr_widths = rx.src_addr_widths; + return 0; + } if (tx.max_sg_burst > 0 && rx.max_sg_burst > 0) dws->dma_sg_burst = min(tx.max_sg_burst, rx.max_sg_burst); @@ -169,40 +193,54 @@ static int dw_spi_dma_init_generic(struct device *dev, struct dw_spi *dws) { int ret; + dws->dma_nr_chans = 0; + dws->rxchan = dma_request_chan(dev, "rx"); if (IS_ERR(dws->rxchan)) { ret = PTR_ERR(dws->rxchan); dws->rxchan = NULL; goto err_exit; } + dws->dma_nr_chans++; + /* + * Some platforms have only one DMA channel for the controller. Keep + * the Rx channel in that case, it can still serve half-duplex + * transfers. + */ dws->txchan = dma_request_chan(dev, "tx"); if (IS_ERR(dws->txchan)) { ret = PTR_ERR(dws->txchan); dws->txchan = NULL; - goto free_rxchan; + if (ret == -EPROBE_DEFER) + goto free_rxchan; + } else { + dws->dma_nr_chans++; } - dws->ctlr->dma_rx = dws->rxchan; - dws->ctlr->dma_tx = dws->txchan; - - init_completion(&dws->dma_completion); - ret = dw_spi_dma_caps_init(dws); if (ret) goto free_txchan; dw_spi_dma_maxburst_init(dws); + init_completion(&dws->dma_completion); + + dws->ctlr->dma_rx = dws->rxchan; + dws->ctlr->dma_tx = dws->txchan; + return 0; free_txchan: - dma_release_channel(dws->txchan); - dws->txchan = NULL; + if (dws->txchan) { + dma_release_channel(dws->txchan); + dws->txchan = NULL; + } free_rxchan: dma_release_channel(dws->rxchan); dws->rxchan = NULL; err_exit: + dws->dma_nr_chans = 0; return ret; } diff --git a/drivers/spi/spi-dw.h b/drivers/spi/spi-dw.h index e62f919f1188..01eb24ae7fe0 100644 --- a/drivers/spi/spi-dw.h +++ b/drivers/spi/spi-dw.h @@ -227,6 +227,7 @@ struct dw_spi { u32 txburst; struct dma_chan *rxchan; u32 rxburst; + unsigned int dma_nr_chans; u32 dma_sg_burst; u32 dma_addr_widths; unsigned long dma_chan_busy; From 8cc7be24bc5e4b4074146486f434dbeada994f6a Mon Sep 17 00:00:00 2001 From: Changhuang Liang Date: Wed, 30 Sep 2026 00:15:35 -0700 Subject: [PATCH 075/136] spi: dw: Add DMA support for enhanced memory operations Implement DMA support for enhanced SPI memory operations. These operations are half-duplex, so they work with either one or two DMA channels. Add enhanced memory DMA callbacks (get_chan, setup, can_dma, transfer) to struct dw_spi_dma_ops and hook them into the generic DMA operations. They reuse the existing channel configuration, submission and wait helpers. With two channels, both are requested at probe time and kept for the lifetime of the controller. With a single channel, as on JHB100, the channel is bound to one direction by its "tx"/"rx" DMA specifier. The channel currently held (dws->txchan or dws->rxchan) records which direction it serves. It is reused as long as the direction stays the same, and only released and requested for the other direction when the transfer direction changes. In dw_spi_exec_enh_mem_op(), record the direction in dws->tx_dir and use the DMA path when a channel is available for that direction, can_dma_enh_mem() accepts the transfer, and the transfer exceeds the FIFO length; otherwise fall back to the interrupt path. Also move the udelay(5) workaround into dw_spi_enh_write_cmd_addr() so it applies to both paths, and enable the generic DMA setup in dw_spi_jhb100_init(). Signed-off-by: Changhuang Liang Link: https://patch.msgid.link/20260930071535.7947-4-changhuang.liang@starfivetech.com Signed-off-by: Mark Brown --- drivers/spi/spi-dw-core.c | 108 +++++++++++++--------- drivers/spi/spi-dw-dma.c | 182 ++++++++++++++++++++++++++++++++++++++ drivers/spi/spi-dw-mmio.c | 2 + drivers/spi/spi-dw.h | 7 ++ 4 files changed, 259 insertions(+), 40 deletions(-) diff --git a/drivers/spi/spi-dw-core.c b/drivers/spi/spi-dw-core.c index 6889f4446ecb..892b3a8787eb 100644 --- a/drivers/spi/spi-dw-core.c +++ b/drivers/spi/spi-dw-core.c @@ -937,6 +937,15 @@ static void dw_spi_enh_write_cmd_addr(struct dw_spi *dws, const struct spi_mem_o dw_spi_set_cs(mem->spi, false); } + + /* + * 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); } static int dw_spi_exec_enh_mem_op(struct spi_mem *mem, const struct spi_mem_op *op) @@ -948,6 +957,8 @@ static int dw_spi_exec_enh_mem_op(struct spi_mem *mem, const struct spi_mem_op * unsigned long long ms; int ret; + dws->dma_mapped = false; + switch (op->data.buswidth) { case 0: case 1: @@ -971,10 +982,13 @@ static int dw_spi_exec_enh_mem_op(struct spi_mem *mem, const struct spi_mem_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) + if (op->data.dir == SPI_MEM_DATA_IN) { cfg.tmode = DW_SPI_CTRLR0_TMOD_RO; - else + dws->tx_dir = false; + } else { cfg.tmode = DW_SPI_CTRLR0_TMOD_TO; + dws->tx_dir = true; + } if (op->data.buswidth == op->addr.buswidth && op->data.buswidth == op->cmd.buswidth) @@ -1010,47 +1024,61 @@ static int dw_spi_exec_enh_mem_op(struct spi_mem *mem, const struct spi_mem_op * } } - dw_spi_enh_write_cmd_addr(dws, op, mem); - - /* - * 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; + if (dws->dma_nr_chans && dws->dma_ops->dma_enh_mem_get_chan && + op->data.nbytes > dws->fifo_len) { + ret = dws->dma_ops->dma_enh_mem_get_chan(ctlr->dev.parent, dws); + if (ret) { + /* fall back to the PIO/IRQ path */ + dev_dbg(&ctlr->dev, "DMA enh mem channel unavailable (%d)\n", ret); + } else if (dws->dma_ops->can_dma_enh_mem(ctlr)) { + ret = dws->dma_ops->dma_enh_mem_setup(dws); + if (ret) + dev_err(&ctlr->dev, "DMA enh mem setup failed (%d)\n", ret); + else + dws->dma_mapped = true; + } } - ret = dw_spi_wait_mem_op_done(dws); + if (dws->dma_mapped) { + dw_spi_enh_write_cmd_addr(dws, op, mem); - dw_spi_stop_mem_op(dws, mem->spi); + ret = dws->dma_ops->dma_enh_mem_transfer(mem, op); + + dw_spi_stop_mem_op(dws, mem->spi); + } else { + dw_spi_enh_write_cmd_addr(dws, op, mem); + + 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; } diff --git a/drivers/spi/spi-dw-dma.c b/drivers/spi/spi-dw-dma.c index 76d7814f0dcf..a86039057988 100644 --- a/drivers/spi/spi-dw-dma.c +++ b/drivers/spi/spi-dw-dma.c @@ -728,6 +728,183 @@ static void dw_spi_dma_stop(struct dw_spi *dws) } } +/* + * The enhanced SPI memory operations are half-duplex, so a single DMA channel + * is enough for them. On a platform with only one channel, the channel is + * bound to a direction by its "tx"/"rx" DMA specifier, and whichever channel + * is currently held tells which direction it serves. Keep it across + * operations and swap it only when the transfer direction changes. + */ +static int dw_spi_enh_mem_dma_get_chan(struct device *dev, struct dw_spi *dws) +{ + struct dma_chan **chan, **other; + const char *name; + int ret; + + if (dws->dma_nr_chans == 2) + return 0; + + if (dws->tx_dir) { + chan = &dws->txchan; + other = &dws->rxchan; + name = "tx"; + } else { + chan = &dws->rxchan; + other = &dws->txchan; + name = "rx"; + } + + if (*chan) + return 0; + + dws->ctlr->dma_tx = NULL; + dws->ctlr->dma_rx = NULL; + + /* + * With a single physical channel, the held channel must be released + * before it can be requested again with the handshake of the other + * direction. On failure both stay NULL, and the next operation + * retries the request and falls back to PIO meanwhile. + */ + + if (*other) { + dma_release_channel(*other); + *other = NULL; + } + + *chan = dma_request_chan(dev, name); + if (IS_ERR(*chan)) { + ret = PTR_ERR(*chan); + *chan = NULL; + return ret; + } + + ret = dw_spi_dma_caps_init(dws); + if (ret) { + dma_release_channel(*chan); + *chan = NULL; + return ret; + } + + dw_spi_dma_maxburst_init(dws); + + dws->ctlr->dma_tx = dws->txchan; + dws->ctlr->dma_rx = dws->rxchan; + + return 0; +} + +static int dw_spi_enh_mem_dma_setup(struct dw_spi *dws) +{ + u16 dma_ctrl, level; + int ret; + + /* Setup DMA channels */ + if (dws->tx_dir) { + ret = dw_spi_dma_config_tx(dws); + if (ret) + return ret; + + dma_ctrl = DW_SPI_DMACR_TDMAE; + } else { + ret = dw_spi_dma_config_rx(dws); + if (ret) + return ret; + + dma_ctrl = DW_SPI_DMACR_RDMAE; + } + + dw_writel(dws, DW_SPI_DMACR, dma_ctrl); + + /* Clear stale error status, it is checked after the transfer */ + dw_readl(dws, DW_SPI_ICR); + + reinit_completion(&dws->dma_completion); + + level = min_t(unsigned int, dws->fifo_len / 2, dws->tx_len); + dw_writel(dws, DW_SPI_TXFTLR, level); + + level = min_t(unsigned int, dws->fifo_len / 2, dws->rx_len); + dw_writel(dws, DW_SPI_RXFTLR, level ? level - 1 : 0); + + return 0; +} + +static bool dw_spi_enh_mem_can_dma(struct spi_controller *ctlr) +{ + struct dw_spi *dws = spi_controller_get_devdata(ctlr); + unsigned int len = dws->tx_dir ? dws->tx_len : dws->rx_len; + enum dma_slave_buswidth dma_bus_width; + + if (len <= dws->fifo_len) + return false; + + dma_bus_width = dw_spi_dma_convert_width(dws->n_bytes); + + return dws->dma_addr_widths & BIT(dma_bus_width); +} + +static void dw_spi_enh_mem_dma_stop(struct dw_spi *dws) +{ + if (dws->tx_dir) { + if (test_bit(DW_SPI_TX_BUSY, &dws->dma_chan_busy)) { + dmaengine_terminate_sync(dws->txchan); + clear_bit(DW_SPI_TX_BUSY, &dws->dma_chan_busy); + } + } else { + if (test_bit(DW_SPI_RX_BUSY, &dws->dma_chan_busy)) { + dmaengine_terminate_sync(dws->rxchan); + clear_bit(DW_SPI_RX_BUSY, &dws->dma_chan_busy); + } + } +} + +static int dw_spi_enh_mem_dma_transfer(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 sg_table sgt; + int ret; + + ret = spi_controller_dma_map_mem_op_data(ctlr, op, &sgt); + if (ret) + goto out_clear_dmac; + + if (dws->tx_dir) { + ret = dw_spi_dma_submit_tx(dws, sgt.sgl, sgt.nents); + if (ret) + goto out_unmap; + + dma_async_issue_pending(dws->txchan); + + ret = dw_spi_dma_wait(dws, dws->tx_len, dws->current_freq); + if (!ret) + ret = dw_spi_dma_wait_tx_done(dws, dws->current_freq); + } else { + ret = dw_spi_dma_submit_rx(dws, sgt.sgl, sgt.nents); + if (ret) + goto out_unmap; + + dma_async_issue_pending(dws->rxchan); + + ret = dw_spi_dma_wait(dws, dws->rx_len, dws->current_freq); + if (!ret) + ret = dw_spi_dma_wait_rx_done(dws); + } + + if (ret) + dw_spi_enh_mem_dma_stop(dws); + else + ret = dw_spi_check_status(dws, true); + +out_unmap: + spi_controller_dma_unmap_mem_op_data(ctlr, op, &sgt); +out_clear_dmac: + dw_writel(dws, DW_SPI_DMACR, 0); + + return ret; +} + static const struct dw_spi_dma_ops dw_spi_dma_mfld_ops = { .dma_init = dw_spi_dma_init_mfld, .dma_exit = dw_spi_dma_exit, @@ -750,6 +927,11 @@ static const struct dw_spi_dma_ops dw_spi_dma_generic_ops = { .can_dma = dw_spi_can_dma, .dma_transfer = dw_spi_dma_transfer, .dma_stop = dw_spi_dma_stop, + + .dma_enh_mem_get_chan = dw_spi_enh_mem_dma_get_chan, + .dma_enh_mem_setup = dw_spi_enh_mem_dma_setup, + .can_dma_enh_mem = dw_spi_enh_mem_can_dma, + .dma_enh_mem_transfer = dw_spi_enh_mem_dma_transfer, }; void dw_spi_dma_setup_generic(struct dw_spi *dws) diff --git a/drivers/spi/spi-dw-mmio.c b/drivers/spi/spi-dw-mmio.c index 8cdb0351605b..9c70947c969c 100644 --- a/drivers/spi/spi-dw-mmio.c +++ b/drivers/spi/spi-dw-mmio.c @@ -363,6 +363,8 @@ static int dw_spi_jhb100_init(struct platform_device *pdev, dw_spi_jhb100_mask_intr(&dwsmmio->dws, 0xff); + dw_spi_dma_setup_generic(&dwsmmio->dws); + return 0; } diff --git a/drivers/spi/spi-dw.h b/drivers/spi/spi-dw.h index 01eb24ae7fe0..bfdf371bcacb 100644 --- a/drivers/spi/spi-dw.h +++ b/drivers/spi/spi-dw.h @@ -183,6 +183,12 @@ struct dw_spi_dma_ops { struct spi_transfer *xfer); int (*dma_transfer)(struct dw_spi *dws, struct spi_transfer *xfer); void (*dma_stop)(struct dw_spi *dws); + + /* enh mem dma ops */ + int (*dma_enh_mem_get_chan)(struct device *dev, struct dw_spi *dws); + int (*dma_enh_mem_setup)(struct dw_spi *dws); + bool (*can_dma_enh_mem)(struct spi_controller *ctlr); + int (*dma_enh_mem_transfer)(struct spi_mem *mem, const struct spi_mem_op *op); }; struct dw_spi { @@ -228,6 +234,7 @@ struct dw_spi { struct dma_chan *rxchan; u32 rxburst; unsigned int dma_nr_chans; + bool tx_dir; u32 dma_sg_burst; u32 dma_addr_widths; unsigned long dma_chan_busy; From 2d57d6fd1f569d350ab8c7fcae05dd455d9ae535 Mon Sep 17 00:00:00 2001 From: Frank Li Date: Wed, 30 Sep 2026 10:02:54 -0400 Subject: [PATCH 076/136] spi: use dmaengine public API instead of raw ops Replace direct calls to the internal DMA device ops device->device_issue_pending() with the proper public DMA engine wrappers: dma_async_issue_pending(). Drivers must not call device ops directly; the wrappers provide locking, debug checks, and a stable interface that hides internal implementation details of the DMA controller driver. Signed-off-by: Frank Li Link: https://patch.msgid.link/20260930140257.490019-1-Frank.Li@oss.nxp.com Signed-off-by: Mark Brown --- drivers/spi/spi-at91-usart.c | 4 ++-- drivers/spi/spi-atmel.c | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/spi/spi-at91-usart.c b/drivers/spi/spi-at91-usart.c index 804f6194c830..cdc771350db9 100644 --- a/drivers/spi/spi-at91-usart.c +++ b/drivers/spi/spi-at91-usart.c @@ -239,8 +239,8 @@ static int at91_usart_spi_dma_transfer(struct spi_controller *ctlr, if (dma_submit_error(cookie)) goto at91_usart_spi_err_dma; - rxchan->device->device_issue_pending(rxchan); - txchan->device->device_issue_pending(txchan); + dma_async_issue_pending(rxchan); + dma_async_issue_pending(txchan); return 0; diff --git a/drivers/spi/spi-atmel.c b/drivers/spi/spi-atmel.c index c8012c82c3a7..c04113ce17f7 100644 --- a/drivers/spi/spi-atmel.c +++ b/drivers/spi/spi-atmel.c @@ -877,8 +877,8 @@ static int atmel_spi_next_xfer_dma_submit(struct spi_controller *host, cookie = txdesc->tx_submit(txdesc); if (dma_submit_error(cookie)) goto err_dma; - rxchan->device->device_issue_pending(rxchan); - txchan->device->device_issue_pending(txchan); + dma_async_issue_pending(rxchan); + dma_async_issue_pending(txchan); return 0; From 853de617c4a62403242d2032e00cb19a0d870130 Mon Sep 17 00:00:00 2001 From: Frank Li Date: Wed, 30 Sep 2026 10:02:55 -0400 Subject: [PATCH 077/136] spi: use dmaengine_get_dma_device() instead of chan->device->dev Replace direct dma_chan::device::dev access with the proper dmaengine_get_dma_device() for consumer API chan->device->dev is not always the device used for DMA mapping. Some DMA engines support per-channel IOMMU mappings, so different channels may use different DMA devices. dmaengine_get_dma_device() returns the correct device for each channel. This also prepares for making the DMA engine provider data structures private. DMA consumers should not access DMA engine internals directly. Assisted-by: LLM Signed-off-by: Frank Li Link: https://patch.msgid.link/20260930140257.490019-2-Frank.Li@oss.nxp.com Signed-off-by: Mark Brown --- drivers/spi/spi-bcm2835.c | 25 ++++++++++++---------- drivers/spi/spi-cadence-quadspi.c | 2 +- drivers/spi/spi-ep93xx.c | 8 ++++--- drivers/spi/spi-fsl-dspi.c | 35 ++++++++++++++++++------------- drivers/spi/spi-imx.c | 10 ++++----- drivers/spi/spi-nxp-xspi.c | 16 +++++++------- drivers/spi/spi-pl022.c | 19 ++++++++++------- drivers/spi/spi-sh-msiof.c | 12 +++++------ 8 files changed, 69 insertions(+), 58 deletions(-) diff --git a/drivers/spi/spi-bcm2835.c b/drivers/spi/spi-bcm2835.c index 23606967f7d1..cc5748973281 100644 --- a/drivers/spi/spi-bcm2835.c +++ b/drivers/spi/spi-bcm2835.c @@ -527,7 +527,7 @@ static void bcm2835_spi_transfer_prologue(struct spi_controller *ctlr, | BCM2835_SPI_CS_CLEAR_TX | BCM2835_SPI_CS_DONE); - dma_sync_single_for_device(ctlr->dma_rx->device->dev, + dma_sync_single_for_device(dmaengine_get_dma_device(ctlr->dma_rx), sg_dma_address(&tfr->rx_sg.sgl[0]), bs->rx_prologue, DMA_FROM_DEVICE); @@ -871,7 +871,7 @@ static void bcm2835_dma_release(struct spi_controller *ctlr, dmaengine_desc_free(bs->fill_tx_desc); if (bs->fill_tx_addr) - dma_unmap_page_attrs(ctlr->dma_tx->device->dev, + dma_unmap_page_attrs(dmaengine_get_dma_device(ctlr->dma_tx), bs->fill_tx_addr, sizeof(u32), DMA_TO_DEVICE, DMA_ATTR_SKIP_CPU_SYNC); @@ -891,6 +891,7 @@ static int bcm2835_dma_init(struct spi_controller *ctlr, struct device *dev, struct bcm2835_spi *bs) { struct dma_slave_config slave_config; + struct device *tx_dma_dev; const __be32 *addr; dma_addr_t dma_reg_base; int ret; @@ -932,11 +933,13 @@ static int bcm2835_dma_init(struct spi_controller *ctlr, struct device *dev, if (ret) goto err_config; - bs->fill_tx_addr = dma_map_page_attrs(ctlr->dma_tx->device->dev, + tx_dma_dev = dmaengine_get_dma_device(ctlr->dma_tx); + + bs->fill_tx_addr = dma_map_page_attrs(tx_dma_dev, ZERO_PAGE(0), 0, sizeof(u32), DMA_TO_DEVICE, DMA_ATTR_SKIP_CPU_SYNC); - if (dma_mapping_error(ctlr->dma_tx->device->dev, bs->fill_tx_addr)) { + if (dma_mapping_error(tx_dma_dev, bs->fill_tx_addr)) { dev_err(dev, "cannot map zero page - not using DMA mode\n"); bs->fill_tx_addr = 0; ret = -ENOMEM; @@ -1157,7 +1160,7 @@ static void bcm2835_spi_cleanup(struct spi_device *spi) dmaengine_desc_free(target->clear_rx_desc); if (target->clear_rx_addr) - dma_unmap_single(ctlr->dma_rx->device->dev, + dma_unmap_single(dmaengine_get_dma_device(ctlr->dma_rx), target->clear_rx_addr, sizeof(u32), DMA_TO_DEVICE); @@ -1174,16 +1177,16 @@ static int bcm2835_spi_setup_dma(struct spi_controller *ctlr, struct bcm2835_spi *bs, struct bcm2835_spidev *target) { + struct device *rx_dev; int ret; if (!ctlr->dma_rx) return 0; - target->clear_rx_addr = dma_map_single(ctlr->dma_rx->device->dev, - &target->clear_rx_cs, - sizeof(u32), - DMA_TO_DEVICE); - if (dma_mapping_error(ctlr->dma_rx->device->dev, target->clear_rx_addr)) { + rx_dev = dmaengine_get_dma_device(ctlr->dma_rx); + target->clear_rx_addr = dma_map_single(rx_dev, &target->clear_rx_cs, + sizeof(u32), DMA_TO_DEVICE); + if (dma_mapping_error(rx_dev, target->clear_rx_addr)) { dev_err(&spi->dev, "cannot map clear_rx_cs\n"); target->clear_rx_addr = 0; return -ENOMEM; @@ -1268,7 +1271,7 @@ static int bcm2835_spi_setup(struct spi_device *spi) target->clear_rx_cs = cs | BCM2835_SPI_CS_TA | BCM2835_SPI_CS_DMAEN | BCM2835_SPI_CS_CLEAR_RX; - dma_sync_single_for_device(ctlr->dma_rx->device->dev, + dma_sync_single_for_device(dmaengine_get_dma_device(ctlr->dma_rx), target->clear_rx_addr, sizeof(u32), DMA_TO_DEVICE); diff --git a/drivers/spi/spi-cadence-quadspi.c b/drivers/spi/spi-cadence-quadspi.c index ecb0be394696..fe5b094f0b6b 100644 --- a/drivers/spi/spi-cadence-quadspi.c +++ b/drivers/spi/spi-cadence-quadspi.c @@ -1382,7 +1382,7 @@ static int cqspi_direct_read_execute(struct cqspi_flash_pdata *f_pdata, return 0; } - ddev = cqspi->rx_chan->device->dev; + ddev = dmaengine_get_dma_device(cqspi->rx_chan); dma_dst = dma_map_single(ddev, buf, len, DMA_FROM_DEVICE); if (dma_mapping_error(ddev, dma_dst)) { dev_err(dev, "dma mapping failed\n"); diff --git a/drivers/spi/spi-ep93xx.c b/drivers/spi/spi-ep93xx.c index bf389d7590d3..d8f4aa38685f 100644 --- a/drivers/spi/spi-ep93xx.c +++ b/drivers/spi/spi-ep93xx.c @@ -273,6 +273,7 @@ ep93xx_spi_dma_prepare(struct spi_controller *host, struct dma_async_tx_descriptor *txd; enum dma_slave_buswidth buswidth; struct dma_slave_config conf; + struct device *dma_dev; struct scatterlist *sg; struct sg_table *sgt; struct dma_chan *chan; @@ -348,14 +349,15 @@ ep93xx_spi_dma_prepare(struct spi_controller *host, return ERR_PTR(-EINVAL); } - nents = dma_map_sg(chan->device->dev, sgt->sgl, sgt->nents, dir); + dma_dev = dmaengine_get_dma_device(chan); + nents = dma_map_sg(dma_dev, sgt->sgl, sgt->nents, dir); if (!nents) return ERR_PTR(-ENOMEM); txd = dmaengine_prep_slave_sg(chan, sgt->sgl, nents, conf.direction, DMA_CTRL_ACK); if (!txd) { - dma_unmap_sg(chan->device->dev, sgt->sgl, sgt->nents, dir); + dma_unmap_sg(dma_dev, sgt->sgl, sgt->nents, dir); return ERR_PTR(-ENOMEM); } return txd; @@ -384,7 +386,7 @@ static void ep93xx_spi_dma_finish(struct spi_controller *host, sgt = &espi->tx_sgt; } - dma_unmap_sg(chan->device->dev, sgt->sgl, sgt->nents, dir); + dma_unmap_sg(dmaengine_get_dma_device(chan), sgt->sgl, sgt->nents, dir); } static void ep93xx_spi_dma_callback(void *callback_param) diff --git a/drivers/spi/spi-fsl-dspi.c b/drivers/spi/spi-fsl-dspi.c index 23e6bc5f202e..f03488870d02 100644 --- a/drivers/spi/spi-fsl-dspi.c +++ b/drivers/spi/spi-fsl-dspi.c @@ -670,6 +670,7 @@ static void dspi_dma_xfer(struct fsl_dspi *dspi) static int dspi_request_dma(struct fsl_dspi *dspi, phys_addr_t phy_addr) { struct device *dev = &dspi->pdev->dev; + struct device *tx_dev, *rx_dev; struct dma_slave_config cfg; struct fsl_dspi_dma *dma; int ret; @@ -688,20 +689,23 @@ static int dspi_request_dma(struct fsl_dspi *dspi, phys_addr_t phy_addr) goto err_tx_channel; } + tx_dev = dmaengine_get_dma_device(dma->chan_tx); + rx_dev = dmaengine_get_dma_device(dma->chan_rx); + if (spi_controller_is_target(dspi->ctlr)) { /* * In target mode we have to be ready to receive the maximum * that can possibly be transferred at once by EDMA without any * FIFO underflows. */ - dma->bufsize = min(dma_get_max_seg_size(dma->chan_rx->device->dev), - dma_get_max_seg_size(dma->chan_tx->device->dev)) * + dma->bufsize = min(dma_get_max_seg_size(rx_dev), + dma_get_max_seg_size(tx_dev)) * DMA_SLAVE_BUSWIDTH_4_BYTES; } else { dma->bufsize = PAGE_SIZE; } - dma->tx_dma_buf = dma_alloc_noncoherent(dma->chan_tx->device->dev, + dma->tx_dma_buf = dma_alloc_noncoherent(tx_dev, dma->bufsize, &dma->tx_dma_phys, DMA_TO_DEVICE, GFP_KERNEL); if (!dma->tx_dma_buf) { @@ -709,7 +713,7 @@ static int dspi_request_dma(struct fsl_dspi *dspi, phys_addr_t phy_addr) goto err_tx_dma_buf; } - dma->rx_dma_buf = dma_alloc_noncoherent(dma->chan_rx->device->dev, + dma->rx_dma_buf = dma_alloc_noncoherent(rx_dev, dma->bufsize, &dma->rx_dma_phys, DMA_FROM_DEVICE, GFP_KERNEL); if (!dma->rx_dma_buf) { @@ -746,12 +750,11 @@ static int dspi_request_dma(struct fsl_dspi *dspi, phys_addr_t phy_addr) return 0; err_slave_config: - dma_free_noncoherent(dma->chan_rx->device->dev, dma->bufsize, - dma->rx_dma_buf, dma->rx_dma_phys, - DMA_FROM_DEVICE); + dma_free_noncoherent(rx_dev, dma->bufsize, dma->rx_dma_buf, + dma->rx_dma_phys, DMA_FROM_DEVICE); err_rx_dma_buf: - dma_free_noncoherent(dma->chan_tx->device->dev, dma->bufsize, - dma->tx_dma_buf, dma->tx_dma_phys, DMA_TO_DEVICE); + dma_free_noncoherent(tx_dev, dma->bufsize, dma->tx_dma_buf, + dma->tx_dma_phys, DMA_TO_DEVICE); err_tx_dma_buf: dma_release_channel(dma->chan_tx); err_tx_channel: @@ -771,16 +774,18 @@ static void dspi_release_dma(struct fsl_dspi *dspi) return; if (dma->chan_tx) { - dma_free_noncoherent(dma->chan_tx->device->dev, dma->bufsize, - dma->tx_dma_buf, dma->tx_dma_phys, - DMA_TO_DEVICE); + struct device *tx_dev = dmaengine_get_dma_device(dma->chan_tx); + + dma_free_noncoherent(tx_dev, dma->bufsize, dma->tx_dma_buf, + dma->tx_dma_phys, DMA_TO_DEVICE); dma_release_channel(dma->chan_tx); } if (dma->chan_rx) { - dma_free_noncoherent(dma->chan_rx->device->dev, dma->bufsize, - dma->rx_dma_buf, dma->rx_dma_phys, - DMA_FROM_DEVICE); + struct device *rx_dev = dmaengine_get_dma_device(dma->chan_rx); + + dma_free_noncoherent(rx_dev, dma->bufsize, dma->rx_dma_buf, + dma->rx_dma_phys, DMA_FROM_DEVICE); dma_release_channel(dma->chan_rx); } } diff --git a/drivers/spi/spi-imx.c b/drivers/spi/spi-imx.c index 6f9686df0486..868f963e6137 100644 --- a/drivers/spi/spi-imx.c +++ b/drivers/spi/spi-imx.c @@ -1467,8 +1467,8 @@ static int spi_imx_calculate_timeout(struct spi_imx_data *spi_imx, int size) static void spi_imx_dma_unmap(struct spi_imx_data *spi_imx, struct dma_data_package *dma_data) { - struct device *tx_dev = spi_imx->controller->dma_tx->device->dev; - struct device *rx_dev = spi_imx->controller->dma_rx->device->dev; + struct device *tx_dev = dmaengine_get_dma_device(spi_imx->controller->dma_tx); + struct device *rx_dev = dmaengine_get_dma_device(spi_imx->controller->dma_rx); dma_unmap_single(tx_dev, dma_data->dma_tx_addr, DMA_CACHE_ALIGNED_LEN(dma_data->dma_len), @@ -1526,8 +1526,8 @@ static int spi_imx_dma_map(struct spi_imx_data *spi_imx, struct dma_data_package *dma_data) { struct spi_controller *controller = spi_imx->controller; - struct device *tx_dev = controller->dma_tx->device->dev; - struct device *rx_dev = controller->dma_rx->device->dev; + struct device *tx_dev = dmaengine_get_dma_device(controller->dma_tx); + struct device *rx_dev = dmaengine_get_dma_device(controller->dma_rx); int ret; dma_data->dma_tx_addr = dma_map_single(tx_dev, dma_data->dma_tx_buf, @@ -1919,7 +1919,7 @@ static int spi_imx_dma_package_transfer(struct spi_imx_data *spi_imx, return ret; /* Trim the DMA RX buffer and copy the actual data to rx_buf */ - dma_sync_single_for_cpu(controller->dma_rx->device->dev, dma_data->dma_rx_addr, + dma_sync_single_for_cpu(dmaengine_get_dma_device(controller->dma_rx), dma_data->dma_rx_addr, dma_data->dma_len, DMA_FROM_DEVICE); spi_imx_dma_rx_data_handle(spi_imx, dma_data, transfer->rx_buf + spi_imx->rx_offset, word_delay); diff --git a/drivers/spi/spi-nxp-xspi.c b/drivers/spi/spi-nxp-xspi.c index 70cbc3ef658b..38c8d6269aad 100644 --- a/drivers/spi/spi-nxp-xspi.c +++ b/drivers/spi/spi-nxp-xspi.c @@ -841,23 +841,23 @@ static int nxp_xspi_ahb_dma_read(struct nxp_xspi *xspi, { struct dma_async_tx_descriptor *desc; struct dma_chan *chan = xspi->dma_rx; + struct device *dma_dev = dmaengine_get_dma_device(chan); 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, + dst_addr = dma_map_single(dma_dev, op->data.buf.in, op->data.nbytes, DMA_FROM_DEVICE); - if (dma_mapping_error(chan->device->dev, dst_addr)) { + if (dma_mapping_error(dma_dev, dst_addr)) { dev_err(xspi->dev, "failed to map DMA dst buffer\n"); return -ENOMEM; } - src_addr = dma_map_resource(chan->device->dev, + src_addr = dma_map_resource(dma_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)) { + if (dma_mapping_error(dma_dev, src_addr)) { dev_err(xspi->dev, "failed to map DMA src resource\n"); ret = -ENOMEM; goto err_unmap_dst; @@ -886,12 +886,10 @@ static int nxp_xspi_ahb_dma_read(struct nxp_xspi *xspi, } err_unmap_src: - dma_unmap_resource(chan->device->dev, src_addr, - op->data.nbytes, DMA_TO_DEVICE, 0); + dma_unmap_resource(dma_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); + dma_unmap_single(dma_dev, dst_addr, op->data.nbytes, DMA_FROM_DEVICE); return ret; } diff --git a/drivers/spi/spi-pl022.c b/drivers/spi/spi-pl022.c index 4877ab909c51..3ff30486b289 100644 --- a/drivers/spi/spi-pl022.c +++ b/drivers/spi/spi-pl022.c @@ -684,9 +684,9 @@ static void readwriter(struct pl022 *pl022) static void unmap_free_dma_scatter(struct pl022 *pl022) { /* Unmap and free the SG tables */ - dma_unmap_sg(pl022->dma_tx_channel->device->dev, pl022->sgt_tx.sgl, + dma_unmap_sg(dmaengine_get_dma_device(pl022->dma_tx_channel), pl022->sgt_tx.sgl, pl022->sgt_tx.nents, DMA_TO_DEVICE); - dma_unmap_sg(pl022->dma_rx_channel->device->dev, pl022->sgt_rx.sgl, + dma_unmap_sg(dmaengine_get_dma_device(pl022->dma_rx_channel), pl022->sgt_rx.sgl, pl022->sgt_rx.nents, DMA_FROM_DEVICE); sg_free_table(&pl022->sgt_rx); sg_free_table(&pl022->sgt_tx); @@ -811,6 +811,7 @@ static int configure_dma(struct pl022 *pl022) struct dma_chan *txchan = pl022->dma_tx_channel; struct dma_async_tx_descriptor *rxdesc; struct dma_async_tx_descriptor *txdesc; + struct device *rx_dev, *tx_dev; /* Check that the channels are available */ if (!rxchan || !txchan) @@ -924,14 +925,16 @@ static int configure_dma(struct pl022 *pl022) setup_dma_scatter(pl022, pl022->tx, pl022->cur_transfer->len, &pl022->sgt_tx); + rx_dev = dmaengine_get_dma_device(rxchan); /* Map DMA buffers */ - rx_sglen = dma_map_sg(rxchan->device->dev, pl022->sgt_rx.sgl, - pl022->sgt_rx.nents, DMA_FROM_DEVICE); + rx_sglen = dma_map_sg(rx_dev, pl022->sgt_rx.sgl, + pl022->sgt_rx.nents, DMA_FROM_DEVICE); if (!rx_sglen) goto err_rx_sgmap; - tx_sglen = dma_map_sg(txchan->device->dev, pl022->sgt_tx.sgl, - pl022->sgt_tx.nents, DMA_TO_DEVICE); + tx_dev = dmaengine_get_dma_device(txchan); + tx_sglen = dma_map_sg(tx_dev, pl022->sgt_tx.sgl, + pl022->sgt_tx.nents, DMA_TO_DEVICE); if (!tx_sglen) goto err_tx_sgmap; @@ -969,10 +972,10 @@ static int configure_dma(struct pl022 *pl022) dmaengine_terminate_all(txchan); err_rxdesc: dmaengine_terminate_all(rxchan); - dma_unmap_sg(txchan->device->dev, pl022->sgt_tx.sgl, + dma_unmap_sg(tx_dev, pl022->sgt_tx.sgl, pl022->sgt_tx.nents, DMA_TO_DEVICE); err_tx_sgmap: - dma_unmap_sg(rxchan->device->dev, pl022->sgt_rx.sgl, + dma_unmap_sg(rx_dev, pl022->sgt_rx.sgl, pl022->sgt_rx.nents, DMA_FROM_DEVICE); err_rx_sgmap: sg_free_table(&pl022->sgt_tx); diff --git a/drivers/spi/spi-sh-msiof.c b/drivers/spi/spi-sh-msiof.c index 90a671e9de70..89fbdde46cfe 100644 --- a/drivers/spi/spi-sh-msiof.c +++ b/drivers/spi/spi-sh-msiof.c @@ -674,7 +674,7 @@ static int sh_msiof_dma_once(struct sh_msiof_spi_priv *p, const void *tx, if (tx) { ier_bits |= SIIER_TDREQE | SIIER_TDMAE; - dma_sync_single_for_device(p->ctlr->dma_tx->device->dev, + dma_sync_single_for_device(dmaengine_get_dma_device(p->ctlr->dma_tx), p->tx_dma_addr, len, DMA_TO_DEVICE); desc_tx = dmaengine_prep_slave_single(p->ctlr->dma_tx, p->tx_dma_addr, len, DMA_MEM_TO_DEV, @@ -754,7 +754,7 @@ static int sh_msiof_dma_once(struct sh_msiof_spi_priv *p, const void *tx, } if (rx) - dma_sync_single_for_cpu(p->ctlr->dma_rx->device->dev, + dma_sync_single_for_cpu(dmaengine_get_dma_device(p->ctlr->dma_rx), p->rx_dma_addr, len, DMA_FROM_DEVICE); return 0; @@ -1142,13 +1142,13 @@ static int sh_msiof_request_dma(struct sh_msiof_spi_priv *p) if (!p->rx_dma_page) goto free_tx_page; - tx_dev = ctlr->dma_tx->device->dev; + tx_dev = dmaengine_get_dma_device(ctlr->dma_tx); p->tx_dma_addr = dma_map_single(tx_dev, p->tx_dma_page, PAGE_SIZE, DMA_TO_DEVICE); if (dma_mapping_error(tx_dev, p->tx_dma_addr)) goto free_rx_page; - rx_dev = ctlr->dma_rx->device->dev; + rx_dev = dmaengine_get_dma_device(ctlr->dma_rx); p->rx_dma_addr = dma_map_single(rx_dev, p->rx_dma_page, PAGE_SIZE, DMA_FROM_DEVICE); if (dma_mapping_error(rx_dev, p->rx_dma_addr)) @@ -1178,9 +1178,9 @@ static void sh_msiof_release_dma(struct sh_msiof_spi_priv *p) if (!ctlr->dma_tx) return; - dma_unmap_single(ctlr->dma_rx->device->dev, p->rx_dma_addr, PAGE_SIZE, + dma_unmap_single(dmaengine_get_dma_device(ctlr->dma_rx), p->rx_dma_addr, PAGE_SIZE, DMA_FROM_DEVICE); - dma_unmap_single(ctlr->dma_tx->device->dev, p->tx_dma_addr, PAGE_SIZE, + dma_unmap_single(dmaengine_get_dma_device(ctlr->dma_tx), p->tx_dma_addr, PAGE_SIZE, DMA_TO_DEVICE); free_page((unsigned long)p->rx_dma_page); free_page((unsigned long)p->tx_dma_page); From 53cdc4266b935724bb2f89e9229c6f92476a1575 Mon Sep 17 00:00:00 2001 From: Han Gao Date: Thu, 1 Oct 2026 02:22:50 +0800 Subject: [PATCH 078/136] spi: sg2044-nor: Return transfer errors The transfer helpers return errors, but both dispatchers discard them. Return these errors to the SPI memory layer instead of reporting success. Fixes: de16c322eefb ("spi: sophgo: add SG2044 SPI NOR controller driver") Signed-off-by: Han Gao Link: https://patch.msgid.link/20261001-fix-sg204xspi-v2-1-014dbaf22b5e@iscas.ac.cn Signed-off-by: Mark Brown --- drivers/spi/spi-sg2044-nor.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/drivers/spi/spi-sg2044-nor.c b/drivers/spi/spi-sg2044-nor.c index f3bcb8a1b92b..ff026ca0f766 100644 --- a/drivers/spi/spi-sg2044-nor.c +++ b/drivers/spi/spi-sg2044-nor.c @@ -307,15 +307,15 @@ static ssize_t sg2044_spifmc_tran_cmd(struct sg2044_spifmc *spifmc, return 0; } -static void sg2044_spifmc_trans(struct sg2044_spifmc *spifmc, - const struct spi_mem_op *op) +static int sg2044_spifmc_trans(struct sg2044_spifmc *spifmc, + const struct spi_mem_op *op) { if (op->data.dir == SPI_MEM_DATA_IN) - sg2044_spifmc_read(spifmc, op); + return sg2044_spifmc_read(spifmc, op); else if (op->data.dir == SPI_MEM_DATA_OUT) - sg2044_spifmc_write(spifmc, op); + return sg2044_spifmc_write(spifmc, op); else - sg2044_spifmc_tran_cmd(spifmc, op); + return sg2044_spifmc_tran_cmd(spifmc, op); } static ssize_t sg2044_spifmc_trans_reg(struct sg2044_spifmc *spifmc, @@ -389,19 +389,20 @@ static int sg2044_spifmc_exec_op(struct spi_mem *mem, const struct spi_mem_op *op) { struct sg2044_spifmc *spifmc; + int ret; spifmc = spi_controller_get_devdata(mem->spi->controller); mutex_lock(&spifmc->lock); if (op->addr.nbytes == 0) - sg2044_spifmc_trans_reg(spifmc, op); + ret = sg2044_spifmc_trans_reg(spifmc, op); else - sg2044_spifmc_trans(spifmc, op); + ret = sg2044_spifmc_trans(spifmc, op); mutex_unlock(&spifmc->lock); - return 0; + return ret; } static const struct spi_controller_mem_ops sg2044_spifmc_mem_ops = { From ed400c241b3cd80fcec6974b87eefdad1b1dd146 Mon Sep 17 00:00:00 2001 From: Han Gao Date: Thu, 1 Oct 2026 02:22:51 +0800 Subject: [PATCH 079/136] spi: sg2044-nor: Honor SPI clock limits The fixed divider ignores device and operation clock limits. Calculate the divider before each transfer so SCK does not exceed either limit. Advertise the controller maximum speed so the SPI core supplies a default when the device omits its maximum speed. Use the fastest divider when no speed limit is supplied. Fixes: de16c322eefb ("spi: sophgo: add SG2044 SPI NOR controller driver") Signed-off-by: Han Gao Link: https://patch.msgid.link/20261001-fix-sg204xspi-v2-2-014dbaf22b5e@iscas.ac.cn Signed-off-by: Mark Brown --- drivers/spi/spi-sg2044-nor.c | 39 ++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/drivers/spi/spi-sg2044-nor.c b/drivers/spi/spi-sg2044-nor.c index ff026ca0f766..bc9a8ee9bce4 100644 --- a/drivers/spi/spi-sg2044-nor.c +++ b/drivers/spi/spi-sg2044-nor.c @@ -385,6 +385,29 @@ static ssize_t sg2044_spifmc_trans_reg(struct sg2044_spifmc *spifmc, return 0; } +static int sg2044_spifmc_set_speed(struct sg2044_spifmc *spifmc, + unsigned int speed_hz) +{ + unsigned long rate = clk_get_rate(spifmc->clk); + u64 div; + u32 reg; + + if (!rate) + return -EINVAL; + + /* SCK = HCLK / (2 * (SckDiv + 1)); round down the resulting SCK. */ + div = speed_hz ? DIV_ROUND_UP_ULL(rate, 2ULL * speed_hz) : 1; + if (div > SPIFMC_CTRL_SCK_DIV_MASK + 1) + return -EINVAL; + + reg = readl(spifmc->io_base + SPIFMC_CTRL); + reg &= ~SPIFMC_CTRL_SCK_DIV_MASK; + reg |= div - 1; + writel(reg, spifmc->io_base + SPIFMC_CTRL); + + return 0; +} + static int sg2044_spifmc_exec_op(struct spi_mem *mem, const struct spi_mem_op *op) { @@ -395,11 +418,18 @@ static int sg2044_spifmc_exec_op(struct spi_mem *mem, mutex_lock(&spifmc->lock); + ret = sg2044_spifmc_set_speed(spifmc, op->max_freq ? + min(op->max_freq, mem->spi->max_speed_hz) : + mem->spi->max_speed_hz); + if (ret) + goto out_unlock; + if (op->addr.nbytes == 0) ret = sg2044_spifmc_trans_reg(spifmc, op); else ret = sg2044_spifmc_trans(spifmc, op); +out_unlock: mutex_unlock(&spifmc->lock); return ret; @@ -409,6 +439,10 @@ static const struct spi_controller_mem_ops sg2044_spifmc_mem_ops = { .exec_op = sg2044_spifmc_exec_op, }; +static const struct spi_controller_mem_caps sg2044_spifmc_mem_caps = { + .per_op_freq = true, +}; + static void sg2044_spifmc_init(struct sg2044_spifmc *spifmc) { u32 tran_csr; @@ -448,6 +482,10 @@ static int sg2044_spifmc_probe(struct platform_device *pdev) if (IS_ERR(spifmc->clk)) return dev_err_probe(dev, PTR_ERR(spifmc->clk), "Cannot get and enable AHB clock\n"); + ctrl->max_speed_hz = clk_get_rate(spifmc->clk) / 2; + if (!ctrl->max_speed_hz) + return dev_err_probe(dev, -EINVAL, "Invalid AHB clock rate\n"); + spifmc->dev = &pdev->dev; spifmc->ctrl = ctrl; @@ -459,6 +497,7 @@ static int sg2044_spifmc_probe(struct platform_device *pdev) ctrl->bits_per_word_mask = SPI_BPW_MASK(8); ctrl->auto_runtime_pm = false; ctrl->mem_ops = &sg2044_spifmc_mem_ops; + ctrl->mem_caps = &sg2044_spifmc_mem_caps; ctrl->mode_bits = SPI_RX_DUAL | SPI_TX_DUAL | SPI_RX_QUAD | SPI_TX_QUAD; ret = devm_mutex_init(dev, &spifmc->lock); From d220c9443db6e5f20a60cad55c6a0ae7a75c63b8 Mon Sep 17 00:00:00 2001 From: Jesse Jeronimus Date: Fri, 2 Oct 2026 13:06:46 +0200 Subject: [PATCH 080/136] Documentation: devres: Remove non-existent SPI register helpers Commit 9b894d65e978 ("Documentation: devres: add missing SPI helpers") added devm_spi_register_host() and devm_spi_register_target() to the list of managed interfaces. These functions do not exist and never existed in the tree. The only managed SPI register helper is devm_spi_register_controller(), which is already in the list. Remove the two entries. Fixes: 9b894d65e978 ("Documentation: devres: add missing SPI helpers") Signed-off-by: Jesse Jeronimus Link: https://patch.msgid.link/20261002110752.89529-1-jesse.jeronimus@gmail.com Signed-off-by: Mark Brown --- Documentation/driver-api/driver-model/devres.rst | 2 -- 1 file changed, 2 deletions(-) diff --git a/Documentation/driver-api/driver-model/devres.rst b/Documentation/driver-api/driver-model/devres.rst index 5067500ded5c..4f0b90e7e138 100644 --- a/Documentation/driver-api/driver-model/devres.rst +++ b/Documentation/driver-api/driver-model/devres.rst @@ -460,8 +460,6 @@ SPI devm_spi_alloc_target() devm_spi_optimize_message() devm_spi_register_controller() - devm_spi_register_host() - devm_spi_register_target() WATCHDOG devm_watchdog_register_device() From 65766f75dce38103e5cbfa40eefffdc285645084 Mon Sep 17 00:00:00 2001 From: Saad Anjum Date: Fri, 2 Oct 2026 20:55:27 +0500 Subject: [PATCH 081/136] spi: bitbang: Drop duplicated word in file header comment Commit 8ee46db14169 ("spi: bitbang: Add missing MODULE_DESCRIPTION()") updated the terminology in the file header from "SPI master controller" to "SPI host controller", but only the word "master" was replaced, so the header now says "controller" twice in a row. Drop the duplicated "controller" so that the comment matches the wording used by the Kconfig entry and the MODULE_DESCRIPTION() string. No functional change. Signed-off-by: Saad Anjum Link: https://patch.msgid.link/20261002155527.24845-1-saadanjum239@gmail.com Signed-off-by: Mark Brown --- drivers/spi/spi-bitbang.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/spi/spi-bitbang.c b/drivers/spi/spi-bitbang.c index fb288ed56d94..347265e80cf1 100644 --- a/drivers/spi/spi-bitbang.c +++ b/drivers/spi/spi-bitbang.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-or-later /* - * Polling/bitbanging SPI host controller controller driver utilities + * Polling/bitbanging SPI host controller driver utilities */ #include From 1cdbe8d7c228770c3d15b96b36b8875ec9abf746 Mon Sep 17 00:00:00 2001 From: Chi-Wen Weng Date: Thu, 1 Oct 2026 21:18:16 +0800 Subject: [PATCH 082/136] spi: dt-bindings: nuvoton,ma35d1-qspi: Add MA35D1 SPI controller Extend the existing MA35D1 QSPI binding to also describe the MA35D1 SPI controller. The SPI and QSPI controllers are separate hardware IPs. QSPI additionally supports dual, quad and DTR transfers and a target-mode timeout function, but these differences do not require additional controller-specific Devicetree properties. Both controllers expose the same Devicetree-visible resources and provide up to two native chip-select signals. GPIO chip selects are supported through the generic SPI controller binding. Since the MA35D1 QSPI driver now supports GPIO chip selects, also remove the existing restriction on cs-gpios for the QSPI compatible. Add the nuvoton,ma35d1-spi compatible and update the binding description and examples to cover both controllers. Signed-off-by: Chi-Wen Weng Link: https://patch.msgid.link/20261001131818.110028-2-cwweng.linux@gmail.com Signed-off-by: Mark Brown --- .../bindings/spi/nuvoton,ma35d1-qspi.yaml | 41 +++++++++++++++++-- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/Documentation/devicetree/bindings/spi/nuvoton,ma35d1-qspi.yaml b/Documentation/devicetree/bindings/spi/nuvoton,ma35d1-qspi.yaml index 40965ec5163b..72f2e1e1219a 100644 --- a/Documentation/devicetree/bindings/spi/nuvoton,ma35d1-qspi.yaml +++ b/Documentation/devicetree/bindings/spi/nuvoton,ma35d1-qspi.yaml @@ -4,7 +4,17 @@ $id: http://devicetree.org/schemas/spi/nuvoton,ma35d1-qspi.yaml# $schema: http://devicetree.org/meta-schemas/core.yaml# -title: Nuvoton MA35D1 Quad SPI Controller +title: Nuvoton MA35D1 SPI and Quad SPI Controllers + +description: | + The Nuvoton MA35D1 SoC contains separate SPI and QSPI controller IPs. + Both controllers expose the same Devicetree-visible resources and provide + two native chip-select signals. GPIO chip selects are supported through the + generic SPI controller binding. + + The QSPI controller additionally supports dual, quad and DTR transfers and + a target-mode timeout function. These capability differences do not require + additional controller properties. maintainers: - Chi-Wen Weng @@ -14,7 +24,9 @@ allOf: properties: compatible: - const: nuvoton,ma35d1-qspi + enum: + - nuvoton,ma35d1-spi + - nuvoton,ma35d1-qspi reg: maxItems: 1 @@ -33,8 +45,6 @@ properties: maximum: 2 default: 2 - cs-gpios: false - required: - compatible - reg @@ -59,7 +69,30 @@ examples: interrupts = ; clocks = <&clk QSPI0_GATE>; resets = <&sys MA35D1_RESET_QSPI0>; + #address-cells = <1>; #size-cells = <0>; }; }; + + - | + #include + #include + #include + + soc { + #address-cells = <2>; + #size-cells = <2>; + + spi@40600000 { + compatible = "nuvoton,ma35d1-spi"; + reg = <0 0x40600000 0 0x10000>; + interrupts = ; + clocks = <&clk SPI0_GATE>; + resets = <&sys MA35D1_RESET_SPI0>; + + #address-cells = <1>; + #size-cells = <0>; + }; + }; +... From dd3fb3b259525238e376d443659e219183b9cf19 Mon Sep 17 00:00:00 2001 From: Chi-Wen Weng Date: Thu, 1 Oct 2026 21:18:17 +0800 Subject: [PATCH 083/136] spi: ma35d1: Add Nuvoton MA35D1 SPI controller support Add support for the SPI controller found in the Nuvoton MA35D1 SoC. The driver supports SPI host mode using polling-based PIO transfers, with SPI modes 0 through 3, LSB-first operation, and word sizes from 8 to 32 bits. Support the two native chip-select outputs as well as GPIO chip selects through the SPI core GPIO descriptor support. Configure the SPI clock divider according to the hardware requirement that only odd CLKDIV register values are valid, ensuring that the generated SPI clock does not exceed the requested transfer frequency. The controller must be disabled before changing CTL, CLKDIV, SSCTL or FIFOCTL. Avoid unnecessary enable/disable cycles by reconfiguring the controller only when the transfer settings change. Add system sleep support using the SPI core suspend and resume helpers. The MA35D1 SPI registers are retained across power-down and deep sleep, so no hardware state restoration is required after resume. Signed-off-by: Chi-Wen Weng Link: https://patch.msgid.link/20261001131818.110028-3-cwweng.linux@gmail.com Signed-off-by: Mark Brown --- drivers/spi/Kconfig | 11 + drivers/spi/Makefile | 1 + drivers/spi/spi-ma35d1.c | 716 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 728 insertions(+) create mode 100644 drivers/spi/spi-ma35d1.c diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index eb042cf5268f..49088e5886d3 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -667,6 +667,17 @@ config SPI_LP8841_RTC Say N here unless you plan to run the kernel on an ICP DAS LP-8x4x industrial computer. +config SPI_MA35D1 + tristate "Nuvoton MA35D1 SPI controller" + depends on ARCH_MA35 || COMPILE_TEST + help + This enables support for the SPI controller found in + Nuvoton MA35D1 SoCs. + + The controller supports standard SPI peripheral devices with + configurable clock polarity and phase, chip select, and data + word sizes from 8 to 32 bits. + config SPI_MA35D1_QSPI tristate "Nuvoton MA35D1 QSPI controller" depends on ARCH_MA35 || COMPILE_TEST diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index 37c6eec9b541..b493f50b93e3 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -86,6 +86,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) += spi-ma35d1.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 diff --git a/drivers/spi/spi-ma35d1.c b/drivers/spi/spi-ma35d1.c new file mode 100644 index 000000000000..7560cb7e0b09 --- /dev/null +++ b/drivers/spi/spi-ma35d1.c @@ -0,0 +1,716 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// +// Nuvoton MA35D1 SPI 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 +#include +#include + +/* Register offset definitions */ +#define NUVOTON_SPI_CTL_OFFSET 0x00 +#define NUVOTON_SPI_CLKDIV_OFFSET 0x04 +#define NUVOTON_SPI_SSCTL_OFFSET 0x08 +#define NUVOTON_SPI_PDMACTL_OFFSET 0x0c +#define NUVOTON_SPI_FIFOCTL_OFFSET 0x10 +#define NUVOTON_SPI_STATUS_OFFSET 0x14 +#define NUVOTON_SPI_TX_OFFSET 0x20 +#define NUVOTON_SPI_RX_OFFSET 0x30 + +/* SPI Control Register bit masks */ +#define NUVOTON_SPI_CTL_DATDIR_MASK BIT(20) +#define NUVOTON_SPI_CTL_REORDER_MASK BIT(19) +#define NUVOTON_SPI_CTL_SLAVE_MASK BIT(18) +#define NUVOTON_SPI_CTL_UNITIEN_MASK BIT(17) +#define NUVOTON_SPI_CTL_RXONLY_MASK BIT(15) +#define NUVOTON_SPI_CTL_HALFDPX_MASK BIT(14) +#define NUVOTON_SPI_CTL_LSB_MASK BIT(13) +#define NUVOTON_SPI_CTL_DWIDTH_MASK GENMASK(12, 8) +#define NUVOTON_SPI_CTL_SUSPITV_MASK GENMASK(7, 4) +#define NUVOTON_SPI_CTL_CLKPOL_MASK BIT(3) +#define NUVOTON_SPI_CTL_TXNEG_MASK BIT(2) +#define NUVOTON_SPI_CTL_RXNEG_MASK BIT(1) +#define NUVOTON_SPI_CTL_SPIEN_MASK BIT(0) + +/* SPI Clock Divider Register bit masks */ +#define NUVOTON_SPI_CLKDIV_MASK GENMASK(8, 0) + +/* SPI Slave Select Control Register bit masks */ +#define NUVOTON_SPI_SSCTL_SS1_MASK BIT(1) +#define NUVOTON_SPI_SSCTL_SS0_MASK BIT(0) +#define NUVOTON_SPI_SSCTL_SSACTPOL_MASK BIT(2) +#define NUVOTON_SPI_SSCTL_AUTOSS_MASK BIT(3) +#define NUVOTON_SPI_SSCTL_SLV3WIRE_MASK BIT(4) +#define NUVOTON_SPI_SSCTL_SLVBEIEN_MASK BIT(8) +#define NUVOTON_SPI_SSCTL_SLVURIEN_MASK BIT(9) +#define NUVOTON_SPI_SSCTL_SSACTIEN_MASK BIT(12) +#define NUVOTON_SPI_SSCTL_SSINAIEN_MASK BIT(13) + +/* SPI PDMA Control Register bit masks */ +#define NUVOTON_SPI_PDMACTL_TXPDMAEN_MASK BIT(0) +#define NUVOTON_SPI_PDMACTL_RXPDMAEN_MASK BIT(1) + +/* SPI FIFO Control Register bit masks */ +#define NUVOTON_SPI_FIFOCTL_SLVBERX_MASK BIT(10) +#define NUVOTON_SPI_FIFOCTL_TXUFIEN_MASK BIT(7) +#define NUVOTON_SPI_FIFOCTL_TXUFPOL_MASK BIT(6) +#define NUVOTON_SPI_FIFOCTL_RXOVIEN_MASK BIT(5) +#define NUVOTON_SPI_FIFOCTL_RXTOIEN_MASK BIT(4) +#define NUVOTON_SPI_FIFOCTL_TXTHIEN_MASK BIT(3) +#define NUVOTON_SPI_FIFOCTL_RXTHIEN_MASK BIT(2) +#define NUVOTON_SPI_FIFOCTL_TXRST_MASK BIT(1) +#define NUVOTON_SPI_FIFOCTL_RXRST_MASK BIT(0) + +/* SPI Status Register bit masks */ +#define NUVOTON_SPI_STATUS_TXRXRST_MASK BIT(23) +#define NUVOTON_SPI_STATUS_TXFULL_MASK BIT(17) +#define NUVOTON_SPI_STATUS_SPIENSTS_MASK BIT(15) +#define NUVOTON_SPI_STATUS_RXEMPTY_MASK BIT(8) +#define NUVOTON_SPI_STATUS_BUSY_MASK BIT(0) + +#define NUVOTON_SPI_MAX_NATIVE_CS 2 +#define NUVOTON_SPI_DEFAULT_NUM_CS 2 +#define NUVOTON_SPI_DEFAULT_BPW 8 +#define NUVOTON_SPI_MAX_SPEED_HZ 100000000U +#define NUVOTON_SPI_MIN_DIVISOR 2U +#define NUVOTON_SPI_MAX_DIVISOR 512U +#define NUVOTON_SPI_RESET_CYCLES 5U + +/* Bound PIO operations to avoid long polling loops. */ +#define NUVOTON_SPI_MAX_TRANSFER_SIZE SZ_4K +#define NUVOTON_SPI_TIMEOUT_US 10000 + +struct nuvoton_spi { + void __iomem *regs; + struct device *dev; + unsigned long clk_rate; +}; + +static u32 nuvoton_spi_read(struct nuvoton_spi *nspi, u32 reg) +{ + return readl(nspi->regs + reg); +} + +static void nuvoton_spi_write(struct nuvoton_spi *nspi, u32 val, u32 reg) +{ + writel(val, nspi->regs + reg); +} + +static void nuvoton_spi_update_bits(struct nuvoton_spi *nspi, u32 reg, + u32 mask, u32 val) +{ + u32 tmp; + + tmp = nuvoton_spi_read(nspi, reg); + tmp &= ~mask; + tmp |= val & mask; + nuvoton_spi_write(nspi, tmp, reg); +} + +static int nuvoton_spi_disable(struct nuvoton_spi *nspi) +{ + u32 ctl; + u32 val; + + ctl = nuvoton_spi_read(nspi, NUVOTON_SPI_CTL_OFFSET); + if (!(ctl & NUVOTON_SPI_CTL_SPIEN_MASK)) + return 0; + + nuvoton_spi_write(nspi, ctl & ~NUVOTON_SPI_CTL_SPIEN_MASK, + NUVOTON_SPI_CTL_OFFSET); + + return readl_poll_timeout_atomic(nspi->regs + NUVOTON_SPI_STATUS_OFFSET, + val, + !(val & NUVOTON_SPI_STATUS_SPIENSTS_MASK), + 1, NUVOTON_SPI_TIMEOUT_US); +} + +static int nuvoton_spi_enable(struct nuvoton_spi *nspi) +{ + u32 ctl; + u32 val; + + ctl = nuvoton_spi_read(nspi, NUVOTON_SPI_CTL_OFFSET); + if (ctl & NUVOTON_SPI_CTL_SPIEN_MASK) + return 0; + + nuvoton_spi_write(nspi, ctl | NUVOTON_SPI_CTL_SPIEN_MASK, + NUVOTON_SPI_CTL_OFFSET); + + return readl_poll_timeout_atomic(nspi->regs + NUVOTON_SPI_STATUS_OFFSET, + val, + val & NUVOTON_SPI_STATUS_SPIENSTS_MASK, + 1, NUVOTON_SPI_TIMEOUT_US); +} + +static int nuvoton_spi_wait_ready(struct nuvoton_spi *nspi) +{ + u32 val; + + return readl_poll_timeout(nspi->regs + NUVOTON_SPI_STATUS_OFFSET, val, + !(val & NUVOTON_SPI_STATUS_BUSY_MASK), + 0, NUVOTON_SPI_TIMEOUT_US); +} + +static int nuvoton_spi_reset_fifo(struct nuvoton_spi *nspi) +{ + u32 val; + + nuvoton_spi_update_bits(nspi, NUVOTON_SPI_FIFOCTL_OFFSET, + NUVOTON_SPI_FIFOCTL_TXRST_MASK | + NUVOTON_SPI_FIFOCTL_RXRST_MASK, + NUVOTON_SPI_FIFOCTL_TXRST_MASK | + NUVOTON_SPI_FIFOCTL_RXRST_MASK); + + /* + * Give the controller a short time to latch the FIFO reset request + * before polling the reset status bit. + */ + udelay(1); + + return readl_poll_timeout(nspi->regs + NUVOTON_SPI_STATUS_OFFSET, val, + !(val & NUVOTON_SPI_STATUS_TXRXRST_MASK), + 1, NUVOTON_SPI_TIMEOUT_US); +} + +static int nuvoton_spi_wait_tx_not_full(struct nuvoton_spi *nspi) +{ + u32 val; + + return readl_poll_timeout(nspi->regs + NUVOTON_SPI_STATUS_OFFSET, val, + !(val & NUVOTON_SPI_STATUS_TXFULL_MASK), + 0, NUVOTON_SPI_TIMEOUT_US); +} + +static int nuvoton_spi_wait_rx_not_empty(struct nuvoton_spi *nspi) +{ + u32 val; + + return readl_poll_timeout(nspi->regs + NUVOTON_SPI_STATUS_OFFSET, val, + !(val & NUVOTON_SPI_STATUS_RXEMPTY_MASK), + 0, NUVOTON_SPI_TIMEOUT_US); +} + +static int nuvoton_spi_calc_divisor(unsigned long clk_rate, u32 speed_hz, + unsigned int *divisor) +{ + unsigned int div; + + div = DIV_ROUND_UP(clk_rate, speed_hz); + if (div < NUVOTON_SPI_MIN_DIVISOR) + div = NUVOTON_SPI_MIN_DIVISOR; + + /* + * CLKDIV only accepts odd register values, corresponding to even + * clock divisors. Round up so the generated clock never exceeds + * the requested frequency. + */ + if (div & 1) + div++; + + if (div > NUVOTON_SPI_MAX_DIVISOR) + return -EINVAL; + + *divisor = div; + + return 0; +} + +static int nuvoton_spi_configure_mode(struct nuvoton_spi *nspi, + struct spi_device *spi) +{ + u32 mask = NUVOTON_SPI_CTL_TXNEG_MASK | + NUVOTON_SPI_CTL_RXNEG_MASK | + NUVOTON_SPI_CTL_CLKPOL_MASK | + NUVOTON_SPI_CTL_LSB_MASK; + u32 ctl = 0; + u32 cur_ctl; + u32 mode; + int ret; + + mode = spi->mode & SPI_MODE_X_MASK; + if (mode == SPI_MODE_0 || mode == SPI_MODE_3) + ctl |= NUVOTON_SPI_CTL_TXNEG_MASK; + else + ctl |= NUVOTON_SPI_CTL_RXNEG_MASK; + + if (spi->mode & SPI_CPOL) + ctl |= NUVOTON_SPI_CTL_CLKPOL_MASK; + + if (spi->mode & SPI_LSB_FIRST) + ctl |= NUVOTON_SPI_CTL_LSB_MASK; + + cur_ctl = nuvoton_spi_read(nspi, NUVOTON_SPI_CTL_OFFSET); + if ((cur_ctl & mask) == ctl) + return 0; + + ret = nuvoton_spi_disable(nspi); + if (ret) + return ret; + + nuvoton_spi_update_bits(nspi, NUVOTON_SPI_CTL_OFFSET, mask, ctl); + + return 0; +} + +static int nuvoton_spi_configure_transfer(struct nuvoton_spi *nspi, + struct spi_transfer *xfer) +{ + u32 ctl_mask = NUVOTON_SPI_CTL_DWIDTH_MASK; + u32 cur_clkdiv; + u32 cur_ctl; + u32 clkdiv; + u32 dwidth; + unsigned int divisor; + int ret; + + ret = nuvoton_spi_calc_divisor(nspi->clk_rate, xfer->speed_hz, + &divisor); + if (ret) { + dev_err(nspi->dev, "unsupported SPI clock %u Hz\n", + xfer->speed_hz); + return ret; + } + + clkdiv = FIELD_PREP(NUVOTON_SPI_CLKDIV_MASK, divisor - 1); + dwidth = xfer->bits_per_word == 32 ? 0 : xfer->bits_per_word; + dwidth = FIELD_PREP(NUVOTON_SPI_CTL_DWIDTH_MASK, dwidth); + + xfer->effective_speed_hz = nspi->clk_rate / divisor; + + cur_ctl = nuvoton_spi_read(nspi, NUVOTON_SPI_CTL_OFFSET); + cur_clkdiv = nuvoton_spi_read(nspi, NUVOTON_SPI_CLKDIV_OFFSET); + + if ((cur_ctl & ctl_mask) == dwidth && + (cur_clkdiv & NUVOTON_SPI_CLKDIV_MASK) == clkdiv) + return 0; + + ret = nuvoton_spi_disable(nspi); + if (ret) + return ret; + + if ((cur_ctl & ctl_mask) != dwidth) + nuvoton_spi_update_bits(nspi, NUVOTON_SPI_CTL_OFFSET, + ctl_mask, dwidth); + + if ((cur_clkdiv & NUVOTON_SPI_CLKDIV_MASK) != clkdiv) + nuvoton_spi_write(nspi, clkdiv, NUVOTON_SPI_CLKDIV_OFFSET); + + return 0; +} + +static u32 nuvoton_spi_get_tx_word(const void *txbuf, unsigned int offset, + unsigned int bytes_per_word) +{ + if (!txbuf) + return 0; + + switch (bytes_per_word) { + case 1: + return ((const u8 *)txbuf)[offset]; + case 2: + return get_unaligned((const u16 *)((const u8 *)txbuf + offset)); + case 4: + return get_unaligned((const u32 *)((const u8 *)txbuf + offset)); + default: + return 0; + } +} + +static void nuvoton_spi_put_rx_word(void *rxbuf, unsigned int offset, + unsigned int bytes_per_word, u32 val) +{ + if (!rxbuf) + return; + + switch (bytes_per_word) { + case 1: + ((u8 *)rxbuf)[offset] = val; + break; + case 2: + put_unaligned((u16)val, (u16 *)((u8 *)rxbuf + offset)); + break; + case 4: + put_unaligned(val, (u32 *)((u8 *)rxbuf + offset)); + break; + } +} + +static int nuvoton_spi_txrx(struct nuvoton_spi *nspi, + struct spi_transfer *xfer) +{ + unsigned int bytes_per_word; + unsigned int offset; + u32 val; + int ret; + + bytes_per_word = spi_bpw_to_bytes(xfer->bits_per_word); + + /* + * Use conservative word-by-word PIO. Each transmitted word produces + * one receive FIFO entry, so always drain RX, including TX-only + * transfers. RX-only transfers send zero-filled dummy words. + */ + for (offset = 0; offset < xfer->len; offset += bytes_per_word) { + ret = nuvoton_spi_wait_tx_not_full(nspi); + if (ret) { + dev_err(nspi->dev, "TX FIFO full timeout\n"); + return ret; + } + + val = nuvoton_spi_get_tx_word(xfer->tx_buf, offset, + bytes_per_word); + nuvoton_spi_write(nspi, val, NUVOTON_SPI_TX_OFFSET); + + ret = nuvoton_spi_wait_rx_not_empty(nspi); + if (ret) { + dev_err(nspi->dev, "RX FIFO empty timeout\n"); + return ret; + } + + val = nuvoton_spi_read(nspi, NUVOTON_SPI_RX_OFFSET); + nuvoton_spi_put_rx_word(xfer->rx_buf, offset, bytes_per_word, + val); + + spi_delay_exec(&xfer->word_delay, xfer); + } + + ret = nuvoton_spi_wait_ready(nspi); + if (ret) + dev_err(nspi->dev, "controller busy timeout\n"); + + return ret; +} + +static void nuvoton_spi_set_cs_level(struct nuvoton_spi *nspi, + unsigned int cs, bool assert) +{ + u32 mask; + + switch (cs) { + case 0: + mask = NUVOTON_SPI_SSCTL_SS0_MASK; + break; + case 1: + mask = NUVOTON_SPI_SSCTL_SS1_MASK; + break; + default: + dev_warn(nspi->dev, "invalid chip select %u\n", cs); + return; + } + + nuvoton_spi_update_bits(nspi, NUVOTON_SPI_SSCTL_OFFSET, + mask, assert ? mask : 0); +} + +static int nuvoton_spi_setup(struct spi_device *spi) +{ + unsigned int cs = spi_get_chipselect(spi, 0); + + if (spi_get_csgpiod(spi, 0)) + return 0; + + if (cs >= NUVOTON_SPI_MAX_NATIVE_CS) { + dev_err(&spi->dev, "invalid native chip select %u\n", cs); + return -EINVAL; + } + + if (spi->mode & SPI_CS_HIGH) { + dev_err(&spi->dev, + "active-high native chip select is not supported\n"); + return -EINVAL; + } + + return 0; +} + +static void nuvoton_spi_set_cs(struct spi_device *spi, bool level) +{ + struct nuvoton_spi *nspi = spi_controller_get_devdata(spi->controller); + int ret; + + /* + * SSCTL may only be changed while SPIEN is clear. Clearing SPIEN + * retains the CLK and MOSI output levels. + */ + ret = nuvoton_spi_disable(nspi); + if (ret) { + dev_err(nspi->dev, "failed to disable controller for chip select\n"); + return; + } + + /* + * The SPI core passes the physical CS level to ->set_cs(). This + * initial driver only supports active-low native chip selects. + */ + nuvoton_spi_set_cs_level(nspi, spi_get_chipselect(spi, 0), !level); +} + +static int nuvoton_spi_prepare_message(struct spi_controller *ctlr, + struct spi_message *message) +{ + struct nuvoton_spi *nspi = spi_controller_get_devdata(ctlr); + int ret; + + ret = nuvoton_spi_configure_mode(nspi, message->spi); + if (ret) + dev_err(nspi->dev, "failed to configure SPI mode\n"); + + return ret; +} + +static int nuvoton_spi_transfer_one(struct spi_controller *ctlr, + struct spi_device *spi, + struct spi_transfer *xfer) +{ + struct nuvoton_spi *nspi = spi_controller_get_devdata(ctlr); + int ret; + + ret = nuvoton_spi_configure_transfer(nspi, xfer); + if (ret) + return ret; + + ret = nuvoton_spi_enable(nspi); + if (ret) { + dev_err(nspi->dev, "failed to enable controller\n"); + return ret; + } + + return nuvoton_spi_txrx(nspi, xfer); +} + +static void nuvoton_spi_handle_err(struct spi_controller *ctlr, + struct spi_message *message) +{ + struct nuvoton_spi *nspi = spi_controller_get_devdata(ctlr); + int ret; + + ret = nuvoton_spi_disable(nspi); + if (ret) { + dev_err(nspi->dev, + "failed to disable controller during recovery\n"); + return; + } + + ret = nuvoton_spi_reset_fifo(nspi); + if (ret) + dev_err(nspi->dev, "failed to reset FIFO during recovery\n"); +} + +static size_t nuvoton_spi_max_transfer_size(struct spi_device *spi) +{ + return NUVOTON_SPI_MAX_TRANSFER_SIZE; +} + +static int nuvoton_spi_hw_init(struct nuvoton_spi *nspi) +{ + u32 ctl_mask; + u32 fifo_mask; + u32 ssctl_mask; + int ret; + + ret = nuvoton_spi_disable(nspi); + if (ret) { + dev_err(nspi->dev, "failed to disable controller\n"); + return ret; + } + + ctl_mask = NUVOTON_SPI_CTL_DATDIR_MASK | + NUVOTON_SPI_CTL_REORDER_MASK | + NUVOTON_SPI_CTL_SLAVE_MASK | + NUVOTON_SPI_CTL_UNITIEN_MASK | + NUVOTON_SPI_CTL_RXONLY_MASK | + NUVOTON_SPI_CTL_HALFDPX_MASK | + NUVOTON_SPI_CTL_LSB_MASK | + NUVOTON_SPI_CTL_DWIDTH_MASK | + NUVOTON_SPI_CTL_SUSPITV_MASK | + NUVOTON_SPI_CTL_CLKPOL_MASK | + NUVOTON_SPI_CTL_TXNEG_MASK | + NUVOTON_SPI_CTL_RXNEG_MASK; + + nuvoton_spi_update_bits(nspi, NUVOTON_SPI_CTL_OFFSET, ctl_mask, + NUVOTON_SPI_CTL_TXNEG_MASK | + FIELD_PREP(NUVOTON_SPI_CTL_DWIDTH_MASK, + NUVOTON_SPI_DEFAULT_BPW)); + + ssctl_mask = NUVOTON_SPI_SSCTL_SS0_MASK | + NUVOTON_SPI_SSCTL_SS1_MASK | + NUVOTON_SPI_SSCTL_SSACTPOL_MASK | + NUVOTON_SPI_SSCTL_AUTOSS_MASK | + NUVOTON_SPI_SSCTL_SLV3WIRE_MASK | + NUVOTON_SPI_SSCTL_SLVBEIEN_MASK | + NUVOTON_SPI_SSCTL_SLVURIEN_MASK | + NUVOTON_SPI_SSCTL_SSACTIEN_MASK | + NUVOTON_SPI_SSCTL_SSINAIEN_MASK; + + nuvoton_spi_update_bits(nspi, NUVOTON_SPI_SSCTL_OFFSET, + ssctl_mask, 0); + + nuvoton_spi_update_bits(nspi, NUVOTON_SPI_PDMACTL_OFFSET, + NUVOTON_SPI_PDMACTL_TXPDMAEN_MASK | + NUVOTON_SPI_PDMACTL_RXPDMAEN_MASK, 0); + + fifo_mask = NUVOTON_SPI_FIFOCTL_SLVBERX_MASK | + NUVOTON_SPI_FIFOCTL_TXUFIEN_MASK | + NUVOTON_SPI_FIFOCTL_TXUFPOL_MASK | + NUVOTON_SPI_FIFOCTL_RXOVIEN_MASK | + NUVOTON_SPI_FIFOCTL_RXTOIEN_MASK | + NUVOTON_SPI_FIFOCTL_TXTHIEN_MASK | + NUVOTON_SPI_FIFOCTL_RXTHIEN_MASK; + + nuvoton_spi_update_bits(nspi, NUVOTON_SPI_FIFOCTL_OFFSET, fifo_mask, 0); + + ret = nuvoton_spi_reset_fifo(nspi); + if (ret) + dev_err(nspi->dev, "FIFO reset timed out\n"); + + return ret; +} + +static int nuvoton_spi_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct spi_controller *ctlr; + struct nuvoton_spi *nspi; + struct reset_control *rst; + struct clk *clk; + unsigned int max_divisor; + u32 num_cs = NUVOTON_SPI_DEFAULT_NUM_CS; + int ret; + + if (device_property_read_bool(dev, "spi-slave")) + return dev_err_probe(dev, -EOPNOTSUPP, + "target mode is not supported\n"); + + ctlr = devm_spi_alloc_host(dev, sizeof(*nspi)); + if (!ctlr) + return -ENOMEM; + + platform_set_drvdata(pdev, ctlr); + + nspi = spi_controller_get_devdata(ctlr); + nspi->dev = dev; + + nspi->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(nspi->regs)) + return PTR_ERR(nspi->regs); + + clk = devm_clk_get_enabled(dev, NULL); + if (IS_ERR(clk)) + return dev_err_probe(dev, PTR_ERR(clk), + "failed to get and enable clock\n"); + + nspi->clk_rate = clk_get_rate(clk); + if (!nspi->clk_rate) + return dev_err_probe(dev, -EINVAL, "invalid clock rate\n"); + + rst = devm_reset_control_get_exclusive_deasserted(dev, NULL); + if (IS_ERR(rst)) + return dev_err_probe(dev, PTR_ERR(rst), + "failed to get and deassert reset\n"); + + /* + * The hardware requires at least five peripheral clock cycles after + * reset deassertion before programming controller registers. + */ + udelay(DIV_ROUND_UP_ULL((u64)NUVOTON_SPI_RESET_CYCLES * + USEC_PER_SEC, nspi->clk_rate)); + + 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_SPI_MAX_NATIVE_CS) + return dev_err_probe(dev, -EINVAL, "invalid num-cs %u\n", + num_cs); + + ctlr->num_chipselect = num_cs; + ctlr->max_native_cs = NUVOTON_SPI_MAX_NATIVE_CS; + ctlr->use_gpio_descriptors = true; + ctlr->max_transfer_size = nuvoton_spi_max_transfer_size; + ctlr->setup = nuvoton_spi_setup; + ctlr->set_cs = nuvoton_spi_set_cs; + ctlr->prepare_message = nuvoton_spi_prepare_message; + ctlr->transfer_one = nuvoton_spi_transfer_one; + ctlr->handle_err = nuvoton_spi_handle_err; + ctlr->bits_per_word_mask = SPI_BPW_RANGE_MASK(8, 32); + ctlr->mode_bits = SPI_CPOL | SPI_CPHA | SPI_LSB_FIRST; + ctlr->min_speed_hz = DIV_ROUND_UP(nspi->clk_rate, + NUVOTON_SPI_MAX_DIVISOR); + + ret = nuvoton_spi_calc_divisor(nspi->clk_rate, + NUVOTON_SPI_MAX_SPEED_HZ, &max_divisor); + if (ret) + return dev_err_probe(dev, ret, + "clock rate does not support SPI transfers\n"); + + ctlr->max_speed_hz = nspi->clk_rate / max_divisor; + + ret = nuvoton_spi_hw_init(nspi); + 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 int nuvoton_spi_suspend(struct device *dev) +{ + struct spi_controller *ctlr = dev_get_drvdata(dev); + + return spi_controller_suspend(ctlr); +} + +static int nuvoton_spi_resume(struct device *dev) +{ + struct spi_controller *ctlr = dev_get_drvdata(dev); + + /* + * The MA35D1 SPI registers are retained across power-down and deep + * sleep, so the controller state does not need to be restored here. + */ + return spi_controller_resume(ctlr); +} + +static DEFINE_SIMPLE_DEV_PM_OPS(nuvoton_spi_pm_ops, nuvoton_spi_suspend, + nuvoton_spi_resume); + +static const struct of_device_id nuvoton_spi_of_match[] = { + { .compatible = "nuvoton,ma35d1-spi" }, + { } +}; +MODULE_DEVICE_TABLE(of, nuvoton_spi_of_match); + +static struct platform_driver nuvoton_spi_driver = { + .driver = { + .name = "ma35d1-spi", + .of_match_table = nuvoton_spi_of_match, + .pm = pm_sleep_ptr(&nuvoton_spi_pm_ops), + }, + .probe = nuvoton_spi_probe, +}; +module_platform_driver(nuvoton_spi_driver); + +MODULE_DESCRIPTION("Nuvoton MA35D1 SPI controller driver"); +MODULE_AUTHOR("Chi-Wen Weng "); +MODULE_LICENSE("GPL"); From 8767f4599551e9a6bc13ab2816d724b6324caff2 Mon Sep 17 00:00:00 2001 From: Chi-Wen Weng Date: Thu, 1 Oct 2026 21:18:18 +0800 Subject: [PATCH 084/136] spi: ma35d1-qspi: Add GPIO chip select support The generic SPI controller binding allows GPIO-based chip selects, but the MA35D1 QSPI driver does not currently enable the SPI core's GPIO descriptor handling. Enable GPIO chip-select support and specify that the controller provides two native chip selects. Add a setup callback to reject active-high polarity only for native chip selects, while allowing GPIO chip selects to use the polarity handled by the GPIO subsystem. This keeps the binding capable of describing valid board wiring while preserving the active-low limitation of the controller's native chip selects. Signed-off-by: Chi-Wen Weng Link: https://patch.msgid.link/20261001131818.110028-4-cwweng.linux@gmail.com Signed-off-by: Mark Brown --- drivers/spi/spi-ma35d1-qspi.c | 39 ++++++++++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/drivers/spi/spi-ma35d1-qspi.c b/drivers/spi/spi-ma35d1-qspi.c index 30e3239f777d..0644bb2990b8 100644 --- a/drivers/spi/spi-ma35d1-qspi.c +++ b/drivers/spi/spi-ma35d1-qspi.c @@ -62,7 +62,7 @@ #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_MAX_NATIVE_CS 2 #define NUVOTON_QSPI_DEFAULT_NUM_CS 2 #define NUVOTON_QSPI_DEFAULT_BPW 8 /* Bound PIO operations to avoid long atomic polling loops. */ @@ -442,13 +442,35 @@ static void nuvoton_qspi_set_cs_level(struct nuvoton_qspi *qspi, spin_unlock_irqrestore(&qspi->ssctl_lock, flags); } +static int nuvoton_qspi_setup(struct spi_device *spi) +{ + unsigned int cs = spi_get_chipselect(spi, 0); + + if (spi_get_csgpiod(spi, 0)) + return 0; + + if (cs >= NUVOTON_QSPI_MAX_NATIVE_CS) { + dev_err(&spi->dev, "invalid native chip select %u\n", cs); + return -EINVAL; + } + + if (spi->mode & SPI_CS_HIGH) { + dev_err(&spi->dev, + "active-high native chip select is not supported\n"); + return -EINVAL; + } + + return 0; +} + 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. + * GPIO chip selects are handled by the SPI core. For native chip + * selects, the core passes the physical CS level to ->set_cs(). + * Native chip selects are active low. */ nuvoton_qspi_set_cs_level(qspi, spi_get_chipselect(spi, 0), !level); } @@ -457,7 +479,11 @@ 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. */ + /* + * Direct spi-mem operations are only executed for native chip + * selects. GPIO chip selects use the regular SPI fallback path. + * This helper takes a logical assertion state. + */ nuvoton_qspi_set_cs_level(qspi, spi_get_chipselect(spi, 0), assert); } @@ -638,18 +664,21 @@ static int nuvoton_qspi_probe(struct platform_device *pdev) goto err_assert; } - if (!num_cs || num_cs > NUVOTON_QSPI_MAX_NUM_CS) { + if (!num_cs || num_cs > NUVOTON_QSPI_MAX_NATIVE_CS) { ret = dev_err_probe(dev, -EINVAL, "invalid num-cs %u\n", num_cs); goto err_assert; } ctlr->num_chipselect = num_cs; + ctlr->max_native_cs = NUVOTON_QSPI_MAX_NATIVE_CS; + ctlr->use_gpio_descriptors = true; 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->dtr_caps = true; + ctlr->setup = nuvoton_qspi_setup; ctlr->set_cs = nuvoton_qspi_set_cs; ctlr->transfer_one = nuvoton_qspi_transfer_one; ctlr->bits_per_word_mask = SPI_BPW_MASK(8); From 38ee6caa8d10d386b9df03d6a6352276c9f469b1 Mon Sep 17 00:00:00 2001 From: Bhargav Joshi Date: Thu, 24 Sep 2026 04:12:40 +0530 Subject: [PATCH 085/136] spi: dt-bindings: ti,davinci-spi: Convert to DT schema Convert the TI DaVinci/Keystone SPI controller binding from text format to DT schema. Changes during the conversion: - Create a separate ti,davinci-spi-peripheral-props.yaml schema to properly define the ti,spi-wdelay peripheral property. - Add missing dmas and dma-names properties. - Add constraints to ti,spi-wdelay based on the 6-bit width of the SPIFMTn.WDELAY hardware register field. Reviewed-by: Rob Herring (Arm) Signed-off-by: Bhargav Joshi Link: https://patch.msgid.link/20260924-ti-davinci-spi-v5-1-ed811dc6cd89@gmail.com Signed-off-by: Mark Brown --- .../devicetree/bindings/spi/spi-davinci.txt | 100 -------------- .../bindings/spi/spi-peripheral-props.yaml | 1 + .../spi/ti,davinci-spi-peripheral-props.yaml | 25 ++++ .../bindings/spi/ti,davinci-spi.yaml | 125 ++++++++++++++++++ 4 files changed, 151 insertions(+), 100 deletions(-) delete mode 100644 Documentation/devicetree/bindings/spi/spi-davinci.txt create mode 100644 Documentation/devicetree/bindings/spi/ti,davinci-spi-peripheral-props.yaml create mode 100644 Documentation/devicetree/bindings/spi/ti,davinci-spi.yaml diff --git a/Documentation/devicetree/bindings/spi/spi-davinci.txt b/Documentation/devicetree/bindings/spi/spi-davinci.txt deleted file mode 100644 index f012888656ec..000000000000 --- a/Documentation/devicetree/bindings/spi/spi-davinci.txt +++ /dev/null @@ -1,100 +0,0 @@ -Davinci SPI controller device bindings - -Links on DM: -Keystone 2 - https://www.ti.com/lit/ug/sprugp2a/sprugp2a.pdf -dm644x - https://www.ti.com/lit/ug/sprue32a/sprue32a.pdf -OMAP-L138/da830 - http://www.ti.com/lit/ug/spruh77a/spruh77a.pdf - -Required properties: -- #address-cells: number of cells required to define a chip select - address on the SPI bus. Should be set to 1. -- #size-cells: should be zero. -- compatible: - - "ti,dm6441-spi" for SPI used similar to that on DM644x SoC family - - "ti,da830-spi" for SPI used similar to that on DA8xx SoC family - - "ti,keystone-spi" for SPI used similar to that on Keystone2 SoC - family -- reg: Offset and length of SPI controller register space -- num-cs: Number of chip selects. This includes internal as well as - GPIO chip selects. -- ti,davinci-spi-intr-line: interrupt line used to connect the SPI - IP to the interrupt controller within the SoC. Possible values - are 0 and 1. Manual says one of the two possible interrupt - lines can be tied to the interrupt controller. Set this - based on a specific SoC configuration. -- interrupts: interrupt number mapped to CPU. -- clocks: spi clk phandle - For 66AK2G this property should be set per binding, - Documentation/devicetree/bindings/clock/ti,sci-clk.yaml - -SoC-specific Required Properties: - -The following are mandatory properties for Keystone 2 66AK2G SoCs only: - -- power-domains: Should contain a phandle to a PM domain provider node - and an args specifier containing the SPI device id - value. This property is as per the binding, - -Optional: -- cs-gpios: gpio chip selects - For example to have 3 internal CS and 2 GPIO CS, user could define - cs-gpios = <0>, <0>, <0>, <&gpio1 30 0>, <&gpio1 31 0>; - where first three are internal CS and last two are GPIO CS. - -Optional properties for slave devices: -SPI slave nodes can contain the following properties. -Not all SPI Peripherals from Texas Instruments support this. -Please check SPI peripheral documentation for a device before using these. - -- ti,spi-wdelay : delay between transmission of words - (SPIFMTn.WDELAY, SPIDAT1.WDEL) must be specified in number of SPI module - clock periods. - - delay = WDELAY * SPI_module_clock_period + 2 * SPI_module_clock_period - -Below is timing diagram which shows functional meaning of -"ti,spi-wdelay" parameter. - - +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ -SPI_CLK | | | | | | | | | | | | | | | | - +----------+ +-+ +-+ +-+ +-+ +---------------------------+ +-+ +-+ +- - -SPI_SOMI/SIMO+-----------------+ +----------- - +----------+ word1 +---------------------------+word2 - +-----------------+ +----------- - WDELAY - <--------------------------> - -Example of a NOR flash slave device (n25q032) connected to DaVinci -SPI controller device over the SPI bus. - -spi0:spi@20bf0000 { - #address-cells = <1>; - #size-cells = <0>; - compatible = "ti,dm6446-spi"; - reg = <0x20BF0000 0x1000>; - num-cs = <4>; - ti,davinci-spi-intr-line = <0>; - interrupts = <338>; - clocks = <&clkspi>; - - flash: flash@0 { - #address-cells = <1>; - #size-cells = <1>; - compatible = "st,m25p32"; - spi-max-frequency = <25000000>; - reg = <0>; - ti,spi-wdelay = <8>; - - partition@0 { - label = "u-boot-spl"; - reg = <0x0 0x80000>; - read-only; - }; - - partition@1 { - label = "test"; - reg = <0x80000 0x380000>; - }; - }; -}; diff --git a/Documentation/devicetree/bindings/spi/spi-peripheral-props.yaml b/Documentation/devicetree/bindings/spi/spi-peripheral-props.yaml index afcf9c41c058..a0f32565d090 100644 --- a/Documentation/devicetree/bindings/spi/spi-peripheral-props.yaml +++ b/Documentation/devicetree/bindings/spi/spi-peripheral-props.yaml @@ -163,5 +163,6 @@ allOf: - $ref: fsl,dspi-peripheral-props.yaml# - $ref: samsung,spi-peripheral-props.yaml# - $ref: nvidia,tegra210-quad-peripheral-props.yaml# + - $ref: ti,davinci-spi-peripheral-props.yaml# additionalProperties: true diff --git a/Documentation/devicetree/bindings/spi/ti,davinci-spi-peripheral-props.yaml b/Documentation/devicetree/bindings/spi/ti,davinci-spi-peripheral-props.yaml new file mode 100644 index 000000000000..1060f176c6cb --- /dev/null +++ b/Documentation/devicetree/bindings/spi/ti,davinci-spi-peripheral-props.yaml @@ -0,0 +1,25 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/spi/ti,davinci-spi-peripheral-props.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: TI DaVinci/Keystone SPI Peripheral Properties + +maintainers: + - Bartosz Golaszewski + +description: + Peripheral-specific properties for SPI devices attached to a TI + DaVinci/Keystone SPI controller. + +properties: + ti,spi-wdelay: + $ref: /schemas/types.yaml#/definitions/uint32 + minimum: 0 + maximum: 63 + description: + delay between transmission of words (SPIFMTn.WDELAY, SPIDAT1.WDEL) + must be specified in number of SPI module clock periods. + +additionalProperties: true diff --git a/Documentation/devicetree/bindings/spi/ti,davinci-spi.yaml b/Documentation/devicetree/bindings/spi/ti,davinci-spi.yaml new file mode 100644 index 000000000000..1c7544d31fb9 --- /dev/null +++ b/Documentation/devicetree/bindings/spi/ti,davinci-spi.yaml @@ -0,0 +1,125 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/spi/ti,davinci-spi.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: TI DaVinci/Keystone SPI Controller + +maintainers: + - Bartosz Golaszewski + +properties: + compatible: + oneOf: + - enum: + - ti,da830-spi + - ti,keystone-spi + - ti,dm6441-spi + - items: + - const: ti,keystone-spi + - const: ti,dm6441-spi + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + clocks: + maxItems: 1 + + power-domains: + maxItems: 1 + description: + Should contain a phandle to a PM domain provider node + and an args specifier containing the SPI device id + value. + + num-cs: + minimum: 1 + default: 1 + description: + Number of chip selects supported by the controller. This + includes both internal and GPIO chip selects. + + ti,davinci-spi-intr-line: + $ref: /schemas/types.yaml#/definitions/uint32 + enum: [0, 1] + description: + Interrupt line used to connect the SPI IP to the interrupt + controller within the SoC. The SPI peripheral can route its + interrupt output to one of two lines (INT0 or INT1). Set + this based on the SoC-specific wiring to the interrupt + controller. + + dmas: + items: + - description: RX DMA channel + - description: TX DMA channel + + dma-names: + items: + - const: rx + - const: tx + + "#address-cells": + const: 1 + +required: + - compatible + - reg + - interrupts + - clocks + - ti,davinci-spi-intr-line + - num-cs + - "#address-cells" + - "#size-cells" + +allOf: + - $ref: spi-controller.yaml# + - if: + properties: + compatible: + items: + - const: ti,keystone-spi + then: + required: + - power-domains + +unevaluatedProperties: false + +examples: + - | + spi@20bf0000 { + compatible = "ti,keystone-spi", "ti,dm6441-spi"; + reg = <0x20bf0000 0x1000>; + num-cs = <4>; + ti,davinci-spi-intr-line = <0>; + interrupts = <338>; + clocks = <&clkspi>; + #address-cells = <1>; + #size-cells = <0>; + + flash@0 { + compatible = "jedec,spi-nor"; + spi-max-frequency = <25000000>; + reg = <0>; + ti,spi-wdelay = <8>; + }; + }; + + - | + spi@41000 { + compatible = "ti,da830-spi"; + reg = <0x41000 0x1000>; + num-cs = <6>; + ti,davinci-spi-intr-line = <1>; + interrupts = <20>; + dmas = <&edma0 14 0>, <&edma0 15 0>; + dma-names = "rx", "tx"; + clocks = <&psc0 4>; + power-domains = <&psc0 4>; + #address-cells = <1>; + #size-cells = <0>; + }; From af1d882d421de504a1e5fac92eb06947b6cbdd8f Mon Sep 17 00:00:00 2001 From: Guangshuo Li Date: Thu, 24 Sep 2026 19:23:27 +0800 Subject: [PATCH 086/136] spi: fsl: unmap immr_spi_cs on remove of_fsl_spi_remove() does not unmap pinfo->immr_spi_cs after a successful probe when CONFIG_FSL_SOC is enabled and fsl,spisel_boot causes of_fsl_spi_probe() to map it with ioremap(). The probe error path already performs the matching iounmap(). Retrieve pinfo from the platform data and call iounmap() when pinfo->immr_spi_cs is present, pairing normal removal with the mapping established by of_fsl_spi_probe(). Fixes: 0f0581b24bd0 ("spi: fsl: Convert to use CS GPIO descriptors") Signed-off-by: Guangshuo Li Link: https://patch.msgid.link/20260924112329.1566051-2-lgs201920130244@gmail.com Signed-off-by: Mark Brown --- drivers/spi/spi-fsl-spi.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/spi/spi-fsl-spi.c b/drivers/spi/spi-fsl-spi.c index e45816ef7b65..665902703973 100644 --- a/drivers/spi/spi-fsl-spi.c +++ b/drivers/spi/spi-fsl-spi.c @@ -702,10 +702,18 @@ static void of_fsl_spi_remove(struct platform_device *ofdev) { struct spi_controller *host = platform_get_drvdata(ofdev); struct mpc8xxx_spi *mpc8xxx_spi = spi_controller_get_devdata(host); +#if IS_ENABLED(CONFIG_FSL_SOC) + struct fsl_spi_platform_data *pdata = dev_get_platdata(&ofdev->dev); + struct mpc8xxx_spi_probe_info *pinfo = to_of_pinfo(pdata); +#endif spi_unregister_controller(host); fsl_spi_cpm_free(mpc8xxx_spi); +#if IS_ENABLED(CONFIG_FSL_SOC) + if (pinfo->immr_spi_cs) + iounmap(pinfo->immr_spi_cs); +#endif } static struct platform_driver of_fsl_spi_driver = { From 38232bd3cea74845c2064eb9f7eac77fa5a21d85 Mon Sep 17 00:00:00 2001 From: Guangshuo Li Date: Thu, 24 Sep 2026 19:23:29 +0800 Subject: [PATCH 087/136] spi: bcm63xx-hsspi: Disable runtime PM on remove bcm63xx_hsspi_probe() enables runtime PM before creating the sysfs group, but bcm63xx_hsspi_remove() does not perform the matching pm_runtime_disable() after a successful probe. The probe error paths already disable runtime PM when sysfs group creation or controller registration fails. Add pm_runtime_disable() to the remove path after removing the sysfs group to balance pm_runtime_enable() from bcm63xx_hsspi_probe(). Fixes: fb8695e3749e ("spi: bcm63xx-hsspi: fix pm_runtime") Signed-off-by: Guangshuo Li Link: https://patch.msgid.link/20260924112329.1566051-4-lgs201920130244@gmail.com Signed-off-by: Mark Brown --- drivers/spi/spi-bcm63xx-hsspi.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/spi/spi-bcm63xx-hsspi.c b/drivers/spi/spi-bcm63xx-hsspi.c index 25f169a2a81d..80bf02bc324a 100644 --- a/drivers/spi/spi-bcm63xx-hsspi.c +++ b/drivers/spi/spi-bcm63xx-hsspi.c @@ -882,6 +882,7 @@ static void bcm63xx_hsspi_remove(struct platform_device *pdev) /* reset the hardware and block queue progress */ __raw_writel(0, bs->regs + HSSPI_INT_MASK_REG); sysfs_remove_group(&pdev->dev.kobj, &bcm63xx_hsspi_group); + pm_runtime_disable(&pdev->dev); } static int bcm63xx_hsspi_suspend(struct device *dev) From 717e45f97246d1070cdf86ccd7a0fb872b45933a Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Wed, 23 Sep 2026 03:29:50 +0200 Subject: [PATCH 088/136] spi: atmel-quadspi: balance runtime PM and pclk across system sleep On the controllers with a generic clock (SAMA7G5, SAMA7D65, SAM9X7 and LAN969x), atmel_qspi_suspend() takes a runtime PM reference and returns without dropping it or suspending the device. It disables the peripheral clock directly. atmel_qspi_resume() only prepares the peripheral clock and then reinitializes the controller with that clock still off. On a SAM9X75 Curiosity board, the tested standby and mem resumes failed: atmel_qspi f0014000.spi: PM: failed to resume: error -110 spi-nor spi0.0: resume() failed Reading the flash afterwards returned no data. The runtime usage count grew by one with each cycle, and the second suspend disabled the peripheral clock again: WARNING: drivers/clk/clk.c:1257 at clk_core_disable+0x6c/0xcc qspi_clk already disabled Use the same runtime PM sequence as the other variants. Suspend disables the controller and forces a runtime suspend. Resume forces a runtime resume, which enables the peripheral clock, before it runs the init callback, and drops the reference afterwards. With this change the tested resumes succeed on the board in both modes, the controller returns to runtime suspend, and the flash reads back. Fixes: 5af42209a4d2 ("spi: atmel-quadspi: Add support for sama7g5 QSPI") Reported-by: Sashiko Closes: https://sashiko.dev/#/patchset/20260709112006.390742-1-robert.marko%40sartura.hr?part=3 Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Karl Mehltretter Link: https://patch.msgid.link/20260923012950.15651-1-kmehltretter@gmail.com Signed-off-by: Mark Brown --- drivers/spi/atmel-quadspi.c | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/drivers/spi/atmel-quadspi.c b/drivers/spi/atmel-quadspi.c index ec19fa0180f0..96ed734b5621 100644 --- a/drivers/spi/atmel-quadspi.c +++ b/drivers/spi/atmel-quadspi.c @@ -1590,12 +1590,14 @@ static int atmel_qspi_suspend(struct device *dev) if (aq->caps->has_gclk) { ret = atmel_qspi_sama7g5_suspend(aq); - clk_disable_unprepare(aq->pclk); - return ret; + if (ret) { + pm_runtime_put_autosuspend(dev); + return ret; + } + } else { + atmel_qspi_write(QSPI_CR_QSPIDIS, aq, QSPI_CR); } - atmel_qspi_write(QSPI_CR_QSPIDIS, aq, QSPI_CR); - pm_runtime_mark_last_busy(dev); pm_runtime_force_suspend(dev); @@ -1621,20 +1623,20 @@ static int atmel_qspi_resume(struct device *dev) return ret; } - if (aq->caps->has_gclk) - return aq->caps->init(aq); - ret = pm_runtime_force_resume(dev); if (ret < 0) return ret; - atmel_qspi_init(aq); - - atmel_qspi_write(aq->scr, aq, QSPI_SCR); + if (aq->caps->has_gclk) { + ret = aq->caps->init(aq); + } else { + atmel_qspi_init(aq); + atmel_qspi_write(aq->scr, aq, QSPI_SCR); + } pm_runtime_put_autosuspend(dev); - return 0; + return ret; } static int atmel_qspi_runtime_suspend(struct device *dev) From ae0f8f861e28b0fbe058aeed6be8f574d8164b92 Mon Sep 17 00:00:00 2001 From: Praveen Talari Date: Sun, 27 Sep 2026 01:26:33 +0530 Subject: [PATCH 089/136] spi: dt-bindings: Document ready-gpios property When operating in SPI target (slave) mode, the GENI SPI controller can use an external GPIO to notify the SPI master about the target's active state. Document the optional ready-gpios property for the qcom,spi-geni-qcom and qcom,sa8255p-geni-spi bindings, restricted to spi-slave nodes. Reviewed-by: Krzysztof Kozlowski Signed-off-by: Praveen Talari Link: https://patch.msgid.link/20260927-use_gpio_to_notify_master_of_spi_target_activity-v5-1-9b973623080f@oss.qualcomm.com Signed-off-by: Mark Brown --- .../devicetree/bindings/spi/qcom,sa8255p-geni-spi.yaml | 9 +++++++++ .../devicetree/bindings/spi/qcom,spi-geni-qcom.yaml | 9 +++++++++ 2 files changed, 18 insertions(+) diff --git a/Documentation/devicetree/bindings/spi/qcom,sa8255p-geni-spi.yaml b/Documentation/devicetree/bindings/spi/qcom,sa8255p-geni-spi.yaml index 6552303a4f52..33eddb395a0a 100644 --- a/Documentation/devicetree/bindings/spi/qcom,sa8255p-geni-spi.yaml +++ b/Documentation/devicetree/bindings/spi/qcom,sa8255p-geni-spi.yaml @@ -39,6 +39,12 @@ properties: - const: power - const: perf + ready-gpios: + description: + GPIO used by the SPI target to notify the SPI master when it is + ready to service a transfer. Only applicable in target (slave) mode. + maxItems: 1 + required: - compatible - reg @@ -46,6 +52,9 @@ required: - power-domains - power-domain-names +dependencies: + ready-gpios: [ spi-slave ] + allOf: - $ref: /schemas/spi/spi-controller.yaml# diff --git a/Documentation/devicetree/bindings/spi/qcom,spi-geni-qcom.yaml b/Documentation/devicetree/bindings/spi/qcom,spi-geni-qcom.yaml index edf399681d7a..8d1c78701657 100644 --- a/Documentation/devicetree/bindings/spi/qcom,spi-geni-qcom.yaml +++ b/Documentation/devicetree/bindings/spi/qcom,spi-geni-qcom.yaml @@ -67,6 +67,12 @@ properties: reg: maxItems: 1 + ready-gpios: + description: + GPIO used by the SPI target to notify the SPI master when it is + ready to service a transfer. Only applicable in target (slave) mode. + maxItems: 1 + required: - compatible - clocks @@ -74,6 +80,9 @@ required: - interrupts - reg +dependencies: + ready-gpios: [ spi-slave ] + unevaluatedProperties: false examples: From bb5805a74e7514d39b956adcc5b24ba3bfd8b988 Mon Sep 17 00:00:00 2001 From: Praveen Talari Date: Sun, 27 Sep 2026 01:26:34 +0530 Subject: [PATCH 090/136] spi: qcom-geni: Use GPIO to notify master of SPI target activity When operating in SPI target mode, the GENI controller relies on an external GPIO to notify the SPI master about the target's active state. Add support for an optional ready GPIO that is asserted when a target transfer begins and deasserted when the transfer completes, is aborted, or hits a timeout. This allows the target to explicitly signal its availability to the master and ensures the GPIO is released in all error and completion paths, preventing the master from observing a stale or incorrect target-ready indication. The ready GPIO is intentionally made optional and is requested only when target mode is enabled. This preserves existing behaviour for systems that do not require the signal and avoids regressions on deployed platforms where the GPIO is already controlled from userspace. Targets without a ready GPIO continue to operate unchanged by using devm_gpiod_get_optional(). Signed-off-by: Praveen Talari Link: https://patch.msgid.link/20260927-use_gpio_to_notify_master_of_spi_target_activity-v5-2-9b973623080f@oss.qualcomm.com Signed-off-by: Mark Brown --- drivers/spi/spi-geni-qcom.c | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/drivers/spi/spi-geni-qcom.c b/drivers/spi/spi-geni-qcom.c index bced6279a062..91e75e213f92 100644 --- a/drivers/spi/spi-geni-qcom.c +++ b/drivers/spi/spi-geni-qcom.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -118,6 +119,7 @@ struct spi_geni_master { int cur_xfer_mode; const struct geni_spi_desc *dev_data; struct notifier_block panic_nb; + struct gpio_desc *ready_gpio; }; static void spi_slv_setup(struct spi_geni_master *mas) @@ -176,6 +178,9 @@ static void handle_se_timeout(struct spi_controller *spi) xfer = mas->cur_xfer; mas->cur_xfer = NULL; + if (spi->target) + gpiod_set_value(mas->ready_gpio, 0); + /* The controller doesn't support the Cancel commnand in target mode */ if (!spi->target) { reinit_completion(&mas->cancel_done); @@ -936,8 +941,12 @@ static int spi_geni_transfer_one(struct spi_controller *spi, if (mas->cur_xfer_mode == GENI_SE_FIFO || mas->cur_xfer_mode == GENI_SE_DMA) { ret = setup_se_xfer(xfer, mas, slv->mode, spi); /* SPI framework expects +ve ret code to wait for transfer complete */ - if (!ret) + if (!ret) { + if (spi->target) + gpiod_set_value(mas->ready_gpio, 1); ret = 1; + } + return ret; } return setup_gsi_xfer(xfer, mas, slv, spi); @@ -1023,6 +1032,8 @@ static irqreturn_t geni_spi_isr(int irq, void *data) if (dma_rx_status & RX_RESET_DONE) complete(&mas->rx_reset_done); if (!mas->tx_rem_bytes && !mas->rx_rem_bytes && xfer) { + if (spi->target) + gpiod_set_value(mas->ready_gpio, 0); spi_finalize_current_transfer(spi); mas->cur_xfer = NULL; } @@ -1192,8 +1203,14 @@ static int spi_geni_probe(struct platform_device *pdev) init_completion(&mas->rx_reset_done); spin_lock_init(&mas->lock); - if (spi->target) + if (spi->target) { spi->target_abort = spi_geni_target_abort; + mas->ready_gpio = devm_gpiod_get_optional(dev, "ready", + GPIOD_OUT_LOW); + if (IS_ERR(mas->ready_gpio)) + return dev_err_probe(dev, PTR_ERR(mas->ready_gpio), + "Failed to request GPIO\n"); + } pm_runtime_use_autosuspend(&pdev->dev); pm_runtime_set_autosuspend_delay(&pdev->dev, 250); From 4825287f6d2346e9273f35a928d1ef40e68bc58b Mon Sep 17 00:00:00 2001 From: Alban Bedel Date: Thu, 1 Oct 2026 17:35:07 +0200 Subject: [PATCH 091/136] spi: Fix ACPI SPI resources using relative path ACPI resources can use relative path when pointing to other resources. This didn't work with SPISerialBus resources because the device defining the resource was not passed to acpi_get_handle(). Pass the ACPI device handle through the lookup context to also allow for relative paths. Signed-off-by: Alban Bedel Link: https://patch.msgid.link/20261001153507.2011806-1-alban.bedel@lht.dlh.de Signed-off-by: Mark Brown --- drivers/spi/spi.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c index 015d93680e5d..40cda0037ea5 100644 --- a/drivers/spi/spi.c +++ b/drivers/spi/spi.c @@ -2992,6 +2992,7 @@ EXPORT_SYMBOL_GPL(devm_spi_new_ancillary_device); #ifdef CONFIG_ACPI struct acpi_spi_lookup { struct spi_controller *ctlr; + acpi_handle device_handle; u32 max_speed_hz; u32 mode; int irq; @@ -3086,7 +3087,7 @@ static int acpi_spi_add_resource(struct acpi_resource *ares, void *data) if (lookup->index != -1 && lookup->n++ != lookup->index) return 1; - status = acpi_get_handle(NULL, + status = acpi_get_handle(lookup->device_handle, sb->resource_source.string_ptr, &parent_handle); @@ -3178,6 +3179,7 @@ struct spi_device *acpi_spi_device_alloc(struct spi_controller *ctlr, return ERR_PTR(-EINVAL); lookup.ctlr = ctlr; + lookup.device_handle = acpi_device_handle(adev); lookup.irq = -1; lookup.index = index; lookup.n = 0; From ec79a280e8dd217cbfa3d1df15d6894f9153580c Mon Sep 17 00:00:00 2001 From: Frank Li Date: Fri, 2 Oct 2026 15:26:14 -0400 Subject: [PATCH 092/136] spi: use dmaengine_submit() instead of ->tx_submit() Replace direct dma_async_tx_descriptor->tx_submit() calls with the proper dmaengine_submit() API wrapper. Assisted-by: LLM Signed-off-by: Frank Li Link: https://patch.msgid.link/20261002192616.161440-1-Frank.Li@oss.nxp.com Signed-off-by: Mark Brown --- drivers/spi/atmel-quadspi.c | 2 +- drivers/spi/spi-at91-usart.c | 4 ++-- drivers/spi/spi-atmel.c | 4 ++-- drivers/spi/spi-cadence-quadspi.c | 2 +- drivers/spi/spi-ti-qspi.c | 2 +- drivers/spi/spi-topcliff-pch.c | 4 ++-- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/drivers/spi/atmel-quadspi.c b/drivers/spi/atmel-quadspi.c index 96ed734b5621..762a3fb0fca8 100644 --- a/drivers/spi/atmel-quadspi.c +++ b/drivers/spi/atmel-quadspi.c @@ -792,7 +792,7 @@ static int atmel_qspi_dma_xfer(struct atmel_qspi *aq, struct dma_chan *chan, reinit_completion(&aq->dma_completion); tx->callback = atmel_qspi_dma_callback; tx->callback_param = aq; - cookie = tx->tx_submit(tx); + cookie = dmaengine_submit(tx); ret = dma_submit_error(cookie); if (ret) { dev_err(&aq->pdev->dev, "dma_submit_error %d\n", cookie); diff --git a/drivers/spi/spi-at91-usart.c b/drivers/spi/spi-at91-usart.c index cdc771350db9..aadcdf734fb0 100644 --- a/drivers/spi/spi-at91-usart.c +++ b/drivers/spi/spi-at91-usart.c @@ -231,11 +231,11 @@ static int at91_usart_spi_dma_transfer(struct spi_controller *ctlr, rxdesc->callback = dma_callback; rxdesc->callback_param = ctlr; - cookie = rxdesc->tx_submit(rxdesc); + cookie = dmaengine_submit(rxdesc); if (dma_submit_error(cookie)) goto at91_usart_spi_err_dma; - cookie = txdesc->tx_submit(txdesc); + cookie = dmaengine_submit(txdesc); if (dma_submit_error(cookie)) goto at91_usart_spi_err_dma; diff --git a/drivers/spi/spi-atmel.c b/drivers/spi/spi-atmel.c index c04113ce17f7..c107c610dc01 100644 --- a/drivers/spi/spi-atmel.c +++ b/drivers/spi/spi-atmel.c @@ -871,10 +871,10 @@ static int atmel_spi_next_xfer_dma_submit(struct spi_controller *host, rxdesc->callback_param = host; /* Submit and fire RX and TX with TX last so we're ready to read! */ - cookie = rxdesc->tx_submit(rxdesc); + cookie = dmaengine_submit(rxdesc); if (dma_submit_error(cookie)) goto err_dma; - cookie = txdesc->tx_submit(txdesc); + cookie = dmaengine_submit(txdesc); if (dma_submit_error(cookie)) goto err_dma; dma_async_issue_pending(rxchan); diff --git a/drivers/spi/spi-cadence-quadspi.c b/drivers/spi/spi-cadence-quadspi.c index fe5b094f0b6b..709aea0f237f 100644 --- a/drivers/spi/spi-cadence-quadspi.c +++ b/drivers/spi/spi-cadence-quadspi.c @@ -1398,7 +1398,7 @@ static int cqspi_direct_read_execute(struct cqspi_flash_pdata *f_pdata, tx->callback = cqspi_rx_dma_callback; tx->callback_param = cqspi; - cookie = tx->tx_submit(tx); + cookie = dmaengine_submit(tx); reinit_completion(&cqspi->rx_dma_complete); ret = dma_submit_error(cookie); diff --git a/drivers/spi/spi-ti-qspi.c b/drivers/spi/spi-ti-qspi.c index 34b154922ff2..24612e93eba1 100644 --- a/drivers/spi/spi-ti-qspi.c +++ b/drivers/spi/spi-ti-qspi.c @@ -454,7 +454,7 @@ static int ti_qspi_dma_xfer(struct ti_qspi *qspi, dma_addr_t dma_dst, tx->callback = ti_qspi_dma_callback; tx->callback_param = qspi; - cookie = tx->tx_submit(tx); + cookie = dmaengine_submit(tx); reinit_completion(&qspi->transfer_complete); ret = dma_submit_error(cookie); diff --git a/drivers/spi/spi-topcliff-pch.c b/drivers/spi/spi-topcliff-pch.c index 98723042c16f..eb3485149e75 100644 --- a/drivers/spi/spi-topcliff-pch.c +++ b/drivers/spi/spi-topcliff-pch.c @@ -1092,8 +1092,8 @@ static void pch_spi_handle_dma(struct pch_spi_data *data, int *bpw) spin_lock_irqsave(&data->lock, flags); pch_spi_writereg(data->host, PCH_SSNXCR, SSN_LOW); - desc_rx->tx_submit(desc_rx); - desc_tx->tx_submit(desc_tx); + dmaengine_submit(desc_rx); + dmaengine_submit(desc_tx); spin_unlock_irqrestore(&data->lock, flags); /* reset transfer complete flag */ From 37bc964c2da721eb5b96604529bb4f592e0e4c90 Mon Sep 17 00:00:00 2001 From: Changhuang Liang Date: Sat, 3 Oct 2026 09:18:07 -0700 Subject: [PATCH 093/136] spi: dw: Unconditionally stop DMA on errors dw_spi_dma_stop() and dw_spi_enh_mem_dma_stop() only terminate a DMA channel when the corresponding busy flag is set. They are only called on error paths, where the flags are not guaranteed to match the state of the DMA engine, so a channel may be left running. Terminate the channels and clear the busy flags unconditionally in dw_spi_dma_stop(). A channel may be missing when only one DMA channel is available, so check for that. With that the enhanced memory operation variant is no longer needed, use dw_spi_dma_stop() there as well. Suggested-by: Mark Brown Signed-off-by: Changhuang Liang Link: https://patch.msgid.link/20261003161807.59429-1-changhuang.liang@starfivetech.com Signed-off-by: Mark Brown --- drivers/spi/spi-dw-dma.c | 33 +++++++++++---------------------- 1 file changed, 11 insertions(+), 22 deletions(-) diff --git a/drivers/spi/spi-dw-dma.c b/drivers/spi/spi-dw-dma.c index a86039057988..b706770334a6 100644 --- a/drivers/spi/spi-dw-dma.c +++ b/drivers/spi/spi-dw-dma.c @@ -718,14 +718,18 @@ static int dw_spi_dma_transfer(struct dw_spi *dws, struct spi_transfer *xfer) static void dw_spi_dma_stop(struct dw_spi *dws) { - if (test_bit(DW_SPI_TX_BUSY, &dws->dma_chan_busy)) { + /* + * This is only called on errors, so don't rely on the busy flags + * being in sync with the DMA engine state. A channel may be missing + * when only one DMA channel is available. + */ + if (dws->txchan) dmaengine_terminate_sync(dws->txchan); - clear_bit(DW_SPI_TX_BUSY, &dws->dma_chan_busy); - } - if (test_bit(DW_SPI_RX_BUSY, &dws->dma_chan_busy)) { + if (dws->rxchan) dmaengine_terminate_sync(dws->rxchan); - clear_bit(DW_SPI_RX_BUSY, &dws->dma_chan_busy); - } + + clear_bit(DW_SPI_TX_BUSY, &dws->dma_chan_busy); + clear_bit(DW_SPI_RX_BUSY, &dws->dma_chan_busy); } /* @@ -844,21 +848,6 @@ static bool dw_spi_enh_mem_can_dma(struct spi_controller *ctlr) return dws->dma_addr_widths & BIT(dma_bus_width); } -static void dw_spi_enh_mem_dma_stop(struct dw_spi *dws) -{ - if (dws->tx_dir) { - if (test_bit(DW_SPI_TX_BUSY, &dws->dma_chan_busy)) { - dmaengine_terminate_sync(dws->txchan); - clear_bit(DW_SPI_TX_BUSY, &dws->dma_chan_busy); - } - } else { - if (test_bit(DW_SPI_RX_BUSY, &dws->dma_chan_busy)) { - dmaengine_terminate_sync(dws->rxchan); - clear_bit(DW_SPI_RX_BUSY, &dws->dma_chan_busy); - } - } -} - static int dw_spi_enh_mem_dma_transfer(struct spi_mem *mem, const struct spi_mem_op *op) { struct spi_controller *ctlr = mem->spi->controller; @@ -893,7 +882,7 @@ static int dw_spi_enh_mem_dma_transfer(struct spi_mem *mem, const struct spi_mem } if (ret) - dw_spi_enh_mem_dma_stop(dws); + dw_spi_dma_stop(dws); else ret = dw_spi_check_status(dws, true); From a143fa85c78d91ace08acc50c274adc38183a3b9 Mon Sep 17 00:00:00 2001 From: Yu-Chun Lin Date: Mon, 5 Oct 2026 11:05:04 +0800 Subject: [PATCH 094/136] spi: rtk-nor: Fix incorrect boolean return value The rtk_spi_supports_op() function is expected to return a boolean value. Fix this by returning false instead. Fixes: aca8cbecdd2e ("spi: spi-mem: Add Realtek SPI NOR flash controller driver") Reported-by: Dan Carpenter Closes: https://lore.kernel.org/lkml/20261002111238.zmU8TY9cdPo1jRPt_Xg1el9kj_ozynuVbJQ2-farWEs@z/ Signed-off-by: Yu-Chun Lin Link: https://patch.msgid.link/20261005030504.2651773-1-eleanor.lin@realtek.com Signed-off-by: Mark Brown --- drivers/spi/spi-rtk-nor.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/spi/spi-rtk-nor.c b/drivers/spi/spi-rtk-nor.c index 7f56346506b3..75856c31d2e6 100644 --- a/drivers/spi/spi-rtk-nor.c +++ b/drivers/spi/spi-rtk-nor.c @@ -344,7 +344,7 @@ static bool rtk_spi_supports_op(struct spi_mem *mem, * multi-byte stateless commands are not supported by this controller. */ if (!op->addr.nbytes && op->data.nbytes > 1) - return -EOPNOTSUPP; + return false; if (op->dummy.nbytes != 0) { if (op->dummy.buswidth > 1 || op->dummy.nbytes > 7) From 921ecc459d4f0b32bdcdb01316dc61f6bc55dc2b Mon Sep 17 00:00:00 2001 From: Chi-Wen Weng Date: Tue, 6 Oct 2026 14:36:43 +0800 Subject: [PATCH 095/136] spi: dt-bindings: nuvoton,ma35d1-qspi: Drop redundant SPI example The SPI and QSPI controllers use the same Devicetree node structure. The additional SPI example only differs in property values and does not demonstrate any binding-specific behavior. Drop the redundant SPI example and keep the existing QSPI example. Signed-off-by: Chi-Wen Weng Link: https://patch.msgid.link/20261006063643.198830-1-cwweng.linux@gmail.com Signed-off-by: Mark Brown --- .../bindings/spi/nuvoton,ma35d1-qspi.yaml | 20 ------------------- 1 file changed, 20 deletions(-) diff --git a/Documentation/devicetree/bindings/spi/nuvoton,ma35d1-qspi.yaml b/Documentation/devicetree/bindings/spi/nuvoton,ma35d1-qspi.yaml index 72f2e1e1219a..9ee61f8ac4d6 100644 --- a/Documentation/devicetree/bindings/spi/nuvoton,ma35d1-qspi.yaml +++ b/Documentation/devicetree/bindings/spi/nuvoton,ma35d1-qspi.yaml @@ -75,24 +75,4 @@ examples: }; }; - - | - #include - #include - #include - - soc { - #address-cells = <2>; - #size-cells = <2>; - - spi@40600000 { - compatible = "nuvoton,ma35d1-spi"; - reg = <0 0x40600000 0 0x10000>; - interrupts = ; - clocks = <&clk SPI0_GATE>; - resets = <&sys MA35D1_RESET_SPI0>; - - #address-cells = <1>; - #size-cells = <0>; - }; - }; ... From f3ce7a3ca82bd9cda9ef61250aeff65fe6b44809 Mon Sep 17 00:00:00 2001 From: Marc Kleine-Budde Date: Mon, 14 Sep 2026 17:35:55 +0200 Subject: [PATCH 096/136] spi: spi-sun4i: sun4i_spi_transfer_one(): fix transfers for SUN4I_FIFO_DEPTH In commit 6d9fe44bd73d ("spi: sun4i: fix FIFO limit"), the TX-FIFO is filled max to SUN4I_FIFO_DEPTH - 1 (= 63) bytes to work around timeouts observed on A10s SoCs. Commit 196737912da5 ("spi: sun4i: Allow transfers larger than FIFO size") added support for transfers larger than the FIFO size. But this commit only enabled the TX-FIFO empty interrupt for transfers larger than the FIFO (= 64) bytes. This breaks transfers with exactly 64 bytes: the TX-FIFO is only filled with 63 bytes but the interrupt to refill the FIFO is not triggered. The problem can be reproduced with the following command: | spidev_test -D /dev/spidev0.1 -S 64 -s 20000000 -I 1 | | [ 7797.548745] spi_master spi0: spi0.1: timeout transferring 64 bytes@20000000Hz for 110(100)ms | [ 7797.557237] spidev spi0.1: SPI transfer failed: -110 | [ 7797.562308] spi_master spi0: failed to transfer one message from queue | [ 7797.568936] spi_master spi0: noqueue transfer failed To fix the problem enable the TX-FIFO interrupt if the total TX length is larger than SUN4I_FIFO_DEPTH - 1 (= 63) bytes. Fixes: 196737912da5 ("spi: sun4i: Allow transfers larger than FIFO size") Signed-off-by: Marc Kleine-Budde Signed-off-by: Jonas Rebmann Link: https://patch.msgid.link/20260914-spi-sun4i-fixes-v3-1-9fe919d5cecf@pengutronix.de Signed-off-by: Mark Brown --- drivers/spi/spi-sun4i.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/spi/spi-sun4i.c b/drivers/spi/spi-sun4i.c index d5c16392cd4d..2e2324453905 100644 --- a/drivers/spi/spi-sun4i.c +++ b/drivers/spi/spi-sun4i.c @@ -322,7 +322,7 @@ static int sun4i_spi_transfer_one(struct spi_controller *host, sun4i_spi_enable_interrupt(sspi, SUN4I_INT_CTL_TC | SUN4I_INT_CTL_RF_F34); /* Only enable Tx FIFO interrupt if we really need it */ - if (tx_len > SUN4I_FIFO_DEPTH) + if (tx_len > SUN4I_FIFO_DEPTH - 1) sun4i_spi_enable_interrupt(sspi, SUN4I_INT_CTL_TF_E34); /* Start the transfer */ From ee109e2bbea0986f6ad34bfbcba90fce36f17538 Mon Sep 17 00:00:00 2001 From: Marc Kleine-Budde Date: Mon, 14 Sep 2026 17:35:56 +0200 Subject: [PATCH 097/136] spi: spi-sun4i: sun4i_spi_fill_fifo(): always apply A10 FIFO fill level workaround In commit 6d9fe44bd73d ("spi: sun4i: fix FIFO limit"), the TX FIFO is filled max to SUN4I_FIFO_DEPTH - 1 (= 63) bytes to work around timeouts observed on A10s SoCs. Commit 196737912da5 ("spi: sun4i: Allow transfers larger than FIFO size") added support for transfers larger than the FIFO size. This commit did not consider the A10 workaround (limit the TX FIFO fill size to SUN4I_FIFO_DEPTH - 1 (= 63) bytes) for refilling the TX FIFO in the IRQ handler. To apply the workaround independent from where sun4i_spi_fill_fifo() is called, remove the length argument from the function and directly take the max fill level of SUN4I_FIFO_DEPTH - 1 into account when calculating the free space in the FIFO. Due to the lack of HW this patch has not been tested on an A10 SoC, but on an A20 SoC. It was not possible to reproduce the timeout on the A20. Fixes: 196737912da5 ("spi: sun4i: Allow transfers larger than FIFO size") Signed-off-by: Marc Kleine-Budde Signed-off-by: Jonas Rebmann Link: https://patch.msgid.link/20260914-spi-sun4i-fixes-v3-2-9fe919d5cecf@pengutronix.de Signed-off-by: Mark Brown --- drivers/spi/spi-sun4i.c | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/drivers/spi/spi-sun4i.c b/drivers/spi/spi-sun4i.c index 2e2324453905..3649bcabcc9a 100644 --- a/drivers/spi/spi-sun4i.c +++ b/drivers/spi/spi-sun4i.c @@ -142,15 +142,21 @@ static inline void sun4i_spi_drain_fifo(struct sun4i_spi *sspi, int len) } } -static inline void sun4i_spi_fill_fifo(struct sun4i_spi *sspi, int len) +static inline void sun4i_spi_fill_fifo(struct sun4i_spi *sspi) { u32 cnt; + int len; u8 byte; - /* See how much data we can fit */ - cnt = SUN4I_FIFO_DEPTH - sun4i_spi_get_tx_fifo_count(sspi); + /* + * See how much data we can fit + * + * Filling the FIFO fully causes timeout for some reason + * at least on spi2 on A10s + */ + cnt = SUN4I_FIFO_DEPTH - 1 - sun4i_spi_get_tx_fifo_count(sspi); - len = min3(len, (int)cnt, sspi->len); + len = min_t(int, cnt, sspi->len); while (len--) { byte = sspi->tx_buf ? *sspi->tx_buf++ : 0; @@ -311,12 +317,8 @@ static int sun4i_spi_transfer_one(struct spi_controller *host, sun4i_spi_write(sspi, SUN4I_BURST_CNT_REG, SUN4I_BURST_CNT(tfr->len)); sun4i_spi_write(sspi, SUN4I_XMIT_CNT_REG, SUN4I_XMIT_CNT(tx_len)); - /* - * Fill the TX FIFO - * Filling the FIFO fully causes timeout for some reason - * at least on spi2 on A10s - */ - sun4i_spi_fill_fifo(sspi, SUN4I_FIFO_DEPTH - 1); + /* Fill the TX FIFO */ + sun4i_spi_fill_fifo(sspi); /* Enable the interrupts */ sun4i_spi_enable_interrupt(sspi, SUN4I_INT_CTL_TC | @@ -373,7 +375,7 @@ static irqreturn_t sun4i_spi_handler(int irq, void *dev_id) /* Transmit FIFO 3/4 empty */ if (status & SUN4I_INT_CTL_TF_E34) { - sun4i_spi_fill_fifo(sspi, SUN4I_FIFO_DEPTH); + sun4i_spi_fill_fifo(sspi); if (!sspi->len) /* nothing left to transmit */ From 672ba6d88324f223634dd2dc4098f6fb6b346b25 Mon Sep 17 00:00:00 2001 From: Marc Kleine-Budde Date: Mon, 14 Sep 2026 17:35:57 +0200 Subject: [PATCH 098/136] spi: spi-sun4i: sun4i_spi_transfer_one(): fix setting of clock rate A SPI transfer defines the _maximum_ speed of the SPI transfer. However the driver doesn't take into account that the clock divider is always rounded down (due to integer arithmetic). This results in a too high clock rate for the SPI transfer. E.g.: with an mclk_rate of 24 MHz and an SPI transfer speed of 10 MHz, the original code calculates a reg of "0", which results in an effective divider of "2" and a 12 MHz clock for the SPI transfer. Use DIV_ROUND_UP() instead of a plain integer division to fix the problem. While there simplify the divider calculation for the CDR1 case, use order_base_2() instead of two ilog2() calculations. Fixes: b5f6517948cc ("spi: sunxi: Add Allwinner A10 SPI controller driver") Signed-off-by: Marc Kleine-Budde Signed-off-by: Jonas Rebmann Link: https://patch.msgid.link/20260914-spi-sun4i-fixes-v3-3-9fe919d5cecf@pengutronix.de Signed-off-by: Mark Brown --- drivers/spi/spi-sun4i.c | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/drivers/spi/spi-sun4i.c b/drivers/spi/spi-sun4i.c index 3649bcabcc9a..8a9dcd3b6b8f 100644 --- a/drivers/spi/spi-sun4i.c +++ b/drivers/spi/spi-sun4i.c @@ -212,7 +212,7 @@ static int sun4i_spi_transfer_one(struct spi_controller *host, struct spi_transfer *tfr) { struct sun4i_spi *sspi = spi_controller_get_devdata(host); - unsigned int mclk_rate, div; + unsigned int mclk_rate, div, div_cdr1, div_cdr2; unsigned long time_left; unsigned int start, end, tx_time; unsigned int tx_len = 0; @@ -296,15 +296,13 @@ static int sun4i_spi_transfer_one(struct spi_controller *host, * First try CDR2, and if we can't reach the expected * frequency, fall back to CDR1. */ - div = mclk_rate / (2 * tfr->speed_hz); - if (div <= (SUN4I_CLK_CTL_CDR2_MASK + 1)) { - if (div > 0) - div--; - - reg = SUN4I_CLK_CTL_CDR2(div) | SUN4I_CLK_CTL_DRS; + div_cdr1 = DIV_ROUND_UP(mclk_rate, tfr->speed_hz); + div_cdr2 = DIV_ROUND_UP(div_cdr1, 2); + if (div_cdr2 <= (SUN4I_CLK_CTL_CDR2_MASK + 1)) { + reg = SUN4I_CLK_CTL_CDR2(div_cdr2 - 1) | SUN4I_CLK_CTL_DRS; } else { - div = ilog2(mclk_rate) - ilog2(tfr->speed_hz); - reg = SUN4I_CLK_CTL_CDR1(div); + div = min(SUN4I_CLK_CTL_CDR1_MASK + 1, order_base_2(div_cdr1)); + reg = SUN4I_CLK_CTL_CDR1(div - 1); } sun4i_spi_write(sspi, SUN4I_CLK_CTL_REG, reg); From b8f09251da7871fbb9c62bd83516af422641d816 Mon Sep 17 00:00:00 2001 From: Marc Kleine-Budde Date: Mon, 14 Sep 2026 17:35:58 +0200 Subject: [PATCH 099/136] spi: spi-sun4i: sun4i_spi_handler(): fix race condition between transfer completion and RX FIFO interrupt In commit 196737912da5 ("spi: sun4i: Allow transfers larger than FIFO size"), support for transfers larger than the FIFO size was added. This commit moves the draining of the RX-FIFO from sun4i_spi_transfer_one() (after completion of the transfer) to the IRQ handler when the IRQ "transfer complete" is handled. In addition, the IRQ "RX-FIFO 3/4 full" is activated for all transfers. However, this does not take into account that the RX-IRQ for transfers that exceed 3/4 of the FIFO size is still pending after the IRQ "transfer complete" has been processed. All interrupt sources are only deactivated after the wait_for_completion_timeout() in sun4i_spi_transfer_one(). This opens a race window for "RX-FIFO 3/4 full" interrupts to come. The sequence is as follows: | sun4i_spi_transfer_one() | sun4i_spi_fill_fifo() // fill TX-FIFO with 48 bytes | // enable RX-FIFO 3/4 full IRQ | wait_for_completion_timeout(); | | // SPI controller transfers 48 bytes | // SPI controller issues "transfer complete" and "RX-FIFO 3/4 full" IRQ | | // IRQ handler start | sun4i_spi_handler() | // ACK "transfer complete" IRQ | sun4i_spi_drain_fifo(); | complete(); ----. | return IRQ_HANDLED; \ | // IRQ handler end \__ race | / window | // wait_for_completion_timeout() continues / | // disable all IRQ sources ----' Avoid the race condition by disabling all interrupts when handling the "transfer complete" IRQ and before calling complete(). Also move the draining of the RX-FIFO back into sun4i_spi_transfer_one() where it was before commit 196737912da5 ("spi: sun4i: Allow transfers larger than FIFO size"). This has the added benefit of spending a little less time in the IRQ handler. With all interrupts disabled once the transfer completed, omit the clearing of the Transfer Complete bit in sun4i_spi_handler. This is safe because sun4i_spi_transfer_one() takes care of this before enabling any interrupts again. Cc: Tobias Schramm Fixes: 196737912da5 ("spi: sun4i: Allow transfers larger than FIFO size") Signed-off-by: Marc Kleine-Budde Signed-off-by: Jonas Rebmann Link: https://patch.msgid.link/20260914-spi-sun4i-fixes-v3-4-9fe919d5cecf@pengutronix.de Signed-off-by: Mark Brown --- drivers/spi/spi-sun4i.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/drivers/spi/spi-sun4i.c b/drivers/spi/spi-sun4i.c index 8a9dcd3b6b8f..ea8be0170fbf 100644 --- a/drivers/spi/spi-sun4i.c +++ b/drivers/spi/spi-sun4i.c @@ -82,6 +82,8 @@ struct sun4i_spi { struct completion done; + int irq; + const u8 *tx_buf; u8 *rx_buf; int len; @@ -333,6 +335,7 @@ static int sun4i_spi_transfer_one(struct spi_controller *host, start = jiffies; time_left = wait_for_completion_timeout(&sspi->done, msecs_to_jiffies(tx_time)); + end = jiffies; if (!time_left) { dev_warn(&host->dev, @@ -340,12 +343,11 @@ static int sun4i_spi_transfer_one(struct spi_controller *host, dev_name(&spi->dev), tfr->len, tfr->speed_hz, jiffies_to_msecs(end - start), tx_time); ret = -ETIMEDOUT; - goto out; + sun4i_spi_write(sspi, SUN4I_INT_CTL_REG, 0); + synchronize_irq(sspi->irq); } - -out: - sun4i_spi_write(sspi, SUN4I_INT_CTL_REG, 0); + sun4i_spi_drain_fifo(sspi, SUN4I_FIFO_DEPTH); return ret; } @@ -357,8 +359,7 @@ static irqreturn_t sun4i_spi_handler(int irq, void *dev_id) /* Transfer complete */ if (status & SUN4I_INT_CTL_TC) { - sun4i_spi_write(sspi, SUN4I_INT_STA_REG, SUN4I_INT_CTL_TC); - sun4i_spi_drain_fifo(sspi, SUN4I_FIFO_DEPTH); + sun4i_spi_write(sspi, SUN4I_INT_CTL_REG, 0); complete(&sspi->done); return IRQ_HANDLED; } @@ -456,6 +457,7 @@ static int sun4i_spi_probe(struct platform_device *pdev) return ret; } + sspi->irq = irq; sspi->host = host; host->max_speed_hz = 100 * 1000 * 1000; host->min_speed_hz = 3 * 1000; From a3df049084c7277ae55284d5d8a788b5495ffe46 Mon Sep 17 00:00:00 2001 From: Jonas Rebmann Date: Mon, 14 Sep 2026 17:35:59 +0200 Subject: [PATCH 100/136] spi: spi-sun4i: sun4i_spi_transfer_one(): add error handling at clock setup Bail out if clk_get_rate() or clk_set_rate() fail during sun4i_spi_transfer_one(). This might happen if the clock isn't known yet or fails to set the requested rate. This also ensures that mclk_rate is nonzero and can be safely divided by in a future commit. Signed-off-by: Jonas Rebmann Link: https://patch.msgid.link/20260914-spi-sun4i-fixes-v3-5-9fe919d5cecf@pengutronix.de Signed-off-by: Mark Brown --- drivers/spi/spi-sun4i.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/drivers/spi/spi-sun4i.c b/drivers/spi/spi-sun4i.c index ea8be0170fbf..94e1c7bbd632 100644 --- a/drivers/spi/spi-sun4i.c +++ b/drivers/spi/spi-sun4i.c @@ -279,9 +279,17 @@ static int sun4i_spi_transfer_one(struct spi_controller *host, /* Ensure that we have a parent clock fast enough */ mclk_rate = clk_get_rate(sspi->mclk); + if (!mclk_rate) + return -EINVAL; + if (mclk_rate < (2 * tfr->speed_hz)) { - clk_set_rate(sspi->mclk, 2 * tfr->speed_hz); + ret = clk_set_rate(sspi->mclk, 2 * tfr->speed_hz); + if (ret) + return ret; + mclk_rate = clk_get_rate(sspi->mclk); + if (!mclk_rate) + return -EINVAL; } /* From b42ff8dc7e540a50d71eb5a6ed0a7b6d094dcae9 Mon Sep 17 00:00:00 2001 From: Marc Kleine-Budde Date: Mon, 14 Sep 2026 17:36:00 +0200 Subject: [PATCH 101/136] spi: spi-sun4i: sun4i_spi_transfer_one(): report effectively used speed_hz of transfer Report the effectively used speed_hz for the transfer by setting tfr->effective_speed_hz. For more information see commit 5d7e2b5ed585 ("spi: core: allow reporting the effectivly used speed_hz for a transfer"), which adds this feature to the SPI core. Signed-off-by: Marc Kleine-Budde Signed-off-by: Jonas Rebmann Link: https://patch.msgid.link/20260914-spi-sun4i-fixes-v3-6-9fe919d5cecf@pengutronix.de Signed-off-by: Mark Brown --- drivers/spi/spi-sun4i.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/spi/spi-sun4i.c b/drivers/spi/spi-sun4i.c index 94e1c7bbd632..59941b38b757 100644 --- a/drivers/spi/spi-sun4i.c +++ b/drivers/spi/spi-sun4i.c @@ -310,9 +310,11 @@ static int sun4i_spi_transfer_one(struct spi_controller *host, div_cdr2 = DIV_ROUND_UP(div_cdr1, 2); if (div_cdr2 <= (SUN4I_CLK_CTL_CDR2_MASK + 1)) { reg = SUN4I_CLK_CTL_CDR2(div_cdr2 - 1) | SUN4I_CLK_CTL_DRS; + tfr->effective_speed_hz = mclk_rate / (2 * div_cdr2); } else { div = min(SUN4I_CLK_CTL_CDR1_MASK + 1, order_base_2(div_cdr1)); reg = SUN4I_CLK_CTL_CDR1(div - 1); + tfr->effective_speed_hz = mclk_rate / (1 << div); } sun4i_spi_write(sspi, SUN4I_CLK_CTL_REG, reg); From 0c5b0c3eb1d6daa1117dacd32589196f52109cab Mon Sep 17 00:00:00 2001 From: Pin-Hao Chen Date: Tue, 6 Oct 2026 06:05:28 +0800 Subject: [PATCH 102/136] spi: stm32: fix double free of SRAM buffer on MDMA fallback When the "rxm2m" MDMA channel cannot be obtained, the driver frees the SRAM buffer and falls back to DMA-only mode. However, sram_rx_buf is left pointing to the freed buffer. As a result, stm32_spi_unprepare_msg() may clear the released SRAM area, while stm32_spi_remove() or a later probe error path may free the buffer again, triggering BUG_ON() in gen_pool_free_owner(). The probe error path also checks sram_pool instead of sram_rx_buf. If the SRAM allocation failed and a later probe step fails, gen_pool_free() may therefore be called with an unallocated buffer. Clear sram_rx_buf after freeing it on the MDMA fallback path and use the buffer pointer to determine whether cleanup is needed. Fixes: d17dd2f1d8a1 ("spi: stm32: use STM32 DMA with STM32 MDMA to enhance DDR use") Cc: stable@vger.kernel.org Signed-off-by: Pin-Hao Chen Link: https://patch.msgid.link/20261005220528.7199-1-billy920225@gmail.com Signed-off-by: Mark Brown --- drivers/spi/spi-stm32.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/spi/spi-stm32.c b/drivers/spi/spi-stm32.c index 5f5c43ae5096..4c3ae546afc0 100644 --- a/drivers/spi/spi-stm32.c +++ b/drivers/spi/spi-stm32.c @@ -2511,6 +2511,7 @@ static int stm32_spi_probe(struct platform_device *pdev) gen_pool_free(spi->sram_pool, (unsigned long)spi->sram_rx_buf, spi->sram_rx_buf_size); + spi->sram_rx_buf = NULL; dev_warn(&pdev->dev, "failed to request rx mdma channel, DMA only\n"); } @@ -2548,7 +2549,7 @@ static int stm32_spi_probe(struct platform_device *pdev) if (spi->mdma_rx) dma_release_channel(spi->mdma_rx); err_pool_free: - if (spi->sram_pool) + if (spi->sram_rx_buf) gen_pool_free(spi->sram_pool, (unsigned long)spi->sram_rx_buf, spi->sram_rx_buf_size); if (spi->dma_rx) From 306439eed9eaa060126e0c2804f8cbeb28aaa7a8 Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Sat, 10 Oct 2026 23:14:59 +0800 Subject: [PATCH 103/136] spi: dw: Make dw_spi_update_config() After commit da0a672268b3 ("spi: dw: Remove not-going-to-be-supported code for Baikal SoC"), no users of dw_spi_update_config() any more. Unexport it and make it static. We can also move two structure definitions to spi-dw-core.c at the same time. Signed-off-by: Jisheng Zhang Link: https://patch.msgid.link/20261010151459.30560-1-jszhang@kernel.org Signed-off-by: Mark Brown --- drivers/spi/spi-dw-core.c | 21 ++++++++++++++++++--- drivers/spi/spi-dw.h | 18 ------------------ 2 files changed, 18 insertions(+), 21 deletions(-) diff --git a/drivers/spi/spi-dw-core.c b/drivers/spi/spi-dw-core.c index 1f0e93800908..2d671e31413e 100644 --- a/drivers/spi/spi-dw-core.c +++ b/drivers/spi/spi-dw-core.c @@ -32,6 +32,22 @@ struct dw_spi_chip_data { u32 rx_sample_dly; /* RX sample delay */ }; +/* Slave spi_transfer/spi_mem_op related */ +struct dw_spi_cfg { + u8 tmode; + u8 dfs; + u32 ndf; + u32 freq; + u8 spi_frf; +}; + +struct dw_spi_enh_cfg { + u8 wait_c; + u8 inst_l; + u8 addr_l; + u8 trans_t; +}; + #ifdef CONFIG_DEBUG_FS #define DW_SPI_DBGFS_REG(_name, _off) \ @@ -342,8 +358,8 @@ static u32 dw_spi_prepare_cr0(struct dw_spi *dws, struct spi_device *spi) return cr0; } -void dw_spi_update_config(struct dw_spi *dws, struct spi_device *spi, - struct dw_spi_cfg *cfg, struct dw_spi_enh_cfg *enh_cfg) +static void dw_spi_update_config(struct dw_spi *dws, struct spi_device *spi, + 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; @@ -406,7 +422,6 @@ void dw_spi_update_config(struct dw_spi *dws, struct spi_device *spi, dw_writel(dws, DW_SPI_SPI_CTRLR0, cr0); } } -EXPORT_SYMBOL_NS_GPL(dw_spi_update_config, "SPI_DW_CORE"); static void dw_spi_irq_setup(struct dw_spi *dws) { diff --git a/drivers/spi/spi-dw.h b/drivers/spi/spi-dw.h index bfdf371bcacb..c16225c5c887 100644 --- a/drivers/spi/spi-dw.h +++ b/drivers/spi/spi-dw.h @@ -158,22 +158,6 @@ #define DW_SPI_GET_BYTE(_val, _idx) \ ((_val) >> (BITS_PER_BYTE * (_idx)) & 0xff) -/* Slave spi_transfer/spi_mem_op related */ -struct dw_spi_cfg { - u8 tmode; - u8 dfs; - u32 ndf; - u32 freq; - 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); @@ -345,8 +329,6 @@ 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_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 516d487c603754466aad03ebcffcd8928b40f179 Mon Sep 17 00:00:00 2001 From: Aayush Kumar Date: Fri, 9 Oct 2026 19:06:30 +0530 Subject: [PATCH 104/136] spi: bcm63xx-hsspi: Use sysfs_emit() instead of sprintf() show() should only use sysfs_emit() or sysfs_emit_at() when formatting the value to be returned to user space. Signed-off-by: Aayush Kumar Link: https://patch.msgid.link/20261009133631.579644-2-code@aayushk.dev Signed-off-by: Mark Brown --- drivers/spi/spi-bcm63xx-hsspi.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/spi/spi-bcm63xx-hsspi.c b/drivers/spi/spi-bcm63xx-hsspi.c index 25f169a2a81d..824e36bedc76 100644 --- a/drivers/spi/spi-bcm63xx-hsspi.c +++ b/drivers/spi/spi-bcm63xx-hsspi.c @@ -152,7 +152,7 @@ static ssize_t wait_mode_show(struct device *dev, struct device_attribute *attr, struct spi_controller *ctrl = dev_get_drvdata(dev); struct bcm63xx_hsspi *bs = spi_controller_get_devdata(ctrl); - return sprintf(buf, "%d\n", bs->wait_mode); + return sysfs_emit(buf, "%d\n", bs->wait_mode); } static ssize_t wait_mode_store(struct device *dev, struct device_attribute *attr, @@ -188,7 +188,7 @@ static ssize_t xfer_mode_show(struct device *dev, struct device_attribute *attr, struct spi_controller *ctrl = dev_get_drvdata(dev); struct bcm63xx_hsspi *bs = spi_controller_get_devdata(ctrl); - return sprintf(buf, "%d\n", bs->xfer_mode); + return sysfs_emit(buf, "%d\n", bs->xfer_mode); } static ssize_t xfer_mode_store(struct device *dev, struct device_attribute *attr, From 2bc8762642593628ecd26955617441762c972a0a Mon Sep 17 00:00:00 2001 From: Aayush Kumar Date: Fri, 9 Oct 2026 19:06:31 +0530 Subject: [PATCH 105/136] spi: bcmbca-hsspi: Use sysfs_emit() instead of sprintf() show() should only use sysfs_emit() or sysfs_emit_at() when formatting the value to be returned to user space. Signed-off-by: Aayush Kumar Link: https://patch.msgid.link/20261009133631.579644-3-code@aayushk.dev Signed-off-by: Mark Brown --- drivers/spi/spi-bcmbca-hsspi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/spi/spi-bcmbca-hsspi.c b/drivers/spi/spi-bcmbca-hsspi.c index 5dc9e0f212ba..8c01475886e8 100644 --- a/drivers/spi/spi-bcmbca-hsspi.c +++ b/drivers/spi/spi-bcmbca-hsspi.c @@ -129,7 +129,7 @@ static ssize_t wait_mode_show(struct device *dev, struct device_attribute *attr, struct spi_controller *ctrl = dev_get_drvdata(dev); struct bcmbca_hsspi *bs = spi_controller_get_devdata(ctrl); - return sprintf(buf, "%d\n", bs->wait_mode); + return sysfs_emit(buf, "%d\n", bs->wait_mode); } static ssize_t wait_mode_store(struct device *dev, struct device_attribute *attr, From 54f4db17f9d335bf2ed84c70906b95487d0ef1a0 Mon Sep 17 00:00:00 2001 From: Aayush Kumar Date: Sat, 10 Oct 2026 14:05:02 +0530 Subject: [PATCH 106/136] spi: tle62x0: Use kstrtoul() instead of simple_strtoul() Use the recommended kstrtoul() instead of the deprecated simple_strtoul(). Check the return value and reject invalid values. Signed-off-by: Aayush Kumar Link: https://patch.msgid.link/20261010083504.26261-2-code@aayushk.dev Signed-off-by: Mark Brown --- drivers/spi/spi-tle62x0.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/spi/spi-tle62x0.c b/drivers/spi/spi-tle62x0.c index dd8bfb3f3544..986d78ab457d 100644 --- a/drivers/spi/spi-tle62x0.c +++ b/drivers/spi/spi-tle62x0.c @@ -151,10 +151,10 @@ static ssize_t tle62x0_gpio_store(struct device *dev, struct tle62x0_state *st = dev_get_drvdata(dev); int gpio_num = to_gpio_num(attr); unsigned long val; - char *endp; + int ret; - val = simple_strtoul(buf, &endp, 0); - if (buf == endp) + ret = kstrtoul(buf, 0, &val); + if (ret) return -EINVAL; dev_dbg(dev, "setting gpio %d to %ld\n", gpio_num, val); From 9efc388655e4abc428368b6d5f47ddc051539faf Mon Sep 17 00:00:00 2001 From: Aayush Kumar Date: Sat, 10 Oct 2026 14:05:03 +0530 Subject: [PATCH 107/136] spi: omap2-mcspi: Use snprintf() instead of sprintf() Use snprintf() instead of sprintf() to avoid potential buffer overflows when formatting DMA channel names. Signed-off-by: Aayush Kumar Link: https://patch.msgid.link/20261010083504.26261-3-code@aayushk.dev Signed-off-by: Mark Brown --- drivers/spi/spi-omap2-mcspi.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/spi/spi-omap2-mcspi.c b/drivers/spi/spi-omap2-mcspi.c index a3355147f5de..311ee0ca94f6 100644 --- a/drivers/spi/spi-omap2-mcspi.c +++ b/drivers/spi/spi-omap2-mcspi.c @@ -1544,8 +1544,10 @@ static int omap2_mcspi_probe(struct platform_device *pdev) return -ENOMEM; for (i = 0; i < ctlr->num_chipselect; i++) { - sprintf(mcspi->dma_channels[i].dma_rx_ch_name, "rx%d", i); - sprintf(mcspi->dma_channels[i].dma_tx_ch_name, "tx%d", i); + snprintf(mcspi->dma_channels[i].dma_rx_ch_name, + sizeof(mcspi->dma_channels[i].dma_rx_ch_name), "rx%d", i); + snprintf(mcspi->dma_channels[i].dma_tx_ch_name, + sizeof(mcspi->dma_channels[i].dma_tx_ch_name), "tx%d", i); status = omap2_mcspi_request_dma(mcspi, &mcspi->dma_channels[i]); From 506fbac9274c0cfd3dc1b529ab5be0ddf9085e34 Mon Sep 17 00:00:00 2001 From: Aayush Kumar Date: Sat, 10 Oct 2026 14:05:04 +0530 Subject: [PATCH 108/136] spi: s3c64xx: Use snprintf() instead of sprintf() Use snprintf() instead of sprintf() to avoid potential buffer overflows when formatting the clock name. Signed-off-by: Aayush Kumar Link: https://patch.msgid.link/20261010083504.26261-4-code@aayushk.dev Signed-off-by: Mark Brown --- drivers/spi/spi-s3c64xx.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/spi/spi-s3c64xx.c b/drivers/spi/spi-s3c64xx.c index 8d4120cf9df1..8195bf376bfe 100644 --- a/drivers/spi/spi-s3c64xx.c +++ b/drivers/spi/spi-s3c64xx.c @@ -1331,7 +1331,8 @@ static int s3c64xx_spi_probe(struct platform_device *pdev) return dev_err_probe(&pdev->dev, PTR_ERR(sdd->clk), "Unable to acquire clock 'spi'\n"); - sprintf(clk_name, "spi_busclk%d", sci->src_clk_nr); + snprintf(clk_name, sizeof(clk_name), "spi_busclk%d", + sci->src_clk_nr); sdd->src_clk = devm_clk_get_enabled(&pdev->dev, clk_name); if (IS_ERR(sdd->src_clk)) return dev_err_probe(&pdev->dev, PTR_ERR(sdd->src_clk), From 2da46f1e9b980a38bc6afd417322ca4963725e16 Mon Sep 17 00:00:00 2001 From: Gabor Juhos Date: Mon, 5 Oct 2026 19:18:50 +0200 Subject: [PATCH 109/136] spi: spi-qpic-snand: skip BBM copy on regular raw reads Trying to read the raw flash contents with nanddump, the second byte of the OOB area contains 0xff instead of the real data in the flash. This happens because the qcom_spi_read_last_cw() function unconditionally copies the bad block marker into the second byte of the output buffer. Add a new flag to the controller's private data structure, and set it when only the first two bytes of the OOB area being read in raw mode. Also change the bad block copying code to run only when the flag is set. This ensures that the SPI NAND framework still can read the BBM bytes, but on regular raw read, the output does not contain the duplicated marker byte. Fixes: 7304d1909080 ("spi: spi-qpic: add driver for QCOM SPI NAND flash Interface") Signed-off-by: Gabor Juhos Link: https://patch.msgid.link/20261005-qpic-snand-skip-bbm-copy-v1-1-04f6aeda4044@gmail.com Signed-off-by: Mark Brown --- drivers/spi/spi-qpic-snand.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/drivers/spi/spi-qpic-snand.c b/drivers/spi/spi-qpic-snand.c index 001e4bd8bb4b..c90c2001ee8d 100644 --- a/drivers/spi/spi-qpic-snand.c +++ b/drivers/spi/spi-qpic-snand.c @@ -115,6 +115,7 @@ struct qpic_spi_nand { __le32 addr2; __le32 cmd; u32 num_cw; + bool bbm_read; bool oob_rw; bool page_rw; bool raw_rw; @@ -457,6 +458,16 @@ static int qcom_spi_ecc_prepare_io_req_pipelined(struct nand_device *nand, if (req->mode == MTD_OPS_RAW) snandc->qspi->raw_rw = true; + /* + * Try to detect when the SPI NAND framework reads the first + * two bytes of the OOB area searching for bad block marker + * bytes. + */ + snandc->qspi->bbm_read = req->mode == MTD_OPS_RAW && + req->type == NAND_PAGE_READ && + req->dataoffs == 0 && req->datalen == 0 && + req->ooboffs == 0 && req->ooblen == 2; + return 0; } @@ -666,7 +677,8 @@ static int qcom_spi_read_last_cw(struct qcom_nand_controller *snandc, * This can be removed once single-byte bad block marker support * gets implemented in the SPINAND code. */ - snandc->data_buffer[bbpos + 1] = snandc->data_buffer[bbpos]; + if (snandc->qspi->bbm_read) + snandc->data_buffer[bbpos + 1] = snandc->data_buffer[bbpos]; memcpy(op->data.buf.in, snandc->data_buffer + bbpos, op->data.nbytes); From 3e3d3f96213dbc530a122b2e66a65859b2a94d2a Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Mon, 5 Oct 2026 23:17:36 +0800 Subject: [PATCH 110/136] spi: dw: remove useless dws->transfer_handler check transfer_handler is initialized before any DW SPI interrupt is unmasked. While the IRQ is registered but the handler is NULL, dw_spi_hw_init() has all DW SPI interrupts disabled. Therefore dw_spi_irq() cannot observe a DW SPI interrupt with a NULL transfer_handler. Signed-off-by: Jisheng Zhang Link: https://patch.msgid.link/20261005151739.25233-2-jszhang@kernel.org Signed-off-by: Mark Brown --- drivers/spi/spi-dw-core.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/spi/spi-dw-core.c b/drivers/spi/spi-dw-core.c index 206d3f9dd83d..d5c82e3e0b14 100644 --- a/drivers/spi/spi-dw-core.c +++ b/drivers/spi/spi-dw-core.c @@ -280,8 +280,7 @@ static irqreturn_t dw_spi_irq(int irq, void *dev_id) if (!irq_status) return IRQ_NONE; - if (!dws->transfer_handler || - (!ctlr->cur_msg && dws->transfer_handler == dw_spi_transfer_handler)) { + if (!ctlr->cur_msg && dws->transfer_handler == dw_spi_transfer_handler) { dw_spi_mask_intr(dws, 0xff); return IRQ_HANDLED; } From ab30c5cdf45777a72f1f7facb03bfce3c1742e38 Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Mon, 5 Oct 2026 23:17:37 +0800 Subject: [PATCH 111/136] spi: dw: remove duplicated "!rx_len && !tx_len" handling from dw_spi_irq dw_spi_irq() need not handle enhanced-transfer's "!rx_len && !tx_len" case as that state is already handled by dw_spi_enh_handler() Signed-off-by: Jisheng Zhang Reviewed-by: Changhuang Liang Link: https://patch.msgid.link/20261005151739.25233-3-jszhang@kernel.org Signed-off-by: Mark Brown --- drivers/spi/spi-dw-core.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/drivers/spi/spi-dw-core.c b/drivers/spi/spi-dw-core.c index d5c82e3e0b14..32ed2618e5d2 100644 --- a/drivers/spi/spi-dw-core.c +++ b/drivers/spi/spi-dw-core.c @@ -284,12 +284,6 @@ static irqreturn_t dw_spi_irq(int irq, void *dev_id) 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); } From 62d2ce4ee2f26029eef69b8b853edc5c60d4e53e Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Mon, 5 Oct 2026 23:17:38 +0800 Subject: [PATCH 112/136] spi: dw: restore previous irq handling behavior when !ctlr->cur_msg Recent "enhanced" support changes the dw_spi_irq() behavior a bit when !!ctlr->cur_msg: if (!ctlr->cur_msg && dws->transfer_handler == dw_spi_transfer_handler) { dw_spi_mask_intr(dws, 0xff); return IRQ_HANDLED; } But it misses the dma case, where the transfer_handler == dw_spi_dma_transfer_handler, so this changes the previous long time working behavior, let's restore the previous handling by only filtering out the dw_spi_enh_handler. Signed-off-by: Jisheng Zhang Reviewed-by: Changhuang Liang Link: https://patch.msgid.link/20261005151739.25233-4-jszhang@kernel.org Signed-off-by: Mark Brown --- drivers/spi/spi-dw-core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/spi/spi-dw-core.c b/drivers/spi/spi-dw-core.c index 32ed2618e5d2..6a50d6d600df 100644 --- a/drivers/spi/spi-dw-core.c +++ b/drivers/spi/spi-dw-core.c @@ -280,7 +280,7 @@ static irqreturn_t dw_spi_irq(int irq, void *dev_id) if (!irq_status) return IRQ_NONE; - if (!ctlr->cur_msg && dws->transfer_handler == dw_spi_transfer_handler) { + if (!ctlr->cur_msg && dws->transfer_handler != dw_spi_enh_handler) { dw_spi_mask_intr(dws, 0xff); return IRQ_HANDLED; } From d9d0f5b84cc3d05e89d78a5a5440e4f3d9c5e61b Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Mon, 5 Oct 2026 23:17:39 +0800 Subject: [PATCH 113/136] spi: dw: use threaded interrupt and optimize the threaded ISR To avoid blocking for an excessive amount of time, eventually impacting on system responsiveness, hard interrupt handlers should finish executing in as little time as possible. Use threaded interrupt and move the SPI transfer handling to an interrupt thread when non-native CS and host only. After that, since the dw_reader() and dw_writer() are called in threaded ISR now, so we can delay the unmasking interrupts until no rx and tx action is taken, thus reduce the interrupt numbers further. Tested with below two cmds ./spidev_test -D /dev/spidev1.3 -s 30000000 -S 327680 -I 1 ./spidev_test -D /dev/spidev1.3 -s 30000000 -S 327680 -I 1000 ./rtla timerlat top -q -k -P f:95 The first cmd is to check the interrupt numbers optmizaion result, the 2nd cmd group is to check the threaded interrupt improvement. Before the patch: each 320KB spi spidev_test transfer triggers 33118 interrupts spidev_test reports ~22090 kbps and rtla reports: Timer Latency 0 00:00:37 | IRQ Timer Latency (us) | Thread Timer Latency (us) CPU COUNT | cur min avg max | cur min avg max 0 #9958 | 1 0 67 103394 | 6 4 2198 105031 1 #36902 | 1 0 1 18 | 5 4 5 29 After the patch: each 320KB spi spidev_test transfer only triggers 1 interrupts spidev_test reports ~23520 kbps and now rtla reports: Timer Latency 0 00:00:58 | IRQ Timer Latency (us) | Thread Timer Latency (us) CPU COUNT | cur min avg max | cur min avg max 0 #58362 | 1 0 0 29 | 6 3 4 56 1 #58363 | 1 0 1 23 | 6 4 5 68 In summary: before the patch after the patch 33118 interrutps 1 interrupts reduced by 33117 times! 103394 us max latency 29 us max latency reduced by 3564 times! 22090 kbps rate 23520 kbps rate improved by 6.5% Signed-off-by: Jisheng Zhang Link: https://patch.msgid.link/20261005151739.25233-5-jszhang@kernel.org Signed-off-by: Mark Brown --- drivers/spi/spi-dw-core.c | 70 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 65 insertions(+), 5 deletions(-) diff --git a/drivers/spi/spi-dw-core.c b/drivers/spi/spi-dw-core.c index 6a50d6d600df..fb94e7e57544 100644 --- a/drivers/spi/spi-dw-core.c +++ b/drivers/spi/spi-dw-core.c @@ -132,10 +132,11 @@ static inline u32 dw_spi_rx_max(struct dw_spi *dws) return min_t(u32, dws->rx_len, dw_readl(dws, DW_SPI_RXFLR)); } -static void dw_writer(struct dw_spi *dws) +static u32 dw_writer(struct dw_spi *dws) { u32 max = dw_spi_tx_max(dws); u32 txw = 0; + u32 tx = 0; while (max--) { if (dws->tx) { @@ -150,13 +151,16 @@ static void dw_writer(struct dw_spi *dws) } dw_write_io_reg(dws, DW_SPI_DR, txw); --dws->tx_len; + ++tx; } + return tx; } -static void dw_reader(struct dw_spi *dws) +static u32 dw_reader(struct dw_spi *dws) { u32 max = dw_spi_rx_max(dws); u32 rxw; + u32 rx = 0; while (max--) { rxw = dw_read_io_reg(dws, DW_SPI_DR); @@ -171,7 +175,9 @@ static void dw_reader(struct dw_spi *dws) dws->rx += dws->n_bytes; } --dws->rx_len; + ++rx; } + return rx; } int dw_spi_check_status(struct dw_spi *dws, bool raw) @@ -210,9 +216,55 @@ int dw_spi_check_status(struct dw_spi *dws, bool raw) } EXPORT_SYMBOL_NS_GPL(dw_spi_check_status, "SPI_DW_CORE"); +static irqreturn_t dw_spi_irq_thread_fn(int irq, void *dev_id) +{ + struct spi_controller *ctlr = dev_id; + struct dw_spi *dws = spi_controller_get_devdata(ctlr); + u32 rx, tx, imask, mask = 0; + bool finalize = false; + + do { + /* + * Read data from the Rx FIFO every time we've got a chance executing + * this method. If there is nothing left to receive, terminate the + * procedure. Otherwise adjust the Rx FIFO Threshold level if it's a + * final stage of the transfer. By doing so we'll get the next IRQ + * right when the leftover incoming data is received. + */ + rx = dw_reader(dws); + if (!dws->rx_len) { + mask |= 0xff; + finalize = true; + } else if (dws->rx_len <= dw_readl(dws, DW_SPI_RXFTLR)) { + dw_writel(dws, DW_SPI_RXFTLR, dws->rx_len - 1); + } + + /* + * Send data out as much as possible. The Tx FIFO Empty IRQ will be + * disabled after the data transmission is finished so not to + * have the TXE IRQ flood at the final stage of the transfer. + */ + tx = dw_writer(dws); + if (!dws->tx_len) + mask |= DW_SPI_INT_TXEI; + cond_resched(); + } while (rx != 0 || tx != 0); + + imask = DW_SPI_INT_TXEI | DW_SPI_INT_TXOI | + DW_SPI_INT_RXUI | DW_SPI_INT_RXOI | DW_SPI_INT_RXFI; + imask &= ~mask; + dw_spi_umask_intr(dws, imask); + + if (finalize) + spi_finalize_current_transfer(dws->ctlr); + + return IRQ_HANDLED; +} + static irqreturn_t dw_spi_transfer_handler(struct dw_spi *dws) { u16 irq_status = dw_readl(dws, DW_SPI_ISR); + bool finalize = false; if (dw_spi_check_status(dws, false)) { spi_finalize_current_transfer(dws->ctlr); @@ -229,6 +281,7 @@ static irqreturn_t dw_spi_transfer_handler(struct dw_spi *dws) dw_reader(dws); if (!dws->rx_len) { dw_spi_mask_intr(dws, 0xff); + finalize = true; spi_finalize_current_transfer(dws->ctlr); } else if (dws->rx_len <= dw_readl(dws, DW_SPI_RXFTLR)) { dw_writel(dws, DW_SPI_RXFTLR, dws->rx_len - 1); @@ -245,7 +298,14 @@ static irqreturn_t dw_spi_transfer_handler(struct dw_spi *dws) dw_spi_mask_intr(dws, DW_SPI_INT_TXEI); } - return IRQ_HANDLED; + if (spi_controller_is_target(dws->ctlr) || finalize || + !dws->rx_len || !dws->tx_len || + !spi_get_csgpiod(dws->ctlr->cur_msg->spi, 0)) { + return IRQ_HANDLED; + } + + dw_spi_mask_intr(dws, 0xff); + return IRQ_WAKE_THREAD; } static irqreturn_t dw_spi_enh_handler(struct dw_spi *dws) @@ -1305,8 +1365,8 @@ int dw_spi_add_controller(struct device *dev, struct dw_spi *dws) /* Basic HW init */ dw_spi_hw_init(dev, dws); - ret = request_irq(dws->irq, dw_spi_irq, IRQF_SHARED, dev_name(dev), - ctlr); + ret = request_threaded_irq(dws->irq, dw_spi_irq, dw_spi_irq_thread_fn, + IRQF_SHARED, dev_name(dev), ctlr); if (ret < 0 && ret != -ENOTCONN) { dev_err(dev, "can not request IRQ\n"); goto err_free_ctlr; From ea29c686f96adcb505071ef228ba8047dc951fac Mon Sep 17 00:00:00 2001 From: Jonathan Santos Date: Wed, 7 Oct 2026 05:52:26 -0300 Subject: [PATCH 114/136] spi: axi-spi-engine: fix stale SYNC IRQ pending MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit spi_engine_setup() sends a SYNC(1) command and polls SYNC_ID to confirm it was parsed by the FPGA, but never clears the corresponding interrupt pending bit (INT_PENDING[SYNC]). When the first real SPI transfer starts and INT_SYNC is enabled, that stale pending bit fires immediately, causing the IRQ handler to see the leftover SYNC_ID from setup, match it against the current transfer's ID, and prematurely signal completion before the hardware finishes. This race manifests at low SPI clock frequencies (~2-3 MHz), where the FPGA takes long enough to execute the transfer that handler is parsed before it finishes. At higher SCLK rates the transfer completes fast enough that the issue is masked. Fix this by clearing INT_PENDING[SYNC] after the polled SYNC, ensuring no stale interrupt is left pending. Reported-by: Dennis Heinzel Closes: https://ez.analog.com/linux-software-drivers/f/q-a/604145/axi-spi-engine-stale-sync-pending-can-complete-first-transfer-early-at-low-spi-clock-2-3-mhz Fixes: 1d0ee0c9df31 ("spi: axi-spi-engine: wait for completion in setup") Reviewed-by: Nuno Sá Signed-off-by: Jonathan Santos Link: https://patch.msgid.link/d3033a01a695c915e4dc5952dfe0587b2f77d733.1791361360.git.Jonathan.Santos@analog.com Signed-off-by: Mark Brown --- drivers/spi/spi-axi-spi-engine.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/drivers/spi/spi-axi-spi-engine.c b/drivers/spi/spi-axi-spi-engine.c index 02bbc5d0cfc5..87c05cb064cc 100644 --- a/drivers/spi/spi-axi-spi-engine.c +++ b/drivers/spi/spi-axi-spi-engine.c @@ -887,6 +887,7 @@ static int spi_engine_setup(struct spi_device *device) struct spi_controller *host = device->controller; struct spi_engine *spi_engine = spi_controller_get_devdata(host); unsigned int reg; + int ret; if (device->mode & SPI_CS_HIGH) spi_engine->cs_inv |= BIT(spi_get_chipselect(device, 0)); @@ -922,8 +923,15 @@ static int spi_engine_setup(struct spi_device *device) writel_relaxed(SPI_ENGINE_CMD_SYNC(1), spi_engine->base + SPI_ENGINE_REG_CMD_FIFO); - return readl_relaxed_poll_timeout(spi_engine->base + SPI_ENGINE_REG_SYNC_ID, - reg, reg == 1, 1, 1000); + ret = readl_relaxed_poll_timeout(spi_engine->base + SPI_ENGINE_REG_SYNC_ID, + reg, reg == 1, 1, 1 * USEC_PER_MSEC); + + /* Clear the stale SYNC pending bit so it doesn't fire when the IRQ is later enabled. */ + writel_relaxed(SPI_ENGINE_INT_SYNC, spi_engine->base + SPI_ENGINE_REG_INT_PENDING); + /* Read back to flush posted write. */ + readl_relaxed(spi_engine->base + SPI_ENGINE_REG_INT_PENDING); + + return ret; } static int spi_engine_transfer_one_message(struct spi_controller *host, From 18febc10a3538b9baa2f6e4481b434f20fdbd93c Mon Sep 17 00:00:00 2001 From: Jonathan Santos Date: Wed, 7 Oct 2026 05:52:46 -0300 Subject: [PATCH 115/136] spi: axi-spi-engine: fix stale SYNC IRQ pending in trigger_enable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit spi_engine_trigger_enable() sends SYNC(1) and polls SYNC_ID, it never clears the INT_PENDING[SYNC] bit afterward. This leaves a stale pending interrupt that can fire spuriously when INT_SYNC is later enabled. Simillarly to the fix on spi_engine_setup(), clear INT_PENDING[SYNC] after polling the SYNC_ID. Fixes: 8fc13b822c74 ("spi: axi-spi-engine: don't repeat mode config for offload") Reviewed-by: Nuno Sá Signed-off-by: Jonathan Santos Link: https://patch.msgid.link/8d3f421e07b6114aae55f964825dba6ac9151cf5.1791361360.git.Jonathan.Santos@analog.com Signed-off-by: Mark Brown --- drivers/spi/spi-axi-spi-engine.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/spi/spi-axi-spi-engine.c b/drivers/spi/spi-axi-spi-engine.c index 87c05cb064cc..ead5b295e680 100644 --- a/drivers/spi/spi-axi-spi-engine.c +++ b/drivers/spi/spi-axi-spi-engine.c @@ -1036,7 +1036,13 @@ static int spi_engine_trigger_enable(struct spi_offload *offload) spi_engine->base + SPI_ENGINE_REG_CMD_FIFO); ret = readl_relaxed_poll_timeout(spi_engine->base + SPI_ENGINE_REG_SYNC_ID, - reg, reg == 1, 1, 1000); + reg, reg == 1, 1, 1 * USEC_PER_MSEC); + + /* Clear the stale SYNC pending bit so it doesn't fire when the IRQ is later enabled */ + writel_relaxed(SPI_ENGINE_INT_SYNC, spi_engine->base + SPI_ENGINE_REG_INT_PENDING); + /* Read back to flush posted write. */ + readl_relaxed(spi_engine->base + SPI_ENGINE_REG_INT_PENDING); + if (ret) return ret; From 3716c71d1c7783c6d731092403eb9f44c2a3a995 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Thu, 1 Oct 2026 18:45:57 +0200 Subject: [PATCH 116/136] spi: cadence-qspi: Prevent SPI NAND continuous reads TI AM62Ax errata i2351, entitled "OSPI: Direct Access Controller (DAC) does not support Continuous Read mode with NAND Flash", explains that the CS can be deasserted almost at any time during a transfer (basically there is an interconnect arbitration every 1023 byte). This is an expected internal behaviour of the controller, but this leads to spurious CS deasserts. These are totally forbidden during SPI NAND continuous read transfers, because they indicate an end of transfer and the continuous read is then stopped on the flash side. I initially tried to query the flash type and geometry to decide whether to apply a spi message size limit (there is a spi helper for that) but we cannot reliably get up to the MTD structure to discriminate if it is a NOR or a NAND we are playing with (it is not relevant to limit SPI NOR transfers, they are not affected). If we actually take this path, what limitation shall we enforce? The errata mentions 1023B, this is super low, less than a typical page size (about 2k or 4k, for most of them), so this is not usable. On my side, I only observed this problem on a 2-page read in octal DTR mode with more than 12 dummy cycles. The writesize summed with the oobsize could be a relevant boundary, but it is still quite arbitrary. Hence, I opted for implementing a controller capability flag, which then is used to decide whether the SPI NAND continuous read feature can be enabled. Signed-off-by: Miquel Raynal Link: https://patch.msgid.link/20261001-winbond-v7-0-rc1-cadence-cont-read-v2-1-703e040b2d07@bootlin.com Signed-off-by: Mark Brown --- drivers/spi/spi-cadence-quadspi.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/spi/spi-cadence-quadspi.c b/drivers/spi/spi-cadence-quadspi.c index 2d5301d82118..73ea51c41f95 100644 --- a/drivers/spi/spi-cadence-quadspi.c +++ b/drivers/spi/spi-cadence-quadspi.c @@ -1741,6 +1741,12 @@ static const struct spi_controller_mem_caps cqspi_mem_caps = { .per_op_freq = true, }; +static const struct spi_controller_mem_caps cqspi_am654_mem_caps = { + .dtr = true, + .per_op_freq = true, + .no_cs_assertion = true, +}; + static int cqspi_setup_flash(struct cqspi_st *cqspi) { struct platform_device *pdev = cqspi->pdev; @@ -1798,6 +1804,8 @@ static int cqspi_probe(struct platform_device *pdev) host->mode_bits = SPI_RX_QUAD | SPI_RX_DUAL; host->mem_ops = &cqspi_mem_ops; host->mem_caps = &cqspi_mem_caps; + if (of_device_is_compatible(pdev->dev.of_node, "ti,am654-ospi")) + host->mem_caps = &cqspi_am654_mem_caps; cqspi = spi_controller_get_devdata(host); if (of_device_is_compatible(pdev->dev.of_node, "starfive,jh7110-qspi")) From 0fe4573c822c4c9152e2fd5269ab85410c9e458f Mon Sep 17 00:00:00 2001 From: Weibin Liu Date: Tue, 13 Oct 2026 11:17:17 +0800 Subject: [PATCH 117/136] spi: ljca: validate the response length before copying the data ljca_spi_read_write() only checks that the response from the adapter carries the packet header and a non-zero payload length before copying r_packet->len bytes from the response into the transfer buffer. The payload length is taken from the packet the device sent back and is neither bounded by the number of bytes actually received nor by the size of the requested transfer: a misbehaving adapter can announce a payload larger than what it actually sent, and the memcpy then reads past the end of the 60-byte input buffer and writes past the end of the caller's transfer buffer. Check that the response carries the full header before reading r_packet->len, then reject responses whose payload is not contained in the data received or is longer than the requested transfer. Fixes: caee8e38da67 ("spi: Add support for Intel LJCA USB SPI driver") Cc: stable@vger.kernel.org # 6.8+ Signed-off-by: Weibin Liu Link: https://patch.msgid.link/20261013031717.3933277-1-liuwb@xiaopeng.com Signed-off-by: Mark Brown --- drivers/spi/spi-ljca.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/spi/spi-ljca.c b/drivers/spi/spi-ljca.c index 0c6e6248d8ba..8fd001610f67 100644 --- a/drivers/spi/spi-ljca.c +++ b/drivers/spi/spi-ljca.c @@ -105,7 +105,12 @@ static int ljca_spi_read_write(struct ljca_spi_dev *ljca_spi, const u8 *w_data, (u8 *)r_packet, LJCA_SPI_BUF_SIZE); if (ret < 0) return ret; - else if (ret < sizeof(*r_packet) || r_packet->len <= 0) + + if (ret < sizeof(*r_packet)) + return -EIO; + + if (!r_packet->len || ret < sizeof(*r_packet) + r_packet->len || + r_packet->len > len) return -EIO; if (r_data) From e796a187fbe32593c426c86777e5ee9fb65530db Mon Sep 17 00:00:00 2001 From: Aayush Kumar Date: Fri, 9 Oct 2026 18:54:20 +0530 Subject: [PATCH 118/136] spi: sh-hspi: Use devm_platform_ioremap_resource() Use the devm_platform_ioremap_resource() helper instead of calling platform_get_resource() and devm_ioremap() separately. Signed-off-by: Aayush Kumar Link: https://patch.msgid.link/20261009132421.557758-2-code@aayushk.dev Signed-off-by: Mark Brown --- drivers/spi/spi-sh-hspi.c | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/drivers/spi/spi-sh-hspi.c b/drivers/spi/spi-sh-hspi.c index f840467cfdb2..56df076b61be 100644 --- a/drivers/spi/spi-sh-hspi.c +++ b/drivers/spi/spi-sh-hspi.c @@ -211,19 +211,11 @@ static int hspi_transfer_one_message(struct spi_controller *ctlr, static int hspi_probe(struct platform_device *pdev) { - struct resource *res; struct spi_controller *ctlr; struct hspi_priv *hspi; struct clk *clk; int ret; - /* get base addr */ - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!res) { - dev_err(&pdev->dev, "invalid resource\n"); - return -EINVAL; - } - ctlr = devm_spi_alloc_host(&pdev->dev, sizeof(*hspi)); if (!ctlr) return -ENOMEM; @@ -241,10 +233,9 @@ static int hspi_probe(struct platform_device *pdev) hspi->ctlr = ctlr; hspi->dev = &pdev->dev; hspi->clk = clk; - hspi->addr = devm_ioremap(hspi->dev, - res->start, resource_size(res)); - if (!hspi->addr) { - ret = -ENOMEM; + hspi->addr = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(hspi->addr)) { + ret = PTR_ERR(hspi->addr); goto error1; } From a0b70663dddf64df0d4eca47127aab3c6fc670ec Mon Sep 17 00:00:00 2001 From: Aayush Kumar Date: Fri, 9 Oct 2026 18:54:21 +0530 Subject: [PATCH 119/136] spi: jcore: Use devm_platform_ioremap_resource() Use the devm_platform_ioremap_resource() helper instead of calling platform_get_resource(), devm_request_mem_region() and devm_ioremap() separately. Signed-off-by: Aayush Kumar Link: https://patch.msgid.link/20261009132421.557758-3-code@aayushk.dev Signed-off-by: Mark Brown --- drivers/spi/spi-jcore.c | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/drivers/spi/spi-jcore.c b/drivers/spi/spi-jcore.c index a75cd61ec7a3..df82e8adb7c6 100644 --- a/drivers/spi/spi-jcore.c +++ b/drivers/spi/spi-jcore.c @@ -143,7 +143,6 @@ static int jcore_spi_probe(struct platform_device *pdev) struct device_node *node = pdev->dev.of_node; struct jcore_spi *hw; struct spi_controller *host; - struct resource *res; u32 clock_freq; struct clk *clk; @@ -164,16 +163,9 @@ static int jcore_spi_probe(struct platform_device *pdev) platform_set_drvdata(pdev, hw); /* Find and map our resources */ - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!res) - return -EBUSY; - if (!devm_request_mem_region(&pdev->dev, res->start, - resource_size(res), pdev->name)) - return -EBUSY; - hw->base = devm_ioremap(&pdev->dev, res->start, - resource_size(res)); - if (!hw->base) - return -EBUSY; + hw->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(hw->base)) + return PTR_ERR(hw->base); /* * The SPI clock rate controlled via a configurable clock divider From e9b641d9296308ede48d4b6b4ef10061a5e86f99 Mon Sep 17 00:00:00 2001 From: Aayush Kumar Date: Fri, 9 Oct 2026 19:06:05 +0530 Subject: [PATCH 120/136] spi: butterfly: Use strscpy() instead of strcpy() Use a safer function strscpy() instead of strcpy() for copying to arrays. Only idiomatic code replacement, and no functional changes. Signed-off-by: Aayush Kumar Link: https://patch.msgid.link/20261009133609.579046-2-code@aayushk.dev Signed-off-by: Mark Brown --- drivers/spi/spi-butterfly.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/spi/spi-butterfly.c b/drivers/spi/spi-butterfly.c index 84eb454ed443..5e1e205bbcd3 100644 --- a/drivers/spi/spi-butterfly.c +++ b/drivers/spi/spi-butterfly.c @@ -259,7 +259,8 @@ static void butterfly_attach(struct parport *p) */ pp->info[0].max_speed_hz = 15 * 1000 * 1000; - strcpy(pp->info[0].modalias, "mtd_dataflash"); + strscpy(pp->info[0].modalias, "mtd_dataflash", + sizeof(pp->info[0].modalias)); pp->info[0].platform_data = &flash; pp->info[0].chip_select = 1; pp->info[0].controller_data = pp; From ae6056b248ebbcbdd0f0134678ce77b165edca1a Mon Sep 17 00:00:00 2001 From: Aayush Kumar Date: Fri, 9 Oct 2026 19:06:06 +0530 Subject: [PATCH 121/136] spi: lm70llp: Use strscpy() instead of strcpy() Use a safer function strscpy() instead of strcpy() for copying to arrays. Only idiomatic code replacement, and no functional changes. Signed-off-by: Aayush Kumar Link: https://patch.msgid.link/20261009133609.579046-3-code@aayushk.dev Signed-off-by: Mark Brown --- drivers/spi/spi-lm70llp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/spi/spi-lm70llp.c b/drivers/spi/spi-lm70llp.c index e61e89b4119f..bd80288920d0 100644 --- a/drivers/spi/spi-lm70llp.c +++ b/drivers/spi/spi-lm70llp.c @@ -252,7 +252,7 @@ static void spi_lm70llp_attach(struct parport *p) * We are binding to the generic drivers/hwmon/lm70.c device * driver. */ - strcpy(pp->info.modalias, "lm70"); + strscpy(pp->info.modalias, "lm70", sizeof(pp->info.modalias)); pp->info.max_speed_hz = 6 * 1000 * 1000; pp->info.chip_select = 0; pp->info.mode = SPI_3WIRE | SPI_MODE_0; From 9a6b86cee9d711178f51f7dbea6ff2cbad32bb30 Mon Sep 17 00:00:00 2001 From: Aayush Kumar Date: Fri, 9 Oct 2026 19:06:07 +0530 Subject: [PATCH 122/136] spi: tle62x0: Use kstrtoul() instead of simple_strtoul() Use the recommended kstrtoul() instead of the deprecated simple_strtoul(). Check the return value and reject invalid values. Signed-off-by: Aayush Kumar Link: https://patch.msgid.link/20261009133609.579046-4-code@aayushk.dev Signed-off-by: Mark Brown --- drivers/spi/spi-tle62x0.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/spi/spi-tle62x0.c b/drivers/spi/spi-tle62x0.c index dd8bfb3f3544..986d78ab457d 100644 --- a/drivers/spi/spi-tle62x0.c +++ b/drivers/spi/spi-tle62x0.c @@ -151,10 +151,10 @@ static ssize_t tle62x0_gpio_store(struct device *dev, struct tle62x0_state *st = dev_get_drvdata(dev); int gpio_num = to_gpio_num(attr); unsigned long val; - char *endp; + int ret; - val = simple_strtoul(buf, &endp, 0); - if (buf == endp) + ret = kstrtoul(buf, 0, &val); + if (ret) return -EINVAL; dev_dbg(dev, "setting gpio %d to %ld\n", gpio_num, val); From 50f3d77e805f3d77b0037b73d536adb5f66d6298 Mon Sep 17 00:00:00 2001 From: Aayush Kumar Date: Fri, 9 Oct 2026 19:06:08 +0530 Subject: [PATCH 123/136] spi: omap2-mcspi: Use snprintf() instead of sprintf() Use snprintf() instead of sprintf() to avoid potential buffer overflows when formatting DMA channel names. Signed-off-by: Aayush Kumar Link: https://patch.msgid.link/20261009133609.579046-5-code@aayushk.dev Signed-off-by: Mark Brown --- drivers/spi/spi-omap2-mcspi.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/spi/spi-omap2-mcspi.c b/drivers/spi/spi-omap2-mcspi.c index a3355147f5de..311ee0ca94f6 100644 --- a/drivers/spi/spi-omap2-mcspi.c +++ b/drivers/spi/spi-omap2-mcspi.c @@ -1544,8 +1544,10 @@ static int omap2_mcspi_probe(struct platform_device *pdev) return -ENOMEM; for (i = 0; i < ctlr->num_chipselect; i++) { - sprintf(mcspi->dma_channels[i].dma_rx_ch_name, "rx%d", i); - sprintf(mcspi->dma_channels[i].dma_tx_ch_name, "tx%d", i); + snprintf(mcspi->dma_channels[i].dma_rx_ch_name, + sizeof(mcspi->dma_channels[i].dma_rx_ch_name), "rx%d", i); + snprintf(mcspi->dma_channels[i].dma_tx_ch_name, + sizeof(mcspi->dma_channels[i].dma_tx_ch_name), "tx%d", i); status = omap2_mcspi_request_dma(mcspi, &mcspi->dma_channels[i]); From 65f9474caced3d765ccdb69d6e67641051ad3b5e Mon Sep 17 00:00:00 2001 From: Aayush Kumar Date: Fri, 9 Oct 2026 19:06:09 +0530 Subject: [PATCH 124/136] spi: s3c64xx: Use snprintf() instead of sprintf() Use snprintf() instead of sprintf() to avoid potential buffer overflows when formatting the clock name. Signed-off-by: Aayush Kumar Link: https://patch.msgid.link/20261009133609.579046-6-code@aayushk.dev Signed-off-by: Mark Brown --- drivers/spi/spi-s3c64xx.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/spi/spi-s3c64xx.c b/drivers/spi/spi-s3c64xx.c index 8d4120cf9df1..8195bf376bfe 100644 --- a/drivers/spi/spi-s3c64xx.c +++ b/drivers/spi/spi-s3c64xx.c @@ -1331,7 +1331,8 @@ static int s3c64xx_spi_probe(struct platform_device *pdev) return dev_err_probe(&pdev->dev, PTR_ERR(sdd->clk), "Unable to acquire clock 'spi'\n"); - sprintf(clk_name, "spi_busclk%d", sci->src_clk_nr); + snprintf(clk_name, sizeof(clk_name), "spi_busclk%d", + sci->src_clk_nr); sdd->src_clk = devm_clk_get_enabled(&pdev->dev, clk_name); if (IS_ERR(sdd->src_clk)) return dev_err_probe(&pdev->dev, PTR_ERR(sdd->src_clk), From 9d71f9fe5b5de916bded51343138b09b1eb103ed Mon Sep 17 00:00:00 2001 From: Hongjian Dai Date: Thu, 8 Oct 2026 20:47:04 +0800 Subject: [PATCH 125/136] spi: meson-spicc: fix invalid DMA unmap on mapping failure meson_spicc_dma_unmap() decided whether a buffer had to be unmapped by testing its DMA address for non-zero. On a mapping failure dma_map_single() returns DMA_MAPPING_ERROR (typically non-zero), so the error path in meson_spicc_transfer_one() passed that error value to dma_unmap_single(), unmapping an address which was never mapped. The same happened for t->rx_dma on TX mapping failure, which is not written at all. Initialize both addresses to DMA_MAPPING_ERROR before mapping and use dma_mapping_error() to decide, so only buffers which were actually mapped are unmapped. Fixes: 18197e98353d ("spi: meson-spicc: add DMA support") Signed-off-by: Hongjian Dai Link: https://patch.msgid.link/35143466F2F5166F+20261008124704.87708-1-daihongjian@kylinsec.com.cn Signed-off-by: Mark Brown --- drivers/spi/spi-meson-spicc.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/drivers/spi/spi-meson-spicc.c b/drivers/spi/spi-meson-spicc.c index fa2d2db2d6e7..b1f8c823d5d0 100644 --- a/drivers/spi/spi-meson-spicc.c +++ b/drivers/spi/spi-meson-spicc.c @@ -235,6 +235,13 @@ static int meson_spicc_dma_map(struct meson_spicc_device *spicc, { struct device *dev = spicc->host->dev.parent; + /* + * Mark the DMA addresses as invalid so that meson_spicc_dma_unmap() + * will not try to unmap a buffer which was not (successfully) mapped. + */ + t->tx_dma = DMA_MAPPING_ERROR; + t->rx_dma = DMA_MAPPING_ERROR; + if (!(t->tx_buf && t->rx_buf)) return -EINVAL; @@ -257,9 +264,9 @@ static void meson_spicc_dma_unmap(struct meson_spicc_device *spicc, { struct device *dev = spicc->host->dev.parent; - if (t->tx_dma) + if (!dma_mapping_error(dev, t->tx_dma)) dma_unmap_single(dev, t->tx_dma, t->len, DMA_TO_DEVICE); - if (t->rx_dma) + if (!dma_mapping_error(dev, t->rx_dma)) dma_unmap_single(dev, t->rx_dma, t->len, DMA_FROM_DEVICE); } From ea9854a169e9c1437df8829bea21c2f2e80b137c Mon Sep 17 00:00:00 2001 From: Weibin Liu Date: Tue, 29 Sep 2026 16:11:44 +0800 Subject: [PATCH 126/136] spi: cadence-xspi: reject SDMA transfers larger than the requested length The XSPI controller reports the size of the slave-DMA transaction through SDMA_SIZE_REG. Both SDMA handlers take this register at face value and copy the reported number of bytes between the SDMA FIFO and the transfer buffers, without checking it against the length of the transfer that was actually requested. While cdns_xspi_adjust_mem_op_size() clamps the size of the operations issued by the SPI core, the device can still report a bigger SDMA size than what was programmed, which makes the handlers read from or write to memory beyond the end of the transfer buffers. Track the number of bytes requested for the current data phase in a new sdma_xfer_len field, set by cdns_xspi_send_stig_command() and by the Marvell b0 transfer path, and reject any SDMA size that exceeds it. Fixes: a16cc8077627 ("spi: cadence: add support for Cadence XSPI controller") Cc: stable@vger.kernel.org # 5.16+ Signed-off-by: Weibin Liu Link: https://patch.msgid.link/20260929081146.41041-2-liuwb@xiaopeng.com Signed-off-by: Mark Brown --- drivers/spi/spi-cadence-xspi.c | 36 +++++++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/drivers/spi/spi-cadence-xspi.c b/drivers/spi/spi-cadence-xspi.c index 39c868a5b171..1f1cd45359d3 100644 --- a/drivers/spi/spi-cadence-xspi.c +++ b/drivers/spi/spi-cadence-xspi.c @@ -332,6 +332,7 @@ struct cdns_xspi_dev { int irq; int cur_cs; unsigned int sdmasize; + unsigned int sdma_xfer_len; struct completion cmd_complete; struct completion auto_cmd_complete; @@ -346,7 +347,7 @@ struct cdns_xspi_dev { u8 hw_num_banks; const struct cdns_xspi_driver_data *driver_data; - void (*sdma_handler)(struct cdns_xspi_dev *cdns_xspi); + int (*sdma_handler)(struct cdns_xspi_dev *cdns_xspi); void (*set_interrupts_handler)(struct cdns_xspi_dev *cdns_xspi, bool enabled); bool xfer_in_progress; @@ -496,7 +497,7 @@ static inline void cdns_xspi_sdma_write(struct cdns_xspi_dev *cdns_xspi, size_t iowrite8_rep(dst, (const u8 *)buf + offset, len); } -static void cdns_xspi_sdma_handle(struct cdns_xspi_dev *cdns_xspi) +static int cdns_xspi_sdma_handle(struct cdns_xspi_dev *cdns_xspi) { u32 sdma_size, sdma_trd_info; u8 sdma_dir; @@ -505,6 +506,13 @@ static void cdns_xspi_sdma_handle(struct cdns_xspi_dev *cdns_xspi) sdma_trd_info = readl(cdns_xspi->iobase + CDNS_XSPI_SDMA_TRD_INFO_REG); sdma_dir = FIELD_GET(CDNS_XSPI_SDMA_DIR, sdma_trd_info); + if (sdma_size > cdns_xspi->sdma_xfer_len) { + dev_err(cdns_xspi->dev, + "SDMA size %u exceeds the requested length %u\n", + sdma_size, cdns_xspi->sdma_xfer_len); + return -EINVAL; + } + switch (sdma_dir) { case CDNS_XSPI_SDMA_DIR_READ: cdns_xspi_sdma_read(cdns_xspi, sdma_size); @@ -514,6 +522,8 @@ static void cdns_xspi_sdma_handle(struct cdns_xspi_dev *cdns_xspi) cdns_xspi_sdma_write(cdns_xspi, sdma_size); break; } + + return 0; } static int cdns_xspi_send_stig_command(struct cdns_xspi_dev *cdns_xspi, @@ -559,6 +569,7 @@ static int cdns_xspi_send_stig_command(struct cdns_xspi_dev *cdns_xspi, cdns_xspi->in_buffer = op->data.buf.in; cdns_xspi->out_buffer = op->data.buf.out; + cdns_xspi->sdma_xfer_len = op->data.nbytes; cdns_xspi_trigger_command(cdns_xspi, cmd_regs); @@ -567,7 +578,11 @@ static int cdns_xspi_send_stig_command(struct cdns_xspi_dev *cdns_xspi, cdns_xspi->set_interrupts_handler(cdns_xspi, false); return -EIO; } - cdns_xspi->sdma_handler(cdns_xspi); + ret = cdns_xspi->sdma_handler(cdns_xspi); + if (ret) { + cdns_xspi->set_interrupts_handler(cdns_xspi, false); + return ret; + } } wait_for_completion(&cdns_xspi->cmd_complete); @@ -950,7 +965,7 @@ static void m_iowriteq(void __iomem *addr, const void *buf, int len) } } -static void marvell_xspi_sdma_handle(struct cdns_xspi_dev *cdns_xspi) +static int marvell_xspi_sdma_handle(struct cdns_xspi_dev *cdns_xspi) { u32 sdma_size, sdma_trd_info; u8 sdma_dir; @@ -959,6 +974,13 @@ static void marvell_xspi_sdma_handle(struct cdns_xspi_dev *cdns_xspi) sdma_trd_info = readl(cdns_xspi->iobase + CDNS_XSPI_SDMA_TRD_INFO_REG); sdma_dir = FIELD_GET(CDNS_XSPI_SDMA_DIR, sdma_trd_info); + if (sdma_size > cdns_xspi->sdma_xfer_len) { + dev_err(cdns_xspi->dev, + "SDMA size %u exceeds the requested length %u\n", + sdma_size, cdns_xspi->sdma_xfer_len); + return -EINVAL; + } + switch (sdma_dir) { case CDNS_XSPI_SDMA_DIR_READ: m_ioreadq(cdns_xspi->sdmabase, @@ -970,6 +992,8 @@ static void marvell_xspi_sdma_handle(struct cdns_xspi_dev *cdns_xspi) cdns_xspi->out_buffer, sdma_size); break; } + + return 0; } static const struct spi_controller_mem_ops marvell_xspi_mem_ops = { @@ -1131,9 +1155,11 @@ static int cdns_xspi_transfer_one_message_b0(struct spi_controller *controller, cdns_xspi_prepare_transfer(cs, 1, current_transfer_len - 1, cmd_regs); cdns_xspi_trigger_command(cdns_xspi, cmd_regs); + cdns_xspi->sdma_xfer_len = current_transfer_len - 1; if (!cdns_xspi_is_sdma_ready(cdns_xspi, true)) return -EIO; - cdns_xspi->sdma_handler(cdns_xspi); + if (cdns_xspi->sdma_handler(cdns_xspi)) + return -EIO; if (!cdns_xspi_is_stig_ready(cdns_xspi, true)) return -EIO; From ef9690269999ddf9b204731baca1bd6b7aea1aa7 Mon Sep 17 00:00:00 2001 From: Weibin Liu Date: Tue, 29 Sep 2026 16:11:45 +0800 Subject: [PATCH 127/136] spi: cadence-xspi: fix stack buffer overflow in the Marvell b0 path transfer_one_message_b0() falls back to a 10-byte stack buffer for transfers which carry no TX data and points both in_buffer and out_buffer into it. With a transfer longer than 10 bytes the SDMA branch of the loop moves up to MRVL_XFER_QWORD_COUNT * MRVL_XFER_QWORD_BYTECOUNT (256) bytes through those pointers and then advances them by the same amount, so both directions overflow the scratch buffer: a read transaction copies SDMA data past the end of the buffer, a write transaction sends stack contents from beyond it to the SPI bus. Size the scratch buffer for the largest chunk the loop can request, keep the buffer pointers pinned to it when the message does not carry the corresponding TX or RX data, and only advance the pointers which reference real transfer buffers. Also zero-initialize the scratch buffer so that padding bytes sent for TX-less transfers no longer leak uninitialized stack contents. Fixes: 5cb7651f78e1 ("Marvell HW overlay support for Cadence xSPI") Cc: stable@vger.kernel.org # 6.11+ Signed-off-by: Weibin Liu Link: https://patch.msgid.link/20260929081146.41041-3-liuwb@xiaopeng.com Signed-off-by: Mark Brown --- drivers/spi/spi-cadence-xspi.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/drivers/spi/spi-cadence-xspi.c b/drivers/spi/spi-cadence-xspi.c index 1f1cd45359d3..3687853e4ce4 100644 --- a/drivers/spi/spi-cadence-xspi.c +++ b/drivers/spi/spi-cadence-xspi.c @@ -268,6 +268,7 @@ #define MRVL_XFER_FUNC_START BIT(0) #define MRVL_XFER_QWORD_COUNT 32 #define MRVL_XFER_QWORD_BYTECOUNT 8 +#define MRVL_XFER_MAX_LEN (MRVL_XFER_QWORD_COUNT * MRVL_XFER_QWORD_BYTECOUNT) #define MRVL_XSPI_POLL_TIMEOUT_US 1000 #define MRVL_XSPI_POLL_DELAY_US 10 @@ -1107,7 +1108,7 @@ static int cdns_xspi_transfer_one_message_b0(struct spi_controller *controller, struct spi_device *spi = m->spi; struct spi_transfer *t = NULL; - const unsigned int max_len = MRVL_XFER_QWORD_BYTECOUNT * MRVL_XFER_QWORD_COUNT; + const unsigned int max_len = MRVL_XFER_MAX_LEN; int current_transfer_len; int cs = spi_get_chipselect(spi, 0); int cs_change = 0; @@ -1130,13 +1131,16 @@ static int cdns_xspi_transfer_one_message_b0(struct spi_controller *controller, list_for_each_entry(t, &m->transfers, transfer_list) { u8 *txd = (u8 *) t->tx_buf; u8 *rxd = (u8 *) t->rx_buf; - u8 data[10]; + u8 data[MRVL_XFER_MAX_LEN] = {0}; u32 cmd_regs[6]; if (!txd) txd = data; - cdns_xspi->in_buffer = txd + 1; + if (rxd) + cdns_xspi->in_buffer = rxd; + else + cdns_xspi->in_buffer = data; cdns_xspi->out_buffer = txd + 1; while (t->len) { @@ -1163,8 +1167,10 @@ static int cdns_xspi_transfer_one_message_b0(struct spi_controller *controller, if (!cdns_xspi_is_stig_ready(cdns_xspi, true)) return -EIO; - cdns_xspi->in_buffer += current_transfer_len; - cdns_xspi->out_buffer += current_transfer_len; + if (rxd) + cdns_xspi->in_buffer += current_transfer_len; + if (t->tx_buf) + cdns_xspi->out_buffer += current_transfer_len; } if (rxd) { From 7db773075501248893c22b11bedc6745d6d0be45 Mon Sep 17 00:00:00 2001 From: Weibin Liu Date: Wed, 14 Oct 2026 22:18:50 +0800 Subject: [PATCH 128/136] spi: jcore: fix divide-by-zero when speed_hz is zero jcore_spi_baudrate() computes hw->clock_freq / 2 / speed without validating speed. The SPI core fills transfer.speed_hz from spi->max_speed_hz when it is zero, and jcore_spi_probe() never sets host->max_speed_hz, so the clamp in __spi_setup() is skipped. A spi_device created via the sysfs new_device interface (or a DT node without spi-max-frequency) therefore has max_speed_hz == 0, and any SPI_IOC_MESSAGE transfer reaches jcore_spi_baudrate() with speed == 0, causing a division by zero and a kernel panic. Clamp speed to a minimum of 1 Hz before the division so the divisor is never zero. The computed divider then truncates to the register field width and the transfer simply runs at the slowest supported rate. Verified by inspection of the __spi_setup() clamp and __spi_validate()'s speed_hz handling; the division-by-zero path requires the J-Core controller, which was not available. Reviewed-by: Guanglei Zhu Fixes: 2cb1b3b3ac0a ("spi: add driver for J-Core SPI controller") Cc: stable@vger.kernel.org # 4.9+ Signed-off-by: Weibin Liu Link: https://patch.msgid.link/20261014-spi-jcore-divzero-v1-1-a8205d30f2d1@xiaopeng.com Signed-off-by: Mark Brown --- drivers/spi/spi-jcore.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/spi/spi-jcore.c b/drivers/spi/spi-jcore.c index df82e8adb7c6..347f14f7568a 100644 --- a/drivers/spi/spi-jcore.c +++ b/drivers/spi/spi-jcore.c @@ -84,6 +84,8 @@ static void jcore_spi_baudrate(struct jcore_spi *hw, int speed) { if (speed == hw->speed_hz) return; + if (speed <= 0) + speed = 1; hw->speed_hz = speed; if (speed >= hw->clock_freq / 2) hw->speed_reg = 0; From c126e07bde21c518f1356937ac240500d1bc81c0 Mon Sep 17 00:00:00 2001 From: Sahaj Chaudhari Date: Thu, 15 Oct 2026 12:13:41 +0530 Subject: [PATCH 129/136] spi: omap2-mcspi: handle DMA setup failures The DMA paths ignore failures from dmaengine_slave_config(), descriptor preparation and dmaengine_submit(), and carry FIXME comments about falling back to PIO. A failed setup can leave a half-started transfer or wait for a completion that never arrives. Check each step and clean up with dmaengine_terminate_sync() on error. Prepare both descriptors before issuing either channel. Fall back to PIO only for host-mode transfers that have a TX buffer, when no DMA has been started and cleanup succeeded. RX-only and target-mode transfers return the error instead, since the bus may already be clocking. The FIFO is enabled before the DMA attempt, but the PIO path polls per-word TXS/RXS and does not expect FIFO mode. Disable the FIFO, with the channel off while its bits change, before falling back. Tested on an AM642 board with a TI 6.12.57 vendor kernel built with this patch and booted from RAM. On a McSPI bus using DMA, 200 iterations of 4096-byte TX and 1024-byte RX spidev transfers completed with no errors, timeouts or kernel warnings. With a temporary change that fails every DMA-sized TX setup, 60 spidev writes fell back to PIO with no errors or timeouts. That test used a bus with no device attached and did not show a difference with and without the FIFO disable, which is based on reading the code. Fixes: 53741ed8f0a3 ("spi: omap2-mcspi: add DMA engine support") Link: https://lore.kernel.org/r/20250914022132.319227-1-alex.t.tran@gmail.com Signed-off-by: Sahaj Chaudhari Link: https://patch.msgid.link/20261015064341.1100422-1-sahaj123.sc@gmail.com Signed-off-by: Mark Brown --- drivers/spi/spi-omap2-mcspi.c | 240 ++++++++++++++++++++++++---------- 1 file changed, 169 insertions(+), 71 deletions(-) diff --git a/drivers/spi/spi-omap2-mcspi.c b/drivers/spi/spi-omap2-mcspi.c index 49d9d9218471..e46b1d9c8cd0 100644 --- a/drivers/spi/spi-omap2-mcspi.c +++ b/drivers/spi/spi-omap2-mcspi.c @@ -413,10 +413,11 @@ static void omap2_mcspi_tx_callback(void *data) complete(&mcspi_dma->dma_tx_completion); } -static void omap2_mcspi_tx_dma(struct spi_device *spi, - struct spi_transfer *xfer, - struct dma_slave_config cfg) +static int omap2_mcspi_tx_dma(struct spi_device *spi, + struct spi_transfer *xfer, + struct dma_slave_config cfg) { + int ret; struct omap2_mcspi *mcspi; struct omap2_mcspi_dma *mcspi_dma; struct dma_async_tx_descriptor *tx; @@ -424,31 +425,31 @@ static void omap2_mcspi_tx_dma(struct spi_device *spi, mcspi = spi_controller_get_devdata(spi->controller); mcspi_dma = &mcspi->dma_channels[spi_get_chipselect(spi, 0)]; - dmaengine_slave_config(mcspi_dma->dma_tx, &cfg); + ret = dmaengine_slave_config(mcspi_dma->dma_tx, &cfg); + if (ret) + return ret; tx = dmaengine_prep_slave_sg(mcspi_dma->dma_tx, xfer->tx_sg.sgl, xfer->tx_sg.nents, DMA_MEM_TO_DEV, DMA_PREP_INTERRUPT | DMA_CTRL_ACK); - if (tx) { - tx->callback = omap2_mcspi_tx_callback; - tx->callback_param = spi; - dmaengine_submit(tx); - } else { - /* FIXME: fall back to PIO? */ - } - dma_async_issue_pending(mcspi_dma->dma_tx); - omap2_mcspi_set_dma_req(spi, 0, 1); + if (!tx) + return -EIO; + + tx->callback = omap2_mcspi_tx_callback; + tx->callback_param = spi; + + return dma_submit_error(dmaengine_submit(tx)); } -static unsigned +static int omap2_mcspi_rx_dma(struct spi_device *spi, struct spi_transfer *xfer, - struct dma_slave_config cfg, - unsigned es) + struct dma_slave_config cfg, unsigned int es, + unsigned int *count, bool *rx_submitted, bool *dma_started) { struct omap2_mcspi *mcspi; struct omap2_mcspi_dma *mcspi_dma; - unsigned int count, transfer_reduction = 0; + unsigned int transfer_reduction = 0; struct scatterlist *sg_out[2]; int nb_sizes = 0, out_mapped_nents[2], ret, x; size_t sizes[2]; @@ -461,7 +462,8 @@ omap2_mcspi_rx_dma(struct spi_device *spi, struct spi_transfer *xfer, mcspi = spi_controller_get_devdata(spi->controller); mcspi_dma = &mcspi->dma_channels[spi_get_chipselect(spi, 0)]; - count = xfer->len; + *count = xfer->len; + *rx_submitted = false; /* * In the "End-of-Transfer Procedure" section for DMA RX in OMAP35x TRM @@ -475,14 +477,16 @@ omap2_mcspi_rx_dma(struct spi_device *spi, struct spi_transfer *xfer, l = mcspi_cached_chconf0(spi); if (word_len <= 8) - element_count = count; + element_count = *count; else if (word_len <= 16) - element_count = count >> 1; + element_count = *count >> 1; else /* word_len <= 32 */ - element_count = count >> 2; + element_count = *count >> 2; - dmaengine_slave_config(mcspi_dma->dma_rx, &cfg); + ret = dmaengine_slave_config(mcspi_dma->dma_rx, &cfg); + if (ret) + return ret; /* * Reduce DMA transfer length by one more if McSPI is @@ -493,7 +497,7 @@ omap2_mcspi_rx_dma(struct spi_device *spi, struct spi_transfer *xfer, if (transfer_reduction) { /* Split sgl into two. The second sgl won't be used. */ - sizes[0] = count - transfer_reduction; + sizes[0] = *count - transfer_reduction; sizes[1] = transfer_reduction; nb_sizes = 2; } else { @@ -501,44 +505,56 @@ omap2_mcspi_rx_dma(struct spi_device *spi, struct spi_transfer *xfer, * Don't bother splitting the sgl. This essentially * clones the original sgl. */ - sizes[0] = count; + sizes[0] = *count; nb_sizes = 1; } ret = sg_split(xfer->rx_sg.sgl, xfer->rx_sg.nents, 0, nb_sizes, sizes, sg_out, out_mapped_nents, GFP_KERNEL); - if (ret < 0) { + if (ret) { dev_err(&spi->dev, "sg_split failed\n"); - return 0; + return ret; } tx = dmaengine_prep_slave_sg(mcspi_dma->dma_rx, sg_out[0], out_mapped_nents[0], DMA_DEV_TO_MEM, DMA_PREP_INTERRUPT | DMA_CTRL_ACK); - if (tx) { - tx->callback = omap2_mcspi_rx_callback; - tx->callback_param = spi; - dmaengine_submit(tx); - } else { - /* FIXME: fall back to PIO? */ + if (!tx) { + ret = -EIO; + goto out_free_sg; } + tx->callback = omap2_mcspi_rx_callback; + tx->callback_param = spi; + ret = dma_submit_error(dmaengine_submit(tx)); + if (ret) + goto out_free_sg; + + *rx_submitted = true; + if (mcspi->target_aborted) { + ret = -EINTR; + goto out_free_sg; + } + *dma_started = true; + if (xfer->tx_buf) { + if (spi_controller_is_target(mcspi->ctlr)) + mcspi_write_reg(mcspi->ctlr, OMAP2_MCSPI_IRQENABLE, + OMAP2_MCSPI_IRQSTATUS_EOW); + dma_async_issue_pending(mcspi_dma->dma_tx); + omap2_mcspi_set_dma_req(spi, 0, 1); + } dma_async_issue_pending(mcspi_dma->dma_rx); omap2_mcspi_set_dma_req(spi, 1, 1); - ret = mcspi_wait_for_completion(mcspi, &mcspi_dma->dma_rx_completion); - if (ret || mcspi->target_aborted) { - dmaengine_terminate_sync(mcspi_dma->dma_rx); - omap2_mcspi_set_dma_req(spi, 1, 0); - return 0; - } + if (ret || mcspi->target_aborted) + goto out_free_sg; for (x = 0; x < nb_sizes; x++) kfree(sg_out[x]); if (mcspi->fifo_depth > 0) - return count; + return 0; /* * Due to the DMA transfer length reduction the missing bytes must @@ -565,9 +581,9 @@ omap2_mcspi_rx_dma(struct spi_device *spi, struct spi_transfer *xfer, } else { int bytes_per_word = mcspi_bytes_per_word(word_len); dev_err(&spi->dev, "DMA RX penultimate word empty\n"); - count -= (bytes_per_word << 1); + *count -= (bytes_per_word << 1); omap2_mcspi_set_enable(spi, 1); - return count; + return 0; } } if (!mcspi_wait_for_reg_bit(chstat_reg, OMAP2_MCSPI_CHSTAT_RXS)) { @@ -582,19 +598,27 @@ omap2_mcspi_rx_dma(struct spi_device *spi, struct spi_transfer *xfer, ((u32 *)xfer->rx_buf)[elements] = w; } else { dev_err(&spi->dev, "DMA RX last word empty\n"); - count -= mcspi_bytes_per_word(word_len); + *count -= mcspi_bytes_per_word(word_len); } omap2_mcspi_set_enable(spi, 1); - return count; + return 0; + +out_free_sg: + if (!ret) + ret = -EINTR; + for (x = 0; x < nb_sizes; x++) + kfree(sg_out[x]); + return ret; } -static unsigned -omap2_mcspi_txrx_dma(struct spi_device *spi, struct spi_transfer *xfer) +static int omap2_mcspi_txrx_dma(struct spi_device *spi, + struct spi_transfer *xfer, + unsigned int *count, + bool *pio_fallback) { struct omap2_mcspi *mcspi; struct omap2_mcspi_cs *cs = spi->controller_state; struct omap2_mcspi_dma *mcspi_dma; - unsigned int count; u8 *rx; const u8 *tx; struct dma_slave_config cfg; @@ -602,10 +626,13 @@ omap2_mcspi_txrx_dma(struct spi_device *spi, struct spi_transfer *xfer) unsigned es; void __iomem *chstat_reg; void __iomem *irqstat_reg; - int wait_res; + bool tx_submitted = false, rx_submitted = false; + bool dma_started = false, terminate_failed = false; + int ret, wait_res; mcspi = spi_controller_get_devdata(spi->controller); mcspi_dma = &mcspi->dma_channels[spi_get_chipselect(spi, 0)]; + *pio_fallback = false; if (cs->word_len <= 8) { width = DMA_SLAVE_BUSWIDTH_1_BYTE; @@ -618,7 +645,7 @@ omap2_mcspi_txrx_dma(struct spi_device *spi, struct spi_transfer *xfer) es = 4; } - count = xfer->len; + *count = xfer->len; memset(&cfg, 0, sizeof(cfg)); cfg.src_addr = cs->phys + OMAP2_MCSPI_RX0; @@ -636,31 +663,39 @@ omap2_mcspi_txrx_dma(struct spi_device *spi, struct spi_transfer *xfer) reinit_completion(&mcspi_dma->dma_rx_completion); reinit_completion(&mcspi->txdone); if (tx) { - /* Enable EOW IRQ to know end of tx in target mode */ - if (spi_controller_is_target(spi->controller)) - mcspi_write_reg(spi->controller, - OMAP2_MCSPI_IRQENABLE, - OMAP2_MCSPI_IRQSTATUS_EOW); - omap2_mcspi_tx_dma(spi, xfer, cfg); + ret = omap2_mcspi_tx_dma(spi, xfer, cfg); + if (ret) + goto out; + tx_submitted = true; } - if (rx != NULL) - count = omap2_mcspi_rx_dma(spi, xfer, cfg, es); - - if (tx != NULL) { - int ret; - - ret = mcspi_wait_for_completion(mcspi, &mcspi_dma->dma_tx_completion); - if (ret || mcspi->target_aborted) { - dmaengine_terminate_sync(mcspi_dma->dma_tx); - omap2_mcspi_set_dma_req(spi, 0, 0); - return 0; + if (rx) { + ret = omap2_mcspi_rx_dma(spi, xfer, cfg, es, count, + &rx_submitted, &dma_started); + if (ret) + goto out; + } else if (tx) { + if (mcspi->target_aborted) { + ret = -EINTR; + goto out; } + if (spi_controller_is_target(spi->controller)) + mcspi_write_reg(spi->controller, OMAP2_MCSPI_IRQENABLE, + OMAP2_MCSPI_IRQSTATUS_EOW); + dma_started = true; + dma_async_issue_pending(mcspi_dma->dma_tx); + omap2_mcspi_set_dma_req(spi, 0, 1); + } + + if (tx) { + ret = mcspi_wait_for_completion(mcspi, &mcspi_dma->dma_tx_completion); + if (ret || mcspi->target_aborted) + goto out; if (spi_controller_is_target(mcspi->ctlr)) { ret = mcspi_wait_for_completion(mcspi, &mcspi->txdone); if (ret || mcspi->target_aborted) - return 0; + goto out; } if (mcspi->fifo_depth > 0) { @@ -694,7 +729,43 @@ omap2_mcspi_txrx_dma(struct spi_device *spi, struct spi_transfer *xfer) dev_err(&spi->dev, "EOT timed out\n"); } } - return count; + return 0; + +out: + if (!ret) + ret = -EINTR; + if (rx_submitted) { + wait_res = dmaengine_terminate_sync(mcspi_dma->dma_rx); + if (wait_res) { + dev_err(&spi->dev, "failed to terminate RX DMA: %d\n", + wait_res); + terminate_failed = true; + ret = wait_res; + } + omap2_mcspi_set_dma_req(spi, 1, 0); + } + if (tx_submitted) { + wait_res = dmaengine_terminate_sync(mcspi_dma->dma_tx); + if (wait_res) { + dev_err(&spi->dev, "failed to terminate TX DMA: %d\n", + wait_res); + terminate_failed = true; + ret = wait_res; + } + omap2_mcspi_set_dma_req(spi, 0, 0); + } + if (spi_controller_is_target(mcspi->ctlr)) + mcspi_write_reg(mcspi->ctlr, OMAP2_MCSPI_IRQENABLE, 0); + + *pio_fallback = !dma_started && !terminate_failed; + return ret; +} + +static bool omap2_mcspi_can_pio_fallback(bool cleanup_ok, + const struct spi_transfer *xfer, + bool is_target) +{ + return cleanup_ok && xfer->tx_buf && !is_target; } static unsigned @@ -1214,6 +1285,8 @@ static int omap2_mcspi_transfer_one(struct spi_controller *ctlr, if (t->len) { unsigned count; + bool pio_fallback; + int ret; if ((mcspi_dma->dma_rx && mcspi_dma->dma_tx) && spi_xfer_is_dma_mapped(ctlr, spi, t)) @@ -1227,10 +1300,31 @@ static int omap2_mcspi_transfer_one(struct spi_controller *ctlr, + OMAP2_MCSPI_TX0); if ((mcspi_dma->dma_rx && mcspi_dma->dma_tx) && - spi_xfer_is_dma_mapped(ctlr, spi, t)) - count = omap2_mcspi_txrx_dma(spi, t); - else + spi_xfer_is_dma_mapped(ctlr, spi, t)) { + ret = omap2_mcspi_txrx_dma(spi, t, &count, + &pio_fallback); + if (ret) { + bool is_target = spi_controller_is_target(ctlr); + bool fallback; + + fallback = omap2_mcspi_can_pio_fallback(pio_fallback, t, + is_target); + if (!fallback) { + status = ret; + goto out; + } + + dev_warn(&spi->dev, + "DMA setup failed, falling back to PIO\n"); + /* PIO does not use the FIFO enabled for DMA */ + omap2_mcspi_set_enable(spi, 0); + omap2_mcspi_set_fifo(spi, t, 0); + omap2_mcspi_set_enable(spi, 1); + count = omap2_mcspi_txrx_pio(spi, t); + } + } else { count = omap2_mcspi_txrx_pio(spi, t); + } if (count != t->len) { status = -EIO; @@ -1246,8 +1340,12 @@ static int omap2_mcspi_transfer_one(struct spi_controller *ctlr, out: /* Restore defaults if they were overridden */ if (par_override) { + int restore_status; + par_override = 0; - status = omap2_mcspi_setup_transfer(spi, NULL); + restore_status = omap2_mcspi_setup_transfer(spi, NULL); + if (!status) + status = restore_status; } omap2_mcspi_set_enable(spi, 0); From f0be928a41384ea93c876364f360779f70359249 Mon Sep 17 00:00:00 2001 From: Weibin Liu Date: Thu, 15 Oct 2026 10:10:12 +0800 Subject: [PATCH 130/136] spi: npcm-fiu: limit UMA data_size to the 16-byte staging buffer npcm_fiu_uma_read() and npcm_fiu_uma_write() use a 16-byte on-stack staging buffer (u32 data_reg[4]) and move data_size bytes to or from it. data_size comes straight from op->data.nbytes, which is supplied by the SPI MEM operation and is not bounded by the driver: npcm_fiu_exec_op() only validates op->addr.nbytes, and no adjust_op_size callback truncates the transfer to the controller FIFO size. A SPI MEM operation with addr.nbytes == 0 and data.nbytes > 16 therefore overflows data_reg[] on the kernel stack (write path) or reads past it (read path), corrupting the stack. Reject data_size larger than the staging buffer in both UMA helpers by returning -EOPNOTSUPP, so spi_mem falls back to the generic SPI message path. The chunked path in npcm_fiu_manualwrite() always passes at most CHUNK_SIZE bytes, so it is unaffected. Fixes: ace55c411b11 ("spi: npcm-fiu: add NPCM FIU controller driver") Cc: stable@vger.kernel.org # 5.4+ Reviewed-by: Guanglei Zhu Signed-off-by: Weibin Liu Link: https://patch.msgid.link/20261015-npcm-fiu-uma-datasize-v1-1-7dee91cadee6@xiaopeng.com Signed-off-by: Mark Brown --- drivers/spi/spi-npcm-fiu.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/spi/spi-npcm-fiu.c b/drivers/spi/spi-npcm-fiu.c index 96dfef4d9067..9c0a655a54b3 100644 --- a/drivers/spi/spi-npcm-fiu.c +++ b/drivers/spi/spi-npcm-fiu.c @@ -343,6 +343,10 @@ static int npcm_fiu_uma_read(struct spi_mem *mem, u32 val; u32 i; + /* data_reg holds at most 16 bytes */ + if (data_size > sizeof(data_reg)) + return -EOPNOTSUPP; + regmap_update_bits(fiu->regmap, NPCM_FIU_UMA_CTS, NPCM_FIU_UMA_CTS_DEV_NUM, (spi_get_chipselect(mem->spi, 0) << @@ -398,6 +402,10 @@ static int npcm_fiu_uma_write(struct spi_mem *mem, u32 val; u32 i; + /* data_reg holds at most 16 bytes */ + if (data_size > sizeof(data_reg)) + return -EOPNOTSUPP; + regmap_update_bits(fiu->regmap, NPCM_FIU_UMA_CTS, NPCM_FIU_UMA_CTS_DEV_NUM, (spi_get_chipselect(mem->spi, 0) << From 9e44f0df3fff215d9033089e85980048c3ac132a Mon Sep 17 00:00:00 2001 From: Weibin Liu Date: Thu, 15 Oct 2026 10:16:12 +0800 Subject: [PATCH 131/136] spi: bcm-qspi: reject oversized dummy bytes in mspi exec_mem_op bcm_qspi_mspi_exec_mem_op() builds a local command buffer cmd[6] holding one opcode byte, up to four address bytes and one spare byte, and sets t[0].len = op->addr.nbytes + op->dummy.nbytes + 1. bcm_qspi_exec_mem_op() only validates op->addr.nbytes <= 4 and never bounds op->dummy.nbytes, which spi_mem_check_op() also leaves unrestricted. With addr.nbytes = 4 and dummy.nbytes >= 2 the length becomes 7 or more, so write_to_hw() reads past cmd[] on the kernel stack and pushes the leaked bytes into the hardware TXRAM. Reject operations whose dummy phase does not fit the remaining byte of cmd[] by requiring op->dummy.nbytes <= 1, so such transfers fall back to the generic SPI path instead of overrunning the buffer. Reviewed-by: Guanglei Zhu Fixes: 5f195ee7d830c ("spi: bcm-qspi: Implement the spi_mem interface") Cc: stable@vger.kernel.org # 4.18+ Signed-off-by: Weibin Liu Link: https://patch.msgid.link/20261015-bcm-qspi-dummy-bytes-v1-1-c319da3c2497@xiaopeng.com Signed-off-by: Mark Brown --- drivers/spi/spi-bcm-qspi.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/spi/spi-bcm-qspi.c b/drivers/spi/spi-bcm-qspi.c index 0f96ac59da11..5031b2142479 100644 --- a/drivers/spi/spi-bcm-qspi.c +++ b/drivers/spi/spi-bcm-qspi.c @@ -1197,8 +1197,9 @@ static int bcm_qspi_exec_mem_op(struct spi_mem *mem, u32 addr = 0, len; u_char *buf; + /* cmd[] holds 1 opcode + 4 address + 1 dummy byte at most */ if (!op->data.nbytes || !op->addr.nbytes || op->addr.nbytes > 4 || - op->data.dir != SPI_MEM_DATA_IN) + op->dummy.nbytes > 1 || op->data.dir != SPI_MEM_DATA_IN) return -EOPNOTSUPP; buf = op->data.buf.in; From f8334fdfd858c27f090ce597806794e4a08c9f4f Mon Sep 17 00:00:00 2001 From: Abraham Zukor Date: Sat, 10 Oct 2026 12:54:05 -0700 Subject: [PATCH 132/136] spi: tegra114: initialize native chip selects inactive tegra_spi_probe() initializes SPI_COMMAND1 with only SPI_M_S, leaving CS_POL_INACTIVE clear for every chip select. This drives every native active-low chip select low until tegra_spi_setup() runs for that device. SPI children are registered and probed one at a time. A synchronous probe of an earlier child can therefore transfer while a later child's chip select is still asserted. On a Tegra234 system with active-low devices on CS0 and CS1, this caused both devices to be selected when the CS1 device probed first. Its initialization then intermittently failed. A logic analyzer showed CS0 remained low throughout the CS1 transfer and went high only after the CS1 probe failed. Walk the controller's device tree children before SPI_COMMAND1 is written and set CS_POL_INACTIVE for each valid chip select without spi-cs-high. That way the line is already deasserted when the first child transfers and does not change level when setup runs. Disabled children are walked too. The core only registers available children, so tegra_spi_setup() never runs for a disabled one and probe is the only chance to park its chip select at the level its hardware needs. If an enabled child shares a chip select with disabled ones, its polarity takes priority. If only disabled children use a chip select and they disagree, warn and leave the line inactive high. Fixes: f333a331adfa ("spi/tegra114: add spi driver") Assisted-by: Claude:Opus-5 Signed-off-by: Abraham Zukor Link: https://patch.msgid.link/20261010195503.623287-1-abe@maticrobots.com Signed-off-by: Mark Brown --- drivers/spi/spi-tegra114.c | 49 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/drivers/spi/spi-tegra114.c b/drivers/spi/spi-tegra114.c index 333fb549214e..ce2d77884059 100644 --- a/drivers/spi/spi-tegra114.c +++ b/drivers/spi/spi-tegra114.c @@ -1301,6 +1301,9 @@ static int tegra_spi_probe(struct platform_device *pdev) struct resource *r; int ret, spi_irq; int bus_num; + u32 conflicting_cs; + u32 enabled_child_cs_polarity = 0, disabled_child_cs_low = 0, disabled_child_cs_high = 0; + u32 enabled_cs_mask = 0; host = devm_spi_alloc_host(&pdev->dev, sizeof(*tspi)); if (!host) { @@ -1395,6 +1398,52 @@ static int tegra_spi_probe(struct platform_device *pdev) udelay(2); reset_control_deassert(tspi->rst); tspi->def_command1_reg = SPI_M_S; + + /* + * SPI_COMMAND1 is written before any child is registered, so a chip + * select with CS_POL_INACTIVE clear stays asserted until + * tegra_spi_setup() runs for that device. Program the level the + * device tree asks for up front instead. + * + * Disabled children are included deliberately. The core never + * registers them, so this is the only chance to park their chip + * select at the level their hardware needs. An enabled child on the + * same CS takes priority. + */ + for_each_child_of_node_scoped(pdev->dev.of_node, np) { + u32 cs; + u32 polarity_bitmask; + u32 cs_polarity = 0; + + if (of_property_read_u32(np, "reg", &cs)) + continue; + + if (cs >= MAX_CHIP_SELECT) + continue; + + polarity_bitmask = SPI_CS_POL_INACTIVE(cs); + if (!of_property_read_bool(np, "spi-cs-high")) + cs_polarity = polarity_bitmask; + + if (of_device_is_available(np)) { + enabled_cs_mask |= polarity_bitmask; + enabled_child_cs_polarity |= cs_polarity; + } else { + if (cs_polarity) + disabled_child_cs_low |= polarity_bitmask; + else + disabled_child_cs_high |= polarity_bitmask; + } + } + conflicting_cs = disabled_child_cs_low & disabled_child_cs_high & ~enabled_cs_mask; + for (u32 cs = 0; cs < MAX_CHIP_SELECT; cs++) { + if (SPI_CS_POL_INACTIVE(cs) & conflicting_cs) + dev_warn(&pdev->dev, + "Conflicting CS polarity on disabled nodes at cs %u\n", cs); + } + + tspi->def_command1_reg |= enabled_child_cs_polarity | + (disabled_child_cs_low & ~enabled_cs_mask); tegra_spi_writel(tspi, tspi->def_command1_reg, SPI_COMMAND1); tspi->spi_cs_timing1 = tegra_spi_readl(tspi, SPI_CS_TIMING1); tspi->spi_cs_timing2 = tegra_spi_readl(tspi, SPI_CS_TIMING2); From e2f4edfc061e493fddde16c4bbe1c4f230f41e4a Mon Sep 17 00:00:00 2001 From: Weibin Liu Date: Wed, 14 Oct 2026 20:39:13 +0800 Subject: [PATCH 133/136] spi: amd: fix len==0 underflow and FIFO overflow amd_spi_fifo_xfer() unconditionally decrements xfer->len when a tx_buf is present. The SPI core does not reject zero-length transfers, so a transfer submitted through the SPI_IOC_MESSAGE ioctl with len == 0 and a tx_buf makes the u32 len wrap to 0xFFFFFFFF, and the following per-byte loop writes far past the FIFO window. The driver also never checks the cumulative FIFO offset against AMD_SPI_FIFO_SIZE. The core splits each transfer down to the controller's max transfer size but does not bound the total message length, so a message of several transfers whose total exceeds the FIFO overflows it with out-of-bounds MMIO writes (tx) and reads (rx). Skip zero-length transfers and reject any transfer that would run past the end of the FIFO window. Tested with a user-space replica of the fifo_xfer length arithmetic (ASAN build): a zero-length transfer with tx_buf makes len-- wrap to 0xffffffff, and skipping zero-length transfers avoids the wrap. No AMD SPI hardware was available, so the FIFO bound check was verified by inspection against AMD_SPI_FIFO_SIZE. Fixes: bbb336f39efc ("spi: spi-amd: Add AMD SPI controller driver support") Cc: stable@vger.kernel.org # 5.8+ Reviewed-by: Guanglei Zhu Signed-off-by: Weibin Liu Link: https://patch.msgid.link/20261014-spi-amd-fifo-fix-v1-1-9fec9ab753e8@xiaopeng.com Signed-off-by: Mark Brown --- drivers/spi/spi-amd.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/drivers/spi/spi-amd.c b/drivers/spi/spi-amd.c index 71a6e5c475b0..ddf89e5353c5 100644 --- a/drivers/spi/spi-amd.c +++ b/drivers/spi/spi-amd.c @@ -322,11 +322,22 @@ static inline int amd_spi_fifo_xfer(struct amd_spi *amd_spi, list_for_each_entry(xfer, &message->transfers, transfer_list) { + /* Skip zero-length transfers to avoid len underflow below */ + if (!xfer->len) + continue; + if (xfer->speed_hz) amd_set_spi_freq(amd_spi, xfer->speed_hz); else amd_set_spi_freq(amd_spi, spi->max_speed_hz); + /* Reject transfers that would overflow the FIFO window */ + if (fifo_pos + xfer->len > + AMD_SPI_FIFO_BASE + AMD_SPI_FIFO_SIZE) { + message->status = -EMSGSIZE; + goto fin_msg; + } + if (xfer->tx_buf) { buf = (u8 *)xfer->tx_buf; if (!tx_len) { @@ -369,6 +380,12 @@ static inline int amd_spi_fifo_xfer(struct amd_spi *amd_spi, list_for_each_entry(xfer, &message->transfers, transfer_list) if (xfer->rx_buf) { + /* Reject transfers that would overflow the FIFO window */ + if (fifo_pos + xfer->len > + AMD_SPI_FIFO_BASE + AMD_SPI_FIFO_SIZE) { + message->status = -EMSGSIZE; + goto fin_msg; + } buf = (u8 *)xfer->rx_buf; /* Read data from FIFO to receive buffer */ for (i = 0; i < xfer->len; i++) From 0b591332a2bdc13d3f9163793131fe3651d1556a Mon Sep 17 00:00:00 2001 From: Weibin Liu Date: Fri, 16 Oct 2026 10:00:36 +0800 Subject: [PATCH 134/136] spi: spi-pci1xxxx: quiesce DMA engines on transfer timeout If a DMA-mapped transfer times out waiting for DMA_TERM and neither DMA abort interrupt arrived, pci1xxxx_spi_transfer_with_dma() returns through the error label while both DMA engines are still enabled, the done/abort interrupt status is still armed and the transfer state (p->xfer, p->tx_sgl, p->rx_sgl) is still registered. The ISRs keep firing afterwards: they reprogram the DMA engines from p->tx_sgl / p->rx_sgl and signal completion through p->xfer and p->spi_xfer_done, all of which the SPI core is free to unmap or reuse once transfer_one() has returned, which is a use-after-free. A late done interrupt can also start the next transfer queued behind the failed one. Stop both DMA engines, drain the pending done/abort interrupt status and synchronize the DMA IRQs before dropping the in-progress state and the transfer pointers, and re-enable the engines afterwards so that subsequent transfers keep working. The engines are only turned back on once both were confirmed to have stopped: re-enabling an engine that failed to stop would cancel the pending stop request and let it keep running into buffers the SPI core is about to unmap or free, which is exactly the window this patch closes. An engine that failed to stop is left disabled, as the abort paths already do. The DMA interrupt status and mask registers are shared between the two SPI instances, so the cleanup drains only this instance's done/abort status bits instead of masking the interrupt lines, which would also suppress the peer instance's completion interrupts. Also make the DMA ISRs ignore interrupts while no transfer is in progress so that a stray MSI after the cleanup can no longer act on the cleared state. No PCI1XXXX hardware was available for testing and QEMU has no model of this PCIe part, so the timeout path was exercised with a userspace replica of the timeout branch and the DMA ISRs, with the register and interrupt accesses mocked: on the unfixed code both a wedged engine that clears after the timeout and a late done interrupt touch the freed transfer state, while with this fix applied neither does and an engine that failed to stop is left disabled. The module was also loaded and unloaded in a QEMU x86_64 guest running a kernel with this fix applied. Fixes: 9538edeb72c9 ("spi: mchp-pci1xxxx: DMA support for copying data to and from SPI Buf") Cc: stable@vger.kernel.org # 6.8+ Signed-off-by: Weibin Liu Link: https://patch.msgid.link/20261016020036.1333548-1-liuwb@xiaopeng.com Signed-off-by: Mark Brown --- drivers/spi/spi-pci1xxxx.c | 71 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/drivers/spi/spi-pci1xxxx.c b/drivers/spi/spi-pci1xxxx.c index af6ed78493e3..c50e6841de52 100644 --- a/drivers/spi/spi-pci1xxxx.c +++ b/drivers/spi/spi-pci1xxxx.c @@ -542,6 +542,8 @@ static int pci1xxxx_spi_transfer_with_dma(struct spi_controller *spi_ctlr, struct pci1xxxx_spi_internal *p = spi_controller_get_devdata(spi_ctlr); struct pci1xxxx_spi *par = p->parent; dma_addr_t tx_dma_addr = 0; + unsigned long flags; + bool engines_stopped; int ret = 0; u32 regval; @@ -618,12 +620,72 @@ static int pci1xxxx_spi_transfer_with_dma(struct spi_controller *spi_ctlr, p->dma_aborted_wr = false; ret = -ECANCELED; } + + /* + * A timed out transfer leaves the DMA engines running and + * the done/abort interrupt status armed with p->xfer, + * p->tx_sgl and p->rx_sgl still registered. Stop the + * engines, drain the pending status and synchronize the + * IRQs before dropping the transfer state, otherwise a + * late interrupt makes the ISRs reprogram the engines + * from or signal completion through memory which the SPI + * core is about to unmap or free. + */ + writel(SPI_DMA_ENGINE_DIS, + par->dma_offset_bar + SPI_DMA_GLOBAL_WR_ENGINE_EN); + writel(SPI_DMA_ENGINE_DIS, + par->dma_offset_bar + SPI_DMA_GLOBAL_RD_ENGINE_EN); + engines_stopped = + !(readl_poll_timeout(par->dma_offset_bar + + SPI_DMA_GLOBAL_WR_ENGINE_EN, + regval, regval == 0x0, 0, + USEC_PER_MSEC) || + readl_poll_timeout(par->dma_offset_bar + + SPI_DMA_GLOBAL_RD_ENGINE_EN, + regval, regval == 0x0, 0, + USEC_PER_MSEC)); + if (!engines_stopped) + dev_warn(&par->dev->dev, + "SPI DMA engines failed to stop\n"); + + spin_lock_irqsave(&par->dma_wr_reg_lock, flags); + writel((SPI_DMA_DONE_INT_MASK(p->hw_inst) | + SPI_DMA_ABORT_INT_MASK(p->hw_inst)), + par->dma_offset_bar + SPI_DMA_INTR_WR_CLR); + spin_unlock_irqrestore(&par->dma_wr_reg_lock, flags); + spin_lock_irqsave(&par->dma_rd_reg_lock, flags); + writel((SPI_DMA_DONE_INT_MASK(p->hw_inst) | + SPI_DMA_ABORT_INT_MASK(p->hw_inst)), + par->dma_offset_bar + SPI_DMA_INTR_RD_CLR); + spin_unlock_irqrestore(&par->dma_rd_reg_lock, flags); + + p->spi_xfer_in_progress = false; + synchronize_irq(p->irq[1]); + synchronize_irq(p->irq[2]); + + /* + * Turn the engines back on only once both are confirmed + * to have stopped: re-enabling an engine that failed to + * stop would cancel the pending stop request and let it + * run into buffers the SPI core is about to unmap or + * free. An engine that failed to stop is left disabled, + * as the abort paths already do. + */ + if (engines_stopped) { + writel(SPI_DMA_ENGINE_EN, + par->dma_offset_bar + SPI_DMA_GLOBAL_WR_ENGINE_EN); + writel(SPI_DMA_ENGINE_EN, + par->dma_offset_bar + SPI_DMA_GLOBAL_RD_ENGINE_EN); + } goto error; } ret = 0; error: p->spi_xfer_in_progress = false; + p->xfer = NULL; + p->tx_sgl = NULL; + p->rx_sgl = NULL; return ret; } @@ -695,6 +757,9 @@ static irqreturn_t pci1xxxx_spi_isr_dma_rd(int irq, void *dev) unsigned long flags; u32 regval; + if (!p->spi_xfer_in_progress) + return IRQ_NONE; + /* Clear the DMA RD INT and start spi xfer*/ regval = readl(p->parent->dma_offset_bar + SPI_DMA_INTR_RD_STS); if (regval) { @@ -723,6 +788,9 @@ static irqreturn_t pci1xxxx_spi_isr_dma_wr(int irq, void *dev) unsigned long flags; u32 regval; + if (!p->spi_xfer_in_progress) + return IRQ_NONE; + /* Clear the DMA WR INT */ regval = readl(p->parent->dma_offset_bar + SPI_DMA_INTR_WR_STS); if (regval) { @@ -755,6 +823,9 @@ static irqreturn_t pci1xxxx_spi_isr_dma(int irq, void *dev) irqreturn_t spi_int_fired = IRQ_NONE; u32 regval; + if (!p->spi_xfer_in_progress) + return IRQ_NONE; + /* Clear the SPI GO_BIT Interrupt */ regval = readl(p->parent->reg_base + SPI_MST_EVENT_REG_OFFSET(p->hw_inst)); if (regval & SPI_INTR) { From 6c59290d62e7dac745a2cbf6a40d2e1f8dedb73f Mon Sep 17 00:00:00 2001 From: Kathiravan Thirumoorthy Date: Fri, 16 Oct 2026 12:53:04 +0530 Subject: [PATCH 135/136] spi: dt-bindings: spi-qpic-snand: Add IPQ9650 compatible The QPIC SPI-NAND flash controller found on IPQ9650 is identical to the one present on IPQ9574. Add the IPQ9650 compatible and use IPQ9574 as the fallback. Signed-off-by: Kathiravan Thirumoorthy Link: https://patch.msgid.link/20261016-ipq9650_spi_nand-v1-1-d341a76559b1@oss.qualcomm.com Signed-off-by: Mark Brown --- Documentation/devicetree/bindings/spi/qcom,spi-qpic-snand.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/spi/qcom,spi-qpic-snand.yaml b/Documentation/devicetree/bindings/spi/qcom,spi-qpic-snand.yaml index 829da22537d4..071e5333721c 100644 --- a/Documentation/devicetree/bindings/spi/qcom,spi-qpic-snand.yaml +++ b/Documentation/devicetree/bindings/spi/qcom,spi-qpic-snand.yaml @@ -28,6 +28,7 @@ properties: - qcom,ipq5210-snand - qcom,ipq5332-snand - qcom,ipq5424-snand + - qcom,ipq9650-snand - const: qcom,ipq9574-snand - const: qcom,ipq9574-snand From 84182c48999f856c3865e4a537c244f9569c9f6c Mon Sep 17 00:00:00 2001 From: Weibin Liu Date: Tue, 13 Oct 2026 13:43:39 +0800 Subject: [PATCH 136/136] spi: ch341: handle transfers without a TX or RX buffer ch341_transfer_one() unconditionally copies from trans->tx_buf into the TX packet and hands trans->rx_buf to usb_bulk_msg() for the readback. The SPI core allows half-duplex transfers where either of the buffers is NULL, so a transfer without TX data crashes the kernel on the memcpy and a transfer without RX data makes usb_bulk_msg() store the received data at address 0. The CH341A clocks data in both directions on every transfer; there is no mode to disable a direction. Set SPI_CONTROLLER_MUST_TX and SPI_CONTROLLER_MUST_RX so the SPI core fills in dummy buffers for half-duplex transfers. Fixes: 8846739f52af ("spi: add ch341a usb2spi driver") Cc: stable@vger.kernel.org # 6.11+ Signed-off-by: Weibin Liu Link: https://patch.msgid.link/20261013054339.77477-1-liuwb@xiaopeng.com Signed-off-by: Mark Brown --- drivers/spi/spi-ch341.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/spi/spi-ch341.c b/drivers/spi/spi-ch341.c index 6448a44a8b67..56a5caa5975a 100644 --- a/drivers/spi/spi-ch341.c +++ b/drivers/spi/spi-ch341.c @@ -189,6 +189,12 @@ static int ch341_probe(struct usb_interface *intf, ctrl->transfer_one = ch341_transfer_one; ctrl->set_cs = ch341_set_cs; ctrl->auto_runtime_pm = false; + /* + * The CH341A SPI_STREAM command clocks data in both directions on + * every transfer, so the driver always needs a TX and an RX buffer; + * let the SPI core supply dummy buffers for half-duplex transfers. + */ + ctrl->flags = SPI_CONTROLLER_MUST_TX | SPI_CONTROLLER_MUST_RX; usb_set_intfdata(intf, ch341);