summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMark Brown <broonie@kernel.org>2026-10-13 18:17:50 +0100
committerMark Brown <broonie@kernel.org>2026-10-13 18:17:50 +0100
commitc506ca085b484fac9d6efe01307941f4e08d2f66 (patch)
tree66408f2806d026ce589fe057bc47a73f7ee06dbe
parentef9158e06988285df74ef68057c26fd38a82e3bb (diff)
parent85b0e19de168d82cbd879a4c8e13fabad5ef4dd8 (diff)
downloadlinux-next-c506ca085b484fac9d6efe01307941f4e08d2f66.tar.gz
linux-next-c506ca085b484fac9d6efe01307941f4e08d2f66.zip
Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/broonie/spi.git
-rw-r--r--Documentation/ABI/testing/sysfs-class-spi-master6
-rw-r--r--Documentation/devicetree/bindings/spi/amlogic,a4-spisg.yaml30
-rw-r--r--Documentation/devicetree/bindings/spi/amlogic,meson-gx-spicc.yaml13
-rw-r--r--Documentation/devicetree/bindings/spi/microchip,pic32mzda-spi.yaml82
-rw-r--r--Documentation/devicetree/bindings/spi/microchip,spi-pic32.txt34
-rw-r--r--Documentation/devicetree/bindings/spi/nuvoton,ma35d1-qspi.yaml21
-rw-r--r--Documentation/devicetree/bindings/spi/qcom,sa8255p-geni-spi.yaml9
-rw-r--r--Documentation/devicetree/bindings/spi/qcom,spi-geni-qcom.yaml9
-rw-r--r--Documentation/devicetree/bindings/spi/realtek,rtd1625-nor.yaml65
-rw-r--r--Documentation/devicetree/bindings/spi/renesas,sh-msiof.yaml9
-rw-r--r--Documentation/devicetree/bindings/spi/spi-davinci.txt100
-rw-r--r--Documentation/devicetree/bindings/spi/spi-peripheral-props.yaml9
-rw-r--r--Documentation/devicetree/bindings/spi/ti,davinci-spi-peripheral-props.yaml25
-rw-r--r--Documentation/devicetree/bindings/spi/ti,davinci-spi.yaml125
-rw-r--r--Documentation/driver-api/driver-model/devres.rst2
-rw-r--r--Documentation/spi/multiple-data-lanes.rst10
-rw-r--r--MAINTAINERS6
-rw-r--r--drivers/spi/Kconfig379
-rw-r--r--drivers/spi/Makefile2
-rw-r--r--drivers/spi/atmel-quadspi.c26
-rw-r--r--drivers/spi/spi-amlogic-spisg.c116
-rw-r--r--drivers/spi/spi-ar934x.c13
-rw-r--r--drivers/spi/spi-at91-usart.c8
-rw-r--r--drivers/spi/spi-atmel.c8
-rw-r--r--drivers/spi/spi-bcm2835.c29
-rw-r--r--drivers/spi/spi-bcm63xx-hsspi.c1
-rw-r--r--drivers/spi/spi-bcm63xx.c2
-rw-r--r--drivers/spi/spi-bitbang.c2
-rw-r--r--drivers/spi/spi-cadence-quadspi.c4
-rw-r--r--drivers/spi/spi-dw-core.c118
-rw-r--r--drivers/spi/spi-dw-dma.c289
-rw-r--r--drivers/spi/spi-dw-mmio.c2
-rw-r--r--drivers/spi/spi-dw.h8
-rw-r--r--drivers/spi/spi-ep93xx.c8
-rw-r--r--drivers/spi/spi-fsl-dspi.c35
-rw-r--r--drivers/spi/spi-fsl-spi.c8
-rw-r--r--drivers/spi/spi-geni-qcom.c114
-rw-r--r--drivers/spi/spi-imx.c12
-rw-r--r--drivers/spi/spi-ingenic.c10
-rw-r--r--drivers/spi/spi-ma35d1-qspi.c64
-rw-r--r--drivers/spi/spi-ma35d1.c716
-rw-r--r--drivers/spi/spi-mem.c14
-rw-r--r--drivers/spi/spi-mtk-nor.c1
-rw-r--r--drivers/spi/spi-mxic.c8
-rw-r--r--drivers/spi/spi-nxp-xspi.c16
-rw-r--r--drivers/spi/spi-omap2-mcspi.c4
-rw-r--r--drivers/spi/spi-orion.c9
-rw-r--r--drivers/spi/spi-pic32.c2
-rw-r--r--drivers/spi/spi-pl022.c19
-rw-r--r--drivers/spi/spi-qpic-snand.c105
-rw-r--r--drivers/spi/spi-realtek-rtl.c36
-rw-r--r--drivers/spi/spi-rockchip-sfc.c21
-rw-r--r--drivers/spi/spi-rspi.c49
-rw-r--r--drivers/spi/spi-rtk-nor.c624
-rw-r--r--drivers/spi/spi-s3c64xx.c2
-rw-r--r--drivers/spi/spi-sh-msiof.c18
-rw-r--r--drivers/spi/spi-stm32.c5
-rw-r--r--drivers/spi/spi-sun4i.c68
-rw-r--r--drivers/spi/spi-sun6i.c2
-rw-r--r--drivers/spi/spi-sunplus-sp7021.c13
-rw-r--r--drivers/spi/spi-tegra210-quad.c509
-rw-r--r--drivers/spi/spi-ti-qspi.c2
-rw-r--r--drivers/spi/spi-topcliff-pch.c4
-rw-r--r--drivers/spi/spi-virtio.c22
-rw-r--r--drivers/spi/spi-xilinx.c2
-rw-r--r--drivers/spi/spi.c19
-rw-r--r--include/linux/spi/spi.h9
-rw-r--r--tools/spi/Makefile2
-rw-r--r--tools/spi/spidev_test.c330
69 files changed, 3543 insertions, 901 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
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 <xianwei.zhao@amlogic.com>
- Sunny Luo <sunny.luo@amlogic.com>
-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:
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
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 <tsbogend@alpha.franken.de>
+
+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 <dt-bindings/interrupt-controller/irq.h>
+ #include <dt-bindings/gpio/gpio.h>
+
+ 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 <fault-irq>, <receive-irq>, <transmit-irq>.
-- 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";
-};
diff --git a/Documentation/devicetree/bindings/spi/nuvoton,ma35d1-qspi.yaml b/Documentation/devicetree/bindings/spi/nuvoton,ma35d1-qspi.yaml
index 40965ec5163b..9ee61f8ac4d6 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 <cwweng@nuvoton.com>
@@ -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,10 @@ examples:
interrupts = <GIC_SPI 57 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clk QSPI0_GATE>;
resets = <&sys MA35D1_RESET_QSPI0>;
+
#address-cells = <1>;
#size-cells = <0>;
};
};
+
+...
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:
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 <jyanchou@realtek.com>
+ - Yu-Chun Lin <eleanor.lin@realtek.com>
+
+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>;
+ };
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
@@ -55,6 +55,11 @@ properties:
- 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
reg:
@@ -100,11 +105,11 @@ properties:
dmas:
minItems: 2
- maxItems: 4
+ maxItems: 6
dma-names:
minItems: 2
- maxItems: 4
+ maxItems: 6
items:
enum: [ tx, rx ]
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 880a9f624566..a0f32565d090 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.
@@ -155,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 <brgl@kernel.org>
+
+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 <brgl@kernel.org>
+
+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>;
+ };
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()
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);
diff --git a/MAINTAINERS b/MAINTAINERS
index e7f407f83be7..cdf2d8f263cc 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -23330,6 +23330,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 <jyanchou@realtek.com>
+M: Yu-Chun Lin <eleanor.lin@realtek.com>
+S: Supported
+F: drivers/spi/spi-rtk-nor.c
+
REALTEK SYSTIMER DRIVER
M: Hao-Wen Ting <haowen.ting@realtek.com>
S: Maintained
diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index a0f37e2df716..49088e5886d3 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
@@ -92,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
@@ -136,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
@@ -153,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
@@ -333,7 +340,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
@@ -388,14 +395,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"
@@ -420,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
@@ -438,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
@@ -463,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
@@ -581,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
@@ -626,73 +667,16 @@ 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
+config SPI_MA35D1
+ tristate "Nuvoton MA35D1 SPI controller"
+ depends on ARCH_MA35 || COMPILE_TEST
help
- Select this option to enable SPI driver for the Intel
- La Jolla Cove Adapter (LJCA) board.
+ This enables support for the SPI controller found in
+ Nuvoton MA35D1 SoCs.
- This driver can also be built as a module. If so, the module
- will be called spi-ljca.
+ 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"
@@ -722,7 +706,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,13 +714,33 @@ 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
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
@@ -773,6 +776,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
@@ -801,13 +817,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"
@@ -838,14 +867,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
@@ -887,7 +908,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 +970,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.
@@ -967,6 +987,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
@@ -1035,12 +1065,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"
@@ -1048,6 +1080,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
@@ -1055,28 +1100,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
@@ -1086,15 +1115,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
@@ -1189,20 +1209,6 @@ config SPI_SYNQUACER
It also supports the new dual-bit and quad-bit SPI protocol.
-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.
-
-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
@@ -1244,6 +1250,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)
@@ -1270,7 +1293,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
@@ -1336,14 +1359,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 SPI_MASTER || 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
#
@@ -1392,15 +1407,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
#
@@ -1431,6 +1437,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
diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
index a18814fbde93..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
@@ -133,6 +134,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/atmel-quadspi.c b/drivers/spi/atmel-quadspi.c
index ec19fa0180f0..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);
@@ -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)
diff --git a/drivers/spi/spi-amlogic-spisg.c b/drivers/spi/spi-amlogic-spisg.c
index 9049a87e9d0f..1b00f34e9e93 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)
@@ -95,7 +101,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
@@ -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;
@@ -175,7 +189,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)
@@ -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,13 +505,16 @@ 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;
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;
+ u32 val, delay = 0;
int ret = -EIO;
if (!aml_spisg_sem_down_read(spisg)) {
@@ -523,15 +548,27 @@ 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))
+ 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 +576,23 @@ 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);
- 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 */
@@ -562,13 +609,16 @@ 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)))
+ 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:
@@ -704,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;
}
@@ -715,6 +767,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 = {
@@ -733,6 +786,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);
@@ -763,6 +817,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);
@@ -841,14 +903,30 @@ 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;
}
+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,
@@ -858,6 +936,10 @@ static const struct of_device_id amlogic_spisg_of_match[] = {
{
.compatible = "amlogic,a4-spisg",
},
+ {
+ .compatible = "amlogic,a9-spisg",
+ .data = &a9_spisg_data,
+ },
{ /* sentinel */ }
};
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);
diff --git a/drivers/spi/spi-at91-usart.c b/drivers/spi/spi-at91-usart.c
index 804f6194c830..aadcdf734fb0 100644
--- a/drivers/spi/spi-at91-usart.c
+++ b/drivers/spi/spi-at91-usart.c
@@ -231,16 +231,16 @@ 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;
- 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 e91e84fdbe8f..ffc896e4621a 100644
--- a/drivers/spi/spi-atmel.c
+++ b/drivers/spi/spi-atmel.c
@@ -871,14 +871,14 @@ 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;
- 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-bcm2835.c b/drivers/spi/spi-bcm2835.c
index 8f8715809c7c..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);
@@ -1297,7 +1300,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;
}
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)
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-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 <linux/spinlock.h>
diff --git a/drivers/spi/spi-cadence-quadspi.c b/drivers/spi/spi-cadence-quadspi.c
index 5a5f1a852cbe..2d5301d82118 100644
--- a/drivers/spi/spi-cadence-quadspi.c
+++ b/drivers/spi/spi-cadence-quadspi.c
@@ -1381,7 +1381,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");
@@ -1397,7 +1397,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-dw-core.c b/drivers/spi/spi-dw-core.c
index 206d3f9dd83d..1f0e93800908 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;
@@ -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);
+ 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;
+ }
+ }
- /*
- * 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);
+ if (dws->dma_mapped) {
+ dw_spi_enh_write_cmd_addr(dws, op, mem);
- dw_spi_enh_irq_setup(dws);
+ ret = dws->dma_ops->dma_enh_mem_transfer(mem, op);
- /* 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);
+ dw_spi_stop_mem_op(dws, mem->spi);
+ } else {
+ dw_spi_enh_write_cmd_addr(dws, op, mem);
- /*
- * 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;
+ dw_spi_enh_irq_setup(dws);
- 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;
- }
+ /* 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);
- ret = dw_spi_wait_mem_op_done(dws);
+ /*
+ * 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;
+ }
- dw_spi_stop_mem_op(dws, mem->spi);
+ ret = dw_spi_wait_mem_op_done(dws);
+
+ dw_spi_stop_mem_op(dws, mem->spi);
+ }
return ret;
}
@@ -1104,12 +1132,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);
@@ -1360,6 +1389,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 f7d848fec9ab..b706770334a6 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;
}
@@ -286,19 +324,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 +705,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;
}
@@ -672,14 +718,180 @@ 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);
+}
+
+/*
+ * 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 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_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 = {
@@ -704,6 +916,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 e62f919f1188..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 {
@@ -227,6 +233,8 @@ struct dw_spi {
u32 txburst;
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;
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-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 = {
diff --git a/drivers/spi/spi-geni-qcom.c b/drivers/spi/spi-geni-qcom.c
index 6c57f8309a3b..91e75e213f92 100644
--- a/drivers/spi/spi-geni-qcom.c
+++ b/drivers/spi/spi-geni-qcom.c
@@ -10,10 +10,13 @@
#include <linux/dmaengine.h>
#include <linux/dma-mapping.h>
#include <linux/dma/qcom-gpi-dma.h>
+#include <linux/gpio/consumer.h>
#include <linux/interrupt.h>
#include <linux/io.h>
+#include <linux/iopoll.h>
#include <linux/log2.h>
#include <linux/module.h>
+#include <linux/panic_notifier.h>
#include <linux/platform_device.h>
#include <linux/pm_opp.h>
#include <linux/pm_runtime.h>
@@ -115,6 +118,8 @@ struct spi_geni_master {
struct dma_chan *rx;
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)
@@ -173,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);
@@ -363,8 +371,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 +398,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 +560,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 +703,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);
@@ -938,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);
@@ -1025,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;
}
@@ -1066,6 +1075,69 @@ 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_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;
@@ -1131,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);
@@ -1154,6 +1232,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);
}
@@ -1241,7 +1328,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),
diff --git a/drivers/spi/spi-imx.c b/drivers/spi/spi-imx.c
index 79a6c1a60b0a..868f963e6137 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
@@ -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-ingenic.c b/drivers/spi/spi-ingenic.c
index adcf85bccbcc..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);
}
@@ -354,8 +351,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;
diff --git a/drivers/spi/spi-ma35d1-qspi.c b/drivers/spi/spi-ma35d1-qspi.c
index 7f938d0c6f1f..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);
}
@@ -633,18 +659,26 @@ 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_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);
@@ -655,14 +689,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[] = {
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 <cwweng@nuvoton.com>
+
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/math.h>
+#include <linux/module.h>
+#include <linux/pm.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/reset.h>
+#include <linux/sizes.h>
+#include <linux/spi/spi.h>
+#include <linux/time.h>
+#include <linux/unaligned.h>
+
+/* 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 <cwweng@nuvoton.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/spi/spi-mem.c b/drivers/spi/spi-mem.c
index 5f973ebfb8b6..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;
@@ -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;
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);
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 = {
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-omap2-mcspi.c b/drivers/spi/spi-omap2-mcspi.c
index a3355147f5de..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);
@@ -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);
}
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;
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-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 @@ err_txdesc:
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-qpic-snand.c b/drivers/spi/spi-qpic-snand.c
index 05efe6313b7f..001e4bd8bb4b 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;
@@ -98,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;
};
@@ -168,8 +166,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;
}
@@ -343,6 +342,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);
/*
@@ -398,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;
@@ -411,6 +417,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 +435,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);
}
@@ -434,9 +443,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;
@@ -500,18 +507,28 @@ 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)
{
- __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);
@@ -563,13 +580,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;
@@ -762,14 +773,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;
@@ -874,14 +878,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;
@@ -962,13 +959,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;
@@ -1204,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;
@@ -1228,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);
@@ -1281,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);
@@ -1303,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 */
@@ -1481,7 +1468,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);
@@ -1579,15 +1566,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;
- const void *dev_data;
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;
@@ -1607,16 +1588,13 @@ static int qcom_spi_probe(struct platform_device *pdev)
snandc->dev = dev;
snandc->qspi = qspi;
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);
@@ -1699,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,
};
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) {
diff --git a/drivers/spi/spi-rockchip-sfc.c b/drivers/spi/spi-rockchip-sfc.c
index 662a994da60b..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);
@@ -743,9 +745,16 @@ static void rockchip_sfc_remove(struct platform_device *pdev)
struct spi_controller *host = sfc->host;
spi_unregister_controller(host);
- dma_unmap_single(&pdev->dev, sfc->dma_buffer, sfc->max_iosize,
- DMA_BIDIRECTIONAL);
- free_pages((unsigned long)sfc->buffer, get_order(sfc->max_iosize));
+ 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);
+ 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);
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 <linux/of.h>
#include <linux/pm_runtime.h>
#include <linux/reset.h>
-#include <linux/sh_dma.h>
#include <linux/spi/spi.h>
#include <linux/spinlock.h>
@@ -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");
diff --git a/drivers/spi/spi-rtk-nor.c b/drivers/spi/spi-rtk-nor.c
new file mode 100644
index 000000000000..75856c31d2e6
--- /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 <linux/bits.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/errno.h>
+#include <linux/iopoll.h>
+#include <linux/minmax.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/reset.h>
+#include <linux/spi/spi.h>
+#include <linux/spi/spi-mem.h>
+#include <linux/types.h>
+
+#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 false;
+
+ 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 <jyanchou@realtek.com>");
+MODULE_LICENSE("GPL");
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-sh-msiof.c b/drivers/spi/spi-sh-msiof.c
index 1aeab7ec0bc8..89fbdde46cfe 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;
@@ -671,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,
@@ -751,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;
@@ -1013,6 +1016,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 */ }
};
@@ -1138,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))
@@ -1174,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);
diff --git a/drivers/spi/spi-stm32.c b/drivers/spi/spi-stm32.c
index be88e62075af..4c3ae546afc0 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);
@@ -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 @@ err_pm_disable:
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)
diff --git a/drivers/spi/spi-sun4i.c b/drivers/spi/spi-sun4i.c
index d5c16392cd4d..59941b38b757 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;
@@ -142,15 +144,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;
@@ -206,7 +214,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;
@@ -271,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;
}
/*
@@ -290,15 +306,15 @@ 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;
+ tfr->effective_speed_hz = mclk_rate / (2 * div_cdr2);
} 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);
+ tfr->effective_speed_hz = mclk_rate / (1 << div);
}
sun4i_spi_write(sspi, SUN4I_CLK_CTL_REG, reg);
@@ -311,18 +327,14 @@ 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 |
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 */
@@ -333,6 +345,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 +353,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 +369,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;
}
@@ -373,7 +384,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 */
@@ -456,6 +467,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;
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-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);
diff --git a/drivers/spi/spi-tegra210-quad.c b/drivers/spi/spi-tegra210-quad.c
index 8ede864c3d3c..d8cca3aa276b 100644
--- a/drivers/spi/spi-tegra210-quad.c
+++ b/drivers/spi/spi-tegra210-quad.c
@@ -191,6 +191,15 @@ struct tegra_qspi {
void __iomem *base;
phys_addr_t phys;
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;
@@ -205,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;
@@ -212,6 +233,7 @@ struct tegra_qspi {
u32 tx_status;
u32 rx_status;
u32 status_reg;
+ u32 trans_status;
bool is_packed;
bool use_dma;
@@ -622,6 +644,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)
@@ -713,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);
@@ -734,9 +773,33 @@ 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;
+ * 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;
+ WRITE_ONCE(qspi->is_curr_dma_xfer, false);
val = qspi->command1_reg;
val |= QSPI_PIO;
tegra_qspi_writel(qspi, val, QSPI_COMMAND1);
@@ -859,6 +922,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) {
@@ -1065,40 +1135,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.
+ *
+ * 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 transfer was completed, -ETIMEDOUT if real timeout
+ * 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;
+ int retval;
+ u32 status, refreshed;
+ u32 lost_fifo_status = 0;
+ u32 lost_tx_status = 0;
+ u32 lost_rx_status = 0;
- /* Check if hardware actually completed the transfer */
- status = tegra_qspi_readl(tqspi, QSPI_TRANS_STATUS);
- if (!(status & QSPI_RDY))
- return -ETIMEDOUT;
+ /*
+ * 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;
+ }
+ }
/*
- * Hardware completed but interrupt was lost/delayed. Manually
- * process the completion by calling the appropriate handler.
+ * 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)
@@ -1232,9 +1468,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 +1587,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 +1742,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 +1788,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 +1824,41 @@ exit:
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);
+ spin_lock_irqsave(&tqspi->lock, flags);
/*
- * 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.
+ * 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.
*/
- spin_lock_irqsave(&tqspi->lock, flags);
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);
/*
@@ -1629,10 +1867,127 @@ static irqreturn_t tegra_qspi_isr_thread(int irq, void *context_data)
* 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);
+}
+
+/**
+ * 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;
+ u32 status_reg, trans_status;
+ u32 tx_status = 0, rx_status = 0;
+
+ if (!READ_ONCE(tqspi->curr_xfer)) {
+ tegra_qspi_mask_clear_irq(tqspi);
+ return IRQ_HANDLED;
+ }
+
+ spin_lock(&tqspi->lock);
+ 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) {
+ 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) {
+ 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);
+ /*
+ * 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;
+ }
+
+ /*
+ * 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);
- return handle_dma_based_xfer(tqspi);
+ spin_unlock(&tqspi->lock);
+
+ return IRQ_HANDLED;
}
static struct tegra_qspi_soc_data tegra210_qspi_soc_data = {
@@ -1800,12 +2155,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 +2181,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 +2196,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);
diff --git a/drivers/spi/spi-ti-qspi.c b/drivers/spi/spi-ti-qspi.c
index fb0d940883fb..091a8b8842f5 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 */
diff --git a/drivers/spi/spi-virtio.c b/drivers/spi/spi-virtio.c
index 3e181bd8bc94..26f9711c99b3 100644
--- a/drivers/spi/spi-virtio.c
+++ b/drivers/spi/spi-virtio.c
@@ -31,10 +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;
- /* Copy of config space max_freq_hz */
- u32 max_freq_hz;
};
static void virtio_spi_msg_done(struct virtqueue *vq)
@@ -162,8 +158,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 */
@@ -258,7 +252,7 @@ msg_done:
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;
@@ -268,18 +262,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,
@@ -304,7 +298,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));
}
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
diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c
index 5b5b3bc5f0d8..40cda0037ea5 100644
--- a/drivers/spi/spi.c
+++ b/drivers/spi/spi.c
@@ -930,6 +930,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
* using SPI_CS_HIGH can't coexist well otherwise...
@@ -1456,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
@@ -2893,7 +2902,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)
@@ -2983,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;
@@ -3077,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);
@@ -3169,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;
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;
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..5bd3159f61a7 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 <avorontsov@ru.mvista.com>
- *
- * Cross-compile with cross-gcc -I/path/to/cross-kernel/include
*/
#include <stdint.h>
@@ -13,6 +11,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <ctype.h>
#include <errno.h>
#include <getopt.h>
#include <fcntl.h>
@@ -22,8 +21,7 @@
#include <sys/stat.h>
#include <linux/types.h>
#include <linux/spi/spidev.h>
-
-#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
+#include <linux/kernel.h>
static void pabort(const char *s)
{
@@ -44,9 +42,16 @@ 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 transfers = 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 predictable;
+static int input_choices;
static uint8_t default_tx[] = {
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
@@ -69,6 +74,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)) {
@@ -92,68 +101,120 @@ 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++;
+
+ 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;
+ }
- src += 4;
- *dst++ = (unsigned char)ch;
- } else {
- *dst++ = *src++;
+ *out++ = '\\';
+ if (!size--)
+ break;
}
- ret++;
+ *out++ = *src++;
}
- return ret;
+ return out - dst;
}
-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,
- };
-
- 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;
+ 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 (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;
+ }
+
+ 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(1), &tr);
- if (ret < 1)
+ ret = ioctl(fd, SPI_IOC_MESSAGE(effective_transfers), tr);
+ free(tr);
+ 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)
+ if (verbose && tx)
hex_dump(tx, len, 32, "TX");
if (output_file) {
@@ -168,19 +229,31 @@ 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 (tx && rx && (compare || 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)
{
- printf("Usage: %s [-2348CDFHILMNORSZbdilopsvw]\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"
" -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"
+ " -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"
@@ -194,8 +267,10 @@ 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"
+ " -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"
" -b --bpw bits per word\n"
@@ -204,6 +279,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);
@@ -218,6 +294,9 @@ 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' },
+ { "no-tx", 0, 0, 't' },
+ { "no-rx", 0, 0, 'r' },
{ "cpha", 0, 0, 'H' },
{ "cpol", 0, 0, 'O' },
{ "rx-cpha-flip", 0, 0, 'F' },
@@ -229,6 +308,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' },
@@ -236,12 +316,14 @@ 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' },
+ { "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:lHOLC3ZFMNR248p:vS:I:",
+ c = getopt_long(argc, argv, "D:s:d:w:b:i:o:lctrHOLC3ZFMNR248p:P:T:vS:zI:",
lopts, NULL);
if (c == -1)
@@ -265,6 +347,7 @@ static void parse_opts(int argc, char *argv[])
break;
case 'i':
input_file = optarg;
+ input_choices++;
break;
case 'o':
output_file = optarg;
@@ -272,6 +355,15 @@ static void parse_opts(int argc, char *argv[])
case 'l':
mode |= SPI_LOOP;
break;
+ case 'c':
+ compare = 1;
+ break;
+ case 't':
+ do_tx = 0;
+ break;
+ case 'r':
+ do_rx = 0;
+ break;
case 'H':
mode |= SPI_CPHA;
break;
@@ -296,6 +388,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;
@@ -307,6 +402,12 @@ static void parse_opts(int argc, char *argv[])
break;
case 'p':
input_tx = optarg;
+ input_choices++;
+ break;
+ case 'P':
+ transfer_size = atoi(optarg);
+ predictable = 1;
+ input_choices++;
break;
case '2':
mode |= SPI_TX_DUAL;
@@ -319,6 +420,11 @@ static void parse_opts(int argc, char *argv[])
break;
case 'S':
transfer_size = atoi(optarg);
+ random_input = 1;
+ input_choices++;
+ break;
+ case 'z':
+ nonzero = 1;
break;
case 'I':
iterations = atoi(optarg);
@@ -341,17 +447,19 @@ 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);
+ size = unescape(str, (char *)tx, size);
transfer(fd, tx, rx, size);
free(rx);
free(tx);
@@ -363,7 +471,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");
@@ -376,9 +484,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)
@@ -409,33 +520,40 @@ 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++) {
+ 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));
+ }
+ }
+ }
- 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 (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);
- }
- }
+ if (do_tx)
+ _write_count += len;
+ if (do_rx)
+ _read_count += len;
free(rx);
free(tx);
@@ -449,8 +567,36 @@ 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), -P (--predictable) may be selected, "
+ "and each may be specified only once");
+
+ 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) and -P (--predictable)");
+
+ if (nonzero && transfer_size < 0)
+ 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)");
+
+ 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) 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)");
+
+ 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)
@@ -507,7 +653,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);
@@ -526,7 +672,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);