summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMark Brown <broonie@kernel.org>2026-09-30 13:15:45 +0100
committerMark Brown <broonie@kernel.org>2026-09-30 13:15:45 +0100
commitd366f5b1dbc9ab26c4575690274dd8f6412805b0 (patch)
treebb0d8666fe947554af5b3f8b7767253aec3842c7
parent3093a9e852b21bf277b4a7b2689d439a24396a26 (diff)
parent168b675c187d40141944d76cedee8a1061beba68 (diff)
downloadlinux-next-d366f5b1dbc9ab26c4575690274dd8f6412805b0.tar.gz
linux-next-d366f5b1dbc9ab26c4575690274dd8f6412805b0.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/renesas,sh-msiof.yaml9
-rw-r--r--Documentation/devicetree/bindings/spi/spi-peripheral-props.yaml8
-rw-r--r--Documentation/spi/multiple-data-lanes.rst10
-rw-r--r--drivers/spi/Kconfig364
-rw-r--r--drivers/spi/spi-amlogic-spisg.c116
-rw-r--r--drivers/spi/spi-ar934x.c13
-rw-r--r--drivers/spi/spi-bcm2835.c4
-rw-r--r--drivers/spi/spi-bcm63xx.c2
-rw-r--r--drivers/spi/spi-dw-core.c15
-rw-r--r--drivers/spi/spi-geni-qcom.c93
-rw-r--r--drivers/spi/spi-imx.c2
-rw-r--r--drivers/spi/spi-ingenic.c5
-rw-r--r--drivers/spi/spi-ma35d1-qspi.c27
-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-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-qpic-snand.c104
-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-s3c64xx.c2
-rw-r--r--drivers/spi/spi-sh-msiof.c6
-rw-r--r--drivers/spi/spi-stm32.c2
-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-virtio.c22
-rw-r--r--drivers/spi/spi-xilinx.c2
-rw-r--r--drivers/spi/spi.c15
-rw-r--r--include/linux/spi/spi.h9
-rw-r--r--tools/spi/Makefile2
-rw-r--r--tools/spi/spidev_test.c330
40 files changed, 1392 insertions, 603 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/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-peripheral-props.yaml b/Documentation/devicetree/bindings/spi/spi-peripheral-props.yaml
index 880a9f624566..afcf9c41c058 100644
--- a/Documentation/devicetree/bindings/spi/spi-peripheral-props.yaml
+++ b/Documentation/devicetree/bindings/spi/spi-peripheral-props.yaml
@@ -91,6 +91,14 @@ properties:
The delay from the default sample time before the actual
sample of the rxd input signal occurs.
+ This describes the board rather than the peripheral, namely the extra
+ delay this particular design needs, for example because of the flight time
+ of the clock and data signals between controller and peripheral.
+
+ Timing parameters of the peripheral itself, such as its
+ clock-to-output-valid time, are the same on every board using that chip
+ and should be described with the chip.
+
spi-tx-bus-width:
description:
Bus width to the SPI bus used for write transfers.
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/drivers/spi/Kconfig b/drivers/spi/Kconfig
index a0f37e2df716..357cebef9917 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,74 +667,6 @@ config SPI_LP8841_RTC
Say N here unless you plan to run the kernel on an ICP DAS
LP-8x4x industrial computer.
-config SPI_MPC52xx
- tristate "Freescale MPC52xx SPI (non-PSC) controller support"
- depends on PPC_MPC52xx
- help
- This drivers supports the MPC52xx SPI controller in master SPI
- mode.
-
-config SPI_MPC52xx_PSC
- tristate "Freescale MPC52xx PSC SPI controller"
- depends on PPC_MPC52xx
- help
- This enables using the Freescale MPC52xx Programmable Serial
- Controller in master SPI mode.
-
-config SPI_MPC512x_PSC
- tristate "Freescale MPC512x PSC SPI controller"
- depends on PPC_MPC512x
- help
- This enables using the Freescale MPC5121 Programmable Serial
- Controller in SPI master mode.
-
-config SPI_FSL_LIB
- tristate
- depends on OF
-
-config SPI_FSL_CPM
- tristate
- depends on FSL_SOC
-
-config SPI_FSL_SPI
- tristate "Freescale SPI controller and Aeroflex Gaisler GRLIB SPI controller"
- depends on OF
- select SPI_FSL_LIB
- select SPI_FSL_CPM if FSL_SOC
- help
- This enables using the Freescale SPI controllers in master mode.
- MPC83xx platform uses the controller in cpu mode or CPM/QE mode.
- MPC8569 uses the controller in QE mode, MPC8610 in cpu mode.
- This also enables using the Aeroflex Gaisler GRLIB SPI controller in
- master mode.
-
-config SPI_FSL_DSPI
- tristate "Freescale DSPI controller"
- select REGMAP_MMIO
- depends on ARCH_MXC || ARCH_NXP || M5441x || COMPILE_TEST
- help
- This enables support for the Freescale DSPI controller in master
- mode. S32, VF610, LS1021A and ColdFire platforms uses the controller.
-
-config SPI_FSL_ESPI
- tristate "Freescale eSPI controller"
- depends on FSL_SOC
- help
- This enables using the Freescale eSPI controllers in master mode.
- From MPC8536, 85xx platform uses the controller, and all P10xx,
- P20xx, P30xx,P40xx, P50xx uses this controller.
-
-config SPI_LJCA
- tristate "Intel La Jolla Cove Adapter SPI support"
- depends on USB_LJCA
- default USB_LJCA
- help
- Select this option to enable SPI driver for the Intel
- La Jolla Cove Adapter (LJCA) board.
-
- This driver can also be built as a module. If so, the module
- will be called spi-ljca.
-
config SPI_MA35D1_QSPI
tristate "Nuvoton MA35D1 QSPI controller"
depends on ARCH_MA35 || COMPILE_TEST
@@ -722,7 +695,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 +703,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 +765,19 @@ config SPI_MTK_SNFI
is implemented as a SPI-MEM controller with pipelined ECC
capability.
+config SPI_MXIC
+ tristate "Macronix MX25F0A SPI controller"
+ imply MTD_NAND_ECC_MXIC
+ help
+ This selects the Macronix MX25F0A SPI controller driver.
+
+config SPI_MXS
+ tristate "Freescale MXS SPI controller"
+ depends on ARCH_MXS
+ select STMP_DEVICE
+ help
+ SPI driver for Freescale MXS devices.
+
config SPI_WPCM_FIU
tristate "Nuvoton WPCM450 Flash Interface Unit"
depends on ARCH_NPCM || COMPILE_TEST
@@ -801,13 +806,26 @@ config SPI_NPCM_PSPI
This driver provides support for Nuvoton NPCM BMC
Peripheral SPI controller in master mode.
-config SPI_LANTIQ_SSC
- tristate "Lantiq SSC SPI controller"
- depends on LANTIQ || X86 || COMPILE_TEST
+config SPI_NXP_FLEXSPI
+ tristate "NXP Flex SPI controller"
+ depends on ARCH_LAYERSCAPE || ARCH_MXC || COMPILE_TEST
+ depends on HAS_IOMEM
help
- This driver supports the Lantiq SSC SPI controller in master
- mode. This controller is found on Intel (former Lantiq) SoCs like
- the Danube, Falcon, xRX200, xRX300, Lightning Mountain.
+ This enables support for the Flex SPI controller in master mode.
+ Up to four slave devices can be connected on two buses with two
+ chipselects each.
+ This controller does not support generic SPI messages and only
+ supports the high-level SPI memory interface.
+
+config SPI_NXP_XSPI
+ tristate "NXP xSPI controller"
+ depends on ARCH_MXC || COMPILE_TEST
+ depends on HAS_IOMEM
+ help
+ This enables support for the xSPI controller. Up to two devices
+ can be connected to one host.
+ This controller does not support generic SPI messages and only
+ supports the high-level SPI memory interface.
config SPI_OC_TINY
tristate "OpenCores tiny SPI"
@@ -838,14 +856,6 @@ config SPI_OMAP24XX
SPI master controller for OMAP24XX and later Multichannel SPI
(McSPI) modules.
-config SPI_TI_QSPI
- tristate "DRA7xxx QSPI controller support"
- depends on ARCH_OMAP2PLUS || COMPILE_TEST
- help
- QSPI master controller for DRA7xxx used for flash devices.
- This device supports single, dual and quad read support, while
- it only supports single write mode.
-
config SPI_ORION
tristate "Orion SPI master"
depends on PLAT_ORION || ARCH_MVEBU || COMPILE_TEST
@@ -887,7 +897,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 +959,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.
@@ -1035,12 +1044,14 @@ config SPI_SC18IS602
help
SPI driver for NXP SC18IS602/602B/603 I2C to SPI bridge.
-config SPI_SH_MSIOF
- tristate "SuperH MSIOF SPI controller"
- depends on HAVE_CLK
- depends on ARCH_SHMOBILE || ARCH_RENESAS || COMPILE_TEST
+config SPI_SG2044_NOR
+ tristate "SG2044 SPI NOR Controller"
+ depends on ARCH_SOPHGO || COMPILE_TEST
help
- SPI driver for SuperH and SH Mobile MSIOF blocks.
+ This enables support for the SG2044 SPI NOR controller,
+ which supports Dual/Quad read and write operations while
+ also supporting 3Byte address devices and 4Byte address
+ devices.
config SPI_SH
tristate "SuperH SPI controller"
@@ -1048,6 +1059,19 @@ config SPI_SH
help
SPI driver for SuperH SPI blocks.
+config SPI_SH_HSPI
+ tristate "SuperH HSPI controller"
+ depends on ARCH_RENESAS || COMPILE_TEST
+ help
+ SPI driver for SuperH HSPI blocks.
+
+config SPI_SH_MSIOF
+ tristate "SuperH MSIOF SPI controller"
+ depends on HAVE_CLK
+ depends on ARCH_SHMOBILE || ARCH_RENESAS || COMPILE_TEST
+ help
+ SPI driver for SuperH and SH Mobile MSIOF blocks.
+
config SPI_SH_SCI
tristate "SuperH SCI SPI controller"
depends on SUPERH
@@ -1055,28 +1079,12 @@ config SPI_SH_SCI
help
SPI driver for SuperH SCI blocks.
-config SPI_SH_HSPI
- tristate "SuperH HSPI controller"
- depends on ARCH_RENESAS || COMPILE_TEST
- help
- SPI driver for SuperH HSPI blocks.
-
config SPI_SIFIVE
tristate "SiFive SPI controller"
depends on HAS_IOMEM
help
This exposes the SPI controller IP from SiFive.
-config SPI_SLAVE_MT27XX
- tristate "MediaTek SPI slave device"
- depends on ARCH_MEDIATEK || COMPILE_TEST
- depends on SPI_SLAVE
- help
- This selects the MediaTek(R) SPI slave device driver.
- If you want to use MediaTek(R) SPI slave interface,
- say Y or M here.If you are not sure, say N.
- SPI slave drivers for Mediatek MT27XX series ARM SoCs.
-
config SPI_SN_F_OSPI
tristate "Socionext F_OSPI SPI flash controller"
depends on OF && HAS_IOMEM
@@ -1086,15 +1094,6 @@ config SPI_SN_F_OSPI
for connecting an SPI Flash memory over up to 8-bit wide bus.
It supports indirect access mode only.
-config SPI_SG2044_NOR
- tristate "SG2044 SPI NOR Controller"
- depends on ARCH_SOPHGO || COMPILE_TEST
- help
- This enables support for the SG2044 SPI NOR controller,
- which supports Dual/Quad read and write operations while
- also supporting 3Byte address devices and 4Byte address
- devices.
-
config SPI_SPACEMIT_K1
tristate "K1 SPI Controller"
depends on ARCH_SPACEMIT || COMPILE_TEST
@@ -1189,20 +1188,6 @@ config SPI_SYNQUACER
It also supports the new dual-bit and quad-bit SPI protocol.
-config SPI_MXIC
- tristate "Macronix MX25F0A SPI controller"
- 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 +1229,23 @@ config SPI_THUNDERX
SPI host driver for the hardware found on Cavium ThunderX
SOCs.
+config SPI_TI_QSPI
+ tristate "DRA7xxx QSPI controller support"
+ depends on ARCH_OMAP2PLUS || COMPILE_TEST
+ help
+ QSPI master controller for DRA7xxx used for flash devices.
+ This device supports single, dual and quad read support, while
+ it only supports single write mode.
+
+config SPI_TLE62X0
+ tristate "Infineon TLE62X0 (for power switching)"
+ depends on SYSFS
+ help
+ SPI driver for Infineon TLE62X0 series line driver chips,
+ such as the TLE6220, TLE6230 and TLE6240. This provides a
+ sysfs interface, with each line presented as a kind of GPIO
+ exposing both switch control and diagnostic feedback.
+
config SPI_TOPCLIFF_PCH
tristate "Intel EG20T PCH/LAPIS Semicon IOH(ML7213/ML7223/ML7831) SPI"
depends on PCI && (X86_32 || MIPS || COMPILE_TEST)
@@ -1270,7 +1272,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 +1338,6 @@ config SPI_ZYNQMP_GQSPI
Enables Xilinx GQSPI controller driver for Zynq UltraScale+ MPSoC.
This controller only supports SPI memory interface.
-config SPI_AMD
- tristate "AMD SPI controller"
- depends on PCI
- depends on 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 +1386,6 @@ config SPI_LOOPBACK_TEST
primarily used for development of spi_master drivers
and to detect regressions
-config SPI_TLE62X0
- tristate "Infineon TLE62X0 (for power switching)"
- depends on SYSFS
- help
- SPI driver for Infineon TLE62X0 series line driver chips,
- such as the TLE6220, TLE6230 and TLE6240. This provides a
- sysfs interface, with each line presented as a kind of GPIO
- exposing both switch control and diagnostic feedback.
-
#
# Add new SPI protocol masters in alphabetical order above this line
#
@@ -1431,6 +1416,15 @@ config SPI_SLAVE_SYSTEM_CONTROL
SPI slave handler to allow remote control of system reboot, power
off, halt, and suspend.
+config SPI_SLAVE_MT27XX
+ tristate "MediaTek SPI slave device"
+ depends on ARCH_MEDIATEK || COMPILE_TEST
+ help
+ This selects the MediaTek(R) SPI slave device driver.
+ If you want to use MediaTek(R) SPI slave interface,
+ say Y or M here.If you are not sure, say N.
+ SPI slave drivers for Mediatek MT27XX series ARM SoCs.
+
endif # SPI_SLAVE
config SPI_DYNAMIC
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-bcm2835.c b/drivers/spi/spi-bcm2835.c
index 8f8715809c7c..23606967f7d1 100644
--- a/drivers/spi/spi-bcm2835.c
+++ b/drivers/spi/spi-bcm2835.c
@@ -1297,7 +1297,9 @@ static int bcm2835_spi_setup(struct spi_device *spi)
}
for (i = 0; i < ARRAY_SIZE(pinctrl_compats); i++) {
- if (of_find_compatible_node(NULL, NULL, pinctrl_compats[i]))
+ struct device_node *np __free(device_node) =
+ of_find_compatible_node(NULL, NULL, pinctrl_compats[i]);
+ if (np)
break;
}
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-dw-core.c b/drivers/spi/spi-dw-core.c
index 206d3f9dd83d..ed6e688cfafb 100644
--- a/drivers/spi/spi-dw-core.c
+++ b/drivers/spi/spi-dw-core.c
@@ -275,7 +275,7 @@ static irqreturn_t dw_spi_irq(int irq, void *dev_id)
{
struct spi_controller *ctlr = dev_id;
struct dw_spi *dws = spi_controller_get_devdata(ctlr);
- u16 irq_status = dw_readl(dws, DW_SPI_ISR) & DW_SPI_INT_MASK;
+ u16 irq_status = dw_readl(dws, DW_SPI_ISR);
if (!irq_status)
return IRQ_NONE;
@@ -1104,12 +1104,13 @@ static int dw_spi_setup(struct spi_device *spi)
if (!chip)
return -ENOMEM;
spi_set_ctldata(spi, chip);
- /* Get specific / default rx-sample-delay */
- if (device_property_read_u32(&spi->dev,
- "rx-sample-delay-ns",
- &rx_sample_dly_ns) != 0)
- /* Use default controller value */
- rx_sample_dly_ns = dws->def_rx_sample_dly_ns;
+ /*
+ * Use the per-device value the core parsed from the peripheral
+ * node, and fall back to the controller-wide default when the
+ * device does not ask for a delay of its own.
+ */
+ rx_sample_dly_ns = spi->rx_sample_delay_ns ?:
+ dws->def_rx_sample_dly_ns;
chip->rx_sample_dly = DIV_ROUND_CLOSEST(rx_sample_dly_ns,
NSEC_PER_SEC /
dws->max_freq);
diff --git a/drivers/spi/spi-geni-qcom.c b/drivers/spi/spi-geni-qcom.c
index 6c57f8309a3b..bced6279a062 100644
--- a/drivers/spi/spi-geni-qcom.c
+++ b/drivers/spi/spi-geni-qcom.c
@@ -12,8 +12,10 @@
#include <linux/dma/qcom-gpi-dma.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 +117,7 @@ struct spi_geni_master {
struct dma_chan *rx;
int cur_xfer_mode;
const struct geni_spi_desc *dev_data;
+ struct notifier_block panic_nb;
};
static void spi_slv_setup(struct spi_geni_master *mas)
@@ -363,8 +366,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 +393,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 +555,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 +698,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);
@@ -1066,6 +1064,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;
@@ -1154,6 +1215,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 +1311,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..6f9686df0486 100644
--- a/drivers/spi/spi-imx.c
+++ b/drivers/spi/spi-imx.c
@@ -673,7 +673,7 @@ static int mx51_ecspi_prepare_message(struct spi_imx_data *spi_imx,
* propagate into the hardware. It takes exactly one tick of the
* SCLK clock, but we will wait two SCLK clock just to be sure. The
* effect of the delay it takes for the hardware to apply changes
- * is noticable if the SCLK clock run very slow. In such a case, if
+ * is noticeable if the SCLK clock run very slow. In such a case, if
* the polarity of SCLK should be inverted, the GPIO ChipSelect might
* be asserted before the SCLK polarity changes, which would disrupt
* the SPI communication as the device on the other end would consider
diff --git a/drivers/spi/spi-ingenic.c b/drivers/spi/spi-ingenic.c
index adcf85bccbcc..7ce4fe965cac 100644
--- a/drivers/spi/spi-ingenic.c
+++ b/drivers/spi/spi-ingenic.c
@@ -354,8 +354,11 @@ static int spi_ingenic_request_dma(struct spi_controller *ctlr,
ctlr->dma_tx = chan;
chan = dma_request_chan(dev, "rx");
- if (IS_ERR(chan))
+ if (IS_ERR(chan)) {
+ dma_release_channel(ctlr->dma_tx);
+ ctlr->dma_tx = NULL;
return PTR_ERR(chan);
+ }
ctlr->dma_rx = chan;
ctlr->can_dma = spi_ingenic_can_dma;
diff --git a/drivers/spi/spi-ma35d1-qspi.c b/drivers/spi/spi-ma35d1-qspi.c
index 7f938d0c6f1f..30e3239f777d 100644
--- a/drivers/spi/spi-ma35d1-qspi.c
+++ b/drivers/spi/spi-ma35d1-qspi.c
@@ -633,18 +633,23 @@ static int nuvoton_qspi_probe(struct platform_device *pdev)
return dev_err_probe(dev, ret, "failed to deassert reset\n");
ret = device_property_read_u32(dev, "num-cs", &num_cs);
- if (ret && ret != -EINVAL)
- return dev_err_probe(dev, ret, "failed to read num-cs\n");
+ if (ret && ret != -EINVAL) {
+ ret = dev_err_probe(dev, ret, "failed to read num-cs\n");
+ goto err_assert;
+ }
- if (!num_cs || num_cs > NUVOTON_QSPI_MAX_NUM_CS)
- return dev_err_probe(dev, -EINVAL, "invalid num-cs %u\n",
+ if (!num_cs || num_cs > NUVOTON_QSPI_MAX_NUM_CS) {
+ ret = dev_err_probe(dev, -EINVAL, "invalid num-cs %u\n",
num_cs);
+ goto err_assert;
+ }
ctlr->num_chipselect = num_cs;
ctlr->max_transfer_size = nuvoton_qspi_max_transfer_size;
ctlr->max_message_size = nuvoton_qspi_max_message_size;
ctlr->mem_ops = &nuvoton_qspi_mem_ops;
ctlr->mem_caps = &nuvoton_qspi_mem_caps;
+ ctlr->dtr_caps = true;
ctlr->set_cs = nuvoton_qspi_set_cs;
ctlr->transfer_one = nuvoton_qspi_transfer_one;
ctlr->bits_per_word_mask = SPI_BPW_MASK(8);
@@ -655,14 +660,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-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-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-qpic-snand.c b/drivers/spi/spi-qpic-snand.c
index 05efe6313b7f..00072c44db7a 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);
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-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..90a671e9de70 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;
@@ -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 */ }
};
diff --git a/drivers/spi/spi-stm32.c b/drivers/spi/spi-stm32.c
index be88e62075af..5f5c43ae5096 100644
--- a/drivers/spi/spi-stm32.c
+++ b/drivers/spi/spi-stm32.c
@@ -515,7 +515,7 @@ static int stm32h7_spi_get_bpw_mask(struct stm32_spi *spi)
/*
* The most significant bit at DSIZE bit field is reserved when the
- * maximum data size of periperal instances is limited to 16-bit
+ * maximum data size of peripheral instances is limited to 16-bit
*/
stm32_spi_set_bits(spi, STM32H7_SPI_CFG1, STM32H7_SPI_CFG1_DSIZE);
diff --git a/drivers/spi/spi-sun6i.c b/drivers/spi/spi-sun6i.c
index 4631e9c7ca1d..6b7a05dd0de8 100644
--- a/drivers/spi/spi-sun6i.c
+++ b/drivers/spi/spi-sun6i.c
@@ -386,7 +386,7 @@ static int sun6i_spi_transfer_one(struct spi_controller *host,
* SPI_CLK = MOD_CLK / (2 ^ cdr)
* Or we can use CDR2, which is calculated with the formula:
* SPI_CLK = MOD_CLK / (2 * (cdr + 1))
- * Wether we use the former or the latter is set through the
+ * Whether we use the former or the latter is set through the
* DRS bit.
*
* First try CDR2, and if we can't reach the expected
diff --git a/drivers/spi/spi-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-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..015d93680e5d 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)
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);