mirror of
https://github.com/torvalds/linux.git
synced 2026-11-07 06:17:02 +01:00
spi: Updates for v7.4
Quite a lot of work in the SPI subsystem this time around, almost all in
drivers rather than the core. As well as the general cleanup/fix work
we're seeing over most of the kernel there's been some substantial
performance work to two existing drivers and support for a number of new
devices including a couple of new drivers. There was also a big
overhaul of the spidev_test utility from the Pengutronix people.
- Improvements for spidev_test to cover additional use cases, plus be a
bit easier to use.
- Substantial performance work in both DesignWare and Tegra210 QSPI
drivers, making their interrupt handling more efficient to drive up
hardware utilisation.
- Documentation clarifications.
- Fixes for some runtime PM and error handling issues.
- Support for Nuvoton MA35D1, Qualcomm QPIC SNAND v2, Renesas R-Car
Gen5 and X5H and Realtek RTD1625.
Some of these updates are also present in my ASoC pull request to
Takashi, b4's base selection logic picked a SPI commit for an ASoC
branch and I didn't notice until far too late after I'd already merged
the ASoC branch and stacked further work on it.
-----BEGIN PGP SIGNATURE-----
iHUEABYKAB0WIQReagvpf3TrrA7BoYYv73Ows9A4iQUCatZPeAAKCRAv73Ows9A4
iel5AQDNHadaO57QtkGtz6oa8dyDOtkDPbnkKfN8ffohRWGV6AD8DSGLh0R+MOlK
aIV6jUtdFqDuPm+F4jBAvWiUezvnRAI=
=7xi4
-----END PGP SIGNATURE-----
Merge tag 'spi-v7.4' of git://git.kernel.org/pub/scm/linux/kernel/git/broonie/spi
Pull spi updates from Mark Brown:
"Quite a lot of work in the SPI subsystem this time around, almost all
in drivers rather than the core.
As well as the general cleanup/fix work we're seeing over most of the
kernel there's been some substantial performance work to two existing
drivers and support for a number of new devices including a couple of
new drivers. There was also a big overhaul of the spidev_test utility
from the Pengutronix people.
- Improvements for spidev_test to cover additional use cases, plus be
a bit easier to use.
- Substantial performance work in both DesignWare and Tegra210 QSPI
drivers, making their interrupt handling more efficient to drive up
hardware utilisation.
- Documentation clarifications.
- Fixes for some runtime PM and error handling issues.
- Support for Nuvoton MA35D1, Qualcomm QPIC SNAND v2, Renesas R-Car
Gen5 and X5H and Realtek RTD1625"
* tag 'spi-v7.4' of git://git.kernel.org/pub/scm/linux/kernel/git/broonie/spi: (136 commits)
spi: ch341: handle transfers without a TX or RX buffer
spi: dt-bindings: spi-qpic-snand: Add IPQ9650 compatible
spi: spi-pci1xxxx: quiesce DMA engines on transfer timeout
spi: amd: fix len==0 underflow and FIFO overflow
spi: tegra114: initialize native chip selects inactive
spi: bcm-qspi: reject oversized dummy bytes in mspi exec_mem_op
spi: npcm-fiu: limit UMA data_size to the 16-byte staging buffer
spi: omap2-mcspi: handle DMA setup failures
spi: jcore: fix divide-by-zero when speed_hz is zero
spi: cadence-xspi: fix stack buffer overflow in the Marvell b0 path
spi: cadence-xspi: reject SDMA transfers larger than the requested length
spi: meson-spicc: fix invalid DMA unmap on mapping failure
spi: s3c64xx: Use snprintf() instead of sprintf()
spi: omap2-mcspi: Use snprintf() instead of sprintf()
spi: tle62x0: Use kstrtoul() instead of simple_strtoul()
spi: lm70llp: Use strscpy() instead of strcpy()
spi: butterfly: Use strscpy() instead of strcpy()
spi: jcore: Use devm_platform_ioremap_resource()
spi: sh-hspi: Use devm_platform_ioremap_resource()
spi: ljca: validate the response length before copying the data
...
This commit is contained in:
commit
6ef8f742f9
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
};
|
||||
|
|
@ -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";
|
||||
};
|
||||
|
|
@ -4,7 +4,17 @@
|
|||
$id: http://devicetree.org/schemas/spi/nuvoton,ma35d1-qspi.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Nuvoton MA35D1 Quad SPI Controller
|
||||
title: Nuvoton MA35D1 SPI and Quad SPI Controllers
|
||||
|
||||
description: |
|
||||
The Nuvoton MA35D1 SoC contains separate SPI and QSPI controller IPs.
|
||||
Both controllers expose the same Devicetree-visible resources and provide
|
||||
two native chip-select signals. GPIO chip selects are supported through the
|
||||
generic SPI controller binding.
|
||||
|
||||
The QSPI controller additionally supports dual, quad and DTR transfers and
|
||||
a target-mode timeout function. These capability differences do not require
|
||||
additional controller properties.
|
||||
|
||||
maintainers:
|
||||
- Chi-Wen Weng <cwweng@nuvoton.com>
|
||||
|
|
@ -14,7 +24,9 @@ allOf:
|
|||
|
||||
properties:
|
||||
compatible:
|
||||
const: nuvoton,ma35d1-qspi
|
||||
enum:
|
||||
- nuvoton,ma35d1-spi
|
||||
- nuvoton,ma35d1-qspi
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
|
@ -33,8 +45,6 @@ properties:
|
|||
maximum: 2
|
||||
default: 2
|
||||
|
||||
cs-gpios: false
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
|
|
@ -59,7 +69,10 @@ examples:
|
|||
interrupts = <GIC_SPI 57 IRQ_TYPE_LEVEL_HIGH>;
|
||||
clocks = <&clk QSPI0_GATE>;
|
||||
resets = <&sys MA35D1_RESET_QSPI0>;
|
||||
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
};
|
||||
};
|
||||
|
||||
...
|
||||
|
|
|
|||
|
|
@ -39,6 +39,12 @@ properties:
|
|||
- const: power
|
||||
- const: perf
|
||||
|
||||
ready-gpios:
|
||||
description:
|
||||
GPIO used by the SPI target to notify the SPI master when it is
|
||||
ready to service a transfer. Only applicable in target (slave) mode.
|
||||
maxItems: 1
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
|
|
@ -46,6 +52,9 @@ required:
|
|||
- power-domains
|
||||
- power-domain-names
|
||||
|
||||
dependencies:
|
||||
ready-gpios: [ spi-slave ]
|
||||
|
||||
allOf:
|
||||
- $ref: /schemas/spi/spi-controller.yaml#
|
||||
|
||||
|
|
|
|||
|
|
@ -67,6 +67,12 @@ properties:
|
|||
reg:
|
||||
maxItems: 1
|
||||
|
||||
ready-gpios:
|
||||
description:
|
||||
GPIO used by the SPI target to notify the SPI master when it is
|
||||
ready to service a transfer. Only applicable in target (slave) mode.
|
||||
maxItems: 1
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- clocks
|
||||
|
|
@ -74,6 +80,9 @@ required:
|
|||
- interrupts
|
||||
- reg
|
||||
|
||||
dependencies:
|
||||
ready-gpios: [ spi-slave ]
|
||||
|
||||
unevaluatedProperties: false
|
||||
|
||||
examples:
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ properties:
|
|||
- qcom,ipq5210-snand
|
||||
- qcom,ipq5332-snand
|
||||
- qcom,ipq5424-snand
|
||||
- qcom,ipq9650-snand
|
||||
- const: qcom,ipq9574-snand
|
||||
- const: qcom,ipq9574-snand
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,65 @@
|
|||
# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/spi/realtek,rtd1625-nor.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Realtek RTD1625 SPI NOR Flash Controller
|
||||
|
||||
maintainers:
|
||||
- Jyan Chou <jyanchou@realtek.com>
|
||||
- Yu-Chun Lin <eleanor.lin@realtek.com>
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
enum:
|
||||
- realtek,rtd1625-nor
|
||||
|
||||
reg:
|
||||
maxItems: 3
|
||||
|
||||
reg-names:
|
||||
items:
|
||||
- const: ctrl
|
||||
- const: dma
|
||||
- const: dirmap
|
||||
|
||||
clocks:
|
||||
maxItems: 1
|
||||
|
||||
resets:
|
||||
maxItems: 1
|
||||
|
||||
"#address-cells":
|
||||
const: 1
|
||||
|
||||
"#size-cells":
|
||||
const: 0
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- reg-names
|
||||
- clocks
|
||||
- resets
|
||||
- "#address-cells"
|
||||
- "#size-cells"
|
||||
|
||||
allOf:
|
||||
- $ref: /schemas/spi/spi-controller.yaml#
|
||||
|
||||
unevaluatedProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
spi@14b000 {
|
||||
compatible = "realtek,rtd1625-nor";
|
||||
reg = <0x14b000 0x50>, <0x14bf00 0x30>, <0x88100000 0x2000000>;
|
||||
reg-names = "ctrl", "dma", "dirmap";
|
||||
clocks = <&cc 18>;
|
||||
resets = <&cc 26>;
|
||||
pinctrl-0 = <&spi_pins>;
|
||||
pinctrl-names = "default";
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
};
|
||||
|
|
@ -54,6 +54,11 @@ properties:
|
|||
- renesas,msiof-r8a779h0 # R-Car V4M
|
||||
- const: renesas,rcar-gen4-msiof # generic R-Car Gen4
|
||||
# compatible device
|
||||
- items:
|
||||
- enum:
|
||||
- renesas,msiof-r8a78000 # R-Car X5H
|
||||
- const: renesas,rcar-gen5-msiof # generic R-Car Gen5
|
||||
# compatible device
|
||||
- items:
|
||||
- const: renesas,sh-msiof # deprecated
|
||||
|
||||
|
|
@ -100,11 +105,11 @@ properties:
|
|||
|
||||
dmas:
|
||||
minItems: 2
|
||||
maxItems: 4
|
||||
maxItems: 6
|
||||
|
||||
dma-names:
|
||||
minItems: 2
|
||||
maxItems: 4
|
||||
maxItems: 6
|
||||
items:
|
||||
enum: [ tx, rx ]
|
||||
|
||||
|
|
|
|||
|
|
@ -1,100 +0,0 @@
|
|||
Davinci SPI controller device bindings
|
||||
|
||||
Links on DM:
|
||||
Keystone 2 - https://www.ti.com/lit/ug/sprugp2a/sprugp2a.pdf
|
||||
dm644x - https://www.ti.com/lit/ug/sprue32a/sprue32a.pdf
|
||||
OMAP-L138/da830 - http://www.ti.com/lit/ug/spruh77a/spruh77a.pdf
|
||||
|
||||
Required properties:
|
||||
- #address-cells: number of cells required to define a chip select
|
||||
address on the SPI bus. Should be set to 1.
|
||||
- #size-cells: should be zero.
|
||||
- compatible:
|
||||
- "ti,dm6441-spi" for SPI used similar to that on DM644x SoC family
|
||||
- "ti,da830-spi" for SPI used similar to that on DA8xx SoC family
|
||||
- "ti,keystone-spi" for SPI used similar to that on Keystone2 SoC
|
||||
family
|
||||
- reg: Offset and length of SPI controller register space
|
||||
- num-cs: Number of chip selects. This includes internal as well as
|
||||
GPIO chip selects.
|
||||
- ti,davinci-spi-intr-line: interrupt line used to connect the SPI
|
||||
IP to the interrupt controller within the SoC. Possible values
|
||||
are 0 and 1. Manual says one of the two possible interrupt
|
||||
lines can be tied to the interrupt controller. Set this
|
||||
based on a specific SoC configuration.
|
||||
- interrupts: interrupt number mapped to CPU.
|
||||
- clocks: spi clk phandle
|
||||
For 66AK2G this property should be set per binding,
|
||||
Documentation/devicetree/bindings/clock/ti,sci-clk.yaml
|
||||
|
||||
SoC-specific Required Properties:
|
||||
|
||||
The following are mandatory properties for Keystone 2 66AK2G SoCs only:
|
||||
|
||||
- power-domains: Should contain a phandle to a PM domain provider node
|
||||
and an args specifier containing the SPI device id
|
||||
value. This property is as per the binding,
|
||||
|
||||
Optional:
|
||||
- cs-gpios: gpio chip selects
|
||||
For example to have 3 internal CS and 2 GPIO CS, user could define
|
||||
cs-gpios = <0>, <0>, <0>, <&gpio1 30 0>, <&gpio1 31 0>;
|
||||
where first three are internal CS and last two are GPIO CS.
|
||||
|
||||
Optional properties for slave devices:
|
||||
SPI slave nodes can contain the following properties.
|
||||
Not all SPI Peripherals from Texas Instruments support this.
|
||||
Please check SPI peripheral documentation for a device before using these.
|
||||
|
||||
- ti,spi-wdelay : delay between transmission of words
|
||||
(SPIFMTn.WDELAY, SPIDAT1.WDEL) must be specified in number of SPI module
|
||||
clock periods.
|
||||
|
||||
delay = WDELAY * SPI_module_clock_period + 2 * SPI_module_clock_period
|
||||
|
||||
Below is timing diagram which shows functional meaning of
|
||||
"ti,spi-wdelay" parameter.
|
||||
|
||||
+-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+
|
||||
SPI_CLK | | | | | | | | | | | | | | | |
|
||||
+----------+ +-+ +-+ +-+ +-+ +---------------------------+ +-+ +-+ +-
|
||||
|
||||
SPI_SOMI/SIMO+-----------------+ +-----------
|
||||
+----------+ word1 +---------------------------+word2
|
||||
+-----------------+ +-----------
|
||||
WDELAY
|
||||
<-------------------------->
|
||||
|
||||
Example of a NOR flash slave device (n25q032) connected to DaVinci
|
||||
SPI controller device over the SPI bus.
|
||||
|
||||
spi0:spi@20bf0000 {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
compatible = "ti,dm6446-spi";
|
||||
reg = <0x20BF0000 0x1000>;
|
||||
num-cs = <4>;
|
||||
ti,davinci-spi-intr-line = <0>;
|
||||
interrupts = <338>;
|
||||
clocks = <&clkspi>;
|
||||
|
||||
flash: flash@0 {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <1>;
|
||||
compatible = "st,m25p32";
|
||||
spi-max-frequency = <25000000>;
|
||||
reg = <0>;
|
||||
ti,spi-wdelay = <8>;
|
||||
|
||||
partition@0 {
|
||||
label = "u-boot-spl";
|
||||
reg = <0x0 0x80000>;
|
||||
read-only;
|
||||
};
|
||||
|
||||
partition@1 {
|
||||
label = "test";
|
||||
reg = <0x80000 0x380000>;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
@ -91,6 +91,14 @@ properties:
|
|||
The delay from the default sample time before the actual
|
||||
sample of the rxd input signal occurs.
|
||||
|
||||
This describes the board rather than the peripheral, namely the extra
|
||||
delay this particular design needs, for example because of the flight time
|
||||
of the clock and data signals between controller and peripheral.
|
||||
|
||||
Timing parameters of the peripheral itself, such as its
|
||||
clock-to-output-valid time, are the same on every board using that chip
|
||||
and should be described with the chip.
|
||||
|
||||
spi-tx-bus-width:
|
||||
description:
|
||||
Bus width to the SPI bus used for write transfers.
|
||||
|
|
@ -155,5 +163,6 @@ allOf:
|
|||
- $ref: fsl,dspi-peripheral-props.yaml#
|
||||
- $ref: samsung,spi-peripheral-props.yaml#
|
||||
- $ref: nvidia,tegra210-quad-peripheral-props.yaml#
|
||||
- $ref: ti,davinci-spi-peripheral-props.yaml#
|
||||
|
||||
additionalProperties: true
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/spi/ti,davinci-spi-peripheral-props.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: TI DaVinci/Keystone SPI Peripheral Properties
|
||||
|
||||
maintainers:
|
||||
- Bartosz Golaszewski <brgl@kernel.org>
|
||||
|
||||
description:
|
||||
Peripheral-specific properties for SPI devices attached to a TI
|
||||
DaVinci/Keystone SPI controller.
|
||||
|
||||
properties:
|
||||
ti,spi-wdelay:
|
||||
$ref: /schemas/types.yaml#/definitions/uint32
|
||||
minimum: 0
|
||||
maximum: 63
|
||||
description:
|
||||
delay between transmission of words (SPIFMTn.WDELAY, SPIDAT1.WDEL)
|
||||
must be specified in number of SPI module clock periods.
|
||||
|
||||
additionalProperties: true
|
||||
125
Documentation/devicetree/bindings/spi/ti,davinci-spi.yaml
Normal file
125
Documentation/devicetree/bindings/spi/ti,davinci-spi.yaml
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/spi/ti,davinci-spi.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: TI DaVinci/Keystone SPI Controller
|
||||
|
||||
maintainers:
|
||||
- Bartosz Golaszewski <brgl@kernel.org>
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
oneOf:
|
||||
- enum:
|
||||
- ti,da830-spi
|
||||
- ti,keystone-spi
|
||||
- ti,dm6441-spi
|
||||
- items:
|
||||
- const: ti,keystone-spi
|
||||
- const: ti,dm6441-spi
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
interrupts:
|
||||
maxItems: 1
|
||||
|
||||
clocks:
|
||||
maxItems: 1
|
||||
|
||||
power-domains:
|
||||
maxItems: 1
|
||||
description:
|
||||
Should contain a phandle to a PM domain provider node
|
||||
and an args specifier containing the SPI device id
|
||||
value.
|
||||
|
||||
num-cs:
|
||||
minimum: 1
|
||||
default: 1
|
||||
description:
|
||||
Number of chip selects supported by the controller. This
|
||||
includes both internal and GPIO chip selects.
|
||||
|
||||
ti,davinci-spi-intr-line:
|
||||
$ref: /schemas/types.yaml#/definitions/uint32
|
||||
enum: [0, 1]
|
||||
description:
|
||||
Interrupt line used to connect the SPI IP to the interrupt
|
||||
controller within the SoC. The SPI peripheral can route its
|
||||
interrupt output to one of two lines (INT0 or INT1). Set
|
||||
this based on the SoC-specific wiring to the interrupt
|
||||
controller.
|
||||
|
||||
dmas:
|
||||
items:
|
||||
- description: RX DMA channel
|
||||
- description: TX DMA channel
|
||||
|
||||
dma-names:
|
||||
items:
|
||||
- const: rx
|
||||
- const: tx
|
||||
|
||||
"#address-cells":
|
||||
const: 1
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- interrupts
|
||||
- clocks
|
||||
- ti,davinci-spi-intr-line
|
||||
- num-cs
|
||||
- "#address-cells"
|
||||
- "#size-cells"
|
||||
|
||||
allOf:
|
||||
- $ref: spi-controller.yaml#
|
||||
- if:
|
||||
properties:
|
||||
compatible:
|
||||
items:
|
||||
- const: ti,keystone-spi
|
||||
then:
|
||||
required:
|
||||
- power-domains
|
||||
|
||||
unevaluatedProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
spi@20bf0000 {
|
||||
compatible = "ti,keystone-spi", "ti,dm6441-spi";
|
||||
reg = <0x20bf0000 0x1000>;
|
||||
num-cs = <4>;
|
||||
ti,davinci-spi-intr-line = <0>;
|
||||
interrupts = <338>;
|
||||
clocks = <&clkspi>;
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
flash@0 {
|
||||
compatible = "jedec,spi-nor";
|
||||
spi-max-frequency = <25000000>;
|
||||
reg = <0>;
|
||||
ti,spi-wdelay = <8>;
|
||||
};
|
||||
};
|
||||
|
||||
- |
|
||||
spi@41000 {
|
||||
compatible = "ti,da830-spi";
|
||||
reg = <0x41000 0x1000>;
|
||||
num-cs = <6>;
|
||||
ti,davinci-spi-intr-line = <1>;
|
||||
interrupts = <20>;
|
||||
dmas = <&edma0 14 0>, <&edma0 15 0>;
|
||||
dma-names = "rx", "tx";
|
||||
clocks = <&psc0 4>;
|
||||
power-domains = <&psc0 4>;
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
};
|
||||
|
|
@ -460,8 +460,6 @@ SPI
|
|||
devm_spi_alloc_target()
|
||||
devm_spi_optimize_message()
|
||||
devm_spi_register_controller()
|
||||
devm_spi_register_host()
|
||||
devm_spi_register_target()
|
||||
|
||||
WATCHDOG
|
||||
devm_watchdog_register_device()
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -23091,6 +23091,12 @@ S: Maintained
|
|||
F: Documentation/devicetree/bindings/spi/realtek,rtl9301-snand.yaml
|
||||
F: drivers/spi/spi-realtek-rtl-snand.c
|
||||
|
||||
REALTEK SPI-NOR
|
||||
M: Jyan Chou <jyanchou@realtek.com>
|
||||
M: Yu-Chun Lin <eleanor.lin@realtek.com>
|
||||
S: Supported
|
||||
F: drivers/spi/spi-rtk-nor.c
|
||||
|
||||
REALTEK SYSTIMER DRIVER
|
||||
M: Hao-Wen Ting <haowen.ting@realtek.com>
|
||||
S: Maintained
|
||||
|
|
|
|||
|
|
@ -63,7 +63,6 @@ comment "SPI Master Controller Drivers"
|
|||
config SPI_AIROHA_SNFI
|
||||
tristate "Airoha SPI NAND Flash Interface"
|
||||
depends on ARCH_AIROHA || COMPILE_TEST
|
||||
depends on SPI_MASTER
|
||||
select REGMAP_MMIO
|
||||
help
|
||||
This enables support for SPI-NAND mode on the Airoha NAND
|
||||
|
|
@ -92,6 +91,14 @@ config SPI_ALTERA_DFL
|
|||
Altera SPI master controller. The SPI master is connected
|
||||
to a SPI slave to Avalon bridge in a Intel MAX BMC.
|
||||
|
||||
config SPI_AMD
|
||||
tristate "AMD SPI controller"
|
||||
depends on PCI
|
||||
depends on X86 || COMPILE_TEST
|
||||
depends on SPI_MEM
|
||||
help
|
||||
Enables SPI controller driver for AMD SoC.
|
||||
|
||||
config SPI_AMLOGIC_SPIFC_A1
|
||||
tristate "Amlogic A1 SPIFC controller"
|
||||
depends on ARCH_MESON || COMPILE_TEST
|
||||
|
|
@ -136,6 +143,13 @@ config SPI_AR934X
|
|||
This enables support for the SPI controller present on the
|
||||
Qualcomm Atheros AR934X/QCA95XX SoCs.
|
||||
|
||||
config SPI_ARMADA_3700
|
||||
tristate "Marvell Armada 3700 SPI Controller"
|
||||
depends on (ARCH_MVEBU && OF) || COMPILE_TEST
|
||||
help
|
||||
This enables support for the SPI controller present on the
|
||||
Marvell Armada 3700 SoCs.
|
||||
|
||||
config SPI_ATCSPI200
|
||||
tristate "Andes ATCSPI200 SPI controller"
|
||||
depends on ARCH_ANDES || COMPILE_TEST
|
||||
|
|
@ -153,13 +167,6 @@ config SPI_ATH79
|
|||
This enables support for the SPI controller present on the
|
||||
Atheros AR71XX/AR724X/AR913X SoCs.
|
||||
|
||||
config SPI_ARMADA_3700
|
||||
tristate "Marvell Armada 3700 SPI Controller"
|
||||
depends on (ARCH_MVEBU && OF) || COMPILE_TEST
|
||||
help
|
||||
This enables support for the SPI controller present on the
|
||||
Marvell Armada 3700 SoCs.
|
||||
|
||||
config SPI_ASPEED_SMC
|
||||
tristate "Aspeed flash controllers in SPI mode"
|
||||
depends on ARCH_ASPEED || COMPILE_TEST
|
||||
|
|
@ -333,7 +340,7 @@ config SPI_CADENCE_XSPI
|
|||
|
||||
config SPI_CH341
|
||||
tristate "CH341 USB2SPI adapter"
|
||||
depends on SPI_MASTER && USB
|
||||
depends on USB
|
||||
help
|
||||
Enables the SPI controller on the CH341a USB to serial chip
|
||||
|
||||
|
|
@ -388,14 +395,14 @@ config SPI_DW_MMIO
|
|||
endif
|
||||
|
||||
config SPI_DLN2
|
||||
tristate "Diolan DLN-2 USB SPI adapter"
|
||||
depends on MFD_DLN2
|
||||
help
|
||||
If you say yes to this option, support will be included for Diolan
|
||||
DLN2, a USB to SPI interface.
|
||||
tristate "Diolan DLN-2 USB SPI adapter"
|
||||
depends on MFD_DLN2
|
||||
help
|
||||
If you say yes to this option, support will be included for Diolan
|
||||
DLN2, a USB to SPI interface.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called spi-dln2.
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called spi-dln2.
|
||||
|
||||
config SPI_EP93XX
|
||||
tristate "Cirrus Logic EP93xx SPI controller"
|
||||
|
|
@ -420,6 +427,22 @@ config SPI_FSI
|
|||
This enables support for the driver for FSI bus attached SPI
|
||||
controllers.
|
||||
|
||||
config SPI_FSL_DSPI
|
||||
tristate "Freescale DSPI controller"
|
||||
select REGMAP_MMIO
|
||||
depends on ARCH_MXC || ARCH_NXP || M5441x || COMPILE_TEST
|
||||
help
|
||||
This enables support for the Freescale DSPI controller in master
|
||||
mode. S32, VF610, LS1021A and ColdFire platforms uses the controller.
|
||||
|
||||
config SPI_FSL_ESPI
|
||||
tristate "Freescale eSPI controller"
|
||||
depends on FSL_SOC
|
||||
help
|
||||
This enables using the Freescale eSPI controllers in master mode.
|
||||
From MPC8536, 85xx platform uses the controller, and all P10xx,
|
||||
P20xx, P30xx,P40xx, P50xx uses this controller.
|
||||
|
||||
config SPI_FSL_LPSPI
|
||||
tristate "Freescale i.MX LPSPI controller"
|
||||
depends on ARCH_MXC || COMPILE_TEST
|
||||
|
|
@ -438,6 +461,26 @@ config SPI_FSL_QUADSPI
|
|||
This controller does not support generic SPI messages. It only
|
||||
supports the high-level SPI memory interface.
|
||||
|
||||
config SPI_FSL_LIB
|
||||
tristate
|
||||
depends on OF
|
||||
|
||||
config SPI_FSL_CPM
|
||||
tristate
|
||||
depends on FSL_SOC
|
||||
|
||||
config SPI_FSL_SPI
|
||||
tristate "Freescale SPI controller and Aeroflex Gaisler GRLIB SPI controller"
|
||||
depends on OF
|
||||
select SPI_FSL_LIB
|
||||
select SPI_FSL_CPM if FSL_SOC
|
||||
help
|
||||
This enables using the Freescale SPI controllers in master mode.
|
||||
MPC83xx platform uses the controller in cpu mode or CPM/QE mode.
|
||||
MPC8569 uses the controller in QE mode, MPC8610 in cpu mode.
|
||||
This also enables using the Aeroflex Gaisler GRLIB SPI controller in
|
||||
master mode.
|
||||
|
||||
config SPI_GXP
|
||||
tristate "GXP SPI driver"
|
||||
depends on ARCH_HPE || COMPILE_TEST
|
||||
|
|
@ -463,27 +506,6 @@ config SPI_HISI_SFC_V3XX
|
|||
This enables support for HiSilicon v3xx SPI NOR flash controller
|
||||
found in hi16xx chipsets.
|
||||
|
||||
config SPI_NXP_FLEXSPI
|
||||
tristate "NXP Flex SPI controller"
|
||||
depends on ARCH_LAYERSCAPE || ARCH_MXC || COMPILE_TEST
|
||||
depends on HAS_IOMEM
|
||||
help
|
||||
This enables support for the Flex SPI controller in master mode.
|
||||
Up to four slave devices can be connected on two buses with two
|
||||
chipselects each.
|
||||
This controller does not support generic SPI messages and only
|
||||
supports the high-level SPI memory interface.
|
||||
|
||||
config SPI_NXP_XSPI
|
||||
tristate "NXP xSPI controller"
|
||||
depends on ARCH_MXC || COMPILE_TEST
|
||||
depends on HAS_IOMEM
|
||||
help
|
||||
This enables support for the xSPI controller. Up to two devices
|
||||
can be connected to one host.
|
||||
This controller does not support generic SPI messages and only
|
||||
supports the high-level SPI memory interface.
|
||||
|
||||
config SPI_GPIO
|
||||
tristate "GPIO-based bitbanging SPI Master"
|
||||
depends on GPIOLIB || COMPILE_TEST
|
||||
|
|
@ -581,6 +603,25 @@ config SPI_KSPI2
|
|||
This driver can also be built as a module. If so, the module
|
||||
will be called spi-kspi2.
|
||||
|
||||
config SPI_LANTIQ_SSC
|
||||
tristate "Lantiq SSC SPI controller"
|
||||
depends on LANTIQ || X86 || COMPILE_TEST
|
||||
help
|
||||
This driver supports the Lantiq SSC SPI controller in master
|
||||
mode. This controller is found on Intel (former Lantiq) SoCs like
|
||||
the Danube, Falcon, xRX200, xRX300, Lightning Mountain.
|
||||
|
||||
config SPI_LJCA
|
||||
tristate "Intel La Jolla Cove Adapter SPI support"
|
||||
depends on USB_LJCA
|
||||
default USB_LJCA
|
||||
help
|
||||
Select this option to enable SPI driver for the Intel
|
||||
La Jolla Cove Adapter (LJCA) board.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called spi-ljca.
|
||||
|
||||
config SPI_LM70_LLP
|
||||
tristate "Parallel port adapter for LM70 eval board (DEVELOPMENT)"
|
||||
depends on PARPORT
|
||||
|
|
@ -626,73 +667,16 @@ config SPI_LP8841_RTC
|
|||
Say N here unless you plan to run the kernel on an ICP DAS
|
||||
LP-8x4x industrial computer.
|
||||
|
||||
config SPI_MPC52xx
|
||||
tristate "Freescale MPC52xx SPI (non-PSC) controller support"
|
||||
depends on PPC_MPC52xx
|
||||
config SPI_MA35D1
|
||||
tristate "Nuvoton MA35D1 SPI controller"
|
||||
depends on ARCH_MA35 || COMPILE_TEST
|
||||
help
|
||||
This drivers supports the MPC52xx SPI controller in master SPI
|
||||
mode.
|
||||
This enables support for the SPI controller found in
|
||||
Nuvoton MA35D1 SoCs.
|
||||
|
||||
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.
|
||||
The controller supports standard SPI peripheral devices with
|
||||
configurable clock polarity and phase, chip select, and data
|
||||
word sizes from 8 to 32 bits.
|
||||
|
||||
config SPI_MA35D1_QSPI
|
||||
tristate "Nuvoton MA35D1 QSPI controller"
|
||||
|
|
@ -722,7 +706,6 @@ config SPI_MESON_SPIFC
|
|||
|
||||
config SPI_MICROCHIP_CORE_QSPI
|
||||
tristate "Microchip FPGA QSPI controllers"
|
||||
depends on SPI_MASTER
|
||||
help
|
||||
This enables the QSPI driver for Microchip FPGA QSPI controllers.
|
||||
Say Y or M here if you want to use the QSPI controllers on
|
||||
|
|
@ -731,13 +714,33 @@ config SPI_MICROCHIP_CORE_QSPI
|
|||
|
||||
config SPI_MICROCHIP_CORE_SPI
|
||||
tristate "Microchip FPGA CoreSPI controller"
|
||||
depends on SPI_MASTER
|
||||
help
|
||||
This enables the SPI driver for Microchip FPGA CoreSPI controller.
|
||||
Say Y or M here if you want to use the "soft" controllers on
|
||||
PolarFire SoC.
|
||||
If built as a module, it will be called spi-microchip-core-spi.
|
||||
|
||||
config SPI_MPC52xx
|
||||
tristate "Freescale MPC52xx SPI (non-PSC) controller support"
|
||||
depends on PPC_MPC52xx
|
||||
help
|
||||
This drivers supports the MPC52xx SPI controller in master SPI
|
||||
mode.
|
||||
|
||||
config SPI_MPC52xx_PSC
|
||||
tristate "Freescale MPC52xx PSC SPI controller"
|
||||
depends on PPC_MPC52xx
|
||||
help
|
||||
This enables using the Freescale MPC52xx Programmable Serial
|
||||
Controller in master SPI mode.
|
||||
|
||||
config SPI_MPC512x_PSC
|
||||
tristate "Freescale MPC512x PSC SPI controller"
|
||||
depends on PPC_MPC512x
|
||||
help
|
||||
This enables using the Freescale MPC5121 Programmable Serial
|
||||
Controller in SPI master mode.
|
||||
|
||||
config SPI_MT65XX
|
||||
tristate "MediaTek SPI controller"
|
||||
depends on ARCH_MEDIATEK || COMPILE_TEST
|
||||
|
|
@ -773,6 +776,19 @@ config SPI_MTK_SNFI
|
|||
is implemented as a SPI-MEM controller with pipelined ECC
|
||||
capability.
|
||||
|
||||
config SPI_MXIC
|
||||
tristate "Macronix MX25F0A SPI controller"
|
||||
imply MTD_NAND_ECC_MXIC
|
||||
help
|
||||
This selects the Macronix MX25F0A SPI controller driver.
|
||||
|
||||
config SPI_MXS
|
||||
tristate "Freescale MXS SPI controller"
|
||||
depends on ARCH_MXS
|
||||
select STMP_DEVICE
|
||||
help
|
||||
SPI driver for Freescale MXS devices.
|
||||
|
||||
config SPI_WPCM_FIU
|
||||
tristate "Nuvoton WPCM450 Flash Interface Unit"
|
||||
depends on ARCH_NPCM || COMPILE_TEST
|
||||
|
|
@ -801,13 +817,26 @@ config SPI_NPCM_PSPI
|
|||
This driver provides support for Nuvoton NPCM BMC
|
||||
Peripheral SPI controller in master mode.
|
||||
|
||||
config SPI_LANTIQ_SSC
|
||||
tristate "Lantiq SSC SPI controller"
|
||||
depends on LANTIQ || X86 || COMPILE_TEST
|
||||
config SPI_NXP_FLEXSPI
|
||||
tristate "NXP Flex SPI controller"
|
||||
depends on ARCH_LAYERSCAPE || ARCH_MXC || COMPILE_TEST
|
||||
depends on HAS_IOMEM
|
||||
help
|
||||
This driver supports the Lantiq SSC SPI controller in master
|
||||
mode. This controller is found on Intel (former Lantiq) SoCs like
|
||||
the Danube, Falcon, xRX200, xRX300, Lightning Mountain.
|
||||
This enables support for the Flex SPI controller in master mode.
|
||||
Up to four slave devices can be connected on two buses with two
|
||||
chipselects each.
|
||||
This controller does not support generic SPI messages and only
|
||||
supports the high-level SPI memory interface.
|
||||
|
||||
config SPI_NXP_XSPI
|
||||
tristate "NXP xSPI controller"
|
||||
depends on ARCH_MXC || COMPILE_TEST
|
||||
depends on HAS_IOMEM
|
||||
help
|
||||
This enables support for the xSPI controller. Up to two devices
|
||||
can be connected to one host.
|
||||
This controller does not support generic SPI messages and only
|
||||
supports the high-level SPI memory interface.
|
||||
|
||||
config SPI_OC_TINY
|
||||
tristate "OpenCores tiny SPI"
|
||||
|
|
@ -838,14 +867,6 @@ config SPI_OMAP24XX
|
|||
SPI master controller for OMAP24XX and later Multichannel SPI
|
||||
(McSPI) modules.
|
||||
|
||||
config SPI_TI_QSPI
|
||||
tristate "DRA7xxx QSPI controller support"
|
||||
depends on ARCH_OMAP2PLUS || COMPILE_TEST
|
||||
help
|
||||
QSPI master controller for DRA7xxx used for flash devices.
|
||||
This device supports single, dual and quad read support, while
|
||||
it only supports single write mode.
|
||||
|
||||
config SPI_ORION
|
||||
tristate "Orion SPI master"
|
||||
depends on PLAT_ORION || ARCH_MVEBU || COMPILE_TEST
|
||||
|
|
@ -887,7 +908,6 @@ config SPI_PL022
|
|||
|
||||
config SPI_POLARFIRE_SOC
|
||||
tristate "Microchip FPGA SPI controllers"
|
||||
depends on SPI_MASTER
|
||||
depends on ARCH_MICROCHIP || COMPILE_TEST
|
||||
help
|
||||
This enables the SPI driver for Microchip FPGA SPI controllers.
|
||||
|
|
@ -950,7 +970,7 @@ config SPI_ROCKCHIP_SFC
|
|||
|
||||
config SPI_RB4XX
|
||||
tristate "Mikrotik RB4XX SPI master"
|
||||
depends on SPI_MASTER && (ATH79 || COMPILE_TEST)
|
||||
depends on ATH79 || COMPILE_TEST
|
||||
depends on OF
|
||||
help
|
||||
SPI controller driver for the Mikrotik RB4xx series boards.
|
||||
|
|
@ -967,6 +987,16 @@ config SPI_RSPI
|
|||
help
|
||||
SPI driver for Renesas RSPI and QSPI blocks.
|
||||
|
||||
config SPI_RTK_NOR
|
||||
tristate "Realtek SPI-NOR flash controller"
|
||||
depends on ARCH_REALTEK || COMPILE_TEST
|
||||
depends on SPI_MEM
|
||||
help
|
||||
This enables support for the SPI NOR Flash Controller found on
|
||||
Realtek DHC SoCs. The controller supports single and dual I/O
|
||||
transfers and offloads the payload to an integrated DMA engine.
|
||||
Say Y or M here if you are building a kernel for a Realtek DHC SoC
|
||||
|
||||
config SPI_RZV2H_RSPI
|
||||
tristate "Renesas RZ/V2H RSPI controller"
|
||||
depends on ARCH_RENESAS || COMPILE_TEST
|
||||
|
|
@ -1035,12 +1065,14 @@ config SPI_SC18IS602
|
|||
help
|
||||
SPI driver for NXP SC18IS602/602B/603 I2C to SPI bridge.
|
||||
|
||||
config SPI_SH_MSIOF
|
||||
tristate "SuperH MSIOF SPI controller"
|
||||
depends on HAVE_CLK
|
||||
depends on ARCH_SHMOBILE || ARCH_RENESAS || COMPILE_TEST
|
||||
config SPI_SG2044_NOR
|
||||
tristate "SG2044 SPI NOR Controller"
|
||||
depends on ARCH_SOPHGO || COMPILE_TEST
|
||||
help
|
||||
SPI driver for SuperH and SH Mobile MSIOF blocks.
|
||||
This enables support for the SG2044 SPI NOR controller,
|
||||
which supports Dual/Quad read and write operations while
|
||||
also supporting 3Byte address devices and 4Byte address
|
||||
devices.
|
||||
|
||||
config SPI_SH
|
||||
tristate "SuperH SPI controller"
|
||||
|
|
@ -1048,6 +1080,19 @@ config SPI_SH
|
|||
help
|
||||
SPI driver for SuperH SPI blocks.
|
||||
|
||||
config SPI_SH_HSPI
|
||||
tristate "SuperH HSPI controller"
|
||||
depends on ARCH_RENESAS || COMPILE_TEST
|
||||
help
|
||||
SPI driver for SuperH HSPI blocks.
|
||||
|
||||
config SPI_SH_MSIOF
|
||||
tristate "SuperH MSIOF SPI controller"
|
||||
depends on HAVE_CLK
|
||||
depends on ARCH_SHMOBILE || ARCH_RENESAS || COMPILE_TEST
|
||||
help
|
||||
SPI driver for SuperH and SH Mobile MSIOF blocks.
|
||||
|
||||
config SPI_SH_SCI
|
||||
tristate "SuperH SCI SPI controller"
|
||||
depends on SUPERH
|
||||
|
|
@ -1055,28 +1100,12 @@ config SPI_SH_SCI
|
|||
help
|
||||
SPI driver for SuperH SCI blocks.
|
||||
|
||||
config SPI_SH_HSPI
|
||||
tristate "SuperH HSPI controller"
|
||||
depends on ARCH_RENESAS || COMPILE_TEST
|
||||
help
|
||||
SPI driver for SuperH HSPI blocks.
|
||||
|
||||
config SPI_SIFIVE
|
||||
tristate "SiFive SPI controller"
|
||||
depends on HAS_IOMEM
|
||||
help
|
||||
This exposes the SPI controller IP from SiFive.
|
||||
|
||||
config SPI_SLAVE_MT27XX
|
||||
tristate "MediaTek SPI slave device"
|
||||
depends on ARCH_MEDIATEK || COMPILE_TEST
|
||||
depends on SPI_SLAVE
|
||||
help
|
||||
This selects the MediaTek(R) SPI slave device driver.
|
||||
If you want to use MediaTek(R) SPI slave interface,
|
||||
say Y or M here.If you are not sure, say N.
|
||||
SPI slave drivers for Mediatek MT27XX series ARM SoCs.
|
||||
|
||||
config SPI_SN_F_OSPI
|
||||
tristate "Socionext F_OSPI SPI flash controller"
|
||||
depends on OF && HAS_IOMEM
|
||||
|
|
@ -1086,15 +1115,6 @@ config SPI_SN_F_OSPI
|
|||
for connecting an SPI Flash memory over up to 8-bit wide bus.
|
||||
It supports indirect access mode only.
|
||||
|
||||
config SPI_SG2044_NOR
|
||||
tristate "SG2044 SPI NOR Controller"
|
||||
depends on ARCH_SOPHGO || COMPILE_TEST
|
||||
help
|
||||
This enables support for the SG2044 SPI NOR controller,
|
||||
which supports Dual/Quad read and write operations while
|
||||
also supporting 3Byte address devices and 4Byte address
|
||||
devices.
|
||||
|
||||
config SPI_SPACEMIT_K1
|
||||
tristate "K1 SPI Controller"
|
||||
depends on ARCH_SPACEMIT || COMPILE_TEST
|
||||
|
|
@ -1189,20 +1209,6 @@ config SPI_SYNQUACER
|
|||
|
||||
It also supports the new dual-bit and quad-bit SPI protocol.
|
||||
|
||||
config SPI_MXIC
|
||||
tristate "Macronix MX25F0A SPI controller"
|
||||
depends on SPI_MASTER
|
||||
imply MTD_NAND_ECC_MXIC
|
||||
help
|
||||
This selects the Macronix MX25F0A SPI controller driver.
|
||||
|
||||
config SPI_MXS
|
||||
tristate "Freescale MXS SPI controller"
|
||||
depends on ARCH_MXS
|
||||
select STMP_DEVICE
|
||||
help
|
||||
SPI driver for Freescale MXS devices.
|
||||
|
||||
config SPI_TEGRA210_QUAD
|
||||
tristate "NVIDIA Tegra QSPI Controller"
|
||||
depends on ARCH_TEGRA || COMPILE_TEST
|
||||
|
|
@ -1244,6 +1250,23 @@ config SPI_THUNDERX
|
|||
SPI host driver for the hardware found on Cavium ThunderX
|
||||
SOCs.
|
||||
|
||||
config SPI_TI_QSPI
|
||||
tristate "DRA7xxx QSPI controller support"
|
||||
depends on ARCH_OMAP2PLUS || COMPILE_TEST
|
||||
help
|
||||
QSPI master controller for DRA7xxx used for flash devices.
|
||||
This device supports single, dual and quad read support, while
|
||||
it only supports single write mode.
|
||||
|
||||
config SPI_TLE62X0
|
||||
tristate "Infineon TLE62X0 (for power switching)"
|
||||
depends on SYSFS
|
||||
help
|
||||
SPI driver for Infineon TLE62X0 series line driver chips,
|
||||
such as the TLE6220, TLE6230 and TLE6240. This provides a
|
||||
sysfs interface, with each line presented as a kind of GPIO
|
||||
exposing both switch control and diagnostic feedback.
|
||||
|
||||
config SPI_TOPCLIFF_PCH
|
||||
tristate "Intel EG20T PCH/LAPIS Semicon IOH(ML7213/ML7223/ML7831) SPI"
|
||||
depends on PCI && (X86_32 || MIPS || COMPILE_TEST)
|
||||
|
|
@ -1270,7 +1293,7 @@ config SPI_UNIPHIER
|
|||
|
||||
config SPI_VIRTIO
|
||||
tristate "Virtio SPI Controller"
|
||||
depends on SPI_MASTER && VIRTIO
|
||||
depends on VIRTIO
|
||||
help
|
||||
If you say yes to this option, support will be included for the virtio
|
||||
SPI controller driver. The hardware can be emulated by any device model
|
||||
|
|
@ -1336,14 +1359,6 @@ config SPI_ZYNQMP_GQSPI
|
|||
Enables Xilinx GQSPI controller driver for Zynq UltraScale+ MPSoC.
|
||||
This controller only supports SPI memory interface.
|
||||
|
||||
config SPI_AMD
|
||||
tristate "AMD SPI controller"
|
||||
depends on PCI
|
||||
depends on SPI_MASTER || X86 || COMPILE_TEST
|
||||
depends on SPI_MEM
|
||||
help
|
||||
Enables SPI controller driver for AMD SoC.
|
||||
|
||||
#
|
||||
# Add new SPI master controllers in alphabetical order above this line
|
||||
#
|
||||
|
|
@ -1392,15 +1407,6 @@ config SPI_LOOPBACK_TEST
|
|||
primarily used for development of spi_master drivers
|
||||
and to detect regressions
|
||||
|
||||
config SPI_TLE62X0
|
||||
tristate "Infineon TLE62X0 (for power switching)"
|
||||
depends on SYSFS
|
||||
help
|
||||
SPI driver for Infineon TLE62X0 series line driver chips,
|
||||
such as the TLE6220, TLE6230 and TLE6240. This provides a
|
||||
sysfs interface, with each line presented as a kind of GPIO
|
||||
exposing both switch control and diagnostic feedback.
|
||||
|
||||
#
|
||||
# Add new SPI protocol masters in alphabetical order above this line
|
||||
#
|
||||
|
|
@ -1431,6 +1437,15 @@ config SPI_SLAVE_SYSTEM_CONTROL
|
|||
SPI slave handler to allow remote control of system reboot, power
|
||||
off, halt, and suspend.
|
||||
|
||||
config SPI_SLAVE_MT27XX
|
||||
tristate "MediaTek SPI slave device"
|
||||
depends on ARCH_MEDIATEK || COMPILE_TEST
|
||||
help
|
||||
This selects the MediaTek(R) SPI slave device driver.
|
||||
If you want to use MediaTek(R) SPI slave interface,
|
||||
say Y or M here.If you are not sure, say N.
|
||||
SPI slave drivers for Mediatek MT27XX series ARM SoCs.
|
||||
|
||||
endif # SPI_SLAVE
|
||||
|
||||
config SPI_DYNAMIC
|
||||
|
|
|
|||
|
|
@ -86,6 +86,7 @@ obj-$(CONFIG_SPI_LOONGSON_CORE) += spi-loongson-core.o
|
|||
obj-$(CONFIG_SPI_LOONGSON_PCI) += spi-loongson-pci.o
|
||||
obj-$(CONFIG_SPI_LOONGSON_PLATFORM) += spi-loongson-plat.o
|
||||
obj-$(CONFIG_SPI_LP8841_RTC) += spi-lp8841-rtc.o
|
||||
obj-$(CONFIG_SPI_MA35D1) += spi-ma35d1.o
|
||||
obj-$(CONFIG_SPI_MA35D1_QSPI) += spi-ma35d1-qspi.o
|
||||
obj-$(CONFIG_SPI_MESON_SPICC) += spi-meson-spicc.o
|
||||
obj-$(CONFIG_SPI_MESON_SPIFC) += spi-meson-spifc.o
|
||||
|
|
@ -133,6 +134,7 @@ obj-$(CONFIG_MACH_REALTEK_RTL) += spi-realtek-rtl.o
|
|||
obj-$(CONFIG_SPI_REALTEK_SNAND) += spi-realtek-rtl-snand.o
|
||||
obj-$(CONFIG_SPI_RPCIF) += spi-rpc-if.o
|
||||
obj-$(CONFIG_SPI_RSPI) += spi-rspi.o
|
||||
obj-$(CONFIG_SPI_RTK_NOR) += spi-rtk-nor.o
|
||||
obj-$(CONFIG_SPI_RZV2H_RSPI) += spi-rzv2h-rspi.o
|
||||
obj-$(CONFIG_SPI_RZV2M_CSI) += spi-rzv2m-csi.o
|
||||
obj-$(CONFIG_SPI_S3C64XX) += spi-s3c64xx.o
|
||||
|
|
|
|||
|
|
@ -792,7 +792,7 @@ static int atmel_qspi_dma_xfer(struct atmel_qspi *aq, struct dma_chan *chan,
|
|||
reinit_completion(&aq->dma_completion);
|
||||
tx->callback = atmel_qspi_dma_callback;
|
||||
tx->callback_param = aq;
|
||||
cookie = tx->tx_submit(tx);
|
||||
cookie = dmaengine_submit(tx);
|
||||
ret = dma_submit_error(cookie);
|
||||
if (ret) {
|
||||
dev_err(&aq->pdev->dev, "dma_submit_error %d\n", cookie);
|
||||
|
|
@ -1590,12 +1590,14 @@ static int atmel_qspi_suspend(struct device *dev)
|
|||
|
||||
if (aq->caps->has_gclk) {
|
||||
ret = atmel_qspi_sama7g5_suspend(aq);
|
||||
clk_disable_unprepare(aq->pclk);
|
||||
return ret;
|
||||
if (ret) {
|
||||
pm_runtime_put_autosuspend(dev);
|
||||
return ret;
|
||||
}
|
||||
} else {
|
||||
atmel_qspi_write(QSPI_CR_QSPIDIS, aq, QSPI_CR);
|
||||
}
|
||||
|
||||
atmel_qspi_write(QSPI_CR_QSPIDIS, aq, QSPI_CR);
|
||||
|
||||
pm_runtime_mark_last_busy(dev);
|
||||
pm_runtime_force_suspend(dev);
|
||||
|
||||
|
|
@ -1621,20 +1623,20 @@ static int atmel_qspi_resume(struct device *dev)
|
|||
return ret;
|
||||
}
|
||||
|
||||
if (aq->caps->has_gclk)
|
||||
return aq->caps->init(aq);
|
||||
|
||||
ret = pm_runtime_force_resume(dev);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
atmel_qspi_init(aq);
|
||||
|
||||
atmel_qspi_write(aq->scr, aq, QSPI_SCR);
|
||||
if (aq->caps->has_gclk) {
|
||||
ret = aq->caps->init(aq);
|
||||
} else {
|
||||
atmel_qspi_init(aq);
|
||||
atmel_qspi_write(aq->scr, aq, QSPI_SCR);
|
||||
}
|
||||
|
||||
pm_runtime_put_autosuspend(dev);
|
||||
|
||||
return 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int atmel_qspi_runtime_suspend(struct device *dev)
|
||||
|
|
|
|||
|
|
@ -322,11 +322,22 @@ static inline int amd_spi_fifo_xfer(struct amd_spi *amd_spi,
|
|||
|
||||
list_for_each_entry(xfer, &message->transfers,
|
||||
transfer_list) {
|
||||
/* Skip zero-length transfers to avoid len underflow below */
|
||||
if (!xfer->len)
|
||||
continue;
|
||||
|
||||
if (xfer->speed_hz)
|
||||
amd_set_spi_freq(amd_spi, xfer->speed_hz);
|
||||
else
|
||||
amd_set_spi_freq(amd_spi, spi->max_speed_hz);
|
||||
|
||||
/* Reject transfers that would overflow the FIFO window */
|
||||
if (fifo_pos + xfer->len >
|
||||
AMD_SPI_FIFO_BASE + AMD_SPI_FIFO_SIZE) {
|
||||
message->status = -EMSGSIZE;
|
||||
goto fin_msg;
|
||||
}
|
||||
|
||||
if (xfer->tx_buf) {
|
||||
buf = (u8 *)xfer->tx_buf;
|
||||
if (!tx_len) {
|
||||
|
|
@ -369,6 +380,12 @@ static inline int amd_spi_fifo_xfer(struct amd_spi *amd_spi,
|
|||
|
||||
list_for_each_entry(xfer, &message->transfers, transfer_list)
|
||||
if (xfer->rx_buf) {
|
||||
/* Reject transfers that would overflow the FIFO window */
|
||||
if (fifo_pos + xfer->len >
|
||||
AMD_SPI_FIFO_BASE + AMD_SPI_FIFO_SIZE) {
|
||||
message->status = -EMSGSIZE;
|
||||
goto fin_msg;
|
||||
}
|
||||
buf = (u8 *)xfer->rx_buf;
|
||||
/* Read data from FIFO to receive buffer */
|
||||
for (i = 0; i < xfer->len; i++)
|
||||
|
|
|
|||
|
|
@ -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 */ }
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -231,16 +231,16 @@ static int at91_usart_spi_dma_transfer(struct spi_controller *ctlr,
|
|||
rxdesc->callback = dma_callback;
|
||||
rxdesc->callback_param = ctlr;
|
||||
|
||||
cookie = rxdesc->tx_submit(rxdesc);
|
||||
cookie = dmaengine_submit(rxdesc);
|
||||
if (dma_submit_error(cookie))
|
||||
goto at91_usart_spi_err_dma;
|
||||
|
||||
cookie = txdesc->tx_submit(txdesc);
|
||||
cookie = dmaengine_submit(txdesc);
|
||||
if (dma_submit_error(cookie))
|
||||
goto at91_usart_spi_err_dma;
|
||||
|
||||
rxchan->device->device_issue_pending(rxchan);
|
||||
txchan->device->device_issue_pending(txchan);
|
||||
dma_async_issue_pending(rxchan);
|
||||
dma_async_issue_pending(txchan);
|
||||
|
||||
return 0;
|
||||
|
||||
|
|
|
|||
|
|
@ -871,14 +871,14 @@ static int atmel_spi_next_xfer_dma_submit(struct spi_controller *host,
|
|||
rxdesc->callback_param = host;
|
||||
|
||||
/* Submit and fire RX and TX with TX last so we're ready to read! */
|
||||
cookie = rxdesc->tx_submit(rxdesc);
|
||||
cookie = dmaengine_submit(rxdesc);
|
||||
if (dma_submit_error(cookie))
|
||||
goto err_dma;
|
||||
cookie = txdesc->tx_submit(txdesc);
|
||||
cookie = dmaengine_submit(txdesc);
|
||||
if (dma_submit_error(cookie))
|
||||
goto err_dma;
|
||||
rxchan->device->device_issue_pending(rxchan);
|
||||
txchan->device->device_issue_pending(txchan);
|
||||
dma_async_issue_pending(rxchan);
|
||||
dma_async_issue_pending(txchan);
|
||||
|
||||
return 0;
|
||||
|
||||
|
|
|
|||
|
|
@ -887,6 +887,7 @@ static int spi_engine_setup(struct spi_device *device)
|
|||
struct spi_controller *host = device->controller;
|
||||
struct spi_engine *spi_engine = spi_controller_get_devdata(host);
|
||||
unsigned int reg;
|
||||
int ret;
|
||||
|
||||
if (device->mode & SPI_CS_HIGH)
|
||||
spi_engine->cs_inv |= BIT(spi_get_chipselect(device, 0));
|
||||
|
|
@ -922,8 +923,15 @@ static int spi_engine_setup(struct spi_device *device)
|
|||
writel_relaxed(SPI_ENGINE_CMD_SYNC(1),
|
||||
spi_engine->base + SPI_ENGINE_REG_CMD_FIFO);
|
||||
|
||||
return readl_relaxed_poll_timeout(spi_engine->base + SPI_ENGINE_REG_SYNC_ID,
|
||||
reg, reg == 1, 1, 1000);
|
||||
ret = readl_relaxed_poll_timeout(spi_engine->base + SPI_ENGINE_REG_SYNC_ID,
|
||||
reg, reg == 1, 1, 1 * USEC_PER_MSEC);
|
||||
|
||||
/* Clear the stale SYNC pending bit so it doesn't fire when the IRQ is later enabled. */
|
||||
writel_relaxed(SPI_ENGINE_INT_SYNC, spi_engine->base + SPI_ENGINE_REG_INT_PENDING);
|
||||
/* Read back to flush posted write. */
|
||||
readl_relaxed(spi_engine->base + SPI_ENGINE_REG_INT_PENDING);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int spi_engine_transfer_one_message(struct spi_controller *host,
|
||||
|
|
@ -1028,7 +1036,13 @@ static int spi_engine_trigger_enable(struct spi_offload *offload)
|
|||
spi_engine->base + SPI_ENGINE_REG_CMD_FIFO);
|
||||
|
||||
ret = readl_relaxed_poll_timeout(spi_engine->base + SPI_ENGINE_REG_SYNC_ID,
|
||||
reg, reg == 1, 1, 1000);
|
||||
reg, reg == 1, 1, 1 * USEC_PER_MSEC);
|
||||
|
||||
/* Clear the stale SYNC pending bit so it doesn't fire when the IRQ is later enabled */
|
||||
writel_relaxed(SPI_ENGINE_INT_SYNC, spi_engine->base + SPI_ENGINE_REG_INT_PENDING);
|
||||
/* Read back to flush posted write. */
|
||||
readl_relaxed(spi_engine->base + SPI_ENGINE_REG_INT_PENDING);
|
||||
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
|
|
|
|||
|
|
@ -1197,8 +1197,9 @@ static int bcm_qspi_exec_mem_op(struct spi_mem *mem,
|
|||
u32 addr = 0, len;
|
||||
u_char *buf;
|
||||
|
||||
/* cmd[] holds 1 opcode + 4 address + 1 dummy byte at most */
|
||||
if (!op->data.nbytes || !op->addr.nbytes || op->addr.nbytes > 4 ||
|
||||
op->data.dir != SPI_MEM_DATA_IN)
|
||||
op->dummy.nbytes > 1 || op->data.dir != SPI_MEM_DATA_IN)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
buf = op->data.buf.in;
|
||||
|
|
|
|||
|
|
@ -527,7 +527,7 @@ static void bcm2835_spi_transfer_prologue(struct spi_controller *ctlr,
|
|||
| BCM2835_SPI_CS_CLEAR_TX
|
||||
| BCM2835_SPI_CS_DONE);
|
||||
|
||||
dma_sync_single_for_device(ctlr->dma_rx->device->dev,
|
||||
dma_sync_single_for_device(dmaengine_get_dma_device(ctlr->dma_rx),
|
||||
sg_dma_address(&tfr->rx_sg.sgl[0]),
|
||||
bs->rx_prologue, DMA_FROM_DEVICE);
|
||||
|
||||
|
|
@ -871,7 +871,7 @@ static void bcm2835_dma_release(struct spi_controller *ctlr,
|
|||
dmaengine_desc_free(bs->fill_tx_desc);
|
||||
|
||||
if (bs->fill_tx_addr)
|
||||
dma_unmap_page_attrs(ctlr->dma_tx->device->dev,
|
||||
dma_unmap_page_attrs(dmaengine_get_dma_device(ctlr->dma_tx),
|
||||
bs->fill_tx_addr, sizeof(u32),
|
||||
DMA_TO_DEVICE,
|
||||
DMA_ATTR_SKIP_CPU_SYNC);
|
||||
|
|
@ -891,6 +891,7 @@ static int bcm2835_dma_init(struct spi_controller *ctlr, struct device *dev,
|
|||
struct bcm2835_spi *bs)
|
||||
{
|
||||
struct dma_slave_config slave_config;
|
||||
struct device *tx_dma_dev;
|
||||
const __be32 *addr;
|
||||
dma_addr_t dma_reg_base;
|
||||
int ret;
|
||||
|
|
@ -932,11 +933,13 @@ static int bcm2835_dma_init(struct spi_controller *ctlr, struct device *dev,
|
|||
if (ret)
|
||||
goto err_config;
|
||||
|
||||
bs->fill_tx_addr = dma_map_page_attrs(ctlr->dma_tx->device->dev,
|
||||
tx_dma_dev = dmaengine_get_dma_device(ctlr->dma_tx);
|
||||
|
||||
bs->fill_tx_addr = dma_map_page_attrs(tx_dma_dev,
|
||||
ZERO_PAGE(0), 0, sizeof(u32),
|
||||
DMA_TO_DEVICE,
|
||||
DMA_ATTR_SKIP_CPU_SYNC);
|
||||
if (dma_mapping_error(ctlr->dma_tx->device->dev, bs->fill_tx_addr)) {
|
||||
if (dma_mapping_error(tx_dma_dev, bs->fill_tx_addr)) {
|
||||
dev_err(dev, "cannot map zero page - not using DMA mode\n");
|
||||
bs->fill_tx_addr = 0;
|
||||
ret = -ENOMEM;
|
||||
|
|
@ -1157,7 +1160,7 @@ static void bcm2835_spi_cleanup(struct spi_device *spi)
|
|||
dmaengine_desc_free(target->clear_rx_desc);
|
||||
|
||||
if (target->clear_rx_addr)
|
||||
dma_unmap_single(ctlr->dma_rx->device->dev,
|
||||
dma_unmap_single(dmaengine_get_dma_device(ctlr->dma_rx),
|
||||
target->clear_rx_addr,
|
||||
sizeof(u32),
|
||||
DMA_TO_DEVICE);
|
||||
|
|
@ -1174,16 +1177,16 @@ static int bcm2835_spi_setup_dma(struct spi_controller *ctlr,
|
|||
struct bcm2835_spi *bs,
|
||||
struct bcm2835_spidev *target)
|
||||
{
|
||||
struct device *rx_dev;
|
||||
int ret;
|
||||
|
||||
if (!ctlr->dma_rx)
|
||||
return 0;
|
||||
|
||||
target->clear_rx_addr = dma_map_single(ctlr->dma_rx->device->dev,
|
||||
&target->clear_rx_cs,
|
||||
sizeof(u32),
|
||||
DMA_TO_DEVICE);
|
||||
if (dma_mapping_error(ctlr->dma_rx->device->dev, target->clear_rx_addr)) {
|
||||
rx_dev = dmaengine_get_dma_device(ctlr->dma_rx);
|
||||
target->clear_rx_addr = dma_map_single(rx_dev, &target->clear_rx_cs,
|
||||
sizeof(u32), DMA_TO_DEVICE);
|
||||
if (dma_mapping_error(rx_dev, target->clear_rx_addr)) {
|
||||
dev_err(&spi->dev, "cannot map clear_rx_cs\n");
|
||||
target->clear_rx_addr = 0;
|
||||
return -ENOMEM;
|
||||
|
|
@ -1268,7 +1271,7 @@ static int bcm2835_spi_setup(struct spi_device *spi)
|
|||
target->clear_rx_cs = cs | BCM2835_SPI_CS_TA |
|
||||
BCM2835_SPI_CS_DMAEN |
|
||||
BCM2835_SPI_CS_CLEAR_RX;
|
||||
dma_sync_single_for_device(ctlr->dma_rx->device->dev,
|
||||
dma_sync_single_for_device(dmaengine_get_dma_device(ctlr->dma_rx),
|
||||
target->clear_rx_addr,
|
||||
sizeof(u32),
|
||||
DMA_TO_DEVICE);
|
||||
|
|
@ -1297,7 +1300,9 @@ static int bcm2835_spi_setup(struct spi_device *spi)
|
|||
}
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(pinctrl_compats); i++) {
|
||||
if (of_find_compatible_node(NULL, NULL, pinctrl_compats[i]))
|
||||
struct device_node *np __free(device_node) =
|
||||
of_find_compatible_node(NULL, NULL, pinctrl_compats[i]);
|
||||
if (np)
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -152,7 +152,7 @@ static ssize_t wait_mode_show(struct device *dev, struct device_attribute *attr,
|
|||
struct spi_controller *ctrl = dev_get_drvdata(dev);
|
||||
struct bcm63xx_hsspi *bs = spi_controller_get_devdata(ctrl);
|
||||
|
||||
return sprintf(buf, "%d\n", bs->wait_mode);
|
||||
return sysfs_emit(buf, "%d\n", bs->wait_mode);
|
||||
}
|
||||
|
||||
static ssize_t wait_mode_store(struct device *dev, struct device_attribute *attr,
|
||||
|
|
@ -188,7 +188,7 @@ static ssize_t xfer_mode_show(struct device *dev, struct device_attribute *attr,
|
|||
struct spi_controller *ctrl = dev_get_drvdata(dev);
|
||||
struct bcm63xx_hsspi *bs = spi_controller_get_devdata(ctrl);
|
||||
|
||||
return sprintf(buf, "%d\n", bs->xfer_mode);
|
||||
return sysfs_emit(buf, "%d\n", bs->xfer_mode);
|
||||
}
|
||||
|
||||
static ssize_t xfer_mode_store(struct device *dev, struct device_attribute *attr,
|
||||
|
|
@ -882,6 +882,7 @@ static void bcm63xx_hsspi_remove(struct platform_device *pdev)
|
|||
/* reset the hardware and block queue progress */
|
||||
__raw_writel(0, bs->regs + HSSPI_INT_MASK_REG);
|
||||
sysfs_remove_group(&pdev->dev.kobj, &bcm63xx_hsspi_group);
|
||||
pm_runtime_disable(&pdev->dev);
|
||||
}
|
||||
|
||||
static int bcm63xx_hsspi_suspend(struct device *dev)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -129,7 +129,7 @@ static ssize_t wait_mode_show(struct device *dev, struct device_attribute *attr,
|
|||
struct spi_controller *ctrl = dev_get_drvdata(dev);
|
||||
struct bcmbca_hsspi *bs = spi_controller_get_devdata(ctrl);
|
||||
|
||||
return sprintf(buf, "%d\n", bs->wait_mode);
|
||||
return sysfs_emit(buf, "%d\n", bs->wait_mode);
|
||||
}
|
||||
|
||||
static ssize_t wait_mode_store(struct device *dev, struct device_attribute *attr,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Polling/bitbanging SPI host controller controller driver utilities
|
||||
* Polling/bitbanging SPI host controller driver utilities
|
||||
*/
|
||||
|
||||
#include <linux/spinlock.h>
|
||||
|
|
|
|||
|
|
@ -259,7 +259,8 @@ static void butterfly_attach(struct parport *p)
|
|||
*/
|
||||
|
||||
pp->info[0].max_speed_hz = 15 * 1000 * 1000;
|
||||
strcpy(pp->info[0].modalias, "mtd_dataflash");
|
||||
strscpy(pp->info[0].modalias, "mtd_dataflash",
|
||||
sizeof(pp->info[0].modalias));
|
||||
pp->info[0].platform_data = &flash;
|
||||
pp->info[0].chip_select = 1;
|
||||
pp->info[0].controller_data = pp;
|
||||
|
|
|
|||
|
|
@ -1381,7 +1381,7 @@ static int cqspi_direct_read_execute(struct cqspi_flash_pdata *f_pdata,
|
|||
return 0;
|
||||
}
|
||||
|
||||
ddev = cqspi->rx_chan->device->dev;
|
||||
ddev = dmaengine_get_dma_device(cqspi->rx_chan);
|
||||
dma_dst = dma_map_single(ddev, buf, len, DMA_FROM_DEVICE);
|
||||
if (dma_mapping_error(ddev, dma_dst)) {
|
||||
dev_err(dev, "dma mapping failed\n");
|
||||
|
|
@ -1397,7 +1397,7 @@ static int cqspi_direct_read_execute(struct cqspi_flash_pdata *f_pdata,
|
|||
|
||||
tx->callback = cqspi_rx_dma_callback;
|
||||
tx->callback_param = cqspi;
|
||||
cookie = tx->tx_submit(tx);
|
||||
cookie = dmaengine_submit(tx);
|
||||
reinit_completion(&cqspi->rx_dma_complete);
|
||||
|
||||
ret = dma_submit_error(cookie);
|
||||
|
|
@ -1741,6 +1741,12 @@ static const struct spi_controller_mem_caps cqspi_mem_caps = {
|
|||
.per_op_freq = true,
|
||||
};
|
||||
|
||||
static const struct spi_controller_mem_caps cqspi_am654_mem_caps = {
|
||||
.dtr = true,
|
||||
.per_op_freq = true,
|
||||
.no_cs_assertion = true,
|
||||
};
|
||||
|
||||
static int cqspi_setup_flash(struct cqspi_st *cqspi)
|
||||
{
|
||||
struct platform_device *pdev = cqspi->pdev;
|
||||
|
|
@ -1798,6 +1804,8 @@ static int cqspi_probe(struct platform_device *pdev)
|
|||
host->mode_bits = SPI_RX_QUAD | SPI_RX_DUAL;
|
||||
host->mem_ops = &cqspi_mem_ops;
|
||||
host->mem_caps = &cqspi_mem_caps;
|
||||
if (of_device_is_compatible(pdev->dev.of_node, "ti,am654-ospi"))
|
||||
host->mem_caps = &cqspi_am654_mem_caps;
|
||||
|
||||
cqspi = spi_controller_get_devdata(host);
|
||||
if (of_device_is_compatible(pdev->dev.of_node, "starfive,jh7110-qspi"))
|
||||
|
|
|
|||
|
|
@ -268,6 +268,7 @@
|
|||
#define MRVL_XFER_FUNC_START BIT(0)
|
||||
#define MRVL_XFER_QWORD_COUNT 32
|
||||
#define MRVL_XFER_QWORD_BYTECOUNT 8
|
||||
#define MRVL_XFER_MAX_LEN (MRVL_XFER_QWORD_COUNT * MRVL_XFER_QWORD_BYTECOUNT)
|
||||
|
||||
#define MRVL_XSPI_POLL_TIMEOUT_US 1000
|
||||
#define MRVL_XSPI_POLL_DELAY_US 10
|
||||
|
|
@ -332,6 +333,7 @@ struct cdns_xspi_dev {
|
|||
int irq;
|
||||
int cur_cs;
|
||||
unsigned int sdmasize;
|
||||
unsigned int sdma_xfer_len;
|
||||
|
||||
struct completion cmd_complete;
|
||||
struct completion auto_cmd_complete;
|
||||
|
|
@ -346,7 +348,7 @@ struct cdns_xspi_dev {
|
|||
u8 hw_num_banks;
|
||||
|
||||
const struct cdns_xspi_driver_data *driver_data;
|
||||
void (*sdma_handler)(struct cdns_xspi_dev *cdns_xspi);
|
||||
int (*sdma_handler)(struct cdns_xspi_dev *cdns_xspi);
|
||||
void (*set_interrupts_handler)(struct cdns_xspi_dev *cdns_xspi, bool enabled);
|
||||
|
||||
bool xfer_in_progress;
|
||||
|
|
@ -496,7 +498,7 @@ static inline void cdns_xspi_sdma_write(struct cdns_xspi_dev *cdns_xspi, size_t
|
|||
iowrite8_rep(dst, (const u8 *)buf + offset, len);
|
||||
}
|
||||
|
||||
static void cdns_xspi_sdma_handle(struct cdns_xspi_dev *cdns_xspi)
|
||||
static int cdns_xspi_sdma_handle(struct cdns_xspi_dev *cdns_xspi)
|
||||
{
|
||||
u32 sdma_size, sdma_trd_info;
|
||||
u8 sdma_dir;
|
||||
|
|
@ -505,6 +507,13 @@ static void cdns_xspi_sdma_handle(struct cdns_xspi_dev *cdns_xspi)
|
|||
sdma_trd_info = readl(cdns_xspi->iobase + CDNS_XSPI_SDMA_TRD_INFO_REG);
|
||||
sdma_dir = FIELD_GET(CDNS_XSPI_SDMA_DIR, sdma_trd_info);
|
||||
|
||||
if (sdma_size > cdns_xspi->sdma_xfer_len) {
|
||||
dev_err(cdns_xspi->dev,
|
||||
"SDMA size %u exceeds the requested length %u\n",
|
||||
sdma_size, cdns_xspi->sdma_xfer_len);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
switch (sdma_dir) {
|
||||
case CDNS_XSPI_SDMA_DIR_READ:
|
||||
cdns_xspi_sdma_read(cdns_xspi, sdma_size);
|
||||
|
|
@ -514,6 +523,8 @@ static void cdns_xspi_sdma_handle(struct cdns_xspi_dev *cdns_xspi)
|
|||
cdns_xspi_sdma_write(cdns_xspi, sdma_size);
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cdns_xspi_send_stig_command(struct cdns_xspi_dev *cdns_xspi,
|
||||
|
|
@ -559,6 +570,7 @@ static int cdns_xspi_send_stig_command(struct cdns_xspi_dev *cdns_xspi,
|
|||
|
||||
cdns_xspi->in_buffer = op->data.buf.in;
|
||||
cdns_xspi->out_buffer = op->data.buf.out;
|
||||
cdns_xspi->sdma_xfer_len = op->data.nbytes;
|
||||
|
||||
cdns_xspi_trigger_command(cdns_xspi, cmd_regs);
|
||||
|
||||
|
|
@ -567,7 +579,11 @@ static int cdns_xspi_send_stig_command(struct cdns_xspi_dev *cdns_xspi,
|
|||
cdns_xspi->set_interrupts_handler(cdns_xspi, false);
|
||||
return -EIO;
|
||||
}
|
||||
cdns_xspi->sdma_handler(cdns_xspi);
|
||||
ret = cdns_xspi->sdma_handler(cdns_xspi);
|
||||
if (ret) {
|
||||
cdns_xspi->set_interrupts_handler(cdns_xspi, false);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
wait_for_completion(&cdns_xspi->cmd_complete);
|
||||
|
|
@ -950,7 +966,7 @@ static void m_iowriteq(void __iomem *addr, const void *buf, int len)
|
|||
}
|
||||
}
|
||||
|
||||
static void marvell_xspi_sdma_handle(struct cdns_xspi_dev *cdns_xspi)
|
||||
static int marvell_xspi_sdma_handle(struct cdns_xspi_dev *cdns_xspi)
|
||||
{
|
||||
u32 sdma_size, sdma_trd_info;
|
||||
u8 sdma_dir;
|
||||
|
|
@ -959,6 +975,13 @@ static void marvell_xspi_sdma_handle(struct cdns_xspi_dev *cdns_xspi)
|
|||
sdma_trd_info = readl(cdns_xspi->iobase + CDNS_XSPI_SDMA_TRD_INFO_REG);
|
||||
sdma_dir = FIELD_GET(CDNS_XSPI_SDMA_DIR, sdma_trd_info);
|
||||
|
||||
if (sdma_size > cdns_xspi->sdma_xfer_len) {
|
||||
dev_err(cdns_xspi->dev,
|
||||
"SDMA size %u exceeds the requested length %u\n",
|
||||
sdma_size, cdns_xspi->sdma_xfer_len);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
switch (sdma_dir) {
|
||||
case CDNS_XSPI_SDMA_DIR_READ:
|
||||
m_ioreadq(cdns_xspi->sdmabase,
|
||||
|
|
@ -970,6 +993,8 @@ static void marvell_xspi_sdma_handle(struct cdns_xspi_dev *cdns_xspi)
|
|||
cdns_xspi->out_buffer, sdma_size);
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct spi_controller_mem_ops marvell_xspi_mem_ops = {
|
||||
|
|
@ -1083,7 +1108,7 @@ static int cdns_xspi_transfer_one_message_b0(struct spi_controller *controller,
|
|||
struct spi_device *spi = m->spi;
|
||||
struct spi_transfer *t = NULL;
|
||||
|
||||
const unsigned int max_len = MRVL_XFER_QWORD_BYTECOUNT * MRVL_XFER_QWORD_COUNT;
|
||||
const unsigned int max_len = MRVL_XFER_MAX_LEN;
|
||||
int current_transfer_len;
|
||||
int cs = spi_get_chipselect(spi, 0);
|
||||
int cs_change = 0;
|
||||
|
|
@ -1106,13 +1131,16 @@ static int cdns_xspi_transfer_one_message_b0(struct spi_controller *controller,
|
|||
list_for_each_entry(t, &m->transfers, transfer_list) {
|
||||
u8 *txd = (u8 *) t->tx_buf;
|
||||
u8 *rxd = (u8 *) t->rx_buf;
|
||||
u8 data[10];
|
||||
u8 data[MRVL_XFER_MAX_LEN] = {0};
|
||||
u32 cmd_regs[6];
|
||||
|
||||
if (!txd)
|
||||
txd = data;
|
||||
|
||||
cdns_xspi->in_buffer = txd + 1;
|
||||
if (rxd)
|
||||
cdns_xspi->in_buffer = rxd;
|
||||
else
|
||||
cdns_xspi->in_buffer = data;
|
||||
cdns_xspi->out_buffer = txd + 1;
|
||||
|
||||
while (t->len) {
|
||||
|
|
@ -1131,14 +1159,18 @@ static int cdns_xspi_transfer_one_message_b0(struct spi_controller *controller,
|
|||
cdns_xspi_prepare_transfer(cs, 1, current_transfer_len - 1,
|
||||
cmd_regs);
|
||||
cdns_xspi_trigger_command(cdns_xspi, cmd_regs);
|
||||
cdns_xspi->sdma_xfer_len = current_transfer_len - 1;
|
||||
if (!cdns_xspi_is_sdma_ready(cdns_xspi, true))
|
||||
return -EIO;
|
||||
cdns_xspi->sdma_handler(cdns_xspi);
|
||||
if (cdns_xspi->sdma_handler(cdns_xspi))
|
||||
return -EIO;
|
||||
if (!cdns_xspi_is_stig_ready(cdns_xspi, true))
|
||||
return -EIO;
|
||||
|
||||
cdns_xspi->in_buffer += current_transfer_len;
|
||||
cdns_xspi->out_buffer += current_transfer_len;
|
||||
if (rxd)
|
||||
cdns_xspi->in_buffer += current_transfer_len;
|
||||
if (t->tx_buf)
|
||||
cdns_xspi->out_buffer += current_transfer_len;
|
||||
}
|
||||
|
||||
if (rxd) {
|
||||
|
|
|
|||
|
|
@ -189,6 +189,12 @@ static int ch341_probe(struct usb_interface *intf,
|
|||
ctrl->transfer_one = ch341_transfer_one;
|
||||
ctrl->set_cs = ch341_set_cs;
|
||||
ctrl->auto_runtime_pm = false;
|
||||
/*
|
||||
* The CH341A SPI_STREAM command clocks data in both directions on
|
||||
* every transfer, so the driver always needs a TX and an RX buffer;
|
||||
* let the SPI core supply dummy buffers for half-duplex transfers.
|
||||
*/
|
||||
ctrl->flags = SPI_CONTROLLER_MUST_TX | SPI_CONTROLLER_MUST_RX;
|
||||
|
||||
usb_set_intfdata(intf, ch341);
|
||||
|
||||
|
|
|
|||
|
|
@ -32,6 +32,22 @@ struct dw_spi_chip_data {
|
|||
u32 rx_sample_dly; /* RX sample delay */
|
||||
};
|
||||
|
||||
/* Slave spi_transfer/spi_mem_op related */
|
||||
struct dw_spi_cfg {
|
||||
u8 tmode;
|
||||
u8 dfs;
|
||||
u32 ndf;
|
||||
u32 freq;
|
||||
u8 spi_frf;
|
||||
};
|
||||
|
||||
struct dw_spi_enh_cfg {
|
||||
u8 wait_c;
|
||||
u8 inst_l;
|
||||
u8 addr_l;
|
||||
u8 trans_t;
|
||||
};
|
||||
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
|
||||
#define DW_SPI_DBGFS_REG(_name, _off) \
|
||||
|
|
@ -132,10 +148,11 @@ static inline u32 dw_spi_rx_max(struct dw_spi *dws)
|
|||
return min_t(u32, dws->rx_len, dw_readl(dws, DW_SPI_RXFLR));
|
||||
}
|
||||
|
||||
static void dw_writer(struct dw_spi *dws)
|
||||
static u32 dw_writer(struct dw_spi *dws)
|
||||
{
|
||||
u32 max = dw_spi_tx_max(dws);
|
||||
u32 txw = 0;
|
||||
u32 tx = 0;
|
||||
|
||||
while (max--) {
|
||||
if (dws->tx) {
|
||||
|
|
@ -150,13 +167,16 @@ static void dw_writer(struct dw_spi *dws)
|
|||
}
|
||||
dw_write_io_reg(dws, DW_SPI_DR, txw);
|
||||
--dws->tx_len;
|
||||
++tx;
|
||||
}
|
||||
return tx;
|
||||
}
|
||||
|
||||
static void dw_reader(struct dw_spi *dws)
|
||||
static u32 dw_reader(struct dw_spi *dws)
|
||||
{
|
||||
u32 max = dw_spi_rx_max(dws);
|
||||
u32 rxw;
|
||||
u32 rx = 0;
|
||||
|
||||
while (max--) {
|
||||
rxw = dw_read_io_reg(dws, DW_SPI_DR);
|
||||
|
|
@ -171,7 +191,9 @@ static void dw_reader(struct dw_spi *dws)
|
|||
dws->rx += dws->n_bytes;
|
||||
}
|
||||
--dws->rx_len;
|
||||
++rx;
|
||||
}
|
||||
return rx;
|
||||
}
|
||||
|
||||
int dw_spi_check_status(struct dw_spi *dws, bool raw)
|
||||
|
|
@ -210,9 +232,55 @@ int dw_spi_check_status(struct dw_spi *dws, bool raw)
|
|||
}
|
||||
EXPORT_SYMBOL_NS_GPL(dw_spi_check_status, "SPI_DW_CORE");
|
||||
|
||||
static irqreturn_t dw_spi_irq_thread_fn(int irq, void *dev_id)
|
||||
{
|
||||
struct spi_controller *ctlr = dev_id;
|
||||
struct dw_spi *dws = spi_controller_get_devdata(ctlr);
|
||||
u32 rx, tx, imask, mask = 0;
|
||||
bool finalize = false;
|
||||
|
||||
do {
|
||||
/*
|
||||
* Read data from the Rx FIFO every time we've got a chance executing
|
||||
* this method. If there is nothing left to receive, terminate the
|
||||
* procedure. Otherwise adjust the Rx FIFO Threshold level if it's a
|
||||
* final stage of the transfer. By doing so we'll get the next IRQ
|
||||
* right when the leftover incoming data is received.
|
||||
*/
|
||||
rx = dw_reader(dws);
|
||||
if (!dws->rx_len) {
|
||||
mask |= 0xff;
|
||||
finalize = true;
|
||||
} else if (dws->rx_len <= dw_readl(dws, DW_SPI_RXFTLR)) {
|
||||
dw_writel(dws, DW_SPI_RXFTLR, dws->rx_len - 1);
|
||||
}
|
||||
|
||||
/*
|
||||
* Send data out as much as possible. The Tx FIFO Empty IRQ will be
|
||||
* disabled after the data transmission is finished so not to
|
||||
* have the TXE IRQ flood at the final stage of the transfer.
|
||||
*/
|
||||
tx = dw_writer(dws);
|
||||
if (!dws->tx_len)
|
||||
mask |= DW_SPI_INT_TXEI;
|
||||
cond_resched();
|
||||
} while (rx != 0 || tx != 0);
|
||||
|
||||
imask = DW_SPI_INT_TXEI | DW_SPI_INT_TXOI |
|
||||
DW_SPI_INT_RXUI | DW_SPI_INT_RXOI | DW_SPI_INT_RXFI;
|
||||
imask &= ~mask;
|
||||
dw_spi_umask_intr(dws, imask);
|
||||
|
||||
if (finalize)
|
||||
spi_finalize_current_transfer(dws->ctlr);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static irqreturn_t dw_spi_transfer_handler(struct dw_spi *dws)
|
||||
{
|
||||
u16 irq_status = dw_readl(dws, DW_SPI_ISR);
|
||||
bool finalize = false;
|
||||
|
||||
if (dw_spi_check_status(dws, false)) {
|
||||
spi_finalize_current_transfer(dws->ctlr);
|
||||
|
|
@ -229,6 +297,7 @@ static irqreturn_t dw_spi_transfer_handler(struct dw_spi *dws)
|
|||
dw_reader(dws);
|
||||
if (!dws->rx_len) {
|
||||
dw_spi_mask_intr(dws, 0xff);
|
||||
finalize = true;
|
||||
spi_finalize_current_transfer(dws->ctlr);
|
||||
} else if (dws->rx_len <= dw_readl(dws, DW_SPI_RXFTLR)) {
|
||||
dw_writel(dws, DW_SPI_RXFTLR, dws->rx_len - 1);
|
||||
|
|
@ -245,7 +314,14 @@ static irqreturn_t dw_spi_transfer_handler(struct dw_spi *dws)
|
|||
dw_spi_mask_intr(dws, DW_SPI_INT_TXEI);
|
||||
}
|
||||
|
||||
return IRQ_HANDLED;
|
||||
if (spi_controller_is_target(dws->ctlr) || finalize ||
|
||||
!dws->rx_len || !dws->tx_len ||
|
||||
!spi_get_csgpiod(dws->ctlr->cur_msg->spi, 0)) {
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
dw_spi_mask_intr(dws, 0xff);
|
||||
return IRQ_WAKE_THREAD;
|
||||
}
|
||||
|
||||
static irqreturn_t dw_spi_enh_handler(struct dw_spi *dws)
|
||||
|
|
@ -275,22 +351,15 @@ 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;
|
||||
|
||||
if (!dws->transfer_handler ||
|
||||
(!ctlr->cur_msg && dws->transfer_handler == dw_spi_transfer_handler)) {
|
||||
if (!ctlr->cur_msg && dws->transfer_handler != dw_spi_enh_handler) {
|
||||
dw_spi_mask_intr(dws, 0xff);
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
if (dws->transfer_handler == dw_spi_enh_handler &&
|
||||
!dws->rx_len && !dws->tx_len) {
|
||||
dw_spi_mask_intr(dws, 0xff);
|
||||
spi_finalize_current_transfer(ctlr);
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
return dws->transfer_handler(dws);
|
||||
}
|
||||
|
|
@ -342,8 +411,8 @@ static u32 dw_spi_prepare_cr0(struct dw_spi *dws, struct spi_device *spi)
|
|||
return cr0;
|
||||
}
|
||||
|
||||
void dw_spi_update_config(struct dw_spi *dws, struct spi_device *spi,
|
||||
struct dw_spi_cfg *cfg, struct dw_spi_enh_cfg *enh_cfg)
|
||||
static void dw_spi_update_config(struct dw_spi *dws, struct spi_device *spi,
|
||||
struct dw_spi_cfg *cfg, struct dw_spi_enh_cfg *enh_cfg)
|
||||
{
|
||||
struct dw_spi_chip_data *chip = spi_get_ctldata(spi);
|
||||
u32 cr0 = chip->cr0;
|
||||
|
|
@ -406,7 +475,6 @@ void dw_spi_update_config(struct dw_spi *dws, struct spi_device *spi,
|
|||
dw_writel(dws, DW_SPI_SPI_CTRLR0, cr0);
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL_NS_GPL(dw_spi_update_config, "SPI_DW_CORE");
|
||||
|
||||
static void dw_spi_irq_setup(struct dw_spi *dws)
|
||||
{
|
||||
|
|
@ -937,6 +1005,15 @@ static void dw_spi_enh_write_cmd_addr(struct dw_spi *dws, const struct spi_mem_o
|
|||
|
||||
dw_spi_set_cs(mem->spi, false);
|
||||
}
|
||||
|
||||
/*
|
||||
* FIXME: The exact reason for this delay is not fully understood,
|
||||
* but empirical testing shows it significantly improves the stability
|
||||
* of read/write operations. Without this delay, occasional transfer
|
||||
* errors or timeouts may occur under certain conditions.
|
||||
* Keeping it as a safeguard based on practical validation.
|
||||
*/
|
||||
udelay(5);
|
||||
}
|
||||
|
||||
static int dw_spi_exec_enh_mem_op(struct spi_mem *mem, const struct spi_mem_op *op)
|
||||
|
|
@ -948,6 +1025,8 @@ static int dw_spi_exec_enh_mem_op(struct spi_mem *mem, const struct spi_mem_op *
|
|||
unsigned long long ms;
|
||||
int ret;
|
||||
|
||||
dws->dma_mapped = false;
|
||||
|
||||
switch (op->data.buswidth) {
|
||||
case 0:
|
||||
case 1:
|
||||
|
|
@ -971,10 +1050,13 @@ static int dw_spi_exec_enh_mem_op(struct spi_mem *mem, const struct spi_mem_op *
|
|||
cfg.dfs = 8;
|
||||
cfg.freq = clamp(op->max_freq, 0U, dws->max_mem_freq);
|
||||
cfg.ndf = op->data.nbytes;
|
||||
if (op->data.dir == SPI_MEM_DATA_IN)
|
||||
if (op->data.dir == SPI_MEM_DATA_IN) {
|
||||
cfg.tmode = DW_SPI_CTRLR0_TMOD_RO;
|
||||
else
|
||||
dws->tx_dir = false;
|
||||
} else {
|
||||
cfg.tmode = DW_SPI_CTRLR0_TMOD_TO;
|
||||
dws->tx_dir = true;
|
||||
}
|
||||
|
||||
if (op->data.buswidth == op->addr.buswidth &&
|
||||
op->data.buswidth == op->cmd.buswidth)
|
||||
|
|
@ -1010,47 +1092,61 @@ static int dw_spi_exec_enh_mem_op(struct spi_mem *mem, const struct spi_mem_op *
|
|||
}
|
||||
}
|
||||
|
||||
dw_spi_enh_write_cmd_addr(dws, op, mem);
|
||||
|
||||
/*
|
||||
* FIXME: The exact reason for this delay is not fully understood,
|
||||
* but empirical testing shows it significantly improves the stability
|
||||
* of read/write operations. Without this delay, occasional transfer
|
||||
* errors or timeouts may occur under certain conditions.
|
||||
* Keeping it as a safeguard based on practical validation.
|
||||
*/
|
||||
udelay(5);
|
||||
|
||||
dw_spi_enh_irq_setup(dws);
|
||||
|
||||
/* Use timeout calculation from spi_transfer_wait() */
|
||||
ms = 8LL * MSEC_PER_SEC * (dws->rx_len ? dws->rx_len : dws->tx_len);
|
||||
do_div(ms, dws->current_freq);
|
||||
|
||||
/*
|
||||
* Increase it twice and add 200 ms tolerance, use
|
||||
* predefined maximum in case of overflow.
|
||||
*/
|
||||
ms += ms + 200;
|
||||
if (ms > UINT_MAX)
|
||||
ms = UINT_MAX;
|
||||
|
||||
ms = wait_for_completion_timeout(&ctlr->xfer_completion,
|
||||
msecs_to_jiffies(ms));
|
||||
if (ms == 0) {
|
||||
dw_spi_mask_intr(dws, 0xff);
|
||||
synchronize_irq(dws->irq);
|
||||
dws->rx = NULL;
|
||||
dws->tx = NULL;
|
||||
dws->rx_len = 0;
|
||||
dws->tx_len = 0;
|
||||
dw_spi_stop_mem_op(dws, mem->spi);
|
||||
return -EIO;
|
||||
if (dws->dma_nr_chans && dws->dma_ops->dma_enh_mem_get_chan &&
|
||||
op->data.nbytes > dws->fifo_len) {
|
||||
ret = dws->dma_ops->dma_enh_mem_get_chan(ctlr->dev.parent, dws);
|
||||
if (ret) {
|
||||
/* fall back to the PIO/IRQ path */
|
||||
dev_dbg(&ctlr->dev, "DMA enh mem channel unavailable (%d)\n", ret);
|
||||
} else if (dws->dma_ops->can_dma_enh_mem(ctlr)) {
|
||||
ret = dws->dma_ops->dma_enh_mem_setup(dws);
|
||||
if (ret)
|
||||
dev_err(&ctlr->dev, "DMA enh mem setup failed (%d)\n", ret);
|
||||
else
|
||||
dws->dma_mapped = true;
|
||||
}
|
||||
}
|
||||
|
||||
ret = dw_spi_wait_mem_op_done(dws);
|
||||
if (dws->dma_mapped) {
|
||||
dw_spi_enh_write_cmd_addr(dws, op, mem);
|
||||
|
||||
dw_spi_stop_mem_op(dws, mem->spi);
|
||||
ret = dws->dma_ops->dma_enh_mem_transfer(mem, op);
|
||||
|
||||
dw_spi_stop_mem_op(dws, mem->spi);
|
||||
} else {
|
||||
dw_spi_enh_write_cmd_addr(dws, op, mem);
|
||||
|
||||
dw_spi_enh_irq_setup(dws);
|
||||
|
||||
/* Use timeout calculation from spi_transfer_wait() */
|
||||
ms = 8LL * MSEC_PER_SEC * (dws->rx_len ? dws->rx_len : dws->tx_len);
|
||||
do_div(ms, dws->current_freq);
|
||||
|
||||
/*
|
||||
* Increase it twice and add 200 ms tolerance, use
|
||||
* predefined maximum in case of overflow.
|
||||
*/
|
||||
ms += ms + 200;
|
||||
if (ms > UINT_MAX)
|
||||
ms = UINT_MAX;
|
||||
|
||||
ms = wait_for_completion_timeout(&ctlr->xfer_completion,
|
||||
msecs_to_jiffies(ms));
|
||||
if (ms == 0) {
|
||||
dw_spi_mask_intr(dws, 0xff);
|
||||
synchronize_irq(dws->irq);
|
||||
dws->rx = NULL;
|
||||
dws->tx = NULL;
|
||||
dws->rx_len = 0;
|
||||
dws->tx_len = 0;
|
||||
dw_spi_stop_mem_op(dws, mem->spi);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
ret = dw_spi_wait_mem_op_done(dws);
|
||||
|
||||
dw_spi_stop_mem_op(dws, mem->spi);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
|
@ -1104,12 +1200,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);
|
||||
|
|
@ -1312,8 +1409,8 @@ int dw_spi_add_controller(struct device *dev, struct dw_spi *dws)
|
|||
/* Basic HW init */
|
||||
dw_spi_hw_init(dev, dws);
|
||||
|
||||
ret = request_irq(dws->irq, dw_spi_irq, IRQF_SHARED, dev_name(dev),
|
||||
ctlr);
|
||||
ret = request_threaded_irq(dws->irq, dw_spi_irq, dw_spi_irq_thread_fn,
|
||||
IRQF_SHARED, dev_name(dev), ctlr);
|
||||
if (ret < 0 && ret != -ENOTCONN) {
|
||||
dev_err(dev, "can not request IRQ\n");
|
||||
goto err_free_ctlr;
|
||||
|
|
@ -1360,6 +1457,9 @@ int dw_spi_add_controller(struct device *dev, struct dw_spi *dws)
|
|||
goto err_free_irq;
|
||||
} else if (ret) {
|
||||
dev_warn(dev, "DMA init failed\n");
|
||||
} else if (dws->dma_nr_chans == 1) {
|
||||
/* Full-duplex DMA transfers need both channels */
|
||||
dev_info(dev, "Single DMA channel, full-duplex DMA disabled\n");
|
||||
} else {
|
||||
ctlr->can_dma = dws->dma_ops->can_dma;
|
||||
ctlr->flags |= SPI_CONTROLLER_MUST_TX;
|
||||
|
|
|
|||
|
|
@ -42,14 +42,19 @@ static void dw_spi_dma_maxburst_init(struct dw_spi *dws)
|
|||
|
||||
def_burst = dws->fifo_len / 2;
|
||||
|
||||
ret = dma_get_slave_caps(dws->rxchan, &caps);
|
||||
if (!ret && caps.max_burst)
|
||||
max_burst = caps.max_burst;
|
||||
else
|
||||
max_burst = DW_SPI_RX_BURST_LEVEL;
|
||||
if (dws->rxchan) {
|
||||
ret = dma_get_slave_caps(dws->rxchan, &caps);
|
||||
if (!ret && caps.max_burst)
|
||||
max_burst = caps.max_burst;
|
||||
else
|
||||
max_burst = DW_SPI_RX_BURST_LEVEL;
|
||||
|
||||
dws->rxburst = min(max_burst, def_burst);
|
||||
dw_writel(dws, DW_SPI_DMARDLR, dws->rxburst - 1);
|
||||
dws->rxburst = min(max_burst, def_burst);
|
||||
dw_writel(dws, DW_SPI_DMARDLR, dws->rxburst - 1);
|
||||
}
|
||||
|
||||
if (!dws->txchan)
|
||||
return;
|
||||
|
||||
ret = dma_get_slave_caps(dws->txchan, &caps);
|
||||
if (!ret && caps.max_burst)
|
||||
|
|
@ -74,20 +79,39 @@ static void dw_spi_dma_maxburst_init(struct dw_spi *dws)
|
|||
|
||||
static int dw_spi_dma_caps_init(struct dw_spi *dws)
|
||||
{
|
||||
struct dma_slave_caps tx, rx;
|
||||
struct dma_slave_caps tx = {}, rx = {};
|
||||
int ret;
|
||||
|
||||
ret = dma_get_slave_caps(dws->txchan, &tx);
|
||||
if (ret)
|
||||
return ret;
|
||||
if (dws->txchan) {
|
||||
ret = dma_get_slave_caps(dws->txchan, &tx);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = dma_get_slave_caps(dws->rxchan, &rx);
|
||||
if (ret)
|
||||
return ret;
|
||||
if (!(tx.directions & BIT(DMA_MEM_TO_DEV)))
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
if (!(tx.directions & BIT(DMA_MEM_TO_DEV) &&
|
||||
rx.directions & BIT(DMA_DEV_TO_MEM)))
|
||||
return -ENXIO;
|
||||
if (dws->rxchan) {
|
||||
ret = dma_get_slave_caps(dws->rxchan, &rx);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (!(rx.directions & BIT(DMA_DEV_TO_MEM)))
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
/* With a single channel only one direction is available at a time */
|
||||
if (!dws->rxchan) {
|
||||
dws->dma_sg_burst = tx.max_sg_burst;
|
||||
dws->dma_addr_widths = tx.dst_addr_widths;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!dws->txchan) {
|
||||
dws->dma_sg_burst = rx.max_sg_burst;
|
||||
dws->dma_addr_widths = rx.src_addr_widths;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (tx.max_sg_burst > 0 && rx.max_sg_burst > 0)
|
||||
dws->dma_sg_burst = min(tx.max_sg_burst, rx.max_sg_burst);
|
||||
|
|
@ -169,40 +193,54 @@ static int dw_spi_dma_init_generic(struct device *dev, struct dw_spi *dws)
|
|||
{
|
||||
int ret;
|
||||
|
||||
dws->dma_nr_chans = 0;
|
||||
|
||||
dws->rxchan = dma_request_chan(dev, "rx");
|
||||
if (IS_ERR(dws->rxchan)) {
|
||||
ret = PTR_ERR(dws->rxchan);
|
||||
dws->rxchan = NULL;
|
||||
goto err_exit;
|
||||
}
|
||||
dws->dma_nr_chans++;
|
||||
|
||||
/*
|
||||
* Some platforms have only one DMA channel for the controller. Keep
|
||||
* the Rx channel in that case, it can still serve half-duplex
|
||||
* transfers.
|
||||
*/
|
||||
dws->txchan = dma_request_chan(dev, "tx");
|
||||
if (IS_ERR(dws->txchan)) {
|
||||
ret = PTR_ERR(dws->txchan);
|
||||
dws->txchan = NULL;
|
||||
goto free_rxchan;
|
||||
if (ret == -EPROBE_DEFER)
|
||||
goto free_rxchan;
|
||||
} else {
|
||||
dws->dma_nr_chans++;
|
||||
}
|
||||
|
||||
dws->ctlr->dma_rx = dws->rxchan;
|
||||
dws->ctlr->dma_tx = dws->txchan;
|
||||
|
||||
init_completion(&dws->dma_completion);
|
||||
|
||||
ret = dw_spi_dma_caps_init(dws);
|
||||
if (ret)
|
||||
goto free_txchan;
|
||||
|
||||
dw_spi_dma_maxburst_init(dws);
|
||||
|
||||
init_completion(&dws->dma_completion);
|
||||
|
||||
dws->ctlr->dma_rx = dws->rxchan;
|
||||
dws->ctlr->dma_tx = dws->txchan;
|
||||
|
||||
return 0;
|
||||
|
||||
free_txchan:
|
||||
dma_release_channel(dws->txchan);
|
||||
dws->txchan = NULL;
|
||||
if (dws->txchan) {
|
||||
dma_release_channel(dws->txchan);
|
||||
dws->txchan = NULL;
|
||||
}
|
||||
free_rxchan:
|
||||
dma_release_channel(dws->rxchan);
|
||||
dws->rxchan = NULL;
|
||||
err_exit:
|
||||
dws->dma_nr_chans = 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
@ -286,19 +324,27 @@ static inline bool dw_spi_dma_tx_busy(struct dw_spi *dws)
|
|||
(DW_SPI_SR_BUSY | DW_SPI_SR_TF_EMPT)) != DW_SPI_SR_TF_EMPT;
|
||||
}
|
||||
|
||||
static int dw_spi_dma_wait_tx_done(struct dw_spi *dws,
|
||||
struct spi_transfer *xfer)
|
||||
static int dw_spi_dma_wait_tx_done(struct dw_spi *dws, u32 speed_hz)
|
||||
{
|
||||
int retry = DW_SPI_WAIT_RETRIES;
|
||||
struct spi_delay delay;
|
||||
unsigned long ns, us;
|
||||
u32 nents;
|
||||
|
||||
nents = dw_readl(dws, DW_SPI_TXFLR);
|
||||
delay.unit = SPI_DELAY_UNIT_SCK;
|
||||
delay.value = nents * dws->n_bytes * BITS_PER_BYTE;
|
||||
ns = DIV_ROUND_UP(NSEC_PER_SEC, speed_hz) * nents *
|
||||
dws->n_bytes * BITS_PER_BYTE;
|
||||
if (ns <= NSEC_PER_USEC) {
|
||||
delay.unit = SPI_DELAY_UNIT_NSECS;
|
||||
delay.value = ns;
|
||||
} else {
|
||||
us = DIV_ROUND_UP(ns, NSEC_PER_USEC);
|
||||
delay.unit = SPI_DELAY_UNIT_USECS;
|
||||
delay.value = clamp_val(us, 0, USHRT_MAX);
|
||||
}
|
||||
|
||||
while (dw_spi_dma_tx_busy(dws) && retry--)
|
||||
spi_delay_exec(&delay, xfer);
|
||||
spi_delay_exec(&delay, NULL);
|
||||
|
||||
if (retry < 0) {
|
||||
dev_err(&dws->ctlr->dev, "Tx hanged up\n");
|
||||
|
|
@ -659,7 +705,7 @@ static int dw_spi_dma_transfer(struct dw_spi *dws, struct spi_transfer *xfer)
|
|||
return ret;
|
||||
|
||||
if (dws->ctlr->cur_msg->status == -EINPROGRESS) {
|
||||
ret = dw_spi_dma_wait_tx_done(dws, xfer);
|
||||
ret = dw_spi_dma_wait_tx_done(dws, xfer->effective_speed_hz);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
|
@ -672,14 +718,180 @@ static int dw_spi_dma_transfer(struct dw_spi *dws, struct spi_transfer *xfer)
|
|||
|
||||
static void dw_spi_dma_stop(struct dw_spi *dws)
|
||||
{
|
||||
if (test_bit(DW_SPI_TX_BUSY, &dws->dma_chan_busy)) {
|
||||
/*
|
||||
* This is only called on errors, so don't rely on the busy flags
|
||||
* being in sync with the DMA engine state. A channel may be missing
|
||||
* when only one DMA channel is available.
|
||||
*/
|
||||
if (dws->txchan)
|
||||
dmaengine_terminate_sync(dws->txchan);
|
||||
clear_bit(DW_SPI_TX_BUSY, &dws->dma_chan_busy);
|
||||
}
|
||||
if (test_bit(DW_SPI_RX_BUSY, &dws->dma_chan_busy)) {
|
||||
if (dws->rxchan)
|
||||
dmaengine_terminate_sync(dws->rxchan);
|
||||
clear_bit(DW_SPI_RX_BUSY, &dws->dma_chan_busy);
|
||||
|
||||
clear_bit(DW_SPI_TX_BUSY, &dws->dma_chan_busy);
|
||||
clear_bit(DW_SPI_RX_BUSY, &dws->dma_chan_busy);
|
||||
}
|
||||
|
||||
/*
|
||||
* The enhanced SPI memory operations are half-duplex, so a single DMA channel
|
||||
* is enough for them. On a platform with only one channel, the channel is
|
||||
* bound to a direction by its "tx"/"rx" DMA specifier, and whichever channel
|
||||
* is currently held tells which direction it serves. Keep it across
|
||||
* operations and swap it only when the transfer direction changes.
|
||||
*/
|
||||
static int dw_spi_enh_mem_dma_get_chan(struct device *dev, struct dw_spi *dws)
|
||||
{
|
||||
struct dma_chan **chan, **other;
|
||||
const char *name;
|
||||
int ret;
|
||||
|
||||
if (dws->dma_nr_chans == 2)
|
||||
return 0;
|
||||
|
||||
if (dws->tx_dir) {
|
||||
chan = &dws->txchan;
|
||||
other = &dws->rxchan;
|
||||
name = "tx";
|
||||
} else {
|
||||
chan = &dws->rxchan;
|
||||
other = &dws->txchan;
|
||||
name = "rx";
|
||||
}
|
||||
|
||||
if (*chan)
|
||||
return 0;
|
||||
|
||||
dws->ctlr->dma_tx = NULL;
|
||||
dws->ctlr->dma_rx = NULL;
|
||||
|
||||
/*
|
||||
* With a single physical channel, the held channel must be released
|
||||
* before it can be requested again with the handshake of the other
|
||||
* direction. On failure both stay NULL, and the next operation
|
||||
* retries the request and falls back to PIO meanwhile.
|
||||
*/
|
||||
|
||||
if (*other) {
|
||||
dma_release_channel(*other);
|
||||
*other = NULL;
|
||||
}
|
||||
|
||||
*chan = dma_request_chan(dev, name);
|
||||
if (IS_ERR(*chan)) {
|
||||
ret = PTR_ERR(*chan);
|
||||
*chan = NULL;
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = dw_spi_dma_caps_init(dws);
|
||||
if (ret) {
|
||||
dma_release_channel(*chan);
|
||||
*chan = NULL;
|
||||
return ret;
|
||||
}
|
||||
|
||||
dw_spi_dma_maxburst_init(dws);
|
||||
|
||||
dws->ctlr->dma_tx = dws->txchan;
|
||||
dws->ctlr->dma_rx = dws->rxchan;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dw_spi_enh_mem_dma_setup(struct dw_spi *dws)
|
||||
{
|
||||
u16 dma_ctrl, level;
|
||||
int ret;
|
||||
|
||||
/* Setup DMA channels */
|
||||
if (dws->tx_dir) {
|
||||
ret = dw_spi_dma_config_tx(dws);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
dma_ctrl = DW_SPI_DMACR_TDMAE;
|
||||
} else {
|
||||
ret = dw_spi_dma_config_rx(dws);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
dma_ctrl = DW_SPI_DMACR_RDMAE;
|
||||
}
|
||||
|
||||
dw_writel(dws, DW_SPI_DMACR, dma_ctrl);
|
||||
|
||||
/* Clear stale error status, it is checked after the transfer */
|
||||
dw_readl(dws, DW_SPI_ICR);
|
||||
|
||||
reinit_completion(&dws->dma_completion);
|
||||
|
||||
level = min_t(unsigned int, dws->fifo_len / 2, dws->tx_len);
|
||||
dw_writel(dws, DW_SPI_TXFTLR, level);
|
||||
|
||||
level = min_t(unsigned int, dws->fifo_len / 2, dws->rx_len);
|
||||
dw_writel(dws, DW_SPI_RXFTLR, level ? level - 1 : 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool dw_spi_enh_mem_can_dma(struct spi_controller *ctlr)
|
||||
{
|
||||
struct dw_spi *dws = spi_controller_get_devdata(ctlr);
|
||||
unsigned int len = dws->tx_dir ? dws->tx_len : dws->rx_len;
|
||||
enum dma_slave_buswidth dma_bus_width;
|
||||
|
||||
if (len <= dws->fifo_len)
|
||||
return false;
|
||||
|
||||
dma_bus_width = dw_spi_dma_convert_width(dws->n_bytes);
|
||||
|
||||
return dws->dma_addr_widths & BIT(dma_bus_width);
|
||||
}
|
||||
|
||||
static int dw_spi_enh_mem_dma_transfer(struct spi_mem *mem, const struct spi_mem_op *op)
|
||||
{
|
||||
struct spi_controller *ctlr = mem->spi->controller;
|
||||
struct dw_spi *dws = spi_controller_get_devdata(ctlr);
|
||||
struct sg_table sgt;
|
||||
int ret;
|
||||
|
||||
ret = spi_controller_dma_map_mem_op_data(ctlr, op, &sgt);
|
||||
if (ret)
|
||||
goto out_clear_dmac;
|
||||
|
||||
if (dws->tx_dir) {
|
||||
ret = dw_spi_dma_submit_tx(dws, sgt.sgl, sgt.nents);
|
||||
if (ret)
|
||||
goto out_unmap;
|
||||
|
||||
dma_async_issue_pending(dws->txchan);
|
||||
|
||||
ret = dw_spi_dma_wait(dws, dws->tx_len, dws->current_freq);
|
||||
if (!ret)
|
||||
ret = dw_spi_dma_wait_tx_done(dws, dws->current_freq);
|
||||
} else {
|
||||
ret = dw_spi_dma_submit_rx(dws, sgt.sgl, sgt.nents);
|
||||
if (ret)
|
||||
goto out_unmap;
|
||||
|
||||
dma_async_issue_pending(dws->rxchan);
|
||||
|
||||
ret = dw_spi_dma_wait(dws, dws->rx_len, dws->current_freq);
|
||||
if (!ret)
|
||||
ret = dw_spi_dma_wait_rx_done(dws);
|
||||
}
|
||||
|
||||
if (ret)
|
||||
dw_spi_dma_stop(dws);
|
||||
else
|
||||
ret = dw_spi_check_status(dws, true);
|
||||
|
||||
out_unmap:
|
||||
spi_controller_dma_unmap_mem_op_data(ctlr, op, &sgt);
|
||||
out_clear_dmac:
|
||||
dw_writel(dws, DW_SPI_DMACR, 0);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct dw_spi_dma_ops dw_spi_dma_mfld_ops = {
|
||||
|
|
@ -704,6 +916,11 @@ static const struct dw_spi_dma_ops dw_spi_dma_generic_ops = {
|
|||
.can_dma = dw_spi_can_dma,
|
||||
.dma_transfer = dw_spi_dma_transfer,
|
||||
.dma_stop = dw_spi_dma_stop,
|
||||
|
||||
.dma_enh_mem_get_chan = dw_spi_enh_mem_dma_get_chan,
|
||||
.dma_enh_mem_setup = dw_spi_enh_mem_dma_setup,
|
||||
.can_dma_enh_mem = dw_spi_enh_mem_can_dma,
|
||||
.dma_enh_mem_transfer = dw_spi_enh_mem_dma_transfer,
|
||||
};
|
||||
|
||||
void dw_spi_dma_setup_generic(struct dw_spi *dws)
|
||||
|
|
|
|||
|
|
@ -363,6 +363,8 @@ static int dw_spi_jhb100_init(struct platform_device *pdev,
|
|||
|
||||
dw_spi_jhb100_mask_intr(&dwsmmio->dws, 0xff);
|
||||
|
||||
dw_spi_dma_setup_generic(&dwsmmio->dws);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -158,22 +158,6 @@
|
|||
#define DW_SPI_GET_BYTE(_val, _idx) \
|
||||
((_val) >> (BITS_PER_BYTE * (_idx)) & 0xff)
|
||||
|
||||
/* Slave spi_transfer/spi_mem_op related */
|
||||
struct dw_spi_cfg {
|
||||
u8 tmode;
|
||||
u8 dfs;
|
||||
u32 ndf;
|
||||
u32 freq;
|
||||
u8 spi_frf;
|
||||
};
|
||||
|
||||
struct dw_spi_enh_cfg {
|
||||
u8 wait_c;
|
||||
u8 inst_l;
|
||||
u8 addr_l;
|
||||
u8 trans_t;
|
||||
};
|
||||
|
||||
struct dw_spi;
|
||||
struct dw_spi_dma_ops {
|
||||
int (*dma_init)(struct device *dev, struct dw_spi *dws);
|
||||
|
|
@ -183,6 +167,12 @@ struct dw_spi_dma_ops {
|
|||
struct spi_transfer *xfer);
|
||||
int (*dma_transfer)(struct dw_spi *dws, struct spi_transfer *xfer);
|
||||
void (*dma_stop)(struct dw_spi *dws);
|
||||
|
||||
/* enh mem dma ops */
|
||||
int (*dma_enh_mem_get_chan)(struct device *dev, struct dw_spi *dws);
|
||||
int (*dma_enh_mem_setup)(struct dw_spi *dws);
|
||||
bool (*can_dma_enh_mem)(struct spi_controller *ctlr);
|
||||
int (*dma_enh_mem_transfer)(struct spi_mem *mem, const struct spi_mem_op *op);
|
||||
};
|
||||
|
||||
struct dw_spi {
|
||||
|
|
@ -227,6 +217,8 @@ struct dw_spi {
|
|||
u32 txburst;
|
||||
struct dma_chan *rxchan;
|
||||
u32 rxburst;
|
||||
unsigned int dma_nr_chans;
|
||||
bool tx_dir;
|
||||
u32 dma_sg_burst;
|
||||
u32 dma_addr_widths;
|
||||
unsigned long dma_chan_busy;
|
||||
|
|
@ -337,8 +329,6 @@ static inline void dw_spi_shutdown_chip(struct dw_spi *dws)
|
|||
}
|
||||
|
||||
extern void dw_spi_set_cs(struct spi_device *spi, bool enable);
|
||||
extern void dw_spi_update_config(struct dw_spi *dws, struct spi_device *spi,
|
||||
struct dw_spi_cfg *cfg, struct dw_spi_enh_cfg *enh_cfg);
|
||||
extern int dw_spi_check_status(struct dw_spi *dws, bool raw);
|
||||
extern int dw_spi_add_controller(struct device *dev, struct dw_spi *dws);
|
||||
extern void dw_spi_remove_controller(struct dw_spi *dws);
|
||||
|
|
|
|||
|
|
@ -273,6 +273,7 @@ ep93xx_spi_dma_prepare(struct spi_controller *host,
|
|||
struct dma_async_tx_descriptor *txd;
|
||||
enum dma_slave_buswidth buswidth;
|
||||
struct dma_slave_config conf;
|
||||
struct device *dma_dev;
|
||||
struct scatterlist *sg;
|
||||
struct sg_table *sgt;
|
||||
struct dma_chan *chan;
|
||||
|
|
@ -348,14 +349,15 @@ ep93xx_spi_dma_prepare(struct spi_controller *host,
|
|||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
|
||||
nents = dma_map_sg(chan->device->dev, sgt->sgl, sgt->nents, dir);
|
||||
dma_dev = dmaengine_get_dma_device(chan);
|
||||
nents = dma_map_sg(dma_dev, sgt->sgl, sgt->nents, dir);
|
||||
if (!nents)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
txd = dmaengine_prep_slave_sg(chan, sgt->sgl, nents, conf.direction,
|
||||
DMA_CTRL_ACK);
|
||||
if (!txd) {
|
||||
dma_unmap_sg(chan->device->dev, sgt->sgl, sgt->nents, dir);
|
||||
dma_unmap_sg(dma_dev, sgt->sgl, sgt->nents, dir);
|
||||
return ERR_PTR(-ENOMEM);
|
||||
}
|
||||
return txd;
|
||||
|
|
@ -384,7 +386,7 @@ static void ep93xx_spi_dma_finish(struct spi_controller *host,
|
|||
sgt = &espi->tx_sgt;
|
||||
}
|
||||
|
||||
dma_unmap_sg(chan->device->dev, sgt->sgl, sgt->nents, dir);
|
||||
dma_unmap_sg(dmaengine_get_dma_device(chan), sgt->sgl, sgt->nents, dir);
|
||||
}
|
||||
|
||||
static void ep93xx_spi_dma_callback(void *callback_param)
|
||||
|
|
|
|||
|
|
@ -670,6 +670,7 @@ static void dspi_dma_xfer(struct fsl_dspi *dspi)
|
|||
static int dspi_request_dma(struct fsl_dspi *dspi, phys_addr_t phy_addr)
|
||||
{
|
||||
struct device *dev = &dspi->pdev->dev;
|
||||
struct device *tx_dev, *rx_dev;
|
||||
struct dma_slave_config cfg;
|
||||
struct fsl_dspi_dma *dma;
|
||||
int ret;
|
||||
|
|
@ -688,20 +689,23 @@ static int dspi_request_dma(struct fsl_dspi *dspi, phys_addr_t phy_addr)
|
|||
goto err_tx_channel;
|
||||
}
|
||||
|
||||
tx_dev = dmaengine_get_dma_device(dma->chan_tx);
|
||||
rx_dev = dmaengine_get_dma_device(dma->chan_rx);
|
||||
|
||||
if (spi_controller_is_target(dspi->ctlr)) {
|
||||
/*
|
||||
* In target mode we have to be ready to receive the maximum
|
||||
* that can possibly be transferred at once by EDMA without any
|
||||
* FIFO underflows.
|
||||
*/
|
||||
dma->bufsize = min(dma_get_max_seg_size(dma->chan_rx->device->dev),
|
||||
dma_get_max_seg_size(dma->chan_tx->device->dev)) *
|
||||
dma->bufsize = min(dma_get_max_seg_size(rx_dev),
|
||||
dma_get_max_seg_size(tx_dev)) *
|
||||
DMA_SLAVE_BUSWIDTH_4_BYTES;
|
||||
} else {
|
||||
dma->bufsize = PAGE_SIZE;
|
||||
}
|
||||
|
||||
dma->tx_dma_buf = dma_alloc_noncoherent(dma->chan_tx->device->dev,
|
||||
dma->tx_dma_buf = dma_alloc_noncoherent(tx_dev,
|
||||
dma->bufsize, &dma->tx_dma_phys,
|
||||
DMA_TO_DEVICE, GFP_KERNEL);
|
||||
if (!dma->tx_dma_buf) {
|
||||
|
|
@ -709,7 +713,7 @@ static int dspi_request_dma(struct fsl_dspi *dspi, phys_addr_t phy_addr)
|
|||
goto err_tx_dma_buf;
|
||||
}
|
||||
|
||||
dma->rx_dma_buf = dma_alloc_noncoherent(dma->chan_rx->device->dev,
|
||||
dma->rx_dma_buf = dma_alloc_noncoherent(rx_dev,
|
||||
dma->bufsize, &dma->rx_dma_phys,
|
||||
DMA_FROM_DEVICE, GFP_KERNEL);
|
||||
if (!dma->rx_dma_buf) {
|
||||
|
|
@ -746,12 +750,11 @@ static int dspi_request_dma(struct fsl_dspi *dspi, phys_addr_t phy_addr)
|
|||
return 0;
|
||||
|
||||
err_slave_config:
|
||||
dma_free_noncoherent(dma->chan_rx->device->dev, dma->bufsize,
|
||||
dma->rx_dma_buf, dma->rx_dma_phys,
|
||||
DMA_FROM_DEVICE);
|
||||
dma_free_noncoherent(rx_dev, dma->bufsize, dma->rx_dma_buf,
|
||||
dma->rx_dma_phys, DMA_FROM_DEVICE);
|
||||
err_rx_dma_buf:
|
||||
dma_free_noncoherent(dma->chan_tx->device->dev, dma->bufsize,
|
||||
dma->tx_dma_buf, dma->tx_dma_phys, DMA_TO_DEVICE);
|
||||
dma_free_noncoherent(tx_dev, dma->bufsize, dma->tx_dma_buf,
|
||||
dma->tx_dma_phys, DMA_TO_DEVICE);
|
||||
err_tx_dma_buf:
|
||||
dma_release_channel(dma->chan_tx);
|
||||
err_tx_channel:
|
||||
|
|
@ -771,16 +774,18 @@ static void dspi_release_dma(struct fsl_dspi *dspi)
|
|||
return;
|
||||
|
||||
if (dma->chan_tx) {
|
||||
dma_free_noncoherent(dma->chan_tx->device->dev, dma->bufsize,
|
||||
dma->tx_dma_buf, dma->tx_dma_phys,
|
||||
DMA_TO_DEVICE);
|
||||
struct device *tx_dev = dmaengine_get_dma_device(dma->chan_tx);
|
||||
|
||||
dma_free_noncoherent(tx_dev, dma->bufsize, dma->tx_dma_buf,
|
||||
dma->tx_dma_phys, DMA_TO_DEVICE);
|
||||
dma_release_channel(dma->chan_tx);
|
||||
}
|
||||
|
||||
if (dma->chan_rx) {
|
||||
dma_free_noncoherent(dma->chan_rx->device->dev, dma->bufsize,
|
||||
dma->rx_dma_buf, dma->rx_dma_phys,
|
||||
DMA_FROM_DEVICE);
|
||||
struct device *rx_dev = dmaengine_get_dma_device(dma->chan_rx);
|
||||
|
||||
dma_free_noncoherent(rx_dev, dma->bufsize, dma->rx_dma_buf,
|
||||
dma->rx_dma_phys, DMA_FROM_DEVICE);
|
||||
dma_release_channel(dma->chan_rx);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -702,10 +702,18 @@ static void of_fsl_spi_remove(struct platform_device *ofdev)
|
|||
{
|
||||
struct spi_controller *host = platform_get_drvdata(ofdev);
|
||||
struct mpc8xxx_spi *mpc8xxx_spi = spi_controller_get_devdata(host);
|
||||
#if IS_ENABLED(CONFIG_FSL_SOC)
|
||||
struct fsl_spi_platform_data *pdata = dev_get_platdata(&ofdev->dev);
|
||||
struct mpc8xxx_spi_probe_info *pinfo = to_of_pinfo(pdata);
|
||||
#endif
|
||||
|
||||
spi_unregister_controller(host);
|
||||
|
||||
fsl_spi_cpm_free(mpc8xxx_spi);
|
||||
#if IS_ENABLED(CONFIG_FSL_SOC)
|
||||
if (pinfo->immr_spi_cs)
|
||||
iounmap(pinfo->immr_spi_cs);
|
||||
#endif
|
||||
}
|
||||
|
||||
static struct platform_driver of_fsl_spi_driver = {
|
||||
|
|
|
|||
|
|
@ -10,10 +10,13 @@
|
|||
#include <linux/dmaengine.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/dma/qcom-gpi-dma.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/iopoll.h>
|
||||
#include <linux/log2.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/panic_notifier.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pm_opp.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
|
|
@ -115,6 +118,8 @@ struct spi_geni_master {
|
|||
struct dma_chan *rx;
|
||||
int cur_xfer_mode;
|
||||
const struct geni_spi_desc *dev_data;
|
||||
struct notifier_block panic_nb;
|
||||
struct gpio_desc *ready_gpio;
|
||||
};
|
||||
|
||||
static void spi_slv_setup(struct spi_geni_master *mas)
|
||||
|
|
@ -173,6 +178,9 @@ static void handle_se_timeout(struct spi_controller *spi)
|
|||
xfer = mas->cur_xfer;
|
||||
mas->cur_xfer = NULL;
|
||||
|
||||
if (spi->target)
|
||||
gpiod_set_value(mas->ready_gpio, 0);
|
||||
|
||||
/* The controller doesn't support the Cancel commnand in target mode */
|
||||
if (!spi->target) {
|
||||
reinit_completion(&mas->cancel_done);
|
||||
|
|
@ -363,8 +371,8 @@ static int geni_spi_set_clock_and_bw(struct geni_se *se,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int setup_fifo_params(struct spi_device *spi_slv,
|
||||
struct spi_controller *spi)
|
||||
static void setup_spi_params(struct spi_device *spi_slv,
|
||||
struct spi_controller *spi)
|
||||
{
|
||||
struct spi_geni_master *mas = spi_controller_get_devdata(spi);
|
||||
struct geni_se *se = &mas->se;
|
||||
|
|
@ -390,8 +398,6 @@ static int setup_fifo_params(struct spi_device *spi_slv,
|
|||
|
||||
trace_geni_spi_setup_params(mas->dev, chipselect, spi_slv->mode,
|
||||
mode_changed, cs_changed);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
|
|
@ -554,17 +560,14 @@ static int spi_geni_prepare_message(struct spi_controller *spi,
|
|||
struct spi_message *spi_msg)
|
||||
{
|
||||
struct spi_geni_master *mas = spi_controller_get_devdata(spi);
|
||||
int ret;
|
||||
|
||||
switch (mas->cur_xfer_mode) {
|
||||
case GENI_SE_FIFO:
|
||||
case GENI_SE_DMA:
|
||||
if (spi_geni_is_abort_still_pending(mas))
|
||||
return -EBUSY;
|
||||
ret = setup_fifo_params(spi_msg->spi, spi);
|
||||
if (ret)
|
||||
dev_err(mas->dev, "Couldn't select mode %d\n", ret);
|
||||
return ret;
|
||||
setup_spi_params(spi_msg->spi, spi);
|
||||
return 0;
|
||||
|
||||
case GENI_GPI_DMA:
|
||||
/* nothing to do for GPI DMA */
|
||||
|
|
@ -700,7 +703,7 @@ static int spi_geni_init(struct spi_geni_master *mas)
|
|||
case 0:
|
||||
mas->cur_xfer_mode = GENI_SE_FIFO;
|
||||
geni_se_select_mode(se, GENI_SE_FIFO);
|
||||
/* setup_fifo_params assumes that these registers start with a zero value */
|
||||
/* setup_spi_params assumes that these registers start with a zero value */
|
||||
writel(0, se->base + SE_SPI_LOOPBACK);
|
||||
writel(0, se->base + SE_SPI_DEMUX_SEL);
|
||||
writel(0, se->base + SE_SPI_CPHA);
|
||||
|
|
@ -938,8 +941,12 @@ static int spi_geni_transfer_one(struct spi_controller *spi,
|
|||
if (mas->cur_xfer_mode == GENI_SE_FIFO || mas->cur_xfer_mode == GENI_SE_DMA) {
|
||||
ret = setup_se_xfer(xfer, mas, slv->mode, spi);
|
||||
/* SPI framework expects +ve ret code to wait for transfer complete */
|
||||
if (!ret)
|
||||
if (!ret) {
|
||||
if (spi->target)
|
||||
gpiod_set_value(mas->ready_gpio, 1);
|
||||
ret = 1;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
return setup_gsi_xfer(xfer, mas, slv, spi);
|
||||
|
|
@ -1025,6 +1032,8 @@ static irqreturn_t geni_spi_isr(int irq, void *data)
|
|||
if (dma_rx_status & RX_RESET_DONE)
|
||||
complete(&mas->rx_reset_done);
|
||||
if (!mas->tx_rem_bytes && !mas->rx_rem_bytes && xfer) {
|
||||
if (spi->target)
|
||||
gpiod_set_value(mas->ready_gpio, 0);
|
||||
spi_finalize_current_transfer(spi);
|
||||
mas->cur_xfer = NULL;
|
||||
}
|
||||
|
|
@ -1066,6 +1075,69 @@ static int spi_geni_target_abort(struct spi_controller *spi)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static void spi_geni_shutdown(struct platform_device *pdev)
|
||||
{
|
||||
struct spi_controller *spi = platform_get_drvdata(pdev);
|
||||
|
||||
spi_controller_suspend(spi);
|
||||
}
|
||||
|
||||
static int spi_geni_panic_notifier(struct notifier_block *nb,
|
||||
unsigned long action, void *data)
|
||||
{
|
||||
struct spi_geni_master *mas = container_of(nb, struct spi_geni_master, panic_nb);
|
||||
struct spi_controller *spi = dev_get_drvdata(mas->dev);
|
||||
struct geni_se *se = &mas->se;
|
||||
u32 val;
|
||||
|
||||
if (!pm_runtime_active(mas->dev))
|
||||
return NOTIFY_OK;
|
||||
|
||||
if (mas->cur_xfer_mode == GENI_GPI_DMA) {
|
||||
dmaengine_terminate_async(mas->tx);
|
||||
dmaengine_terminate_async(mas->rx);
|
||||
return NOTIFY_OK;
|
||||
}
|
||||
|
||||
if (!(readl_relaxed(se->base + SE_GENI_STATUS) & M_GENI_CMD_ACTIVE))
|
||||
return NOTIFY_OK;
|
||||
|
||||
if (!spi->target) {
|
||||
geni_se_cancel_m_cmd(se);
|
||||
if (!readl_poll_timeout_atomic(se->base + SE_GENI_M_IRQ_STATUS, val,
|
||||
val & M_CMD_CANCEL_EN, 10, 50000)) {
|
||||
writel_relaxed(M_CMD_CANCEL_EN, se->base + SE_GENI_M_IRQ_CLEAR);
|
||||
return NOTIFY_OK;
|
||||
}
|
||||
}
|
||||
|
||||
geni_se_abort_m_cmd(se);
|
||||
if (!readl_poll_timeout_atomic(se->base + SE_GENI_M_IRQ_STATUS, val,
|
||||
val & M_CMD_ABORT_EN, 10, 50000))
|
||||
writel_relaxed(M_CMD_ABORT_EN, se->base + SE_GENI_M_IRQ_CLEAR);
|
||||
|
||||
if (mas->cur_xfer_mode == GENI_SE_DMA) {
|
||||
writel_relaxed(1, se->base + SE_DMA_TX_FSM_RST);
|
||||
readl_poll_timeout_atomic(se->base + SE_DMA_TX_IRQ_STAT, val,
|
||||
val & TX_RESET_DONE, 10, 50000);
|
||||
writel_relaxed(val, se->base + SE_DMA_TX_IRQ_CLR);
|
||||
|
||||
writel_relaxed(1, se->base + SE_DMA_RX_FSM_RST);
|
||||
readl_poll_timeout_atomic(se->base + SE_DMA_RX_IRQ_STAT, val,
|
||||
val & RX_RESET_DONE, 10, 50000);
|
||||
writel_relaxed(val, se->base + SE_DMA_RX_IRQ_CLR);
|
||||
}
|
||||
|
||||
return NOTIFY_OK;
|
||||
}
|
||||
|
||||
static void spi_geni_unregister_notifiers(void *data)
|
||||
{
|
||||
struct spi_geni_master *mas = data;
|
||||
|
||||
atomic_notifier_chain_unregister(&panic_notifier_list, &mas->panic_nb);
|
||||
}
|
||||
|
||||
static int spi_geni_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret, irq;
|
||||
|
|
@ -1131,8 +1203,14 @@ static int spi_geni_probe(struct platform_device *pdev)
|
|||
init_completion(&mas->rx_reset_done);
|
||||
spin_lock_init(&mas->lock);
|
||||
|
||||
if (spi->target)
|
||||
if (spi->target) {
|
||||
spi->target_abort = spi_geni_target_abort;
|
||||
mas->ready_gpio = devm_gpiod_get_optional(dev, "ready",
|
||||
GPIOD_OUT_LOW);
|
||||
if (IS_ERR(mas->ready_gpio))
|
||||
return dev_err_probe(dev, PTR_ERR(mas->ready_gpio),
|
||||
"Failed to request GPIO\n");
|
||||
}
|
||||
|
||||
pm_runtime_use_autosuspend(&pdev->dev);
|
||||
pm_runtime_set_autosuspend_delay(&pdev->dev, 250);
|
||||
|
|
@ -1154,6 +1232,15 @@ static int spi_geni_probe(struct platform_device *pdev)
|
|||
if (ret)
|
||||
return ret;
|
||||
|
||||
mas->panic_nb.notifier_call = spi_geni_panic_notifier;
|
||||
ret = atomic_notifier_chain_register(&panic_notifier_list, &mas->panic_nb);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = devm_add_action_or_reset(dev, spi_geni_unregister_notifiers, mas);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return devm_spi_register_controller(dev, spi);
|
||||
}
|
||||
|
||||
|
|
@ -1241,7 +1328,8 @@ static const struct of_device_id spi_geni_dt_match[] = {
|
|||
MODULE_DEVICE_TABLE(of, spi_geni_dt_match);
|
||||
|
||||
static struct platform_driver spi_geni_driver = {
|
||||
.probe = spi_geni_probe,
|
||||
.probe = spi_geni_probe,
|
||||
.shutdown = spi_geni_shutdown,
|
||||
.driver = {
|
||||
.name = "geni_spi",
|
||||
.pm = pm_ptr(&spi_geni_pm_ops),
|
||||
|
|
|
|||
|
|
@ -673,7 +673,7 @@ static int mx51_ecspi_prepare_message(struct spi_imx_data *spi_imx,
|
|||
* propagate into the hardware. It takes exactly one tick of the
|
||||
* SCLK clock, but we will wait two SCLK clock just to be sure. The
|
||||
* effect of the delay it takes for the hardware to apply changes
|
||||
* is noticable if the SCLK clock run very slow. In such a case, if
|
||||
* is noticeable if the SCLK clock run very slow. In such a case, if
|
||||
* the polarity of SCLK should be inverted, the GPIO ChipSelect might
|
||||
* be asserted before the SCLK polarity changes, which would disrupt
|
||||
* the SPI communication as the device on the other end would consider
|
||||
|
|
@ -1467,8 +1467,8 @@ static int spi_imx_calculate_timeout(struct spi_imx_data *spi_imx, int size)
|
|||
static void spi_imx_dma_unmap(struct spi_imx_data *spi_imx,
|
||||
struct dma_data_package *dma_data)
|
||||
{
|
||||
struct device *tx_dev = spi_imx->controller->dma_tx->device->dev;
|
||||
struct device *rx_dev = spi_imx->controller->dma_rx->device->dev;
|
||||
struct device *tx_dev = dmaengine_get_dma_device(spi_imx->controller->dma_tx);
|
||||
struct device *rx_dev = dmaengine_get_dma_device(spi_imx->controller->dma_rx);
|
||||
|
||||
dma_unmap_single(tx_dev, dma_data->dma_tx_addr,
|
||||
DMA_CACHE_ALIGNED_LEN(dma_data->dma_len),
|
||||
|
|
@ -1526,8 +1526,8 @@ static int spi_imx_dma_map(struct spi_imx_data *spi_imx,
|
|||
struct dma_data_package *dma_data)
|
||||
{
|
||||
struct spi_controller *controller = spi_imx->controller;
|
||||
struct device *tx_dev = controller->dma_tx->device->dev;
|
||||
struct device *rx_dev = controller->dma_rx->device->dev;
|
||||
struct device *tx_dev = dmaengine_get_dma_device(controller->dma_tx);
|
||||
struct device *rx_dev = dmaengine_get_dma_device(controller->dma_rx);
|
||||
int ret;
|
||||
|
||||
dma_data->dma_tx_addr = dma_map_single(tx_dev, dma_data->dma_tx_buf,
|
||||
|
|
@ -1921,7 +1921,7 @@ static int spi_imx_dma_package_transfer(struct spi_imx_data *spi_imx,
|
|||
return ret;
|
||||
|
||||
/* Trim the DMA RX buffer and copy the actual data to rx_buf */
|
||||
dma_sync_single_for_cpu(controller->dma_rx->device->dev, dma_data->dma_rx_addr,
|
||||
dma_sync_single_for_cpu(dmaengine_get_dma_device(controller->dma_rx), dma_data->dma_rx_addr,
|
||||
DMA_CACHE_ALIGNED_LEN(dma_data->dma_len),
|
||||
DMA_FROM_DEVICE);
|
||||
spi_imx_dma_rx_data_handle(spi_imx, dma_data, transfer->rx_buf + spi_imx->rx_offset,
|
||||
|
|
|
|||
|
|
@ -164,10 +164,8 @@ spi_ingenic_prepare_dma(struct spi_controller *ctlr, struct dma_chan *chan,
|
|||
cookie = dmaengine_submit(desc);
|
||||
|
||||
ret = dma_submit_error(cookie);
|
||||
if (ret) {
|
||||
dmaengine_desc_free(desc);
|
||||
if (ret)
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
return desc;
|
||||
}
|
||||
|
|
@ -186,7 +184,6 @@ static int spi_ingenic_dma_tx(struct spi_controller *ctlr,
|
|||
&xfer->tx_sg, DMA_MEM_TO_DEV, bits);
|
||||
if (IS_ERR(tx_desc)) {
|
||||
dmaengine_terminate_async(ctlr->dma_rx);
|
||||
dmaengine_desc_free(rx_desc);
|
||||
return PTR_ERR(tx_desc);
|
||||
}
|
||||
|
||||
|
|
@ -354,8 +351,11 @@ static int spi_ingenic_request_dma(struct spi_controller *ctlr,
|
|||
ctlr->dma_tx = chan;
|
||||
|
||||
chan = dma_request_chan(dev, "rx");
|
||||
if (IS_ERR(chan))
|
||||
if (IS_ERR(chan)) {
|
||||
dma_release_channel(ctlr->dma_tx);
|
||||
ctlr->dma_tx = NULL;
|
||||
return PTR_ERR(chan);
|
||||
}
|
||||
ctlr->dma_rx = chan;
|
||||
|
||||
ctlr->can_dma = spi_ingenic_can_dma;
|
||||
|
|
|
|||
|
|
@ -84,6 +84,8 @@ static void jcore_spi_baudrate(struct jcore_spi *hw, int speed)
|
|||
{
|
||||
if (speed == hw->speed_hz)
|
||||
return;
|
||||
if (speed <= 0)
|
||||
speed = 1;
|
||||
hw->speed_hz = speed;
|
||||
if (speed >= hw->clock_freq / 2)
|
||||
hw->speed_reg = 0;
|
||||
|
|
@ -143,7 +145,6 @@ static int jcore_spi_probe(struct platform_device *pdev)
|
|||
struct device_node *node = pdev->dev.of_node;
|
||||
struct jcore_spi *hw;
|
||||
struct spi_controller *host;
|
||||
struct resource *res;
|
||||
u32 clock_freq;
|
||||
struct clk *clk;
|
||||
|
||||
|
|
@ -164,16 +165,9 @@ static int jcore_spi_probe(struct platform_device *pdev)
|
|||
platform_set_drvdata(pdev, hw);
|
||||
|
||||
/* Find and map our resources */
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
if (!res)
|
||||
return -EBUSY;
|
||||
if (!devm_request_mem_region(&pdev->dev, res->start,
|
||||
resource_size(res), pdev->name))
|
||||
return -EBUSY;
|
||||
hw->base = devm_ioremap(&pdev->dev, res->start,
|
||||
resource_size(res));
|
||||
if (!hw->base)
|
||||
return -EBUSY;
|
||||
hw->base = devm_platform_ioremap_resource(pdev, 0);
|
||||
if (IS_ERR(hw->base))
|
||||
return PTR_ERR(hw->base);
|
||||
|
||||
/*
|
||||
* The SPI clock rate controlled via a configurable clock divider
|
||||
|
|
|
|||
|
|
@ -105,7 +105,12 @@ static int ljca_spi_read_write(struct ljca_spi_dev *ljca_spi, const u8 *w_data,
|
|||
(u8 *)r_packet, LJCA_SPI_BUF_SIZE);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
else if (ret < sizeof(*r_packet) || r_packet->len <= 0)
|
||||
|
||||
if (ret < sizeof(*r_packet))
|
||||
return -EIO;
|
||||
|
||||
if (!r_packet->len || ret < sizeof(*r_packet) + r_packet->len ||
|
||||
r_packet->len > len)
|
||||
return -EIO;
|
||||
|
||||
if (r_data)
|
||||
|
|
|
|||
|
|
@ -252,7 +252,7 @@ static void spi_lm70llp_attach(struct parport *p)
|
|||
* We are binding to the generic drivers/hwmon/lm70.c device
|
||||
* driver.
|
||||
*/
|
||||
strcpy(pp->info.modalias, "lm70");
|
||||
strscpy(pp->info.modalias, "lm70", sizeof(pp->info.modalias));
|
||||
pp->info.max_speed_hz = 6 * 1000 * 1000;
|
||||
pp->info.chip_select = 0;
|
||||
pp->info.mode = SPI_3WIRE | SPI_MODE_0;
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@
|
|||
#define NUVOTON_QSPI_STATUS_RXEMPTY_MASK BIT(8) /* Receive FIFO Empty */
|
||||
#define NUVOTON_QSPI_STATUS_BUSY_MASK BIT(0) /* Busy Status */
|
||||
|
||||
#define NUVOTON_QSPI_MAX_NUM_CS 2
|
||||
#define NUVOTON_QSPI_MAX_NATIVE_CS 2
|
||||
#define NUVOTON_QSPI_DEFAULT_NUM_CS 2
|
||||
#define NUVOTON_QSPI_DEFAULT_BPW 8
|
||||
/* Bound PIO operations to avoid long atomic polling loops. */
|
||||
|
|
@ -442,13 +442,35 @@ static void nuvoton_qspi_set_cs_level(struct nuvoton_qspi *qspi,
|
|||
spin_unlock_irqrestore(&qspi->ssctl_lock, flags);
|
||||
}
|
||||
|
||||
static int nuvoton_qspi_setup(struct spi_device *spi)
|
||||
{
|
||||
unsigned int cs = spi_get_chipselect(spi, 0);
|
||||
|
||||
if (spi_get_csgpiod(spi, 0))
|
||||
return 0;
|
||||
|
||||
if (cs >= NUVOTON_QSPI_MAX_NATIVE_CS) {
|
||||
dev_err(&spi->dev, "invalid native chip select %u\n", cs);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (spi->mode & SPI_CS_HIGH) {
|
||||
dev_err(&spi->dev,
|
||||
"active-high native chip select is not supported\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void nuvoton_qspi_set_cs(struct spi_device *spi, bool level)
|
||||
{
|
||||
struct nuvoton_qspi *qspi = spi_controller_get_devdata(spi->controller);
|
||||
|
||||
/*
|
||||
* The SPI core passes the physical CS level to ->set_cs(). This
|
||||
* initial driver only supports active-low native chip selects.
|
||||
* GPIO chip selects are handled by the SPI core. For native chip
|
||||
* selects, the core passes the physical CS level to ->set_cs().
|
||||
* Native chip selects are active low.
|
||||
*/
|
||||
nuvoton_qspi_set_cs_level(qspi, spi_get_chipselect(spi, 0), !level);
|
||||
}
|
||||
|
|
@ -457,7 +479,11 @@ static void nuvoton_qspi_mem_set_cs(struct spi_device *spi, bool assert)
|
|||
{
|
||||
struct nuvoton_qspi *qspi = spi_controller_get_devdata(spi->controller);
|
||||
|
||||
/* The direct spi-mem path passes a logical assertion state. */
|
||||
/*
|
||||
* Direct spi-mem operations are only executed for native chip
|
||||
* selects. GPIO chip selects use the regular SPI fallback path.
|
||||
* This helper takes a logical assertion state.
|
||||
*/
|
||||
nuvoton_qspi_set_cs_level(qspi, spi_get_chipselect(spi, 0), assert);
|
||||
}
|
||||
|
||||
|
|
@ -633,18 +659,26 @@ static int nuvoton_qspi_probe(struct platform_device *pdev)
|
|||
return dev_err_probe(dev, ret, "failed to deassert reset\n");
|
||||
|
||||
ret = device_property_read_u32(dev, "num-cs", &num_cs);
|
||||
if (ret && ret != -EINVAL)
|
||||
return dev_err_probe(dev, ret, "failed to read num-cs\n");
|
||||
if (ret && ret != -EINVAL) {
|
||||
ret = dev_err_probe(dev, ret, "failed to read num-cs\n");
|
||||
goto err_assert;
|
||||
}
|
||||
|
||||
if (!num_cs || num_cs > NUVOTON_QSPI_MAX_NUM_CS)
|
||||
return dev_err_probe(dev, -EINVAL, "invalid num-cs %u\n",
|
||||
if (!num_cs || num_cs > NUVOTON_QSPI_MAX_NATIVE_CS) {
|
||||
ret = dev_err_probe(dev, -EINVAL, "invalid num-cs %u\n",
|
||||
num_cs);
|
||||
goto err_assert;
|
||||
}
|
||||
|
||||
ctlr->num_chipselect = num_cs;
|
||||
ctlr->max_native_cs = NUVOTON_QSPI_MAX_NATIVE_CS;
|
||||
ctlr->use_gpio_descriptors = true;
|
||||
ctlr->max_transfer_size = nuvoton_qspi_max_transfer_size;
|
||||
ctlr->max_message_size = nuvoton_qspi_max_message_size;
|
||||
ctlr->mem_ops = &nuvoton_qspi_mem_ops;
|
||||
ctlr->mem_caps = &nuvoton_qspi_mem_caps;
|
||||
ctlr->dtr_caps = true;
|
||||
ctlr->setup = nuvoton_qspi_setup;
|
||||
ctlr->set_cs = nuvoton_qspi_set_cs;
|
||||
ctlr->transfer_one = nuvoton_qspi_transfer_one;
|
||||
ctlr->bits_per_word_mask = SPI_BPW_MASK(8);
|
||||
|
|
@ -655,14 +689,20 @@ static int nuvoton_qspi_probe(struct platform_device *pdev)
|
|||
|
||||
ret = nuvoton_qspi_hw_init(qspi);
|
||||
if (ret)
|
||||
return ret;
|
||||
goto err_assert;
|
||||
|
||||
ret = devm_spi_register_controller(dev, ctlr);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret,
|
||||
"failed to register spi controller\n");
|
||||
if (ret) {
|
||||
ret = dev_err_probe(dev, ret,
|
||||
"failed to register spi controller\n");
|
||||
goto err_assert;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_assert:
|
||||
reset_control_assert(rst);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct of_device_id nuvoton_qspi_of_match[] = {
|
||||
|
|
|
|||
716
drivers/spi/spi-ma35d1.c
Normal file
716
drivers/spi/spi-ma35d1.c
Normal file
|
|
@ -0,0 +1,716 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
//
|
||||
// Nuvoton MA35D1 SPI controller driver
|
||||
//
|
||||
// Copyright (c) 2026 Nuvoton Technology Corp.
|
||||
// Author: Chi-Wen Weng <cwweng@nuvoton.com>
|
||||
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/bits.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/iopoll.h>
|
||||
#include <linux/math.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/pm.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/property.h>
|
||||
#include <linux/reset.h>
|
||||
#include <linux/sizes.h>
|
||||
#include <linux/spi/spi.h>
|
||||
#include <linux/time.h>
|
||||
#include <linux/unaligned.h>
|
||||
|
||||
/* Register offset definitions */
|
||||
#define NUVOTON_SPI_CTL_OFFSET 0x00
|
||||
#define NUVOTON_SPI_CLKDIV_OFFSET 0x04
|
||||
#define NUVOTON_SPI_SSCTL_OFFSET 0x08
|
||||
#define NUVOTON_SPI_PDMACTL_OFFSET 0x0c
|
||||
#define NUVOTON_SPI_FIFOCTL_OFFSET 0x10
|
||||
#define NUVOTON_SPI_STATUS_OFFSET 0x14
|
||||
#define NUVOTON_SPI_TX_OFFSET 0x20
|
||||
#define NUVOTON_SPI_RX_OFFSET 0x30
|
||||
|
||||
/* SPI Control Register bit masks */
|
||||
#define NUVOTON_SPI_CTL_DATDIR_MASK BIT(20)
|
||||
#define NUVOTON_SPI_CTL_REORDER_MASK BIT(19)
|
||||
#define NUVOTON_SPI_CTL_SLAVE_MASK BIT(18)
|
||||
#define NUVOTON_SPI_CTL_UNITIEN_MASK BIT(17)
|
||||
#define NUVOTON_SPI_CTL_RXONLY_MASK BIT(15)
|
||||
#define NUVOTON_SPI_CTL_HALFDPX_MASK BIT(14)
|
||||
#define NUVOTON_SPI_CTL_LSB_MASK BIT(13)
|
||||
#define NUVOTON_SPI_CTL_DWIDTH_MASK GENMASK(12, 8)
|
||||
#define NUVOTON_SPI_CTL_SUSPITV_MASK GENMASK(7, 4)
|
||||
#define NUVOTON_SPI_CTL_CLKPOL_MASK BIT(3)
|
||||
#define NUVOTON_SPI_CTL_TXNEG_MASK BIT(2)
|
||||
#define NUVOTON_SPI_CTL_RXNEG_MASK BIT(1)
|
||||
#define NUVOTON_SPI_CTL_SPIEN_MASK BIT(0)
|
||||
|
||||
/* SPI Clock Divider Register bit masks */
|
||||
#define NUVOTON_SPI_CLKDIV_MASK GENMASK(8, 0)
|
||||
|
||||
/* SPI Slave Select Control Register bit masks */
|
||||
#define NUVOTON_SPI_SSCTL_SS1_MASK BIT(1)
|
||||
#define NUVOTON_SPI_SSCTL_SS0_MASK BIT(0)
|
||||
#define NUVOTON_SPI_SSCTL_SSACTPOL_MASK BIT(2)
|
||||
#define NUVOTON_SPI_SSCTL_AUTOSS_MASK BIT(3)
|
||||
#define NUVOTON_SPI_SSCTL_SLV3WIRE_MASK BIT(4)
|
||||
#define NUVOTON_SPI_SSCTL_SLVBEIEN_MASK BIT(8)
|
||||
#define NUVOTON_SPI_SSCTL_SLVURIEN_MASK BIT(9)
|
||||
#define NUVOTON_SPI_SSCTL_SSACTIEN_MASK BIT(12)
|
||||
#define NUVOTON_SPI_SSCTL_SSINAIEN_MASK BIT(13)
|
||||
|
||||
/* SPI PDMA Control Register bit masks */
|
||||
#define NUVOTON_SPI_PDMACTL_TXPDMAEN_MASK BIT(0)
|
||||
#define NUVOTON_SPI_PDMACTL_RXPDMAEN_MASK BIT(1)
|
||||
|
||||
/* SPI FIFO Control Register bit masks */
|
||||
#define NUVOTON_SPI_FIFOCTL_SLVBERX_MASK BIT(10)
|
||||
#define NUVOTON_SPI_FIFOCTL_TXUFIEN_MASK BIT(7)
|
||||
#define NUVOTON_SPI_FIFOCTL_TXUFPOL_MASK BIT(6)
|
||||
#define NUVOTON_SPI_FIFOCTL_RXOVIEN_MASK BIT(5)
|
||||
#define NUVOTON_SPI_FIFOCTL_RXTOIEN_MASK BIT(4)
|
||||
#define NUVOTON_SPI_FIFOCTL_TXTHIEN_MASK BIT(3)
|
||||
#define NUVOTON_SPI_FIFOCTL_RXTHIEN_MASK BIT(2)
|
||||
#define NUVOTON_SPI_FIFOCTL_TXRST_MASK BIT(1)
|
||||
#define NUVOTON_SPI_FIFOCTL_RXRST_MASK BIT(0)
|
||||
|
||||
/* SPI Status Register bit masks */
|
||||
#define NUVOTON_SPI_STATUS_TXRXRST_MASK BIT(23)
|
||||
#define NUVOTON_SPI_STATUS_TXFULL_MASK BIT(17)
|
||||
#define NUVOTON_SPI_STATUS_SPIENSTS_MASK BIT(15)
|
||||
#define NUVOTON_SPI_STATUS_RXEMPTY_MASK BIT(8)
|
||||
#define NUVOTON_SPI_STATUS_BUSY_MASK BIT(0)
|
||||
|
||||
#define NUVOTON_SPI_MAX_NATIVE_CS 2
|
||||
#define NUVOTON_SPI_DEFAULT_NUM_CS 2
|
||||
#define NUVOTON_SPI_DEFAULT_BPW 8
|
||||
#define NUVOTON_SPI_MAX_SPEED_HZ 100000000U
|
||||
#define NUVOTON_SPI_MIN_DIVISOR 2U
|
||||
#define NUVOTON_SPI_MAX_DIVISOR 512U
|
||||
#define NUVOTON_SPI_RESET_CYCLES 5U
|
||||
|
||||
/* Bound PIO operations to avoid long polling loops. */
|
||||
#define NUVOTON_SPI_MAX_TRANSFER_SIZE SZ_4K
|
||||
#define NUVOTON_SPI_TIMEOUT_US 10000
|
||||
|
||||
struct nuvoton_spi {
|
||||
void __iomem *regs;
|
||||
struct device *dev;
|
||||
unsigned long clk_rate;
|
||||
};
|
||||
|
||||
static u32 nuvoton_spi_read(struct nuvoton_spi *nspi, u32 reg)
|
||||
{
|
||||
return readl(nspi->regs + reg);
|
||||
}
|
||||
|
||||
static void nuvoton_spi_write(struct nuvoton_spi *nspi, u32 val, u32 reg)
|
||||
{
|
||||
writel(val, nspi->regs + reg);
|
||||
}
|
||||
|
||||
static void nuvoton_spi_update_bits(struct nuvoton_spi *nspi, u32 reg,
|
||||
u32 mask, u32 val)
|
||||
{
|
||||
u32 tmp;
|
||||
|
||||
tmp = nuvoton_spi_read(nspi, reg);
|
||||
tmp &= ~mask;
|
||||
tmp |= val & mask;
|
||||
nuvoton_spi_write(nspi, tmp, reg);
|
||||
}
|
||||
|
||||
static int nuvoton_spi_disable(struct nuvoton_spi *nspi)
|
||||
{
|
||||
u32 ctl;
|
||||
u32 val;
|
||||
|
||||
ctl = nuvoton_spi_read(nspi, NUVOTON_SPI_CTL_OFFSET);
|
||||
if (!(ctl & NUVOTON_SPI_CTL_SPIEN_MASK))
|
||||
return 0;
|
||||
|
||||
nuvoton_spi_write(nspi, ctl & ~NUVOTON_SPI_CTL_SPIEN_MASK,
|
||||
NUVOTON_SPI_CTL_OFFSET);
|
||||
|
||||
return readl_poll_timeout_atomic(nspi->regs + NUVOTON_SPI_STATUS_OFFSET,
|
||||
val,
|
||||
!(val & NUVOTON_SPI_STATUS_SPIENSTS_MASK),
|
||||
1, NUVOTON_SPI_TIMEOUT_US);
|
||||
}
|
||||
|
||||
static int nuvoton_spi_enable(struct nuvoton_spi *nspi)
|
||||
{
|
||||
u32 ctl;
|
||||
u32 val;
|
||||
|
||||
ctl = nuvoton_spi_read(nspi, NUVOTON_SPI_CTL_OFFSET);
|
||||
if (ctl & NUVOTON_SPI_CTL_SPIEN_MASK)
|
||||
return 0;
|
||||
|
||||
nuvoton_spi_write(nspi, ctl | NUVOTON_SPI_CTL_SPIEN_MASK,
|
||||
NUVOTON_SPI_CTL_OFFSET);
|
||||
|
||||
return readl_poll_timeout_atomic(nspi->regs + NUVOTON_SPI_STATUS_OFFSET,
|
||||
val,
|
||||
val & NUVOTON_SPI_STATUS_SPIENSTS_MASK,
|
||||
1, NUVOTON_SPI_TIMEOUT_US);
|
||||
}
|
||||
|
||||
static int nuvoton_spi_wait_ready(struct nuvoton_spi *nspi)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
return readl_poll_timeout(nspi->regs + NUVOTON_SPI_STATUS_OFFSET, val,
|
||||
!(val & NUVOTON_SPI_STATUS_BUSY_MASK),
|
||||
0, NUVOTON_SPI_TIMEOUT_US);
|
||||
}
|
||||
|
||||
static int nuvoton_spi_reset_fifo(struct nuvoton_spi *nspi)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
nuvoton_spi_update_bits(nspi, NUVOTON_SPI_FIFOCTL_OFFSET,
|
||||
NUVOTON_SPI_FIFOCTL_TXRST_MASK |
|
||||
NUVOTON_SPI_FIFOCTL_RXRST_MASK,
|
||||
NUVOTON_SPI_FIFOCTL_TXRST_MASK |
|
||||
NUVOTON_SPI_FIFOCTL_RXRST_MASK);
|
||||
|
||||
/*
|
||||
* Give the controller a short time to latch the FIFO reset request
|
||||
* before polling the reset status bit.
|
||||
*/
|
||||
udelay(1);
|
||||
|
||||
return readl_poll_timeout(nspi->regs + NUVOTON_SPI_STATUS_OFFSET, val,
|
||||
!(val & NUVOTON_SPI_STATUS_TXRXRST_MASK),
|
||||
1, NUVOTON_SPI_TIMEOUT_US);
|
||||
}
|
||||
|
||||
static int nuvoton_spi_wait_tx_not_full(struct nuvoton_spi *nspi)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
return readl_poll_timeout(nspi->regs + NUVOTON_SPI_STATUS_OFFSET, val,
|
||||
!(val & NUVOTON_SPI_STATUS_TXFULL_MASK),
|
||||
0, NUVOTON_SPI_TIMEOUT_US);
|
||||
}
|
||||
|
||||
static int nuvoton_spi_wait_rx_not_empty(struct nuvoton_spi *nspi)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
return readl_poll_timeout(nspi->regs + NUVOTON_SPI_STATUS_OFFSET, val,
|
||||
!(val & NUVOTON_SPI_STATUS_RXEMPTY_MASK),
|
||||
0, NUVOTON_SPI_TIMEOUT_US);
|
||||
}
|
||||
|
||||
static int nuvoton_spi_calc_divisor(unsigned long clk_rate, u32 speed_hz,
|
||||
unsigned int *divisor)
|
||||
{
|
||||
unsigned int div;
|
||||
|
||||
div = DIV_ROUND_UP(clk_rate, speed_hz);
|
||||
if (div < NUVOTON_SPI_MIN_DIVISOR)
|
||||
div = NUVOTON_SPI_MIN_DIVISOR;
|
||||
|
||||
/*
|
||||
* CLKDIV only accepts odd register values, corresponding to even
|
||||
* clock divisors. Round up so the generated clock never exceeds
|
||||
* the requested frequency.
|
||||
*/
|
||||
if (div & 1)
|
||||
div++;
|
||||
|
||||
if (div > NUVOTON_SPI_MAX_DIVISOR)
|
||||
return -EINVAL;
|
||||
|
||||
*divisor = div;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int nuvoton_spi_configure_mode(struct nuvoton_spi *nspi,
|
||||
struct spi_device *spi)
|
||||
{
|
||||
u32 mask = NUVOTON_SPI_CTL_TXNEG_MASK |
|
||||
NUVOTON_SPI_CTL_RXNEG_MASK |
|
||||
NUVOTON_SPI_CTL_CLKPOL_MASK |
|
||||
NUVOTON_SPI_CTL_LSB_MASK;
|
||||
u32 ctl = 0;
|
||||
u32 cur_ctl;
|
||||
u32 mode;
|
||||
int ret;
|
||||
|
||||
mode = spi->mode & SPI_MODE_X_MASK;
|
||||
if (mode == SPI_MODE_0 || mode == SPI_MODE_3)
|
||||
ctl |= NUVOTON_SPI_CTL_TXNEG_MASK;
|
||||
else
|
||||
ctl |= NUVOTON_SPI_CTL_RXNEG_MASK;
|
||||
|
||||
if (spi->mode & SPI_CPOL)
|
||||
ctl |= NUVOTON_SPI_CTL_CLKPOL_MASK;
|
||||
|
||||
if (spi->mode & SPI_LSB_FIRST)
|
||||
ctl |= NUVOTON_SPI_CTL_LSB_MASK;
|
||||
|
||||
cur_ctl = nuvoton_spi_read(nspi, NUVOTON_SPI_CTL_OFFSET);
|
||||
if ((cur_ctl & mask) == ctl)
|
||||
return 0;
|
||||
|
||||
ret = nuvoton_spi_disable(nspi);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
nuvoton_spi_update_bits(nspi, NUVOTON_SPI_CTL_OFFSET, mask, ctl);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int nuvoton_spi_configure_transfer(struct nuvoton_spi *nspi,
|
||||
struct spi_transfer *xfer)
|
||||
{
|
||||
u32 ctl_mask = NUVOTON_SPI_CTL_DWIDTH_MASK;
|
||||
u32 cur_clkdiv;
|
||||
u32 cur_ctl;
|
||||
u32 clkdiv;
|
||||
u32 dwidth;
|
||||
unsigned int divisor;
|
||||
int ret;
|
||||
|
||||
ret = nuvoton_spi_calc_divisor(nspi->clk_rate, xfer->speed_hz,
|
||||
&divisor);
|
||||
if (ret) {
|
||||
dev_err(nspi->dev, "unsupported SPI clock %u Hz\n",
|
||||
xfer->speed_hz);
|
||||
return ret;
|
||||
}
|
||||
|
||||
clkdiv = FIELD_PREP(NUVOTON_SPI_CLKDIV_MASK, divisor - 1);
|
||||
dwidth = xfer->bits_per_word == 32 ? 0 : xfer->bits_per_word;
|
||||
dwidth = FIELD_PREP(NUVOTON_SPI_CTL_DWIDTH_MASK, dwidth);
|
||||
|
||||
xfer->effective_speed_hz = nspi->clk_rate / divisor;
|
||||
|
||||
cur_ctl = nuvoton_spi_read(nspi, NUVOTON_SPI_CTL_OFFSET);
|
||||
cur_clkdiv = nuvoton_spi_read(nspi, NUVOTON_SPI_CLKDIV_OFFSET);
|
||||
|
||||
if ((cur_ctl & ctl_mask) == dwidth &&
|
||||
(cur_clkdiv & NUVOTON_SPI_CLKDIV_MASK) == clkdiv)
|
||||
return 0;
|
||||
|
||||
ret = nuvoton_spi_disable(nspi);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if ((cur_ctl & ctl_mask) != dwidth)
|
||||
nuvoton_spi_update_bits(nspi, NUVOTON_SPI_CTL_OFFSET,
|
||||
ctl_mask, dwidth);
|
||||
|
||||
if ((cur_clkdiv & NUVOTON_SPI_CLKDIV_MASK) != clkdiv)
|
||||
nuvoton_spi_write(nspi, clkdiv, NUVOTON_SPI_CLKDIV_OFFSET);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static u32 nuvoton_spi_get_tx_word(const void *txbuf, unsigned int offset,
|
||||
unsigned int bytes_per_word)
|
||||
{
|
||||
if (!txbuf)
|
||||
return 0;
|
||||
|
||||
switch (bytes_per_word) {
|
||||
case 1:
|
||||
return ((const u8 *)txbuf)[offset];
|
||||
case 2:
|
||||
return get_unaligned((const u16 *)((const u8 *)txbuf + offset));
|
||||
case 4:
|
||||
return get_unaligned((const u32 *)((const u8 *)txbuf + offset));
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void nuvoton_spi_put_rx_word(void *rxbuf, unsigned int offset,
|
||||
unsigned int bytes_per_word, u32 val)
|
||||
{
|
||||
if (!rxbuf)
|
||||
return;
|
||||
|
||||
switch (bytes_per_word) {
|
||||
case 1:
|
||||
((u8 *)rxbuf)[offset] = val;
|
||||
break;
|
||||
case 2:
|
||||
put_unaligned((u16)val, (u16 *)((u8 *)rxbuf + offset));
|
||||
break;
|
||||
case 4:
|
||||
put_unaligned(val, (u32 *)((u8 *)rxbuf + offset));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static int nuvoton_spi_txrx(struct nuvoton_spi *nspi,
|
||||
struct spi_transfer *xfer)
|
||||
{
|
||||
unsigned int bytes_per_word;
|
||||
unsigned int offset;
|
||||
u32 val;
|
||||
int ret;
|
||||
|
||||
bytes_per_word = spi_bpw_to_bytes(xfer->bits_per_word);
|
||||
|
||||
/*
|
||||
* Use conservative word-by-word PIO. Each transmitted word produces
|
||||
* one receive FIFO entry, so always drain RX, including TX-only
|
||||
* transfers. RX-only transfers send zero-filled dummy words.
|
||||
*/
|
||||
for (offset = 0; offset < xfer->len; offset += bytes_per_word) {
|
||||
ret = nuvoton_spi_wait_tx_not_full(nspi);
|
||||
if (ret) {
|
||||
dev_err(nspi->dev, "TX FIFO full timeout\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
val = nuvoton_spi_get_tx_word(xfer->tx_buf, offset,
|
||||
bytes_per_word);
|
||||
nuvoton_spi_write(nspi, val, NUVOTON_SPI_TX_OFFSET);
|
||||
|
||||
ret = nuvoton_spi_wait_rx_not_empty(nspi);
|
||||
if (ret) {
|
||||
dev_err(nspi->dev, "RX FIFO empty timeout\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
val = nuvoton_spi_read(nspi, NUVOTON_SPI_RX_OFFSET);
|
||||
nuvoton_spi_put_rx_word(xfer->rx_buf, offset, bytes_per_word,
|
||||
val);
|
||||
|
||||
spi_delay_exec(&xfer->word_delay, xfer);
|
||||
}
|
||||
|
||||
ret = nuvoton_spi_wait_ready(nspi);
|
||||
if (ret)
|
||||
dev_err(nspi->dev, "controller busy timeout\n");
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void nuvoton_spi_set_cs_level(struct nuvoton_spi *nspi,
|
||||
unsigned int cs, bool assert)
|
||||
{
|
||||
u32 mask;
|
||||
|
||||
switch (cs) {
|
||||
case 0:
|
||||
mask = NUVOTON_SPI_SSCTL_SS0_MASK;
|
||||
break;
|
||||
case 1:
|
||||
mask = NUVOTON_SPI_SSCTL_SS1_MASK;
|
||||
break;
|
||||
default:
|
||||
dev_warn(nspi->dev, "invalid chip select %u\n", cs);
|
||||
return;
|
||||
}
|
||||
|
||||
nuvoton_spi_update_bits(nspi, NUVOTON_SPI_SSCTL_OFFSET,
|
||||
mask, assert ? mask : 0);
|
||||
}
|
||||
|
||||
static int nuvoton_spi_setup(struct spi_device *spi)
|
||||
{
|
||||
unsigned int cs = spi_get_chipselect(spi, 0);
|
||||
|
||||
if (spi_get_csgpiod(spi, 0))
|
||||
return 0;
|
||||
|
||||
if (cs >= NUVOTON_SPI_MAX_NATIVE_CS) {
|
||||
dev_err(&spi->dev, "invalid native chip select %u\n", cs);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (spi->mode & SPI_CS_HIGH) {
|
||||
dev_err(&spi->dev,
|
||||
"active-high native chip select is not supported\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void nuvoton_spi_set_cs(struct spi_device *spi, bool level)
|
||||
{
|
||||
struct nuvoton_spi *nspi = spi_controller_get_devdata(spi->controller);
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* SSCTL may only be changed while SPIEN is clear. Clearing SPIEN
|
||||
* retains the CLK and MOSI output levels.
|
||||
*/
|
||||
ret = nuvoton_spi_disable(nspi);
|
||||
if (ret) {
|
||||
dev_err(nspi->dev, "failed to disable controller for chip select\n");
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* The SPI core passes the physical CS level to ->set_cs(). This
|
||||
* initial driver only supports active-low native chip selects.
|
||||
*/
|
||||
nuvoton_spi_set_cs_level(nspi, spi_get_chipselect(spi, 0), !level);
|
||||
}
|
||||
|
||||
static int nuvoton_spi_prepare_message(struct spi_controller *ctlr,
|
||||
struct spi_message *message)
|
||||
{
|
||||
struct nuvoton_spi *nspi = spi_controller_get_devdata(ctlr);
|
||||
int ret;
|
||||
|
||||
ret = nuvoton_spi_configure_mode(nspi, message->spi);
|
||||
if (ret)
|
||||
dev_err(nspi->dev, "failed to configure SPI mode\n");
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int nuvoton_spi_transfer_one(struct spi_controller *ctlr,
|
||||
struct spi_device *spi,
|
||||
struct spi_transfer *xfer)
|
||||
{
|
||||
struct nuvoton_spi *nspi = spi_controller_get_devdata(ctlr);
|
||||
int ret;
|
||||
|
||||
ret = nuvoton_spi_configure_transfer(nspi, xfer);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = nuvoton_spi_enable(nspi);
|
||||
if (ret) {
|
||||
dev_err(nspi->dev, "failed to enable controller\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
return nuvoton_spi_txrx(nspi, xfer);
|
||||
}
|
||||
|
||||
static void nuvoton_spi_handle_err(struct spi_controller *ctlr,
|
||||
struct spi_message *message)
|
||||
{
|
||||
struct nuvoton_spi *nspi = spi_controller_get_devdata(ctlr);
|
||||
int ret;
|
||||
|
||||
ret = nuvoton_spi_disable(nspi);
|
||||
if (ret) {
|
||||
dev_err(nspi->dev,
|
||||
"failed to disable controller during recovery\n");
|
||||
return;
|
||||
}
|
||||
|
||||
ret = nuvoton_spi_reset_fifo(nspi);
|
||||
if (ret)
|
||||
dev_err(nspi->dev, "failed to reset FIFO during recovery\n");
|
||||
}
|
||||
|
||||
static size_t nuvoton_spi_max_transfer_size(struct spi_device *spi)
|
||||
{
|
||||
return NUVOTON_SPI_MAX_TRANSFER_SIZE;
|
||||
}
|
||||
|
||||
static int nuvoton_spi_hw_init(struct nuvoton_spi *nspi)
|
||||
{
|
||||
u32 ctl_mask;
|
||||
u32 fifo_mask;
|
||||
u32 ssctl_mask;
|
||||
int ret;
|
||||
|
||||
ret = nuvoton_spi_disable(nspi);
|
||||
if (ret) {
|
||||
dev_err(nspi->dev, "failed to disable controller\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ctl_mask = NUVOTON_SPI_CTL_DATDIR_MASK |
|
||||
NUVOTON_SPI_CTL_REORDER_MASK |
|
||||
NUVOTON_SPI_CTL_SLAVE_MASK |
|
||||
NUVOTON_SPI_CTL_UNITIEN_MASK |
|
||||
NUVOTON_SPI_CTL_RXONLY_MASK |
|
||||
NUVOTON_SPI_CTL_HALFDPX_MASK |
|
||||
NUVOTON_SPI_CTL_LSB_MASK |
|
||||
NUVOTON_SPI_CTL_DWIDTH_MASK |
|
||||
NUVOTON_SPI_CTL_SUSPITV_MASK |
|
||||
NUVOTON_SPI_CTL_CLKPOL_MASK |
|
||||
NUVOTON_SPI_CTL_TXNEG_MASK |
|
||||
NUVOTON_SPI_CTL_RXNEG_MASK;
|
||||
|
||||
nuvoton_spi_update_bits(nspi, NUVOTON_SPI_CTL_OFFSET, ctl_mask,
|
||||
NUVOTON_SPI_CTL_TXNEG_MASK |
|
||||
FIELD_PREP(NUVOTON_SPI_CTL_DWIDTH_MASK,
|
||||
NUVOTON_SPI_DEFAULT_BPW));
|
||||
|
||||
ssctl_mask = NUVOTON_SPI_SSCTL_SS0_MASK |
|
||||
NUVOTON_SPI_SSCTL_SS1_MASK |
|
||||
NUVOTON_SPI_SSCTL_SSACTPOL_MASK |
|
||||
NUVOTON_SPI_SSCTL_AUTOSS_MASK |
|
||||
NUVOTON_SPI_SSCTL_SLV3WIRE_MASK |
|
||||
NUVOTON_SPI_SSCTL_SLVBEIEN_MASK |
|
||||
NUVOTON_SPI_SSCTL_SLVURIEN_MASK |
|
||||
NUVOTON_SPI_SSCTL_SSACTIEN_MASK |
|
||||
NUVOTON_SPI_SSCTL_SSINAIEN_MASK;
|
||||
|
||||
nuvoton_spi_update_bits(nspi, NUVOTON_SPI_SSCTL_OFFSET,
|
||||
ssctl_mask, 0);
|
||||
|
||||
nuvoton_spi_update_bits(nspi, NUVOTON_SPI_PDMACTL_OFFSET,
|
||||
NUVOTON_SPI_PDMACTL_TXPDMAEN_MASK |
|
||||
NUVOTON_SPI_PDMACTL_RXPDMAEN_MASK, 0);
|
||||
|
||||
fifo_mask = NUVOTON_SPI_FIFOCTL_SLVBERX_MASK |
|
||||
NUVOTON_SPI_FIFOCTL_TXUFIEN_MASK |
|
||||
NUVOTON_SPI_FIFOCTL_TXUFPOL_MASK |
|
||||
NUVOTON_SPI_FIFOCTL_RXOVIEN_MASK |
|
||||
NUVOTON_SPI_FIFOCTL_RXTOIEN_MASK |
|
||||
NUVOTON_SPI_FIFOCTL_TXTHIEN_MASK |
|
||||
NUVOTON_SPI_FIFOCTL_RXTHIEN_MASK;
|
||||
|
||||
nuvoton_spi_update_bits(nspi, NUVOTON_SPI_FIFOCTL_OFFSET, fifo_mask, 0);
|
||||
|
||||
ret = nuvoton_spi_reset_fifo(nspi);
|
||||
if (ret)
|
||||
dev_err(nspi->dev, "FIFO reset timed out\n");
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int nuvoton_spi_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct spi_controller *ctlr;
|
||||
struct nuvoton_spi *nspi;
|
||||
struct reset_control *rst;
|
||||
struct clk *clk;
|
||||
unsigned int max_divisor;
|
||||
u32 num_cs = NUVOTON_SPI_DEFAULT_NUM_CS;
|
||||
int ret;
|
||||
|
||||
if (device_property_read_bool(dev, "spi-slave"))
|
||||
return dev_err_probe(dev, -EOPNOTSUPP,
|
||||
"target mode is not supported\n");
|
||||
|
||||
ctlr = devm_spi_alloc_host(dev, sizeof(*nspi));
|
||||
if (!ctlr)
|
||||
return -ENOMEM;
|
||||
|
||||
platform_set_drvdata(pdev, ctlr);
|
||||
|
||||
nspi = spi_controller_get_devdata(ctlr);
|
||||
nspi->dev = dev;
|
||||
|
||||
nspi->regs = devm_platform_ioremap_resource(pdev, 0);
|
||||
if (IS_ERR(nspi->regs))
|
||||
return PTR_ERR(nspi->regs);
|
||||
|
||||
clk = devm_clk_get_enabled(dev, NULL);
|
||||
if (IS_ERR(clk))
|
||||
return dev_err_probe(dev, PTR_ERR(clk),
|
||||
"failed to get and enable clock\n");
|
||||
|
||||
nspi->clk_rate = clk_get_rate(clk);
|
||||
if (!nspi->clk_rate)
|
||||
return dev_err_probe(dev, -EINVAL, "invalid clock rate\n");
|
||||
|
||||
rst = devm_reset_control_get_exclusive_deasserted(dev, NULL);
|
||||
if (IS_ERR(rst))
|
||||
return dev_err_probe(dev, PTR_ERR(rst),
|
||||
"failed to get and deassert reset\n");
|
||||
|
||||
/*
|
||||
* The hardware requires at least five peripheral clock cycles after
|
||||
* reset deassertion before programming controller registers.
|
||||
*/
|
||||
udelay(DIV_ROUND_UP_ULL((u64)NUVOTON_SPI_RESET_CYCLES *
|
||||
USEC_PER_SEC, nspi->clk_rate));
|
||||
|
||||
ret = device_property_read_u32(dev, "num-cs", &num_cs);
|
||||
if (ret && ret != -EINVAL)
|
||||
return dev_err_probe(dev, ret, "failed to read num-cs\n");
|
||||
|
||||
if (!num_cs || num_cs > NUVOTON_SPI_MAX_NATIVE_CS)
|
||||
return dev_err_probe(dev, -EINVAL, "invalid num-cs %u\n",
|
||||
num_cs);
|
||||
|
||||
ctlr->num_chipselect = num_cs;
|
||||
ctlr->max_native_cs = NUVOTON_SPI_MAX_NATIVE_CS;
|
||||
ctlr->use_gpio_descriptors = true;
|
||||
ctlr->max_transfer_size = nuvoton_spi_max_transfer_size;
|
||||
ctlr->setup = nuvoton_spi_setup;
|
||||
ctlr->set_cs = nuvoton_spi_set_cs;
|
||||
ctlr->prepare_message = nuvoton_spi_prepare_message;
|
||||
ctlr->transfer_one = nuvoton_spi_transfer_one;
|
||||
ctlr->handle_err = nuvoton_spi_handle_err;
|
||||
ctlr->bits_per_word_mask = SPI_BPW_RANGE_MASK(8, 32);
|
||||
ctlr->mode_bits = SPI_CPOL | SPI_CPHA | SPI_LSB_FIRST;
|
||||
ctlr->min_speed_hz = DIV_ROUND_UP(nspi->clk_rate,
|
||||
NUVOTON_SPI_MAX_DIVISOR);
|
||||
|
||||
ret = nuvoton_spi_calc_divisor(nspi->clk_rate,
|
||||
NUVOTON_SPI_MAX_SPEED_HZ, &max_divisor);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret,
|
||||
"clock rate does not support SPI transfers\n");
|
||||
|
||||
ctlr->max_speed_hz = nspi->clk_rate / max_divisor;
|
||||
|
||||
ret = nuvoton_spi_hw_init(nspi);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = devm_spi_register_controller(dev, ctlr);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret,
|
||||
"failed to register SPI controller\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int nuvoton_spi_suspend(struct device *dev)
|
||||
{
|
||||
struct spi_controller *ctlr = dev_get_drvdata(dev);
|
||||
|
||||
return spi_controller_suspend(ctlr);
|
||||
}
|
||||
|
||||
static int nuvoton_spi_resume(struct device *dev)
|
||||
{
|
||||
struct spi_controller *ctlr = dev_get_drvdata(dev);
|
||||
|
||||
/*
|
||||
* The MA35D1 SPI registers are retained across power-down and deep
|
||||
* sleep, so the controller state does not need to be restored here.
|
||||
*/
|
||||
return spi_controller_resume(ctlr);
|
||||
}
|
||||
|
||||
static DEFINE_SIMPLE_DEV_PM_OPS(nuvoton_spi_pm_ops, nuvoton_spi_suspend,
|
||||
nuvoton_spi_resume);
|
||||
|
||||
static const struct of_device_id nuvoton_spi_of_match[] = {
|
||||
{ .compatible = "nuvoton,ma35d1-spi" },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, nuvoton_spi_of_match);
|
||||
|
||||
static struct platform_driver nuvoton_spi_driver = {
|
||||
.driver = {
|
||||
.name = "ma35d1-spi",
|
||||
.of_match_table = nuvoton_spi_of_match,
|
||||
.pm = pm_sleep_ptr(&nuvoton_spi_pm_ops),
|
||||
},
|
||||
.probe = nuvoton_spi_probe,
|
||||
};
|
||||
module_platform_driver(nuvoton_spi_driver);
|
||||
|
||||
MODULE_DESCRIPTION("Nuvoton MA35D1 SPI controller driver");
|
||||
MODULE_AUTHOR("Chi-Wen Weng <cwweng@nuvoton.com>");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -235,6 +235,13 @@ static int meson_spicc_dma_map(struct meson_spicc_device *spicc,
|
|||
{
|
||||
struct device *dev = spicc->host->dev.parent;
|
||||
|
||||
/*
|
||||
* Mark the DMA addresses as invalid so that meson_spicc_dma_unmap()
|
||||
* will not try to unmap a buffer which was not (successfully) mapped.
|
||||
*/
|
||||
t->tx_dma = DMA_MAPPING_ERROR;
|
||||
t->rx_dma = DMA_MAPPING_ERROR;
|
||||
|
||||
if (!(t->tx_buf && t->rx_buf))
|
||||
return -EINVAL;
|
||||
|
||||
|
|
@ -257,9 +264,9 @@ static void meson_spicc_dma_unmap(struct meson_spicc_device *spicc,
|
|||
{
|
||||
struct device *dev = spicc->host->dev.parent;
|
||||
|
||||
if (t->tx_dma)
|
||||
if (!dma_mapping_error(dev, t->tx_dma))
|
||||
dma_unmap_single(dev, t->tx_dma, t->len, DMA_TO_DEVICE);
|
||||
if (t->rx_dma)
|
||||
if (!dma_mapping_error(dev, t->rx_dma))
|
||||
dma_unmap_single(dev, t->rx_dma, t->len, DMA_FROM_DEVICE);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -343,6 +343,10 @@ static int npcm_fiu_uma_read(struct spi_mem *mem,
|
|||
u32 val;
|
||||
u32 i;
|
||||
|
||||
/* data_reg holds at most 16 bytes */
|
||||
if (data_size > sizeof(data_reg))
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
regmap_update_bits(fiu->regmap, NPCM_FIU_UMA_CTS,
|
||||
NPCM_FIU_UMA_CTS_DEV_NUM,
|
||||
(spi_get_chipselect(mem->spi, 0) <<
|
||||
|
|
@ -398,6 +402,10 @@ static int npcm_fiu_uma_write(struct spi_mem *mem,
|
|||
u32 val;
|
||||
u32 i;
|
||||
|
||||
/* data_reg holds at most 16 bytes */
|
||||
if (data_size > sizeof(data_reg))
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
regmap_update_bits(fiu->regmap, NPCM_FIU_UMA_CTS,
|
||||
NPCM_FIU_UMA_CTS_DEV_NUM,
|
||||
(spi_get_chipselect(mem->spi, 0) <<
|
||||
|
|
|
|||
|
|
@ -841,23 +841,23 @@ static int nxp_xspi_ahb_dma_read(struct nxp_xspi *xspi,
|
|||
{
|
||||
struct dma_async_tx_descriptor *desc;
|
||||
struct dma_chan *chan = xspi->dma_rx;
|
||||
struct device *dma_dev = dmaengine_get_dma_device(chan);
|
||||
unsigned long timeout;
|
||||
dma_addr_t src_addr, dst_addr;
|
||||
int ret = 0;
|
||||
|
||||
dst_addr = dma_map_single(chan->device->dev,
|
||||
op->data.buf.in, op->data.nbytes,
|
||||
dst_addr = dma_map_single(dma_dev, op->data.buf.in, op->data.nbytes,
|
||||
DMA_FROM_DEVICE);
|
||||
if (dma_mapping_error(chan->device->dev, dst_addr)) {
|
||||
if (dma_mapping_error(dma_dev, dst_addr)) {
|
||||
dev_err(xspi->dev, "failed to map DMA dst buffer\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
src_addr = dma_map_resource(chan->device->dev,
|
||||
src_addr = dma_map_resource(dma_dev,
|
||||
(phys_addr_t)xspi->memmap_phy + op->addr.val,
|
||||
op->data.nbytes,
|
||||
DMA_TO_DEVICE, 0);
|
||||
if (dma_mapping_error(chan->device->dev, src_addr)) {
|
||||
if (dma_mapping_error(dma_dev, src_addr)) {
|
||||
dev_err(xspi->dev, "failed to map DMA src resource\n");
|
||||
ret = -ENOMEM;
|
||||
goto err_unmap_dst;
|
||||
|
|
@ -886,12 +886,10 @@ static int nxp_xspi_ahb_dma_read(struct nxp_xspi *xspi,
|
|||
}
|
||||
|
||||
err_unmap_src:
|
||||
dma_unmap_resource(chan->device->dev, src_addr,
|
||||
op->data.nbytes, DMA_TO_DEVICE, 0);
|
||||
dma_unmap_resource(dma_dev, src_addr, op->data.nbytes, DMA_TO_DEVICE, 0);
|
||||
|
||||
err_unmap_dst:
|
||||
dma_unmap_single(chan->device->dev, dst_addr,
|
||||
op->data.nbytes, DMA_FROM_DEVICE);
|
||||
dma_unmap_single(dma_dev, dst_addr, op->data.nbytes, DMA_FROM_DEVICE);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -413,10 +413,11 @@ static void omap2_mcspi_tx_callback(void *data)
|
|||
complete(&mcspi_dma->dma_tx_completion);
|
||||
}
|
||||
|
||||
static void omap2_mcspi_tx_dma(struct spi_device *spi,
|
||||
struct spi_transfer *xfer,
|
||||
struct dma_slave_config cfg)
|
||||
static int omap2_mcspi_tx_dma(struct spi_device *spi,
|
||||
struct spi_transfer *xfer,
|
||||
struct dma_slave_config cfg)
|
||||
{
|
||||
int ret;
|
||||
struct omap2_mcspi *mcspi;
|
||||
struct omap2_mcspi_dma *mcspi_dma;
|
||||
struct dma_async_tx_descriptor *tx;
|
||||
|
|
@ -424,31 +425,31 @@ static void omap2_mcspi_tx_dma(struct spi_device *spi,
|
|||
mcspi = spi_controller_get_devdata(spi->controller);
|
||||
mcspi_dma = &mcspi->dma_channels[spi_get_chipselect(spi, 0)];
|
||||
|
||||
dmaengine_slave_config(mcspi_dma->dma_tx, &cfg);
|
||||
ret = dmaengine_slave_config(mcspi_dma->dma_tx, &cfg);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
tx = dmaengine_prep_slave_sg(mcspi_dma->dma_tx, xfer->tx_sg.sgl,
|
||||
xfer->tx_sg.nents,
|
||||
DMA_MEM_TO_DEV,
|
||||
DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
|
||||
if (tx) {
|
||||
tx->callback = omap2_mcspi_tx_callback;
|
||||
tx->callback_param = spi;
|
||||
dmaengine_submit(tx);
|
||||
} else {
|
||||
/* FIXME: fall back to PIO? */
|
||||
}
|
||||
dma_async_issue_pending(mcspi_dma->dma_tx);
|
||||
omap2_mcspi_set_dma_req(spi, 0, 1);
|
||||
if (!tx)
|
||||
return -EIO;
|
||||
|
||||
tx->callback = omap2_mcspi_tx_callback;
|
||||
tx->callback_param = spi;
|
||||
|
||||
return dma_submit_error(dmaengine_submit(tx));
|
||||
}
|
||||
|
||||
static unsigned
|
||||
static int
|
||||
omap2_mcspi_rx_dma(struct spi_device *spi, struct spi_transfer *xfer,
|
||||
struct dma_slave_config cfg,
|
||||
unsigned es)
|
||||
struct dma_slave_config cfg, unsigned int es,
|
||||
unsigned int *count, bool *rx_submitted, bool *dma_started)
|
||||
{
|
||||
struct omap2_mcspi *mcspi;
|
||||
struct omap2_mcspi_dma *mcspi_dma;
|
||||
unsigned int count, transfer_reduction = 0;
|
||||
unsigned int transfer_reduction = 0;
|
||||
struct scatterlist *sg_out[2];
|
||||
int nb_sizes = 0, out_mapped_nents[2], ret, x;
|
||||
size_t sizes[2];
|
||||
|
|
@ -461,7 +462,8 @@ omap2_mcspi_rx_dma(struct spi_device *spi, struct spi_transfer *xfer,
|
|||
|
||||
mcspi = spi_controller_get_devdata(spi->controller);
|
||||
mcspi_dma = &mcspi->dma_channels[spi_get_chipselect(spi, 0)];
|
||||
count = xfer->len;
|
||||
*count = xfer->len;
|
||||
*rx_submitted = false;
|
||||
|
||||
/*
|
||||
* In the "End-of-Transfer Procedure" section for DMA RX in OMAP35x TRM
|
||||
|
|
@ -475,14 +477,16 @@ omap2_mcspi_rx_dma(struct spi_device *spi, struct spi_transfer *xfer,
|
|||
l = mcspi_cached_chconf0(spi);
|
||||
|
||||
if (word_len <= 8)
|
||||
element_count = count;
|
||||
element_count = *count;
|
||||
else if (word_len <= 16)
|
||||
element_count = count >> 1;
|
||||
element_count = *count >> 1;
|
||||
else /* word_len <= 32 */
|
||||
element_count = count >> 2;
|
||||
element_count = *count >> 2;
|
||||
|
||||
|
||||
dmaengine_slave_config(mcspi_dma->dma_rx, &cfg);
|
||||
ret = dmaengine_slave_config(mcspi_dma->dma_rx, &cfg);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* Reduce DMA transfer length by one more if McSPI is
|
||||
|
|
@ -493,7 +497,7 @@ omap2_mcspi_rx_dma(struct spi_device *spi, struct spi_transfer *xfer,
|
|||
|
||||
if (transfer_reduction) {
|
||||
/* Split sgl into two. The second sgl won't be used. */
|
||||
sizes[0] = count - transfer_reduction;
|
||||
sizes[0] = *count - transfer_reduction;
|
||||
sizes[1] = transfer_reduction;
|
||||
nb_sizes = 2;
|
||||
} else {
|
||||
|
|
@ -501,44 +505,56 @@ omap2_mcspi_rx_dma(struct spi_device *spi, struct spi_transfer *xfer,
|
|||
* Don't bother splitting the sgl. This essentially
|
||||
* clones the original sgl.
|
||||
*/
|
||||
sizes[0] = count;
|
||||
sizes[0] = *count;
|
||||
nb_sizes = 1;
|
||||
}
|
||||
|
||||
ret = sg_split(xfer->rx_sg.sgl, xfer->rx_sg.nents, 0, nb_sizes,
|
||||
sizes, sg_out, out_mapped_nents, GFP_KERNEL);
|
||||
|
||||
if (ret < 0) {
|
||||
if (ret) {
|
||||
dev_err(&spi->dev, "sg_split failed\n");
|
||||
return 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
tx = dmaengine_prep_slave_sg(mcspi_dma->dma_rx, sg_out[0],
|
||||
out_mapped_nents[0], DMA_DEV_TO_MEM,
|
||||
DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
|
||||
if (tx) {
|
||||
tx->callback = omap2_mcspi_rx_callback;
|
||||
tx->callback_param = spi;
|
||||
dmaengine_submit(tx);
|
||||
} else {
|
||||
/* FIXME: fall back to PIO? */
|
||||
if (!tx) {
|
||||
ret = -EIO;
|
||||
goto out_free_sg;
|
||||
}
|
||||
|
||||
tx->callback = omap2_mcspi_rx_callback;
|
||||
tx->callback_param = spi;
|
||||
ret = dma_submit_error(dmaengine_submit(tx));
|
||||
if (ret)
|
||||
goto out_free_sg;
|
||||
|
||||
*rx_submitted = true;
|
||||
if (mcspi->target_aborted) {
|
||||
ret = -EINTR;
|
||||
goto out_free_sg;
|
||||
}
|
||||
*dma_started = true;
|
||||
if (xfer->tx_buf) {
|
||||
if (spi_controller_is_target(mcspi->ctlr))
|
||||
mcspi_write_reg(mcspi->ctlr, OMAP2_MCSPI_IRQENABLE,
|
||||
OMAP2_MCSPI_IRQSTATUS_EOW);
|
||||
dma_async_issue_pending(mcspi_dma->dma_tx);
|
||||
omap2_mcspi_set_dma_req(spi, 0, 1);
|
||||
}
|
||||
dma_async_issue_pending(mcspi_dma->dma_rx);
|
||||
omap2_mcspi_set_dma_req(spi, 1, 1);
|
||||
|
||||
ret = mcspi_wait_for_completion(mcspi, &mcspi_dma->dma_rx_completion);
|
||||
if (ret || mcspi->target_aborted) {
|
||||
dmaengine_terminate_sync(mcspi_dma->dma_rx);
|
||||
omap2_mcspi_set_dma_req(spi, 1, 0);
|
||||
return 0;
|
||||
}
|
||||
if (ret || mcspi->target_aborted)
|
||||
goto out_free_sg;
|
||||
|
||||
for (x = 0; x < nb_sizes; x++)
|
||||
kfree(sg_out[x]);
|
||||
|
||||
if (mcspi->fifo_depth > 0)
|
||||
return count;
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* Due to the DMA transfer length reduction the missing bytes must
|
||||
|
|
@ -565,9 +581,9 @@ omap2_mcspi_rx_dma(struct spi_device *spi, struct spi_transfer *xfer,
|
|||
} else {
|
||||
int bytes_per_word = mcspi_bytes_per_word(word_len);
|
||||
dev_err(&spi->dev, "DMA RX penultimate word empty\n");
|
||||
count -= (bytes_per_word << 1);
|
||||
*count -= (bytes_per_word << 1);
|
||||
omap2_mcspi_set_enable(spi, 1);
|
||||
return count;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
if (!mcspi_wait_for_reg_bit(chstat_reg, OMAP2_MCSPI_CHSTAT_RXS)) {
|
||||
|
|
@ -582,19 +598,27 @@ omap2_mcspi_rx_dma(struct spi_device *spi, struct spi_transfer *xfer,
|
|||
((u32 *)xfer->rx_buf)[elements] = w;
|
||||
} else {
|
||||
dev_err(&spi->dev, "DMA RX last word empty\n");
|
||||
count -= mcspi_bytes_per_word(word_len);
|
||||
*count -= mcspi_bytes_per_word(word_len);
|
||||
}
|
||||
omap2_mcspi_set_enable(spi, 1);
|
||||
return count;
|
||||
return 0;
|
||||
|
||||
out_free_sg:
|
||||
if (!ret)
|
||||
ret = -EINTR;
|
||||
for (x = 0; x < nb_sizes; x++)
|
||||
kfree(sg_out[x]);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static unsigned
|
||||
omap2_mcspi_txrx_dma(struct spi_device *spi, struct spi_transfer *xfer)
|
||||
static int omap2_mcspi_txrx_dma(struct spi_device *spi,
|
||||
struct spi_transfer *xfer,
|
||||
unsigned int *count,
|
||||
bool *pio_fallback)
|
||||
{
|
||||
struct omap2_mcspi *mcspi;
|
||||
struct omap2_mcspi_cs *cs = spi->controller_state;
|
||||
struct omap2_mcspi_dma *mcspi_dma;
|
||||
unsigned int count;
|
||||
u8 *rx;
|
||||
const u8 *tx;
|
||||
struct dma_slave_config cfg;
|
||||
|
|
@ -602,10 +626,13 @@ omap2_mcspi_txrx_dma(struct spi_device *spi, struct spi_transfer *xfer)
|
|||
unsigned es;
|
||||
void __iomem *chstat_reg;
|
||||
void __iomem *irqstat_reg;
|
||||
int wait_res;
|
||||
bool tx_submitted = false, rx_submitted = false;
|
||||
bool dma_started = false, terminate_failed = false;
|
||||
int ret, wait_res;
|
||||
|
||||
mcspi = spi_controller_get_devdata(spi->controller);
|
||||
mcspi_dma = &mcspi->dma_channels[spi_get_chipselect(spi, 0)];
|
||||
*pio_fallback = false;
|
||||
|
||||
if (cs->word_len <= 8) {
|
||||
width = DMA_SLAVE_BUSWIDTH_1_BYTE;
|
||||
|
|
@ -618,7 +645,7 @@ omap2_mcspi_txrx_dma(struct spi_device *spi, struct spi_transfer *xfer)
|
|||
es = 4;
|
||||
}
|
||||
|
||||
count = xfer->len;
|
||||
*count = xfer->len;
|
||||
|
||||
memset(&cfg, 0, sizeof(cfg));
|
||||
cfg.src_addr = cs->phys + OMAP2_MCSPI_RX0;
|
||||
|
|
@ -636,31 +663,39 @@ omap2_mcspi_txrx_dma(struct spi_device *spi, struct spi_transfer *xfer)
|
|||
reinit_completion(&mcspi_dma->dma_rx_completion);
|
||||
reinit_completion(&mcspi->txdone);
|
||||
if (tx) {
|
||||
/* Enable EOW IRQ to know end of tx in target mode */
|
||||
if (spi_controller_is_target(spi->controller))
|
||||
mcspi_write_reg(spi->controller,
|
||||
OMAP2_MCSPI_IRQENABLE,
|
||||
OMAP2_MCSPI_IRQSTATUS_EOW);
|
||||
omap2_mcspi_tx_dma(spi, xfer, cfg);
|
||||
ret = omap2_mcspi_tx_dma(spi, xfer, cfg);
|
||||
if (ret)
|
||||
goto out;
|
||||
tx_submitted = true;
|
||||
}
|
||||
|
||||
if (rx != NULL)
|
||||
count = omap2_mcspi_rx_dma(spi, xfer, cfg, es);
|
||||
|
||||
if (tx != NULL) {
|
||||
int ret;
|
||||
|
||||
ret = mcspi_wait_for_completion(mcspi, &mcspi_dma->dma_tx_completion);
|
||||
if (ret || mcspi->target_aborted) {
|
||||
dmaengine_terminate_sync(mcspi_dma->dma_tx);
|
||||
omap2_mcspi_set_dma_req(spi, 0, 0);
|
||||
return 0;
|
||||
if (rx) {
|
||||
ret = omap2_mcspi_rx_dma(spi, xfer, cfg, es, count,
|
||||
&rx_submitted, &dma_started);
|
||||
if (ret)
|
||||
goto out;
|
||||
} else if (tx) {
|
||||
if (mcspi->target_aborted) {
|
||||
ret = -EINTR;
|
||||
goto out;
|
||||
}
|
||||
if (spi_controller_is_target(spi->controller))
|
||||
mcspi_write_reg(spi->controller, OMAP2_MCSPI_IRQENABLE,
|
||||
OMAP2_MCSPI_IRQSTATUS_EOW);
|
||||
dma_started = true;
|
||||
dma_async_issue_pending(mcspi_dma->dma_tx);
|
||||
omap2_mcspi_set_dma_req(spi, 0, 1);
|
||||
}
|
||||
|
||||
if (tx) {
|
||||
ret = mcspi_wait_for_completion(mcspi, &mcspi_dma->dma_tx_completion);
|
||||
if (ret || mcspi->target_aborted)
|
||||
goto out;
|
||||
|
||||
if (spi_controller_is_target(mcspi->ctlr)) {
|
||||
ret = mcspi_wait_for_completion(mcspi, &mcspi->txdone);
|
||||
if (ret || mcspi->target_aborted)
|
||||
return 0;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (mcspi->fifo_depth > 0) {
|
||||
|
|
@ -694,7 +729,43 @@ omap2_mcspi_txrx_dma(struct spi_device *spi, struct spi_transfer *xfer)
|
|||
dev_err(&spi->dev, "EOT timed out\n");
|
||||
}
|
||||
}
|
||||
return count;
|
||||
return 0;
|
||||
|
||||
out:
|
||||
if (!ret)
|
||||
ret = -EINTR;
|
||||
if (rx_submitted) {
|
||||
wait_res = dmaengine_terminate_sync(mcspi_dma->dma_rx);
|
||||
if (wait_res) {
|
||||
dev_err(&spi->dev, "failed to terminate RX DMA: %d\n",
|
||||
wait_res);
|
||||
terminate_failed = true;
|
||||
ret = wait_res;
|
||||
}
|
||||
omap2_mcspi_set_dma_req(spi, 1, 0);
|
||||
}
|
||||
if (tx_submitted) {
|
||||
wait_res = dmaengine_terminate_sync(mcspi_dma->dma_tx);
|
||||
if (wait_res) {
|
||||
dev_err(&spi->dev, "failed to terminate TX DMA: %d\n",
|
||||
wait_res);
|
||||
terminate_failed = true;
|
||||
ret = wait_res;
|
||||
}
|
||||
omap2_mcspi_set_dma_req(spi, 0, 0);
|
||||
}
|
||||
if (spi_controller_is_target(mcspi->ctlr))
|
||||
mcspi_write_reg(mcspi->ctlr, OMAP2_MCSPI_IRQENABLE, 0);
|
||||
|
||||
*pio_fallback = !dma_started && !terminate_failed;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static bool omap2_mcspi_can_pio_fallback(bool cleanup_ok,
|
||||
const struct spi_transfer *xfer,
|
||||
bool is_target)
|
||||
{
|
||||
return cleanup_ok && xfer->tx_buf && !is_target;
|
||||
}
|
||||
|
||||
static unsigned
|
||||
|
|
@ -1214,6 +1285,8 @@ static int omap2_mcspi_transfer_one(struct spi_controller *ctlr,
|
|||
|
||||
if (t->len) {
|
||||
unsigned count;
|
||||
bool pio_fallback;
|
||||
int ret;
|
||||
|
||||
if ((mcspi_dma->dma_rx && mcspi_dma->dma_tx) &&
|
||||
spi_xfer_is_dma_mapped(ctlr, spi, t))
|
||||
|
|
@ -1227,10 +1300,31 @@ static int omap2_mcspi_transfer_one(struct spi_controller *ctlr,
|
|||
+ OMAP2_MCSPI_TX0);
|
||||
|
||||
if ((mcspi_dma->dma_rx && mcspi_dma->dma_tx) &&
|
||||
spi_xfer_is_dma_mapped(ctlr, spi, t))
|
||||
count = omap2_mcspi_txrx_dma(spi, t);
|
||||
else
|
||||
spi_xfer_is_dma_mapped(ctlr, spi, t)) {
|
||||
ret = omap2_mcspi_txrx_dma(spi, t, &count,
|
||||
&pio_fallback);
|
||||
if (ret) {
|
||||
bool is_target = spi_controller_is_target(ctlr);
|
||||
bool fallback;
|
||||
|
||||
fallback = omap2_mcspi_can_pio_fallback(pio_fallback, t,
|
||||
is_target);
|
||||
if (!fallback) {
|
||||
status = ret;
|
||||
goto out;
|
||||
}
|
||||
|
||||
dev_warn(&spi->dev,
|
||||
"DMA setup failed, falling back to PIO\n");
|
||||
/* PIO does not use the FIFO enabled for DMA */
|
||||
omap2_mcspi_set_enable(spi, 0);
|
||||
omap2_mcspi_set_fifo(spi, t, 0);
|
||||
omap2_mcspi_set_enable(spi, 1);
|
||||
count = omap2_mcspi_txrx_pio(spi, t);
|
||||
}
|
||||
} else {
|
||||
count = omap2_mcspi_txrx_pio(spi, t);
|
||||
}
|
||||
|
||||
if (count != t->len) {
|
||||
status = -EIO;
|
||||
|
|
@ -1244,10 +1338,14 @@ 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) {
|
||||
int restore_status;
|
||||
|
||||
par_override = 0;
|
||||
status = omap2_mcspi_setup_transfer(spi, NULL);
|
||||
restore_status = omap2_mcspi_setup_transfer(spi, NULL);
|
||||
if (!status)
|
||||
status = restore_status;
|
||||
}
|
||||
|
||||
omap2_mcspi_set_enable(spi, 0);
|
||||
|
|
@ -1544,8 +1642,10 @@ static int omap2_mcspi_probe(struct platform_device *pdev)
|
|||
return -ENOMEM;
|
||||
|
||||
for (i = 0; i < ctlr->num_chipselect; i++) {
|
||||
sprintf(mcspi->dma_channels[i].dma_rx_ch_name, "rx%d", i);
|
||||
sprintf(mcspi->dma_channels[i].dma_tx_ch_name, "tx%d", i);
|
||||
snprintf(mcspi->dma_channels[i].dma_rx_ch_name,
|
||||
sizeof(mcspi->dma_channels[i].dma_rx_ch_name), "rx%d", i);
|
||||
snprintf(mcspi->dma_channels[i].dma_tx_ch_name,
|
||||
sizeof(mcspi->dma_channels[i].dma_tx_ch_name), "tx%d", i);
|
||||
|
||||
status = omap2_mcspi_request_dma(mcspi,
|
||||
&mcspi->dma_channels[i]);
|
||||
|
|
@ -1595,7 +1695,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 +1712,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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -542,6 +542,8 @@ static int pci1xxxx_spi_transfer_with_dma(struct spi_controller *spi_ctlr,
|
|||
struct pci1xxxx_spi_internal *p = spi_controller_get_devdata(spi_ctlr);
|
||||
struct pci1xxxx_spi *par = p->parent;
|
||||
dma_addr_t tx_dma_addr = 0;
|
||||
unsigned long flags;
|
||||
bool engines_stopped;
|
||||
int ret = 0;
|
||||
u32 regval;
|
||||
|
||||
|
|
@ -618,12 +620,72 @@ static int pci1xxxx_spi_transfer_with_dma(struct spi_controller *spi_ctlr,
|
|||
p->dma_aborted_wr = false;
|
||||
ret = -ECANCELED;
|
||||
}
|
||||
|
||||
/*
|
||||
* A timed out transfer leaves the DMA engines running and
|
||||
* the done/abort interrupt status armed with p->xfer,
|
||||
* p->tx_sgl and p->rx_sgl still registered. Stop the
|
||||
* engines, drain the pending status and synchronize the
|
||||
* IRQs before dropping the transfer state, otherwise a
|
||||
* late interrupt makes the ISRs reprogram the engines
|
||||
* from or signal completion through memory which the SPI
|
||||
* core is about to unmap or free.
|
||||
*/
|
||||
writel(SPI_DMA_ENGINE_DIS,
|
||||
par->dma_offset_bar + SPI_DMA_GLOBAL_WR_ENGINE_EN);
|
||||
writel(SPI_DMA_ENGINE_DIS,
|
||||
par->dma_offset_bar + SPI_DMA_GLOBAL_RD_ENGINE_EN);
|
||||
engines_stopped =
|
||||
!(readl_poll_timeout(par->dma_offset_bar +
|
||||
SPI_DMA_GLOBAL_WR_ENGINE_EN,
|
||||
regval, regval == 0x0, 0,
|
||||
USEC_PER_MSEC) ||
|
||||
readl_poll_timeout(par->dma_offset_bar +
|
||||
SPI_DMA_GLOBAL_RD_ENGINE_EN,
|
||||
regval, regval == 0x0, 0,
|
||||
USEC_PER_MSEC));
|
||||
if (!engines_stopped)
|
||||
dev_warn(&par->dev->dev,
|
||||
"SPI DMA engines failed to stop\n");
|
||||
|
||||
spin_lock_irqsave(&par->dma_wr_reg_lock, flags);
|
||||
writel((SPI_DMA_DONE_INT_MASK(p->hw_inst) |
|
||||
SPI_DMA_ABORT_INT_MASK(p->hw_inst)),
|
||||
par->dma_offset_bar + SPI_DMA_INTR_WR_CLR);
|
||||
spin_unlock_irqrestore(&par->dma_wr_reg_lock, flags);
|
||||
spin_lock_irqsave(&par->dma_rd_reg_lock, flags);
|
||||
writel((SPI_DMA_DONE_INT_MASK(p->hw_inst) |
|
||||
SPI_DMA_ABORT_INT_MASK(p->hw_inst)),
|
||||
par->dma_offset_bar + SPI_DMA_INTR_RD_CLR);
|
||||
spin_unlock_irqrestore(&par->dma_rd_reg_lock, flags);
|
||||
|
||||
p->spi_xfer_in_progress = false;
|
||||
synchronize_irq(p->irq[1]);
|
||||
synchronize_irq(p->irq[2]);
|
||||
|
||||
/*
|
||||
* Turn the engines back on only once both are confirmed
|
||||
* to have stopped: re-enabling an engine that failed to
|
||||
* stop would cancel the pending stop request and let it
|
||||
* run into buffers the SPI core is about to unmap or
|
||||
* free. An engine that failed to stop is left disabled,
|
||||
* as the abort paths already do.
|
||||
*/
|
||||
if (engines_stopped) {
|
||||
writel(SPI_DMA_ENGINE_EN,
|
||||
par->dma_offset_bar + SPI_DMA_GLOBAL_WR_ENGINE_EN);
|
||||
writel(SPI_DMA_ENGINE_EN,
|
||||
par->dma_offset_bar + SPI_DMA_GLOBAL_RD_ENGINE_EN);
|
||||
}
|
||||
goto error;
|
||||
}
|
||||
ret = 0;
|
||||
|
||||
error:
|
||||
p->spi_xfer_in_progress = false;
|
||||
p->xfer = NULL;
|
||||
p->tx_sgl = NULL;
|
||||
p->rx_sgl = NULL;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
|
@ -695,6 +757,9 @@ static irqreturn_t pci1xxxx_spi_isr_dma_rd(int irq, void *dev)
|
|||
unsigned long flags;
|
||||
u32 regval;
|
||||
|
||||
if (!p->spi_xfer_in_progress)
|
||||
return IRQ_NONE;
|
||||
|
||||
/* Clear the DMA RD INT and start spi xfer*/
|
||||
regval = readl(p->parent->dma_offset_bar + SPI_DMA_INTR_RD_STS);
|
||||
if (regval) {
|
||||
|
|
@ -723,6 +788,9 @@ static irqreturn_t pci1xxxx_spi_isr_dma_wr(int irq, void *dev)
|
|||
unsigned long flags;
|
||||
u32 regval;
|
||||
|
||||
if (!p->spi_xfer_in_progress)
|
||||
return IRQ_NONE;
|
||||
|
||||
/* Clear the DMA WR INT */
|
||||
regval = readl(p->parent->dma_offset_bar + SPI_DMA_INTR_WR_STS);
|
||||
if (regval) {
|
||||
|
|
@ -755,6 +823,9 @@ static irqreturn_t pci1xxxx_spi_isr_dma(int irq, void *dev)
|
|||
irqreturn_t spi_int_fired = IRQ_NONE;
|
||||
u32 regval;
|
||||
|
||||
if (!p->spi_xfer_in_progress)
|
||||
return IRQ_NONE;
|
||||
|
||||
/* Clear the SPI GO_BIT Interrupt */
|
||||
regval = readl(p->parent->reg_base + SPI_MST_EVENT_REG_OFFSET(p->hw_inst));
|
||||
if (regval & SPI_INTR) {
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -684,9 +684,9 @@ static void readwriter(struct pl022 *pl022)
|
|||
static void unmap_free_dma_scatter(struct pl022 *pl022)
|
||||
{
|
||||
/* Unmap and free the SG tables */
|
||||
dma_unmap_sg(pl022->dma_tx_channel->device->dev, pl022->sgt_tx.sgl,
|
||||
dma_unmap_sg(dmaengine_get_dma_device(pl022->dma_tx_channel), pl022->sgt_tx.sgl,
|
||||
pl022->sgt_tx.nents, DMA_TO_DEVICE);
|
||||
dma_unmap_sg(pl022->dma_rx_channel->device->dev, pl022->sgt_rx.sgl,
|
||||
dma_unmap_sg(dmaengine_get_dma_device(pl022->dma_rx_channel), pl022->sgt_rx.sgl,
|
||||
pl022->sgt_rx.nents, DMA_FROM_DEVICE);
|
||||
sg_free_table(&pl022->sgt_rx);
|
||||
sg_free_table(&pl022->sgt_tx);
|
||||
|
|
@ -811,6 +811,7 @@ static int configure_dma(struct pl022 *pl022)
|
|||
struct dma_chan *txchan = pl022->dma_tx_channel;
|
||||
struct dma_async_tx_descriptor *rxdesc;
|
||||
struct dma_async_tx_descriptor *txdesc;
|
||||
struct device *rx_dev, *tx_dev;
|
||||
|
||||
/* Check that the channels are available */
|
||||
if (!rxchan || !txchan)
|
||||
|
|
@ -924,14 +925,16 @@ static int configure_dma(struct pl022 *pl022)
|
|||
setup_dma_scatter(pl022, pl022->tx,
|
||||
pl022->cur_transfer->len, &pl022->sgt_tx);
|
||||
|
||||
rx_dev = dmaengine_get_dma_device(rxchan);
|
||||
/* Map DMA buffers */
|
||||
rx_sglen = dma_map_sg(rxchan->device->dev, pl022->sgt_rx.sgl,
|
||||
pl022->sgt_rx.nents, DMA_FROM_DEVICE);
|
||||
rx_sglen = dma_map_sg(rx_dev, pl022->sgt_rx.sgl,
|
||||
pl022->sgt_rx.nents, DMA_FROM_DEVICE);
|
||||
if (!rx_sglen)
|
||||
goto err_rx_sgmap;
|
||||
|
||||
tx_sglen = dma_map_sg(txchan->device->dev, pl022->sgt_tx.sgl,
|
||||
pl022->sgt_tx.nents, DMA_TO_DEVICE);
|
||||
tx_dev = dmaengine_get_dma_device(txchan);
|
||||
tx_sglen = dma_map_sg(tx_dev, pl022->sgt_tx.sgl,
|
||||
pl022->sgt_tx.nents, DMA_TO_DEVICE);
|
||||
if (!tx_sglen)
|
||||
goto err_tx_sgmap;
|
||||
|
||||
|
|
@ -969,10 +972,10 @@ static int configure_dma(struct pl022 *pl022)
|
|||
dmaengine_terminate_all(txchan);
|
||||
err_rxdesc:
|
||||
dmaengine_terminate_all(rxchan);
|
||||
dma_unmap_sg(txchan->device->dev, pl022->sgt_tx.sgl,
|
||||
dma_unmap_sg(tx_dev, pl022->sgt_tx.sgl,
|
||||
pl022->sgt_tx.nents, DMA_TO_DEVICE);
|
||||
err_tx_sgmap:
|
||||
dma_unmap_sg(rxchan->device->dev, pl022->sgt_rx.sgl,
|
||||
dma_unmap_sg(rx_dev, pl022->sgt_rx.sgl,
|
||||
pl022->sgt_rx.nents, DMA_FROM_DEVICE);
|
||||
err_rx_sgmap:
|
||||
sg_free_table(&pl022->sgt_tx);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
|
@ -117,6 +115,7 @@ struct qpic_spi_nand {
|
|||
__le32 addr2;
|
||||
__le32 cmd;
|
||||
u32 num_cw;
|
||||
bool bbm_read;
|
||||
bool oob_rw;
|
||||
bool page_rw;
|
||||
bool raw_rw;
|
||||
|
|
@ -168,8 +167,9 @@ static void qcom_spi_set_read_loc_last(struct qcom_nand_controller *snandc,
|
|||
static struct qcom_nand_controller *nand_to_qcom_snand(struct nand_device *nand)
|
||||
{
|
||||
struct nand_ecc_engine *eng = nand->ecc.engine;
|
||||
struct qpic_spi_nand *qspi = ecceng_to_qspi(eng);
|
||||
struct qpic_spi_nand *qspi;
|
||||
|
||||
qspi = container_of(eng, struct qpic_spi_nand, ecc_eng);
|
||||
return qspi->snandc;
|
||||
}
|
||||
|
||||
|
|
@ -343,6 +343,15 @@ static int qcom_spi_ecc_init_ctx_pipelined(struct nand_device *nand)
|
|||
ecc_cfg->cw_size = ecc_cfg->cw_data + ecc_cfg->bytes;
|
||||
bad_block_byte = mtd->writesize - ecc_cfg->cw_size * (cwperpage - 1) + 1;
|
||||
|
||||
if ((ecc_cfg->cw_size * cwperpage) > (mtd->writesize + mtd->oobsize)) {
|
||||
dev_err(snandc->dev,
|
||||
"Current ECC settings require %d bytes, but the flash only has %d+%d bytes.\n",
|
||||
(ecc_cfg->cw_size * cwperpage), mtd->writesize,
|
||||
mtd->oobsize);
|
||||
ret = -EINVAL;
|
||||
goto err_free_ecc_cfg;
|
||||
}
|
||||
|
||||
mtd_set_ooblayout(mtd, &qcom_spi_ooblayout);
|
||||
|
||||
/*
|
||||
|
|
@ -398,8 +407,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 +418,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 +436,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 +444,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;
|
||||
|
|
@ -450,6 +458,16 @@ static int qcom_spi_ecc_prepare_io_req_pipelined(struct nand_device *nand,
|
|||
if (req->mode == MTD_OPS_RAW)
|
||||
snandc->qspi->raw_rw = true;
|
||||
|
||||
/*
|
||||
* Try to detect when the SPI NAND framework reads the first
|
||||
* two bytes of the OOB area searching for bad block marker
|
||||
* bytes.
|
||||
*/
|
||||
snandc->qspi->bbm_read = req->mode == MTD_OPS_RAW &&
|
||||
req->type == NAND_PAGE_READ &&
|
||||
req->dataoffs == 0 && req->datalen == 0 &&
|
||||
req->ooboffs == 0 && req->ooblen == 2;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -500,18 +518,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 +591,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;
|
||||
|
|
@ -655,7 +677,8 @@ static int qcom_spi_read_last_cw(struct qcom_nand_controller *snandc,
|
|||
* This can be removed once single-byte bad block marker support
|
||||
* gets implemented in the SPINAND code.
|
||||
*/
|
||||
snandc->data_buffer[bbpos + 1] = snandc->data_buffer[bbpos];
|
||||
if (snandc->qspi->bbm_read)
|
||||
snandc->data_buffer[bbpos + 1] = snandc->data_buffer[bbpos];
|
||||
|
||||
memcpy(op->data.buf.in, snandc->data_buffer + bbpos, op->data.nbytes);
|
||||
|
||||
|
|
@ -762,14 +785,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 +890,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 +971,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 +1207,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 +1230,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 +1282,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 +1303,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 +1480,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 +1578,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 +1600,13 @@ static int qcom_spi_probe(struct platform_device *pdev)
|
|||
snandc->dev = dev;
|
||||
snandc->qspi = qspi;
|
||||
snandc->qspi->ctlr = ctlr;
|
||||
snandc->qspi->ecc = ecc;
|
||||
|
||||
dev_data = of_device_get_match_data(dev);
|
||||
if (!dev_data) {
|
||||
snandc->props = of_device_get_match_data(dev);
|
||||
if (!snandc->props) {
|
||||
dev_err(&pdev->dev, "failed to get device data\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
snandc->props = dev_data;
|
||||
|
||||
snandc->core_clk = devm_clk_get_enabled(dev, "core");
|
||||
if (IS_ERR(snandc->core_clk))
|
||||
return PTR_ERR(snandc->core_clk);
|
||||
|
|
@ -1699,6 +1689,7 @@ static void qcom_spi_remove(struct platform_device *pdev)
|
|||
static const struct qcom_nandc_props ipq9574_snandc_props = {
|
||||
.dev_cmd_reg_start = 0x7000,
|
||||
.bam_offset = 0x30000,
|
||||
.qpic_version2 = true,
|
||||
.supports_bam = true,
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
||||
|
|
|
|||
624
drivers/spi/spi-rtk-nor.c
Normal file
624
drivers/spi/spi-rtk-nor.c
Normal file
|
|
@ -0,0 +1,624 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
//
|
||||
// Realtek SPI Nor Flash Controller Driver (SFC)
|
||||
//
|
||||
// Copyright (c) 2024-2026 Realtek Semiconductor Corp.
|
||||
|
||||
#include <linux/bits.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/iopoll.h>
|
||||
#include <linux/minmax.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/reset.h>
|
||||
#include <linux/spi/spi.h>
|
||||
#include <linux/spi/spi-mem.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#define SFC_OPCODE 0x00
|
||||
#define DUAL_MODE_EN BIT(9)
|
||||
|
||||
#define SFC_CTL 0x04
|
||||
#define RW_DATAEN BIT(4)
|
||||
#define ADDR_EN BIT(3)
|
||||
|
||||
#define SFC_SCK 0x08
|
||||
#define FDIV_MASK GENMASK(7, 0)
|
||||
|
||||
#define SFC_CE 0x0c
|
||||
#define DESLT_TIME 0x1a
|
||||
#define DESLT_TIME_SHIFT 16
|
||||
#define PH_CNT 0x13
|
||||
#define PH_CNT_SHIFT 8
|
||||
#define PL_CNT 0x7
|
||||
|
||||
#define WT_PROM_DONE BIT(8)
|
||||
|
||||
#define SFC_POS_LATCH 0x14
|
||||
#define FALLING_EDGE_EN 0x0
|
||||
|
||||
#define SFC_WAIT_WR 0x18
|
||||
#define SFC_EN_WR 0x1c
|
||||
#define WT_PROM_EN BIT(8)
|
||||
|
||||
#define SFC_ADR_FOUR_BYTE_EN 0x28
|
||||
|
||||
#define MD_FDMA_DDR_SADDR 0x0c
|
||||
#define MD_FDMA_FL_SADDR 0x10
|
||||
|
||||
#define MD_FDMA_CTRL2 0x14
|
||||
#define MAX_XFER_DMA_LEN (BIT(26) | BIT(27))
|
||||
#define MAX_XFER_256 BIT(26)
|
||||
#define DMA_TO_FLASH BIT(25)
|
||||
|
||||
#define MD_FDMA_CTRL1 0x18
|
||||
#define DMA_W_EN_START BIT(3)
|
||||
#define DMA_END BIT(2)
|
||||
#define DMA_W_EN BIT(1)
|
||||
#define DMA_START BIT(0)
|
||||
|
||||
#define MD_FDMA_DDR_SADDR1 0x20
|
||||
|
||||
#define SFC_DMA_TIMEOUT 20000
|
||||
#define SFC_DMA_MAX_LEN 0x100
|
||||
|
||||
#define SFC_CTL_DMYCNT_MASK GENMASK(31, 24)
|
||||
#define SFC_CTL_DMYCNT_SHIFT 24
|
||||
|
||||
#define DMA_HIGH_BITS_MASK 0x7
|
||||
|
||||
#define RTK_SPI_OP_RDSR 0x05
|
||||
#define RTK_SPI_OP_WREN 0x06
|
||||
#define RTK_SPI_OP_EN4B 0xb7
|
||||
#define RTK_SPI_OP_EX4B 0xe9
|
||||
|
||||
#define SFC_AUTOSUSPEND_TIMEOUT 2000
|
||||
|
||||
struct rtk_spi_host {
|
||||
struct device *dev;
|
||||
struct clk *clk;
|
||||
struct reset_control *rstc;
|
||||
void __iomem *regbase;
|
||||
void __iomem *iobase;
|
||||
void __iomem *mdbase;
|
||||
void *buffer;
|
||||
bool is_4byte;
|
||||
dma_addr_t dma_buffer;
|
||||
resource_size_t flash_phys_base;
|
||||
size_t dirmap_size;
|
||||
};
|
||||
|
||||
static u32 rtk_spi_calc_dummy_cycles(const struct spi_mem_op *op)
|
||||
{
|
||||
if (!op->dummy.nbytes)
|
||||
return 0;
|
||||
|
||||
return (op->dummy.nbytes * 8) / op->dummy.buswidth;
|
||||
}
|
||||
|
||||
static void rtk_spi_read_mode(struct rtk_spi_host *host, const struct spi_mem_op *op)
|
||||
{
|
||||
u32 opcode = op->cmd.opcode;
|
||||
u32 dummy_cycles, val;
|
||||
|
||||
if (op->data.buswidth == 2)
|
||||
opcode |= DUAL_MODE_EN;
|
||||
|
||||
writel(opcode, host->regbase + SFC_OPCODE);
|
||||
|
||||
val = readl(host->regbase + SFC_CTL);
|
||||
val |= RW_DATAEN | ADDR_EN;
|
||||
|
||||
dummy_cycles = rtk_spi_calc_dummy_cycles(op);
|
||||
|
||||
val &= ~SFC_CTL_DMYCNT_MASK;
|
||||
val |= (dummy_cycles << SFC_CTL_DMYCNT_SHIFT) & SFC_CTL_DMYCNT_MASK;
|
||||
writel(val, host->regbase + SFC_CTL);
|
||||
|
||||
readl(host->iobase);
|
||||
}
|
||||
|
||||
static void rtk_spi_write_mode(struct rtk_spi_host *host,
|
||||
const struct spi_mem_op *op)
|
||||
{
|
||||
u32 opcode = op->cmd.opcode;
|
||||
u32 val;
|
||||
|
||||
writel(opcode, host->regbase + SFC_OPCODE);
|
||||
|
||||
val = readl(host->regbase + SFC_CTL);
|
||||
val |= RW_DATAEN | ADDR_EN;
|
||||
writel(val, host->regbase + SFC_CTL);
|
||||
}
|
||||
|
||||
static void rtk_spi_enable_auto_write(struct rtk_spi_host *host)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
val = WT_PROM_DONE | RTK_SPI_OP_RDSR;
|
||||
writel(val, host->regbase + SFC_WAIT_WR);
|
||||
|
||||
val = WT_PROM_EN | RTK_SPI_OP_WREN;
|
||||
writel(val, host->regbase + SFC_EN_WR);
|
||||
}
|
||||
|
||||
static void rtk_spi_disable_auto_write(struct rtk_spi_host *host)
|
||||
{
|
||||
writel(RTK_SPI_OP_RDSR, host->regbase + SFC_WAIT_WR);
|
||||
writel(RTK_SPI_OP_WREN, host->regbase + SFC_EN_WR);
|
||||
}
|
||||
|
||||
static int rtk_spi_byte_transfer(struct rtk_spi_host *host, loff_t offset,
|
||||
size_t len, unsigned char *buf, bool is_read)
|
||||
{
|
||||
if (offset + len > host->dirmap_size) {
|
||||
dev_err(host->dev, "PIO transfer out of bounds\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (is_read)
|
||||
memcpy_fromio(buf, host->iobase + offset, len);
|
||||
else
|
||||
memcpy_toio(host->iobase + offset, buf, len);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void rtk_spi_init(struct rtk_spi_host *host)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
val = readl(host->regbase + SFC_SCK);
|
||||
val &= ~FDIV_MASK;
|
||||
val |= (3 << 0) & FDIV_MASK;
|
||||
writel(val, host->regbase + SFC_SCK);
|
||||
|
||||
val = (DESLT_TIME << DESLT_TIME_SHIFT) | (PH_CNT << PH_CNT_SHIFT) | PL_CNT;
|
||||
writel(val, host->regbase + SFC_CE);
|
||||
|
||||
writel(FALLING_EDGE_EN, host->regbase + SFC_POS_LATCH);
|
||||
writel(RTK_SPI_OP_RDSR, host->regbase + SFC_WAIT_WR);
|
||||
writel(RTK_SPI_OP_WREN, host->regbase + SFC_EN_WR);
|
||||
|
||||
writel(host->is_4byte ? 0x1 : 0x0, host->regbase + SFC_ADR_FOUR_BYTE_EN);
|
||||
}
|
||||
|
||||
static int rtk_spi_command_read(struct rtk_spi_host *host, const struct spi_mem_op *op)
|
||||
{
|
||||
size_t len = op->data.nbytes;
|
||||
loff_t offset = op->addr.val;
|
||||
u8 opcode = op->cmd.opcode;
|
||||
u32 dummy_cycles, val;
|
||||
|
||||
writel(opcode, host->regbase + SFC_OPCODE);
|
||||
|
||||
val = readl(host->regbase + SFC_CTL);
|
||||
val &= ~(SFC_CTL_DMYCNT_MASK | ADDR_EN);
|
||||
val |= RW_DATAEN;
|
||||
|
||||
if (op->addr.nbytes > 0)
|
||||
val |= ADDR_EN;
|
||||
|
||||
dummy_cycles = rtk_spi_calc_dummy_cycles(op);
|
||||
val |= (dummy_cycles << SFC_CTL_DMYCNT_SHIFT) & SFC_CTL_DMYCNT_MASK;
|
||||
writel(val, host->regbase + SFC_CTL);
|
||||
|
||||
return rtk_spi_byte_transfer(host, offset, len, op->data.buf.in, true);
|
||||
}
|
||||
|
||||
static int rtk_spi_do_write_and_cmds(struct rtk_spi_host *host, const struct spi_mem_op *op)
|
||||
{
|
||||
u8 opcode = op->cmd.opcode;
|
||||
u32 ctl_val = 0;
|
||||
|
||||
writel(opcode, host->regbase + SFC_OPCODE);
|
||||
|
||||
if (op->data.nbytes > 0)
|
||||
ctl_val |= RW_DATAEN;
|
||||
|
||||
if (op->addr.nbytes > 0)
|
||||
ctl_val |= ADDR_EN;
|
||||
|
||||
writel(ctl_val, host->regbase + SFC_CTL);
|
||||
|
||||
if (op->data.nbytes > 0) {
|
||||
const u8 *buf = op->data.buf.out;
|
||||
|
||||
if (op->addr.val + op->data.nbytes > host->dirmap_size)
|
||||
return -EINVAL;
|
||||
|
||||
writeb(*buf, host->iobase + op->addr.val);
|
||||
} else {
|
||||
if (op->addr.val >= host->dirmap_size)
|
||||
return -EINVAL;
|
||||
|
||||
readb(host->iobase + op->addr.val);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rtk_spi_dma_transfer(struct rtk_spi_host *host, loff_t offset,
|
||||
size_t len, bool is_read)
|
||||
{
|
||||
u64 dma_buffer, timeout_us = SFC_DMA_TIMEOUT * 100;
|
||||
u32 flash_phys_addr, val;
|
||||
int ret;
|
||||
|
||||
if (offset + len > host->dirmap_size) {
|
||||
dev_err(host->dev, "DMA transfer out of bounds\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
writel(DMA_W_EN_START | DMA_W_EN, host->mdbase + MD_FDMA_CTRL1);
|
||||
|
||||
dma_buffer = host->dma_buffer;
|
||||
|
||||
/* Setup MD DDR address and flash address */
|
||||
writel(lower_32_bits(dma_buffer), host->mdbase + MD_FDMA_DDR_SADDR);
|
||||
writel(upper_32_bits(dma_buffer) & DMA_HIGH_BITS_MASK,
|
||||
host->mdbase + MD_FDMA_DDR_SADDR1);
|
||||
|
||||
/* MD_FDMA_FL_SADDR is a 32-bit hardware register */
|
||||
flash_phys_addr = lower_32_bits(host->flash_phys_base + offset);
|
||||
writel(flash_phys_addr, host->mdbase + MD_FDMA_FL_SADDR);
|
||||
|
||||
if (is_read)
|
||||
val = MAX_XFER_DMA_LEN | len;
|
||||
else
|
||||
val = DMA_TO_FLASH | MAX_XFER_256 | len;
|
||||
|
||||
writel(val, host->mdbase + MD_FDMA_CTRL2);
|
||||
|
||||
writel(DMA_W_EN | DMA_START, host->mdbase + MD_FDMA_CTRL1);
|
||||
udelay(1);
|
||||
|
||||
ret = readl_poll_timeout(host->mdbase + MD_FDMA_CTRL1, val,
|
||||
!(val & DMA_START), 100, timeout_us);
|
||||
if (ret) {
|
||||
dev_err(host->dev, "DMA transfer timed out\n");
|
||||
val = readl(host->mdbase + MD_FDMA_CTRL1);
|
||||
val |= DMA_END | DMA_W_EN_START;
|
||||
writel(val, host->mdbase + MD_FDMA_CTRL1);
|
||||
|
||||
if (readl_poll_timeout(host->mdbase + MD_FDMA_CTRL1, val,
|
||||
!(val & DMA_END), 10, 1000)) {
|
||||
dev_err(host->dev, "Failed to stop DMA gracefully\n");
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rtk_spi_exec_op(struct spi_mem *mem, const struct spi_mem_op *op)
|
||||
{
|
||||
struct rtk_spi_host *host = spi_controller_get_devdata(mem->spi->controller);
|
||||
u32 opcode = op->cmd.opcode;
|
||||
|
||||
/*
|
||||
* Hardware Workaround:
|
||||
* The controller's auto-mode engine requires SFC_ADR_FOUR_BYTE_EN to be
|
||||
* explicitly updated when the flash enters or exits 4-byte mode via control
|
||||
* commands.
|
||||
*/
|
||||
if ((op->addr.nbytes == 4 || opcode == RTK_SPI_OP_EN4B) && !host->is_4byte) {
|
||||
host->is_4byte = true;
|
||||
writel(0x1, host->regbase + SFC_ADR_FOUR_BYTE_EN);
|
||||
} else if ((op->addr.nbytes == 3 || opcode == RTK_SPI_OP_EX4B) && host->is_4byte) {
|
||||
host->is_4byte = false;
|
||||
writel(0x0, host->regbase + SFC_ADR_FOUR_BYTE_EN);
|
||||
}
|
||||
|
||||
if (op->data.dir == SPI_MEM_DATA_IN)
|
||||
return rtk_spi_command_read(host, op);
|
||||
|
||||
return rtk_spi_do_write_and_cmds(host, op);
|
||||
}
|
||||
|
||||
static bool rtk_spi_supports_op(struct spi_mem *mem,
|
||||
const struct spi_mem_op *op)
|
||||
{
|
||||
if (op->cmd.buswidth != 1)
|
||||
return false;
|
||||
|
||||
if (op->cmd.dtr || op->addr.dtr || op->data.dtr)
|
||||
return false;
|
||||
|
||||
if (op->addr.nbytes != 0) {
|
||||
if (op->addr.buswidth > 1)
|
||||
return false;
|
||||
if (op->addr.nbytes < 3 || op->addr.nbytes > 4)
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* The data window is access-triggered: each CPU access to host->iobase
|
||||
* generates one complete SPI transaction. Consecutive byte accesses
|
||||
* therefore cannot be merged into a single transaction, so
|
||||
* multi-byte stateless commands are not supported by this controller.
|
||||
*/
|
||||
if (!op->addr.nbytes && op->data.nbytes > 1)
|
||||
return false;
|
||||
|
||||
if (op->dummy.nbytes != 0) {
|
||||
if (op->dummy.buswidth > 1 || op->dummy.nbytes > 7)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (op->data.nbytes != 0 && op->data.buswidth > 2)
|
||||
return false;
|
||||
|
||||
return spi_mem_default_supports_op(mem, op);
|
||||
}
|
||||
|
||||
static int rtk_spi_dirmap_create(struct spi_mem_dirmap_desc *desc)
|
||||
{
|
||||
const struct spi_mem_op *op = desc->info.op_tmpl;
|
||||
|
||||
if (op->data.dir == SPI_MEM_DATA_IN && op->addr.nbytes != 3 && op->addr.nbytes != 4)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
if (op->data.dir != SPI_MEM_DATA_IN && op->data.dir != SPI_MEM_DATA_OUT)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t rtk_spi_dirmap_read(struct spi_mem_dirmap_desc *desc,
|
||||
u64 offs, size_t len, void *buf)
|
||||
{
|
||||
struct rtk_spi_host *host = spi_controller_get_devdata(desc->mem->spi->controller);
|
||||
const struct spi_mem_op *op = desc->info.op_tmpl;
|
||||
loff_t addr = desc->info.offset + offs;
|
||||
size_t chunk_len;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* Handle unaligned address bytes at the beginning of the read operation.
|
||||
* The hardware requires 4-byte alignment for DMA transfers.
|
||||
*/
|
||||
if (addr & 0x3) {
|
||||
rtk_spi_read_mode(host, op);
|
||||
chunk_len = min_t(size_t, 4 - (addr & 0x3), len);
|
||||
ret = rtk_spi_byte_transfer(host, addr, chunk_len, buf, true);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return chunk_len;
|
||||
}
|
||||
|
||||
rtk_spi_read_mode(host, op);
|
||||
chunk_len = min_t(size_t, len, SFC_DMA_MAX_LEN);
|
||||
ret = rtk_spi_dma_transfer(host, addr, chunk_len, true);
|
||||
if (ret) {
|
||||
dev_err(host->dev, "DMA read transfer failed: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
memcpy(buf, host->buffer, chunk_len);
|
||||
|
||||
return chunk_len;
|
||||
}
|
||||
|
||||
static ssize_t rtk_spi_dirmap_write(struct spi_mem_dirmap_desc *desc,
|
||||
u64 offs, size_t len, const void *buf)
|
||||
{
|
||||
struct rtk_spi_host *host = spi_controller_get_devdata(desc->mem->spi->controller);
|
||||
const struct spi_mem_op *op = desc->info.op_tmpl;
|
||||
loff_t addr = desc->info.offset + offs;
|
||||
size_t chunk_len;
|
||||
int ret = 0;
|
||||
|
||||
rtk_spi_enable_auto_write(host);
|
||||
rtk_spi_write_mode(host, op);
|
||||
|
||||
/*
|
||||
* Handle unaligned address bytes at the beginning of the write operation.
|
||||
* The hardware requires 4-byte alignment for DMA transfers.
|
||||
*/
|
||||
if (addr & 0x3) {
|
||||
chunk_len = min_t(size_t, 4 - (addr & 0x3), len);
|
||||
ret = rtk_spi_byte_transfer(host, addr, chunk_len, (u8 *)buf, false);
|
||||
|
||||
goto out;
|
||||
}
|
||||
|
||||
chunk_len = min_t(size_t, len, SFC_DMA_MAX_LEN);
|
||||
|
||||
memcpy(host->buffer, buf, chunk_len);
|
||||
|
||||
ret = rtk_spi_dma_transfer(host, addr, chunk_len, false);
|
||||
if (ret)
|
||||
dev_err(host->dev, "DMA write transfer failed: %d\n", ret);
|
||||
|
||||
out:
|
||||
rtk_spi_disable_auto_write(host);
|
||||
|
||||
return ret < 0 ? ret : chunk_len;
|
||||
}
|
||||
|
||||
static const struct spi_controller_mem_ops rtk_spi_mem_ops = {
|
||||
.supports_op = rtk_spi_supports_op,
|
||||
.exec_op = rtk_spi_exec_op,
|
||||
.dirmap_create = rtk_spi_dirmap_create,
|
||||
.dirmap_read = rtk_spi_dirmap_read,
|
||||
.dirmap_write = rtk_spi_dirmap_write,
|
||||
};
|
||||
|
||||
static int rtk_spi_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct spi_controller *ctrl;
|
||||
struct rtk_spi_host *host;
|
||||
struct resource *res;
|
||||
int ret;
|
||||
|
||||
ctrl = devm_spi_alloc_host(dev, sizeof(*host));
|
||||
if (!ctrl)
|
||||
return -ENOMEM;
|
||||
|
||||
platform_set_drvdata(pdev, ctrl);
|
||||
host = spi_controller_get_devdata(ctrl);
|
||||
host->dev = dev;
|
||||
|
||||
host->clk = devm_clk_get(dev, NULL);
|
||||
if (IS_ERR(host->clk))
|
||||
return PTR_ERR(host->clk);
|
||||
|
||||
host->rstc = devm_reset_control_get_optional_exclusive(dev, NULL);
|
||||
if (IS_ERR(host->rstc))
|
||||
return PTR_ERR(host->rstc);
|
||||
|
||||
ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(35));
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to set dma mask\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
host->buffer = dmam_alloc_coherent(dev, SFC_DMA_MAX_LEN,
|
||||
&host->dma_buffer, GFP_KERNEL);
|
||||
if (!host->buffer)
|
||||
return -ENOMEM;
|
||||
|
||||
host->regbase = devm_platform_ioremap_resource_byname(pdev, "ctrl");
|
||||
if (IS_ERR(host->regbase))
|
||||
return PTR_ERR(host->regbase);
|
||||
|
||||
host->mdbase = devm_platform_ioremap_resource_byname(pdev, "dma");
|
||||
if (IS_ERR(host->mdbase))
|
||||
return PTR_ERR(host->mdbase);
|
||||
|
||||
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dirmap");
|
||||
host->iobase = devm_ioremap_resource(dev, res);
|
||||
if (IS_ERR(host->iobase))
|
||||
return PTR_ERR(host->iobase);
|
||||
|
||||
host->dirmap_size = resource_size(res);
|
||||
host->flash_phys_base = res->start;
|
||||
host->is_4byte = false;
|
||||
|
||||
ret = clk_prepare_enable(host->clk);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = reset_control_assert(host->rstc);
|
||||
usleep_range(10, 20);
|
||||
ret = reset_control_deassert(host->rstc);
|
||||
|
||||
rtk_spi_init(host);
|
||||
|
||||
pm_runtime_set_autosuspend_delay(dev, SFC_AUTOSUSPEND_TIMEOUT);
|
||||
pm_runtime_use_autosuspend(dev);
|
||||
pm_runtime_set_active(dev);
|
||||
pm_runtime_enable(dev);
|
||||
pm_runtime_get_noresume(dev);
|
||||
|
||||
ctrl->mode_bits = SPI_RX_DUAL | SPI_TX_DUAL;
|
||||
ctrl->bus_num = -1;
|
||||
ctrl->mem_ops = &rtk_spi_mem_ops;
|
||||
ctrl->num_chipselect = 1;
|
||||
ctrl->auto_runtime_pm = true;
|
||||
|
||||
ret = spi_register_controller(ctrl);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "failed to register controller\n");
|
||||
goto err_pm_disable;
|
||||
}
|
||||
|
||||
pm_runtime_put_autosuspend(dev);
|
||||
|
||||
return 0;
|
||||
|
||||
err_pm_disable:
|
||||
pm_runtime_put_noidle(dev);
|
||||
pm_runtime_disable(dev);
|
||||
pm_runtime_set_suspended(dev);
|
||||
pm_runtime_dont_use_autosuspend(dev);
|
||||
reset_control_assert(host->rstc);
|
||||
clk_disable_unprepare(host->clk);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void rtk_spi_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct spi_controller *ctrl = platform_get_drvdata(pdev);
|
||||
struct rtk_spi_host *host = spi_controller_get_devdata(ctrl);
|
||||
struct device *dev = &pdev->dev;
|
||||
|
||||
spi_unregister_controller(ctrl);
|
||||
|
||||
if (pm_runtime_resume_and_get(dev) < 0)
|
||||
dev_warn(dev, "Failed to resume device during remove\n");
|
||||
|
||||
pm_runtime_put_noidle(dev);
|
||||
pm_runtime_disable(dev);
|
||||
pm_runtime_set_suspended(dev);
|
||||
pm_runtime_dont_use_autosuspend(dev);
|
||||
reset_control_assert(host->rstc);
|
||||
clk_disable_unprepare(host->clk);
|
||||
}
|
||||
|
||||
static const struct of_device_id rtk_spi_dt_ids[] = {
|
||||
{ .compatible = "realtek,rtd1625-nor" },
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(of, rtk_spi_dt_ids);
|
||||
|
||||
static int rtk_spi_runtime_suspend(struct device *dev)
|
||||
{
|
||||
struct spi_controller *ctlr = dev_get_drvdata(dev);
|
||||
struct rtk_spi_host *host = spi_controller_get_devdata(ctlr);
|
||||
|
||||
reset_control_assert(host->rstc);
|
||||
|
||||
clk_disable_unprepare(host->clk);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rtk_spi_runtime_resume(struct device *dev)
|
||||
{
|
||||
struct spi_controller *ctlr = dev_get_drvdata(dev);
|
||||
struct rtk_spi_host *host = spi_controller_get_devdata(ctlr);
|
||||
int ret;
|
||||
|
||||
ret = clk_prepare_enable(host->clk);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "clk_prepare_enable failed: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
reset_control_assert(host->rstc);
|
||||
usleep_range(10, 20);
|
||||
reset_control_deassert(host->rstc);
|
||||
|
||||
rtk_spi_init(host);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static DEFINE_RUNTIME_DEV_PM_OPS(rtk_spi_pm_ops, rtk_spi_runtime_suspend,
|
||||
rtk_spi_runtime_resume, NULL);
|
||||
|
||||
static struct platform_driver rtk_spi_driver = {
|
||||
.driver = {
|
||||
.name = "rtk-spi-nor",
|
||||
.of_match_table = rtk_spi_dt_ids,
|
||||
.pm = pm_ptr(&rtk_spi_pm_ops),
|
||||
},
|
||||
.probe = rtk_spi_probe,
|
||||
.remove = rtk_spi_remove,
|
||||
};
|
||||
module_platform_driver(rtk_spi_driver);
|
||||
|
||||
MODULE_DESCRIPTION("Realtek SPI Nor Controller Driver");
|
||||
MODULE_AUTHOR("Jyan Chou <jyanchou@realtek.com>");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
|
@ -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;
|
||||
|
|
@ -1331,7 +1331,8 @@ static int s3c64xx_spi_probe(struct platform_device *pdev)
|
|||
return dev_err_probe(&pdev->dev, PTR_ERR(sdd->clk),
|
||||
"Unable to acquire clock 'spi'\n");
|
||||
|
||||
sprintf(clk_name, "spi_busclk%d", sci->src_clk_nr);
|
||||
snprintf(clk_name, sizeof(clk_name), "spi_busclk%d",
|
||||
sci->src_clk_nr);
|
||||
sdd->src_clk = devm_clk_get_enabled(&pdev->dev, clk_name);
|
||||
if (IS_ERR(sdd->src_clk))
|
||||
return dev_err_probe(&pdev->dev, PTR_ERR(sdd->src_clk),
|
||||
|
|
|
|||
|
|
@ -211,19 +211,11 @@ static int hspi_transfer_one_message(struct spi_controller *ctlr,
|
|||
|
||||
static int hspi_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct resource *res;
|
||||
struct spi_controller *ctlr;
|
||||
struct hspi_priv *hspi;
|
||||
struct clk *clk;
|
||||
int ret;
|
||||
|
||||
/* get base addr */
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
if (!res) {
|
||||
dev_err(&pdev->dev, "invalid resource\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ctlr = devm_spi_alloc_host(&pdev->dev, sizeof(*hspi));
|
||||
if (!ctlr)
|
||||
return -ENOMEM;
|
||||
|
|
@ -241,10 +233,9 @@ static int hspi_probe(struct platform_device *pdev)
|
|||
hspi->ctlr = ctlr;
|
||||
hspi->dev = &pdev->dev;
|
||||
hspi->clk = clk;
|
||||
hspi->addr = devm_ioremap(hspi->dev,
|
||||
res->start, resource_size(res));
|
||||
if (!hspi->addr) {
|
||||
ret = -ENOMEM;
|
||||
hspi->addr = devm_platform_ioremap_resource(pdev, 0);
|
||||
if (IS_ERR(hspi->addr)) {
|
||||
ret = PTR_ERR(hspi->addr);
|
||||
goto error1;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -453,6 +453,7 @@ static int sh_msiof_spi_setup(struct spi_device *spi)
|
|||
struct sh_msiof_spi_priv *p =
|
||||
spi_controller_get_devdata(spi->controller);
|
||||
u32 clr, set, tmp;
|
||||
int ret;
|
||||
|
||||
if (spi_get_csgpiod(spi, 0) || spi_controller_is_target(p->ctlr))
|
||||
return 0;
|
||||
|
|
@ -468,7 +469,9 @@ static int sh_msiof_spi_setup(struct spi_device *spi)
|
|||
clr |= SIMDR1_SYNCAC;
|
||||
else
|
||||
set |= SIMDR1_SYNCAC;
|
||||
pm_runtime_get_sync(&p->pdev->dev);
|
||||
ret = pm_runtime_resume_and_get(&p->pdev->dev);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
tmp = sh_msiof_read(p, SITMDR1) & ~clr;
|
||||
sh_msiof_write(p, SITMDR1, tmp | set | SIMDR1_TRMD | SITMDR1_PCON);
|
||||
tmp = sh_msiof_read(p, SIRMDR1) & ~clr;
|
||||
|
|
@ -671,7 +674,7 @@ static int sh_msiof_dma_once(struct sh_msiof_spi_priv *p, const void *tx,
|
|||
|
||||
if (tx) {
|
||||
ier_bits |= SIIER_TDREQE | SIIER_TDMAE;
|
||||
dma_sync_single_for_device(p->ctlr->dma_tx->device->dev,
|
||||
dma_sync_single_for_device(dmaengine_get_dma_device(p->ctlr->dma_tx),
|
||||
p->tx_dma_addr, len, DMA_TO_DEVICE);
|
||||
desc_tx = dmaengine_prep_slave_single(p->ctlr->dma_tx,
|
||||
p->tx_dma_addr, len, DMA_MEM_TO_DEV,
|
||||
|
|
@ -751,7 +754,7 @@ static int sh_msiof_dma_once(struct sh_msiof_spi_priv *p, const void *tx,
|
|||
}
|
||||
|
||||
if (rx)
|
||||
dma_sync_single_for_cpu(p->ctlr->dma_rx->device->dev,
|
||||
dma_sync_single_for_cpu(dmaengine_get_dma_device(p->ctlr->dma_rx),
|
||||
p->rx_dma_addr, len, DMA_FROM_DEVICE);
|
||||
|
||||
return 0;
|
||||
|
|
@ -1013,6 +1016,7 @@ static const struct of_device_id sh_msiof_match[] __maybe_unused = {
|
|||
{ .compatible = "renesas,msiof-r8a779a0", .data = &rcar_gen3_data },
|
||||
{ .compatible = "renesas,msiof-r8a779f0", .data = &rcar_gen3_data },
|
||||
{ .compatible = "renesas,rcar-gen4-msiof", .data = &rcar_gen4_data },
|
||||
{ .compatible = "renesas,rcar-gen5-msiof", .data = &rcar_gen4_data },
|
||||
{ .compatible = "renesas,sh-msiof", .data = &sh_data }, /* Deprecated */
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
|
|
@ -1138,13 +1142,13 @@ static int sh_msiof_request_dma(struct sh_msiof_spi_priv *p)
|
|||
if (!p->rx_dma_page)
|
||||
goto free_tx_page;
|
||||
|
||||
tx_dev = ctlr->dma_tx->device->dev;
|
||||
tx_dev = dmaengine_get_dma_device(ctlr->dma_tx);
|
||||
p->tx_dma_addr = dma_map_single(tx_dev, p->tx_dma_page, PAGE_SIZE,
|
||||
DMA_TO_DEVICE);
|
||||
if (dma_mapping_error(tx_dev, p->tx_dma_addr))
|
||||
goto free_rx_page;
|
||||
|
||||
rx_dev = ctlr->dma_rx->device->dev;
|
||||
rx_dev = dmaengine_get_dma_device(ctlr->dma_rx);
|
||||
p->rx_dma_addr = dma_map_single(rx_dev, p->rx_dma_page, PAGE_SIZE,
|
||||
DMA_FROM_DEVICE);
|
||||
if (dma_mapping_error(rx_dev, p->rx_dma_addr))
|
||||
|
|
@ -1174,9 +1178,9 @@ static void sh_msiof_release_dma(struct sh_msiof_spi_priv *p)
|
|||
if (!ctlr->dma_tx)
|
||||
return;
|
||||
|
||||
dma_unmap_single(ctlr->dma_rx->device->dev, p->rx_dma_addr, PAGE_SIZE,
|
||||
dma_unmap_single(dmaengine_get_dma_device(ctlr->dma_rx), p->rx_dma_addr, PAGE_SIZE,
|
||||
DMA_FROM_DEVICE);
|
||||
dma_unmap_single(ctlr->dma_tx->device->dev, p->tx_dma_addr, PAGE_SIZE,
|
||||
dma_unmap_single(dmaengine_get_dma_device(ctlr->dma_tx), p->tx_dma_addr, PAGE_SIZE,
|
||||
DMA_TO_DEVICE);
|
||||
free_page((unsigned long)p->rx_dma_page);
|
||||
free_page((unsigned long)p->tx_dma_page);
|
||||
|
|
|
|||
|
|
@ -515,7 +515,7 @@ static int stm32h7_spi_get_bpw_mask(struct stm32_spi *spi)
|
|||
|
||||
/*
|
||||
* The most significant bit at DSIZE bit field is reserved when the
|
||||
* maximum data size of periperal instances is limited to 16-bit
|
||||
* maximum data size of peripheral instances is limited to 16-bit
|
||||
*/
|
||||
stm32_spi_set_bits(spi, STM32H7_SPI_CFG1, STM32H7_SPI_CFG1_DSIZE);
|
||||
|
||||
|
|
@ -2511,6 +2511,7 @@ static int stm32_spi_probe(struct platform_device *pdev)
|
|||
gen_pool_free(spi->sram_pool,
|
||||
(unsigned long)spi->sram_rx_buf,
|
||||
spi->sram_rx_buf_size);
|
||||
spi->sram_rx_buf = NULL;
|
||||
dev_warn(&pdev->dev,
|
||||
"failed to request rx mdma channel, DMA only\n");
|
||||
}
|
||||
|
|
@ -2548,7 +2549,7 @@ static int stm32_spi_probe(struct platform_device *pdev)
|
|||
if (spi->mdma_rx)
|
||||
dma_release_channel(spi->mdma_rx);
|
||||
err_pool_free:
|
||||
if (spi->sram_pool)
|
||||
if (spi->sram_rx_buf)
|
||||
gen_pool_free(spi->sram_pool, (unsigned long)spi->sram_rx_buf,
|
||||
spi->sram_rx_buf_size);
|
||||
if (spi->dma_rx)
|
||||
|
|
|
|||
|
|
@ -82,6 +82,8 @@ struct sun4i_spi {
|
|||
|
||||
struct completion done;
|
||||
|
||||
int irq;
|
||||
|
||||
const u8 *tx_buf;
|
||||
u8 *rx_buf;
|
||||
int len;
|
||||
|
|
@ -142,15 +144,21 @@ static inline void sun4i_spi_drain_fifo(struct sun4i_spi *sspi, int len)
|
|||
}
|
||||
}
|
||||
|
||||
static inline void sun4i_spi_fill_fifo(struct sun4i_spi *sspi, int len)
|
||||
static inline void sun4i_spi_fill_fifo(struct sun4i_spi *sspi)
|
||||
{
|
||||
u32 cnt;
|
||||
int len;
|
||||
u8 byte;
|
||||
|
||||
/* See how much data we can fit */
|
||||
cnt = SUN4I_FIFO_DEPTH - sun4i_spi_get_tx_fifo_count(sspi);
|
||||
/*
|
||||
* See how much data we can fit
|
||||
*
|
||||
* Filling the FIFO fully causes timeout for some reason
|
||||
* at least on spi2 on A10s
|
||||
*/
|
||||
cnt = SUN4I_FIFO_DEPTH - 1 - sun4i_spi_get_tx_fifo_count(sspi);
|
||||
|
||||
len = min3(len, (int)cnt, sspi->len);
|
||||
len = min_t(int, cnt, sspi->len);
|
||||
|
||||
while (len--) {
|
||||
byte = sspi->tx_buf ? *sspi->tx_buf++ : 0;
|
||||
|
|
@ -206,7 +214,7 @@ static int sun4i_spi_transfer_one(struct spi_controller *host,
|
|||
struct spi_transfer *tfr)
|
||||
{
|
||||
struct sun4i_spi *sspi = spi_controller_get_devdata(host);
|
||||
unsigned int mclk_rate, div;
|
||||
unsigned int mclk_rate, div, div_cdr1, div_cdr2;
|
||||
unsigned long time_left;
|
||||
unsigned int start, end, tx_time;
|
||||
unsigned int tx_len = 0;
|
||||
|
|
@ -271,9 +279,17 @@ static int sun4i_spi_transfer_one(struct spi_controller *host,
|
|||
|
||||
/* Ensure that we have a parent clock fast enough */
|
||||
mclk_rate = clk_get_rate(sspi->mclk);
|
||||
if (!mclk_rate)
|
||||
return -EINVAL;
|
||||
|
||||
if (mclk_rate < (2 * tfr->speed_hz)) {
|
||||
clk_set_rate(sspi->mclk, 2 * tfr->speed_hz);
|
||||
ret = clk_set_rate(sspi->mclk, 2 * tfr->speed_hz);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
mclk_rate = clk_get_rate(sspi->mclk);
|
||||
if (!mclk_rate)
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -290,15 +306,15 @@ static int sun4i_spi_transfer_one(struct spi_controller *host,
|
|||
* First try CDR2, and if we can't reach the expected
|
||||
* frequency, fall back to CDR1.
|
||||
*/
|
||||
div = mclk_rate / (2 * tfr->speed_hz);
|
||||
if (div <= (SUN4I_CLK_CTL_CDR2_MASK + 1)) {
|
||||
if (div > 0)
|
||||
div--;
|
||||
|
||||
reg = SUN4I_CLK_CTL_CDR2(div) | SUN4I_CLK_CTL_DRS;
|
||||
div_cdr1 = DIV_ROUND_UP(mclk_rate, tfr->speed_hz);
|
||||
div_cdr2 = DIV_ROUND_UP(div_cdr1, 2);
|
||||
if (div_cdr2 <= (SUN4I_CLK_CTL_CDR2_MASK + 1)) {
|
||||
reg = SUN4I_CLK_CTL_CDR2(div_cdr2 - 1) | SUN4I_CLK_CTL_DRS;
|
||||
tfr->effective_speed_hz = mclk_rate / (2 * div_cdr2);
|
||||
} else {
|
||||
div = ilog2(mclk_rate) - ilog2(tfr->speed_hz);
|
||||
reg = SUN4I_CLK_CTL_CDR1(div);
|
||||
div = min(SUN4I_CLK_CTL_CDR1_MASK + 1, order_base_2(div_cdr1));
|
||||
reg = SUN4I_CLK_CTL_CDR1(div - 1);
|
||||
tfr->effective_speed_hz = mclk_rate / (1 << div);
|
||||
}
|
||||
|
||||
sun4i_spi_write(sspi, SUN4I_CLK_CTL_REG, reg);
|
||||
|
|
@ -311,18 +327,14 @@ static int sun4i_spi_transfer_one(struct spi_controller *host,
|
|||
sun4i_spi_write(sspi, SUN4I_BURST_CNT_REG, SUN4I_BURST_CNT(tfr->len));
|
||||
sun4i_spi_write(sspi, SUN4I_XMIT_CNT_REG, SUN4I_XMIT_CNT(tx_len));
|
||||
|
||||
/*
|
||||
* Fill the TX FIFO
|
||||
* Filling the FIFO fully causes timeout for some reason
|
||||
* at least on spi2 on A10s
|
||||
*/
|
||||
sun4i_spi_fill_fifo(sspi, SUN4I_FIFO_DEPTH - 1);
|
||||
/* Fill the TX FIFO */
|
||||
sun4i_spi_fill_fifo(sspi);
|
||||
|
||||
/* Enable the interrupts */
|
||||
sun4i_spi_enable_interrupt(sspi, SUN4I_INT_CTL_TC |
|
||||
SUN4I_INT_CTL_RF_F34);
|
||||
/* Only enable Tx FIFO interrupt if we really need it */
|
||||
if (tx_len > SUN4I_FIFO_DEPTH)
|
||||
if (tx_len > SUN4I_FIFO_DEPTH - 1)
|
||||
sun4i_spi_enable_interrupt(sspi, SUN4I_INT_CTL_TF_E34);
|
||||
|
||||
/* Start the transfer */
|
||||
|
|
@ -333,6 +345,7 @@ static int sun4i_spi_transfer_one(struct spi_controller *host,
|
|||
start = jiffies;
|
||||
time_left = wait_for_completion_timeout(&sspi->done,
|
||||
msecs_to_jiffies(tx_time));
|
||||
|
||||
end = jiffies;
|
||||
if (!time_left) {
|
||||
dev_warn(&host->dev,
|
||||
|
|
@ -340,12 +353,11 @@ static int sun4i_spi_transfer_one(struct spi_controller *host,
|
|||
dev_name(&spi->dev), tfr->len, tfr->speed_hz,
|
||||
jiffies_to_msecs(end - start), tx_time);
|
||||
ret = -ETIMEDOUT;
|
||||
goto out;
|
||||
sun4i_spi_write(sspi, SUN4I_INT_CTL_REG, 0);
|
||||
synchronize_irq(sspi->irq);
|
||||
}
|
||||
|
||||
|
||||
out:
|
||||
sun4i_spi_write(sspi, SUN4I_INT_CTL_REG, 0);
|
||||
sun4i_spi_drain_fifo(sspi, SUN4I_FIFO_DEPTH);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
|
@ -357,8 +369,7 @@ static irqreturn_t sun4i_spi_handler(int irq, void *dev_id)
|
|||
|
||||
/* Transfer complete */
|
||||
if (status & SUN4I_INT_CTL_TC) {
|
||||
sun4i_spi_write(sspi, SUN4I_INT_STA_REG, SUN4I_INT_CTL_TC);
|
||||
sun4i_spi_drain_fifo(sspi, SUN4I_FIFO_DEPTH);
|
||||
sun4i_spi_write(sspi, SUN4I_INT_CTL_REG, 0);
|
||||
complete(&sspi->done);
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
|
@ -373,7 +384,7 @@ static irqreturn_t sun4i_spi_handler(int irq, void *dev_id)
|
|||
|
||||
/* Transmit FIFO 3/4 empty */
|
||||
if (status & SUN4I_INT_CTL_TF_E34) {
|
||||
sun4i_spi_fill_fifo(sspi, SUN4I_FIFO_DEPTH);
|
||||
sun4i_spi_fill_fifo(sspi);
|
||||
|
||||
if (!sspi->len)
|
||||
/* nothing left to transmit */
|
||||
|
|
@ -456,6 +467,7 @@ static int sun4i_spi_probe(struct platform_device *pdev)
|
|||
return ret;
|
||||
}
|
||||
|
||||
sspi->irq = irq;
|
||||
sspi->host = host;
|
||||
host->max_speed_hz = 100 * 1000 * 1000;
|
||||
host->min_speed_hz = 3 * 1000;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -1308,6 +1308,9 @@ static int tegra_spi_probe(struct platform_device *pdev)
|
|||
struct resource *r;
|
||||
int ret, spi_irq;
|
||||
int bus_num;
|
||||
u32 conflicting_cs;
|
||||
u32 enabled_child_cs_polarity = 0, disabled_child_cs_low = 0, disabled_child_cs_high = 0;
|
||||
u32 enabled_cs_mask = 0;
|
||||
|
||||
host = devm_spi_alloc_host(&pdev->dev, sizeof(*tspi));
|
||||
if (!host) {
|
||||
|
|
@ -1402,6 +1405,52 @@ static int tegra_spi_probe(struct platform_device *pdev)
|
|||
udelay(2);
|
||||
reset_control_deassert(tspi->rst);
|
||||
tspi->def_command1_reg = SPI_M_S;
|
||||
|
||||
/*
|
||||
* SPI_COMMAND1 is written before any child is registered, so a chip
|
||||
* select with CS_POL_INACTIVE clear stays asserted until
|
||||
* tegra_spi_setup() runs for that device. Program the level the
|
||||
* device tree asks for up front instead.
|
||||
*
|
||||
* Disabled children are included deliberately. The core never
|
||||
* registers them, so this is the only chance to park their chip
|
||||
* select at the level their hardware needs. An enabled child on the
|
||||
* same CS takes priority.
|
||||
*/
|
||||
for_each_child_of_node_scoped(pdev->dev.of_node, np) {
|
||||
u32 cs;
|
||||
u32 polarity_bitmask;
|
||||
u32 cs_polarity = 0;
|
||||
|
||||
if (of_property_read_u32(np, "reg", &cs))
|
||||
continue;
|
||||
|
||||
if (cs >= MAX_CHIP_SELECT)
|
||||
continue;
|
||||
|
||||
polarity_bitmask = SPI_CS_POL_INACTIVE(cs);
|
||||
if (!of_property_read_bool(np, "spi-cs-high"))
|
||||
cs_polarity = polarity_bitmask;
|
||||
|
||||
if (of_device_is_available(np)) {
|
||||
enabled_cs_mask |= polarity_bitmask;
|
||||
enabled_child_cs_polarity |= cs_polarity;
|
||||
} else {
|
||||
if (cs_polarity)
|
||||
disabled_child_cs_low |= polarity_bitmask;
|
||||
else
|
||||
disabled_child_cs_high |= polarity_bitmask;
|
||||
}
|
||||
}
|
||||
conflicting_cs = disabled_child_cs_low & disabled_child_cs_high & ~enabled_cs_mask;
|
||||
for (u32 cs = 0; cs < MAX_CHIP_SELECT; cs++) {
|
||||
if (SPI_CS_POL_INACTIVE(cs) & conflicting_cs)
|
||||
dev_warn(&pdev->dev,
|
||||
"Conflicting CS polarity on disabled nodes at cs %u\n", cs);
|
||||
}
|
||||
|
||||
tspi->def_command1_reg |= enabled_child_cs_polarity |
|
||||
(disabled_child_cs_low & ~enabled_cs_mask);
|
||||
tegra_spi_writel(tspi, tspi->def_command1_reg, SPI_COMMAND1);
|
||||
tspi->spi_cs_timing1 = tegra_spi_readl(tspi, SPI_CS_TIMING1);
|
||||
tspi->spi_cs_timing2 = tegra_spi_readl(tspi, SPI_CS_TIMING2);
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
*
|
||||
* Returns: 0 if transfer was completed, -ETIMEDOUT if real timeout
|
||||
* When the bottom half advanced the transfer by only one chunk of a
|
||||
* multi-chunk DMA/PIO transfer without signalling xfer_completion, a
|
||||
* fallback that ran handle_{cpu,dma}_based_xfer() here would race with
|
||||
* the DMA engine already moving the next chunk into the client buffer
|
||||
* (spi_finalize_current_message() would then release the buffer while
|
||||
* the controller is still writing memory). Fake completion is therefore
|
||||
* only attempted when the current chunk is the last chunk of the
|
||||
* transfer; multi-chunk continuation timeouts return -ETIMEDOUT and
|
||||
* let the caller reset the controller.
|
||||
*
|
||||
* Returns: 0 if the transfer completed, -ETIMEDOUT otherwise.
|
||||
*/
|
||||
static int tegra_qspi_handle_timeout(struct tegra_qspi *tqspi)
|
||||
{
|
||||
struct spi_transfer *t;
|
||||
unsigned long flags;
|
||||
bool is_last_chunk;
|
||||
bool lost_irq_snapshot = false;
|
||||
irqreturn_t ret;
|
||||
u32 status;
|
||||
|
||||
/* Check if hardware actually completed the transfer */
|
||||
status = tegra_qspi_readl(tqspi, QSPI_TRANS_STATUS);
|
||||
if (!(status & QSPI_RDY))
|
||||
return -ETIMEDOUT;
|
||||
int retval;
|
||||
u32 status, refreshed;
|
||||
u32 lost_fifo_status = 0;
|
||||
u32 lost_tx_status = 0;
|
||||
u32 lost_rx_status = 0;
|
||||
|
||||
/*
|
||||
* Hardware completed but interrupt was lost/delayed. Manually
|
||||
* process the completion by calling the appropriate handler.
|
||||
* Snapshot both the ISR cache and (if the cache is empty) the
|
||||
* live status registers BEFORE entering recovery. The recovery
|
||||
* path calls tegra_qspi_mask_clear_irq() below, which performs
|
||||
* W1Cs on QSPI_TRANS_STATUS and on the QSPI_FIFO_STATUS error
|
||||
* bits: a lost-IRQ recovery must capture the current FIFO error
|
||||
* state before the mask erases it.
|
||||
*
|
||||
* Cache-live-cache retry: if the initial cache load returns zero
|
||||
* we fall back to a live QSPI_TRANS_STATUS read, and if that also
|
||||
* returns zero we retry the cache once more. That closes the
|
||||
* interleaving where an ISR on another CPU publishes trans_status
|
||||
* with release semantics and then W1Cs the hardware between our
|
||||
* cache load and our live load: the second cache load observes
|
||||
* the now-visible release and we correctly classify the transfer
|
||||
* as complete rather than reporting a false timeout.
|
||||
*
|
||||
* The trans_status cache is reset to zero in
|
||||
* tegra_qspi_start_{cpu,dma}_based_transfer() before unmasking
|
||||
* the IRQ for every chunk, so a stale RDY from the previous
|
||||
* chunk of a multi-chunk transfer cannot survive into this
|
||||
* check.
|
||||
*/
|
||||
status = smp_load_acquire(&tqspi->trans_status);
|
||||
if (!status) {
|
||||
status = tegra_qspi_readl(tqspi, QSPI_TRANS_STATUS);
|
||||
if (!status) {
|
||||
/* Retry cache; pairs with release in ISR post-store. */
|
||||
status = smp_load_acquire(&tqspi->trans_status);
|
||||
} else {
|
||||
/*
|
||||
* Live register shows RDY but the ISR cache is
|
||||
* empty: either the ISR ran and cleared HW between
|
||||
* our two loads (the cache retry above would have
|
||||
* observed it, so we would not be here), or the IRQ
|
||||
* was genuinely lost. Snapshot the live FIFO error
|
||||
* status now so tegra_qspi_mask_clear_irq() below
|
||||
* does not W1C it away before the manual handler
|
||||
* downstream can see it.
|
||||
*/
|
||||
lost_fifo_status = tegra_qspi_readl(tqspi,
|
||||
QSPI_FIFO_STATUS);
|
||||
lost_tx_status = lost_fifo_status &
|
||||
(QSPI_TX_FIFO_UNF | QSPI_TX_FIFO_OVF);
|
||||
lost_rx_status = lost_fifo_status &
|
||||
(QSPI_RX_FIFO_OVF | QSPI_RX_FIFO_UNF);
|
||||
lost_irq_snapshot = true;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Enter recovery unconditionally. Every expired
|
||||
* wait_for_completion_timeout() must serialise against a delayed
|
||||
* ISR or worker before the caller runs dma_stop() +
|
||||
* device_reset() + curr_xfer clear: publishing
|
||||
* recovery_in_progress under tqspi->lock, masking the controller
|
||||
* IRQ, calling synchronize_irq() to drain any in-flight ISR
|
||||
* (including the small-PIO hard-IRQ fastpath), and finally
|
||||
* cancel_work_sync() to drain the workqueue gives us that
|
||||
* serialisation regardless of whether the hardware finished. A
|
||||
* genuine hardware timeout still ends up as -ETIMEDOUT further
|
||||
* down, but only after ISR and workqueue activity are quiesced.
|
||||
*
|
||||
* cancel_work_sync() cancels a pending worker without executing
|
||||
* it and waits for a currently running one to finish; the
|
||||
* recovery_in_progress guard checked inside tegra_qspi_isr()
|
||||
* under tqspi->lock is atomic with its queue_work() and small-PIO
|
||||
* fastpath dispatch decisions, so no new bottom-half work is
|
||||
* enqueued once we publish the flag.
|
||||
*
|
||||
* tegra_qspi_mask_clear_irq() is idempotent: its read-modify-write
|
||||
* of QSPI_INTR_MASK and W1C of QSPI_TRANS_STATUS / FIFO error
|
||||
* status all tolerate a double-write, so it is safe whether or
|
||||
* not the ISR has already run for this transfer.
|
||||
*/
|
||||
spin_lock_irqsave(&tqspi->lock, flags);
|
||||
WRITE_ONCE(tqspi->recovery_in_progress, true);
|
||||
spin_unlock_irqrestore(&tqspi->lock, flags);
|
||||
|
||||
tegra_qspi_mask_clear_irq(tqspi);
|
||||
synchronize_irq(tqspi->irq);
|
||||
cancel_work_sync(&tqspi->irq_work);
|
||||
|
||||
if (try_wait_for_completion(&tqspi->xfer_completion)) {
|
||||
retval = 0;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/*
|
||||
* Re-check the cache after the drain: the worker we just drained
|
||||
* may have published a completion status the entry snapshot did
|
||||
* not observe (for example the ISR fired on another CPU after we
|
||||
* loaded the cache but before we masked).
|
||||
*/
|
||||
refreshed = smp_load_acquire(&tqspi->trans_status);
|
||||
if (refreshed)
|
||||
status = refreshed;
|
||||
|
||||
if (!(status & QSPI_RDY)) {
|
||||
retval = -ETIMEDOUT;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/*
|
||||
* If the ISR never ran (lost IRQ path) publish the FIFO error
|
||||
* snapshot we captured before mask_clear_irq() so the manual
|
||||
* handler downstream has fresh error state rather than stale
|
||||
* fields from a previous chunk's ISR.
|
||||
*/
|
||||
if (lost_irq_snapshot) {
|
||||
WRITE_ONCE(tqspi->status_reg, lost_fifo_status);
|
||||
WRITE_ONCE(tqspi->tx_status, lost_tx_status);
|
||||
WRITE_ONCE(tqspi->rx_status, lost_rx_status);
|
||||
}
|
||||
|
||||
/*
|
||||
* The bottom half did not signal full completion. Either the work
|
||||
* ran and advanced the transfer by one chunk (possibly arming the
|
||||
* next chunk of a multi-chunk transfer), or it was cancelled
|
||||
* before it could run, or the current chunk really did not
|
||||
* complete. Only fake completion when the current chunk is the
|
||||
* last chunk of the transfer; otherwise the DMA engine may still
|
||||
* be moving the next chunk into memory, and returning 0 here would
|
||||
* let seq_xfer clear curr_xfer and finalise the message while the
|
||||
* hardware is still writing.
|
||||
*
|
||||
* The last-chunk arithmetic mirrors tegra_qspi_start_cpu_based_
|
||||
* transfer(), which uses cur_pos + curr_dma_words * bytes_per_word
|
||||
* >= t->len to set is_last_pio_chunk before arming the IRQ.
|
||||
*/
|
||||
spin_lock_irqsave(&tqspi->lock, flags);
|
||||
t = tqspi->curr_xfer;
|
||||
if (!t) {
|
||||
/* CPU-path handler already cleared curr_xfer */
|
||||
spin_unlock_irqrestore(&tqspi->lock, flags);
|
||||
retval = 0;
|
||||
goto out;
|
||||
}
|
||||
is_last_chunk = (tqspi->cur_pos +
|
||||
tqspi->curr_dma_words * tqspi->bytes_per_word) >= t->len;
|
||||
spin_unlock_irqrestore(&tqspi->lock, flags);
|
||||
|
||||
if (!is_last_chunk) {
|
||||
retval = -ETIMEDOUT;
|
||||
goto out;
|
||||
}
|
||||
|
||||
dev_warn_ratelimited(tqspi->dev,
|
||||
"QSPI interrupt timeout, but transfer complete\n");
|
||||
|
||||
/* Clear the transfer status */
|
||||
status = tegra_qspi_readl(tqspi, QSPI_TRANS_STATUS);
|
||||
tegra_qspi_writel(tqspi, status, QSPI_TRANS_STATUS);
|
||||
|
||||
/* Manually trigger completion handler */
|
||||
if (!tqspi->is_curr_dma_xfer)
|
||||
if (!READ_ONCE(tqspi->is_curr_dma_xfer))
|
||||
ret = handle_cpu_based_xfer(tqspi);
|
||||
else
|
||||
ret = handle_dma_based_xfer(tqspi);
|
||||
|
||||
return (ret == IRQ_HANDLED) ? 0 : -EIO;
|
||||
retval = (ret == IRQ_HANDLED) ? 0 : -EIO;
|
||||
|
||||
out:
|
||||
/*
|
||||
* The drained bottom half may have unmasked the controller IRQ
|
||||
* to arm the next chunk of a multi-chunk transfer. Re-mask and
|
||||
* synchronize before clearing recovery_in_progress so that no
|
||||
* lingering ISR can queue fresh work behind the caller's back
|
||||
* (the caller's dma_stop() + device_reset() + curr_xfer clear
|
||||
* runs immediately after we return on the error path).
|
||||
*/
|
||||
tegra_qspi_mask_clear_irq(tqspi);
|
||||
synchronize_irq(tqspi->irq);
|
||||
WRITE_ONCE(tqspi->recovery_in_progress, false);
|
||||
return retval;
|
||||
}
|
||||
|
||||
static u32 tegra_qspi_cmd_config(bool is_ddr, u8 bus_width, u8 len)
|
||||
|
|
@ -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 @@ static irqreturn_t handle_dma_based_xfer(struct tegra_qspi *tqspi)
|
|||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static irqreturn_t tegra_qspi_isr_thread(int irq, void *context_data)
|
||||
/**
|
||||
* tegra_qspi_work_handler - Workqueue handler for interrupt bottom-half
|
||||
* @work: work_struct embedded in tegra_qspi
|
||||
*
|
||||
* Runs in process context and can sleep (needed for DMA completion waits).
|
||||
* Runs on any CPU in the WQ_UNBOUND pool, so the bottom half can migrate off
|
||||
* the interrupt-taking CPU that the previous threaded IRQ pinned to
|
||||
* (irq_thread() calls set_cpus_allowed_ptr() with the IRQ affinity mask).
|
||||
*
|
||||
* The hard IRQ handler has already:
|
||||
* - Verified this is our interrupt (QSPI_RDY was set)
|
||||
* - Cached FIFO status in tqspi->status_reg
|
||||
* - Parsed tx_status / rx_status from FIFO status
|
||||
* - Masked further interrupts
|
||||
*/
|
||||
static void tegra_qspi_work_handler(struct work_struct *work)
|
||||
{
|
||||
struct tegra_qspi *tqspi = context_data;
|
||||
struct tegra_qspi *tqspi = container_of(work, struct tegra_qspi, irq_work);
|
||||
unsigned long flags;
|
||||
u32 status;
|
||||
|
||||
/*
|
||||
* Read transfer status to check if interrupt was triggered by transfer
|
||||
* completion
|
||||
*/
|
||||
status = tegra_qspi_readl(tqspi, QSPI_TRANS_STATUS);
|
||||
|
||||
/*
|
||||
* Occasionally the IRQ thread takes a long time to wake up (usually
|
||||
* when the CPU that it's running on is excessively busy) and we have
|
||||
* already reached the timeout before and cleaned up the timed out
|
||||
* transfer. Avoid any processing in that case and bail out early.
|
||||
*
|
||||
* If no transfer is in progress, check if this was a real interrupt
|
||||
* that the timeout handler already processed, or a spurious one.
|
||||
*/
|
||||
spin_lock_irqsave(&tqspi->lock, flags);
|
||||
|
||||
/*
|
||||
* tegra_qspi_handle_timeout() sets recovery_in_progress under
|
||||
* tqspi->lock and then calls cancel_work_sync(), so any running
|
||||
* worker is drained and tegra_qspi_isr() cannot enqueue a new
|
||||
* one while recovery runs. The curr_xfer NULL check catches the
|
||||
* case where the timeout path already tore the transfer down
|
||||
* before this work got a chance to run.
|
||||
*/
|
||||
if (!tqspi->curr_xfer) {
|
||||
spin_unlock_irqrestore(&tqspi->lock, flags);
|
||||
/* 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)
|
||||
return handle_cpu_based_xfer(tqspi);
|
||||
if (!READ_ONCE(tqspi->is_curr_dma_xfer))
|
||||
handle_cpu_based_xfer(tqspi);
|
||||
else
|
||||
handle_dma_based_xfer(tqspi);
|
||||
}
|
||||
|
||||
return handle_dma_based_xfer(tqspi);
|
||||
/**
|
||||
* tegra_qspi_isr - Hard IRQ handler
|
||||
* @irq: IRQ number
|
||||
* @context_data: QSPI controller instance
|
||||
*
|
||||
* Runs in hard IRQ context with minimal latency. Cannot sleep.
|
||||
*
|
||||
* Tegra QSPI uses a dedicated, non-shared GIC SPI line on every SoC that
|
||||
* uses this driver. The handler always returns IRQ_HANDLED and always
|
||||
* acknowledges/re-masks the controller IRQ, so the level-triggered line
|
||||
* cannot stay asserted and trip the kernel spurious-IRQ detector into
|
||||
* disabling the line. On a stray IRQ where curr_xfer is NULL (e.g. the
|
||||
* timeout path has already torn the transfer down) the FIFO/status
|
||||
* processing and bottom-half scheduling are skipped because there is no
|
||||
* transfer to drive forward.
|
||||
*
|
||||
* Return: IRQ_HANDLED.
|
||||
*/
|
||||
static irqreturn_t tegra_qspi_isr(int irq, void *context_data)
|
||||
{
|
||||
struct tegra_qspi *tqspi = context_data;
|
||||
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);
|
||||
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -454,7 +454,7 @@ static int ti_qspi_dma_xfer(struct ti_qspi *qspi, dma_addr_t dma_dst,
|
|||
|
||||
tx->callback = ti_qspi_dma_callback;
|
||||
tx->callback_param = qspi;
|
||||
cookie = tx->tx_submit(tx);
|
||||
cookie = dmaengine_submit(tx);
|
||||
reinit_completion(&qspi->transfer_complete);
|
||||
|
||||
ret = dma_submit_error(cookie);
|
||||
|
|
|
|||
|
|
@ -151,10 +151,10 @@ static ssize_t tle62x0_gpio_store(struct device *dev,
|
|||
struct tle62x0_state *st = dev_get_drvdata(dev);
|
||||
int gpio_num = to_gpio_num(attr);
|
||||
unsigned long val;
|
||||
char *endp;
|
||||
int ret;
|
||||
|
||||
val = simple_strtoul(buf, &endp, 0);
|
||||
if (buf == endp)
|
||||
ret = kstrtoul(buf, 0, &val);
|
||||
if (ret)
|
||||
return -EINVAL;
|
||||
|
||||
dev_dbg(dev, "setting gpio %d to %ld\n", gpio_num, val);
|
||||
|
|
|
|||
|
|
@ -1092,8 +1092,8 @@ static void pch_spi_handle_dma(struct pch_spi_data *data, int *bpw)
|
|||
|
||||
spin_lock_irqsave(&data->lock, flags);
|
||||
pch_spi_writereg(data->host, PCH_SSNXCR, SSN_LOW);
|
||||
desc_rx->tx_submit(desc_rx);
|
||||
desc_tx->tx_submit(desc_tx);
|
||||
dmaengine_submit(desc_rx);
|
||||
dmaengine_submit(desc_tx);
|
||||
spin_unlock_irqrestore(&data->lock, flags);
|
||||
|
||||
/* reset transfer complete flag */
|
||||
|
|
|
|||
|
|
@ -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 @@ static int virtio_spi_transfer_one(struct spi_controller *ctrl,
|
|||
static void virtio_spi_read_config(struct virtio_device *vdev)
|
||||
{
|
||||
struct spi_controller *ctrl = dev_get_drvdata(&vdev->dev);
|
||||
struct virtio_spi_priv *priv = vdev->priv;
|
||||
u32 mode_func_supported;
|
||||
u8 cs_max_number;
|
||||
u8 tx_nbits_supported;
|
||||
u8 rx_nbits_supported;
|
||||
|
|
@ -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));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -929,6 +929,15 @@ static int __spi_add_device(struct spi_device *spi, struct spi_device *parent)
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Peripheral properties the core handles on behalf of controller
|
||||
* drivers are parsed here, rather than in the firmware specific
|
||||
* instantiation paths, so that device tree, ACPI and software nodes
|
||||
* are covered alike, and early enough for ->setup() to act on them.
|
||||
*/
|
||||
device_property_read_u32(&spi->dev, "rx-sample-delay-ns",
|
||||
&spi->rx_sample_delay_ns);
|
||||
|
||||
/*
|
||||
* Drivers may modify this initial i/o setup, but will
|
||||
* normally rely on the device being setup. Devices
|
||||
|
|
@ -1456,14 +1465,14 @@ int __spi_map_msg(struct spi_controller *ctlr, struct spi_message *msg)
|
|||
return 0;
|
||||
|
||||
if (ctlr->dma_tx)
|
||||
tx_dev = ctlr->dma_tx->device->dev;
|
||||
tx_dev = dmaengine_get_dma_device(ctlr->dma_tx);
|
||||
else if (ctlr->dma_map_dev)
|
||||
tx_dev = ctlr->dma_map_dev;
|
||||
else
|
||||
tx_dev = ctlr->dev.parent;
|
||||
|
||||
if (ctlr->dma_rx)
|
||||
rx_dev = ctlr->dma_rx->device->dev;
|
||||
rx_dev = dmaengine_get_dma_device(ctlr->dma_rx);
|
||||
else if (ctlr->dma_map_dev)
|
||||
rx_dev = ctlr->dma_map_dev;
|
||||
else
|
||||
|
|
@ -2893,7 +2902,7 @@ static void of_register_spi_devices(struct spi_controller *ctlr) { }
|
|||
*
|
||||
* This may only be called from main SPI device's probe routine.
|
||||
*
|
||||
* Return: 0 on success; negative errno on failure
|
||||
* Return: the new device on success; an ERR_PTR() on failure
|
||||
*/
|
||||
struct spi_device *spi_new_ancillary_device(struct spi_device *spi,
|
||||
u8 chip_select)
|
||||
|
|
@ -2983,6 +2992,7 @@ EXPORT_SYMBOL_GPL(devm_spi_new_ancillary_device);
|
|||
#ifdef CONFIG_ACPI
|
||||
struct acpi_spi_lookup {
|
||||
struct spi_controller *ctlr;
|
||||
acpi_handle device_handle;
|
||||
u32 max_speed_hz;
|
||||
u32 mode;
|
||||
int irq;
|
||||
|
|
@ -3077,7 +3087,7 @@ static int acpi_spi_add_resource(struct acpi_resource *ares, void *data)
|
|||
if (lookup->index != -1 && lookup->n++ != lookup->index)
|
||||
return 1;
|
||||
|
||||
status = acpi_get_handle(NULL,
|
||||
status = acpi_get_handle(lookup->device_handle,
|
||||
sb->resource_source.string_ptr,
|
||||
&parent_handle);
|
||||
|
||||
|
|
@ -3169,6 +3179,7 @@ struct spi_device *acpi_spi_device_alloc(struct spi_controller *ctlr,
|
|||
return ERR_PTR(-EINVAL);
|
||||
|
||||
lookup.ctlr = ctlr;
|
||||
lookup.device_handle = acpi_device_handle(adev);
|
||||
lookup.irq = -1;
|
||||
lookup.index = index;
|
||||
lookup.n = 0;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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++;
|
||||
|
||||
src += 4;
|
||||
*dst++ = (unsigned char)ch;
|
||||
} else {
|
||||
*dst++ = *src++;
|
||||
if (src[0] == 'x' &&
|
||||
isxdigit((unsigned char)src[1]) &&
|
||||
isxdigit((unsigned char)src[2]) &&
|
||||
sscanf(&src[1], "%2x", &ch)) {
|
||||
*out++ = (unsigned char)ch;
|
||||
src += 3;
|
||||
continue;
|
||||
}
|
||||
|
||||
*out++ = '\\';
|
||||
if (!size--)
|
||||
break;
|
||||
}
|
||||
ret++;
|
||||
*out++ = *src++;
|
||||
}
|
||||
return ret;
|
||||
return out - dst;
|
||||
}
|
||||
|
||||
static void transfer(int fd, uint8_t const *tx, uint8_t const *rx, size_t len)
|
||||
static void transfer(int fd, uint8_t const * const tx, uint8_t const * const rx, size_t len)
|
||||
{
|
||||
int ret;
|
||||
int out_fd;
|
||||
struct spi_ioc_transfer tr = {
|
||||
.tx_buf = (unsigned long)tx,
|
||||
.rx_buf = (unsigned long)rx,
|
||||
.len = len,
|
||||
.delay_usecs = delay,
|
||||
.word_delay_usecs = word_delay,
|
||||
.speed_hz = speed,
|
||||
.bits_per_word = bits,
|
||||
};
|
||||
size_t bytes_per_word = DIV_ROUND_UP(bits, 8);
|
||||
int effective_transfers;
|
||||
struct spi_ioc_transfer *tr;
|
||||
size_t len_per_transfer;
|
||||
const uint8_t *tx_buf = tx;
|
||||
const uint8_t *rx_buf = rx;
|
||||
size_t rem;
|
||||
|
||||
if (mode & SPI_TX_OCTAL)
|
||||
tr.tx_nbits = 8;
|
||||
else if (mode & SPI_TX_QUAD)
|
||||
tr.tx_nbits = 4;
|
||||
else if (mode & SPI_TX_DUAL)
|
||||
tr.tx_nbits = 2;
|
||||
if (mode & SPI_RX_OCTAL)
|
||||
tr.rx_nbits = 8;
|
||||
else if (mode & SPI_RX_QUAD)
|
||||
tr.rx_nbits = 4;
|
||||
else if (mode & SPI_RX_DUAL)
|
||||
tr.rx_nbits = 2;
|
||||
if (!(mode & SPI_LOOP)) {
|
||||
if (mode & (SPI_TX_OCTAL | SPI_TX_QUAD | SPI_TX_DUAL))
|
||||
tr.rx_buf = 0;
|
||||
else if (mode & (SPI_RX_OCTAL | SPI_RX_QUAD | SPI_RX_DUAL))
|
||||
tr.tx_buf = 0;
|
||||
if (len) {
|
||||
effective_transfers = min_t(size_t, transfers, DIV_ROUND_UP(len, bytes_per_word));
|
||||
len_per_transfer = roundup(DIV_ROUND_UP(len, effective_transfers), bytes_per_word);
|
||||
} else {
|
||||
/* zero-length transfers may be handled by the driver, deliver as ordered */
|
||||
effective_transfers = transfers;
|
||||
len_per_transfer = 0;
|
||||
}
|
||||
|
||||
ret = ioctl(fd, SPI_IOC_MESSAGE(1), &tr);
|
||||
if (ret < 1)
|
||||
pabort("can't send spi message");
|
||||
if (SPI_MSGSIZE(effective_transfers) == 0)
|
||||
pabort("too many transfers, SPI_IOC_MESSAGE size limit exceeded");
|
||||
|
||||
if (verbose)
|
||||
tr = calloc(effective_transfers, sizeof(*tr));
|
||||
if (!tr)
|
||||
pabort("can't allocate transfer array");
|
||||
|
||||
rem = len;
|
||||
|
||||
for (int i = 0; i < effective_transfers; i++) {
|
||||
size_t n = min(rem, len_per_transfer);
|
||||
|
||||
tr[i] = (struct spi_ioc_transfer) {
|
||||
.tx_buf = (unsigned long)tx_buf,
|
||||
.rx_buf = (unsigned long)rx_buf,
|
||||
.len = n,
|
||||
.delay_usecs = delay,
|
||||
.word_delay_usecs = word_delay,
|
||||
.speed_hz = speed,
|
||||
.bits_per_word = bits,
|
||||
};
|
||||
|
||||
if (tx_buf)
|
||||
tx_buf += n;
|
||||
|
||||
if (rx_buf)
|
||||
rx_buf += n;
|
||||
|
||||
rem -= n;
|
||||
|
||||
if (mode & SPI_TX_OCTAL)
|
||||
tr[i].tx_nbits = 8;
|
||||
else if (mode & SPI_TX_QUAD)
|
||||
tr[i].tx_nbits = 4;
|
||||
else if (mode & SPI_TX_DUAL)
|
||||
tr[i].tx_nbits = 2;
|
||||
if (mode & SPI_RX_OCTAL)
|
||||
tr[i].rx_nbits = 8;
|
||||
else if (mode & SPI_RX_QUAD)
|
||||
tr[i].rx_nbits = 4;
|
||||
else if (mode & SPI_RX_DUAL)
|
||||
tr[i].rx_nbits = 2;
|
||||
if (!(mode & SPI_LOOP)) {
|
||||
if (mode & (SPI_TX_OCTAL | SPI_TX_QUAD | SPI_TX_DUAL))
|
||||
tr[i].rx_buf = 0;
|
||||
else if (mode & (SPI_RX_OCTAL | SPI_RX_QUAD | SPI_RX_DUAL))
|
||||
tr[i].tx_buf = 0;
|
||||
}
|
||||
}
|
||||
|
||||
ret = ioctl(fd, SPI_IOC_MESSAGE(effective_transfers), tr);
|
||||
free(tr);
|
||||
if (ret < 0) {
|
||||
const size_t dump_len = min_t(size_t, len, 256);
|
||||
int saved_errno = errno;
|
||||
|
||||
if (tx) {
|
||||
hex_dump(tx, dump_len, 32, "TX");
|
||||
if (len > dump_len)
|
||||
printf("... (%zu more bytes)\n", len - dump_len);
|
||||
}
|
||||
errno = saved_errno;
|
||||
pabort("can't send spi message");
|
||||
}
|
||||
|
||||
if (verbose && tx)
|
||||
hex_dump(tx, len, 32, "TX");
|
||||
|
||||
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,34 +520,41 @@ 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;
|
||||
|
||||
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 (nonzero)
|
||||
v = ((v % 254) + 254) % 254 + 1;
|
||||
tx[i] = (char)v;
|
||||
} else {
|
||||
do {
|
||||
tx[i] = random();
|
||||
} while (nonzero && (tx[i] == 0x0 || tx[i] == 0xff));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (do_rx) {
|
||||
rx = malloc(len);
|
||||
if (!rx)
|
||||
pabort("can't allocate rx buffer");
|
||||
}
|
||||
|
||||
transfer(fd, tx, rx, len);
|
||||
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);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user