spi: Updates for v6.15

The biggest change for SPI this release is the addition of offload
 support from David Lechner, allowing the hardware to trigger SPI
 transactions autonomously.  The initial use case is for triggering IIO
 operations but there are other applications where having the hardware
 ready to go at a minimal signal is useful for synchronising with
 external inputs (eg, interrupt handling) or reducing latency (eg, CAN
 networking).  Otherwise there's the usual fixes, improvements and
 cleanups, plus support for a bunch of new devices.
 
  - Support for offloaing support from David Lechner.
  - Support for GOcontrol1 Moduline modules, Mediatek MT7988, NXP i.MX94,
    Qualcomm SPI NAMD, Rockchip RK3562, Sophogo SG2044 and ST STM32 OSPI.
 -----BEGIN PGP SIGNATURE-----
 
 iQEzBAABCgAdFiEEreZoqmdXGLWf4p/qJNaLcl1Uh9AFAmfhbQgACgkQJNaLcl1U
 h9DQrAf6A30I4GJGwD4qWwCdsChxr0of8r3q0zIAcC53UaMqLBTdwnQT+wD4MTe8
 vnUY54Z8PYoDKaPx54YxmWh0zB4OQgxvtcJyaZ9biLWtPy3kZdhHlC5xrVQbzLc5
 ki/AW2Fsl2GQ1+5mtI3yP6awQ5HQ17gXS1q+nl2/D1njv2sI1Z1998nBkmsaNwNb
 SPmaDV5UIQkO9wENnJCbX7bRlM+FgkgYo10JFffmWCnntsaZ01NlUC+BBg9y9xAy
 padE2ucWWDqdmiHOC1z7kJU5MFGZWGSSVATLZZz3GL+vw/Oan/Mb5K2zoZh0IcD9
 QPJxNnokYtYFR/IlH/obL0EMutaoSQ==
 =vqZa
 -----END PGP SIGNATURE-----

Merge tag 'spi-v6.15' of git://git.kernel.org/pub/scm/linux/kernel/git/broonie/spi

Pull spi updates from Mark Brown:
 "The biggest change for SPI this release is the addition of offload
  support from David Lechner, allowing the hardware to trigger SPI
  transactions autonomously. The initial use case is for triggering IIO
  operations but there are other applications where having the hardware
  ready to go at a minimal signal is useful for synchronising with
  external inputs (eg, interrupt handling) or reducing latency (eg, CAN
  networking).

  Otherwise there's the usual fixes, improvements and cleanups, plus
  support for a bunch of new devices.

   - Support for offloading support from David Lechner

   - Support for GOcontrol1 Moduline modules, Mediatek MT7988, NXP
     i.MX94, Qualcomm SPI NAMD, Rockchip RK3562, Sophogo SG2044 and ST
     STM32 OSPI"

* tag 'spi-v6.15' of git://git.kernel.org/pub/scm/linux/kernel/git/broonie/spi: (59 commits)
  spi: spi-mem: Introduce a default ->exec_op() debug log
  spi: dt-bindings: cdns,qspi-nor: Require some peripheral properties
  spi: dt-bindings: cdns,qspi-nor: Deprecate the Cadence compatible alone
  spi: dt-bindings: cdns,qspi-nor: Be more descriptive regarding what this controller is
  spi: stm32-ospi: Include "gpio/consumer.h"
  MAINTAINERS: adjust the file entry in GOCONTROLL MODULINE MODULE SLOT
  spi: spi-qpic-snand: avoid memleak in qcom_spi_ecc_init_ctx_pipelined()
  spi: spi-mux: Fix coverity issue, unchecked return value
  spi: sophgo: fix incorrect type for ret in sg2044_spifmc_write()
  spi: sg2044-nor: fix uninitialized variable in probe
  spi: sg2044-nor: fix signedness bug in sg2044_spifmc_write()
  spi: sg2044-nor: Convert to dev_err_probe()
  spi: sg2044-nor: Fully convert to device managed resources
  dt-bindings: spi: add compatibles for mt7988
  spi: spidev: Add an entry for the gocontroll moduline module slot
  MAINTAINERS: add maintainer for the GOcontroll Moduline module slot
  dt-bindings: connector: Add the GOcontroll Moduline module slot bindings
  dt-bindings: vendor-prefixes: add GOcontroll
  spi: Use inclusive language
  spi: cadence-qspi: Improve spi memory performance
  ...
This commit is contained in:
Linus Torvalds 2025-03-25 19:28:14 -07:00
commit 47618bc875
47 changed files with 5188 additions and 307 deletions

View file

@ -0,0 +1,88 @@
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/connector/gocontroll,moduline-module-slot.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: GOcontroll Moduline Module slot
maintainers:
- Maud Spierings <maudspierings@gocontroll.com>
description:
The GOcontroll Moduline module slot represents a connector that fullfills the
Moduline slot specification, and can thus house any IO module that is also
built to this spec.
properties:
compatible:
const: gocontroll,moduline-module-slot
reg:
maxItems: 1
interrupts:
description: indicates readiness, high means busy.
maxItems: 1
reset-gpios:
description: resets the module, active low.
maxItems: 1
sync-gpios:
description: sync line between all module slots.
maxItems: 1
vdd-supply:
description: low power 3v3 supply generally for the microcontroller.
vddp-supply:
description: medium power 5v0 supply for on module low power peripherals.
vddhpp-supply:
description: high power 6v-8v supply for on module high power peripherals.
power-supply:
description: high power 6v-30v supply for high power module circuits.
i2c-bus:
description: i2c bus shared between module slots and the SoC
$ref: /schemas/types.yaml#/definitions/phandle
slot-number:
description:
The number of the module slot representing the location of on the pcb.
This enables access to the modules based on slot location.
$ref: /schemas/types.yaml#/definitions/uint32
spi-max-frequency: true
required:
- compatible
- reg
- reset-gpios
- interrupts
- sync-gpios
- i2c-bus
- slot-number
additionalProperties: false
examples:
- |
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/interrupt-controller/irq.h>
spi {
#address-cells = <1>;
#size-cells = <0>;
connector@0 {
reg = <0>;
compatible = "gocontroll,moduline-module-slot";
reset-gpios = <&gpio5 10 GPIO_ACTIVE_LOW>;
sync-gpios = <&gpio4 16 GPIO_ACTIVE_HIGH>;
interrupt-parent = <&gpio4>;
interrupts = <5 IRQ_TYPE_EDGE_FALLING>;
vdd-supply = <&reg_3v3_per>;
vddp-supply = <&reg_5v0>;
vddhpp-supply = <&reg_6v4>;
i2c-bus = <&i2c2>;
slot-number = <1>;
};
};

View file

@ -41,6 +41,26 @@ properties:
- const: s_axi_aclk
- const: spi_clk
trigger-sources:
description:
An array of trigger source phandles for offload instances. The index in
the array corresponds to the offload instance number.
minItems: 1
maxItems: 32
dmas:
description:
DMA channels connected to the input or output stream interface of an
offload instance.
minItems: 1
maxItems: 32
dma-names:
items:
pattern: "^offload(?:[12]?[0-9]|3[01])-[tr]x$"
minItems: 1
maxItems: 32
required:
- compatible
- reg
@ -59,6 +79,10 @@ examples:
clocks = <&clkc 15>, <&clkc 15>;
clock-names = "s_axi_aclk", "spi_clk";
trigger-sources = <&trigger_clock>;
dmas = <&dma 0>;
dma-names = "offload0-rx";
#address-cells = <1>;
#size-cells = <0>;

View file

@ -4,7 +4,7 @@
$id: http://devicetree.org/schemas/spi/cdns,qspi-nor.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Cadence Quad SPI controller
title: Cadence Quad/Octal SPI controller
maintainers:
- Vaishnav Achath <vaishnav.a@ti.com>
@ -76,8 +76,12 @@ properties:
- ti,am654-ospi
- ti,k2g-qspi
- xlnx,versal-ospi-1.0
# The compatible is qspi-nor for historical reasons but such
# controllers are meant to be used with flashes of all kinds,
# ie. also NAND flashes, not only NOR flashes.
- const: cdns,qspi-nor
- const: cdns,qspi-nor
deprecated: true
reg:
items:
@ -142,6 +146,18 @@ properties:
items:
enum: [ qspi, qspi-ocp, rstc_ref ]
patternProperties:
"^flash@[0-9a-f]+$":
type: object
$ref: cdns,qspi-nor-peripheral-props.yaml
additionalProperties: true
required:
- cdns,read-delay
- cdns,tshsl-ns
- cdns,tsd2d-ns
- cdns,tchsh-ns
- cdns,tslch-ns
required:
- compatible
- reg
@ -157,7 +173,7 @@ unevaluatedProperties: false
examples:
- |
qspi: spi@ff705000 {
compatible = "cdns,qspi-nor";
compatible = "intel,socfpga-qspi", "cdns,qspi-nor";
#address-cells = <1>;
#size-cells = <0>;
reg = <0xff705000 0x1000>,
@ -173,5 +189,10 @@ examples:
flash@0 {
compatible = "jedec,spi-nor";
reg = <0x0>;
cdns,read-delay = <4>;
cdns,tshsl-ns = <60>;
cdns,tsd2d-ns = <60>;
cdns,tchsh-ns = <60>;
cdns,tslch-ns = <60>;
};
};

View file

@ -0,0 +1,65 @@
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/spi/fsl,espi.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Freescale eSPI (Enhanced Serial Peripheral Interface) controller
maintainers:
- J. Neuschäfer <j.ne@posteo.net>
properties:
compatible:
const: fsl,mpc8536-espi
reg:
maxItems: 1
interrupts:
maxItems: 1
fsl,espi-num-chipselects:
$ref: /schemas/types.yaml#/definitions/uint32
enum: [ 1, 4 ]
description: The number of the chipselect signals.
fsl,csbef:
$ref: /schemas/types.yaml#/definitions/uint32
minimum: 0
maximum: 15
description: Chip select assertion time in bits before frame starts
fsl,csaft:
$ref: /schemas/types.yaml#/definitions/uint32
minimum: 0
maximum: 15
description: Chip select negation time in bits after frame ends
required:
- compatible
- reg
- interrupts
- fsl,espi-num-chipselects
allOf:
- $ref: spi-controller.yaml#
unevaluatedProperties: false
examples:
- |
#include <dt-bindings/interrupt-controller/irq.h>
spi@110000 {
compatible = "fsl,mpc8536-espi";
reg = <0x110000 0x1000>;
#address-cells = <1>;
#size-cells = <0>;
interrupts = <53 IRQ_TYPE_EDGE_FALLING>;
fsl,espi-num-chipselects = <4>;
fsl,csbef = <1>;
fsl,csaft = <1>;
};
...

View file

@ -0,0 +1,74 @@
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/spi/fsl,spi.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Freescale SPI (Serial Peripheral Interface) controller
maintainers:
- J. Neuschäfer <j.ne@posteo.net>
properties:
compatible:
enum:
- fsl,spi
- aeroflexgaisler,spictrl
reg:
maxItems: 1
cell-index:
$ref: /schemas/types.yaml#/definitions/uint32
description: |
QE SPI subblock index.
0: QE subblock SPI1
1: QE subblock SPI2
mode:
description: SPI operation mode
enum:
- cpu
- cpu-qe
interrupts:
maxItems: 1
clock-frequency:
description: input clock frequency to non FSL_SOC cores
cs-gpios: true
fsl,spisel_boot:
$ref: /schemas/types.yaml#/definitions/flag
description:
For the MPC8306 and MPC8309, specifies that the SPISEL_BOOT signal is used
as chip select for a slave device. Use reg = <number of gpios> in the
corresponding child node, i.e. 0 if the cs-gpios property is not present.
required:
- compatible
- reg
- mode
- interrupts
allOf:
- $ref: spi-controller.yaml#
unevaluatedProperties: false
examples:
- |
#include <dt-bindings/interrupt-controller/irq.h>
spi@4c0 {
compatible = "fsl,spi";
reg = <0x4c0 0x40>;
cell-index = <0>;
interrupts = <82 0>;
mode = "cpu";
cs-gpios = <&gpio 18 IRQ_TYPE_EDGE_RISING // device reg=<0>
&gpio 19 IRQ_TYPE_EDGE_RISING>; // device reg=<1>
};
...

View file

@ -1,62 +0,0 @@
* SPI (Serial Peripheral Interface)
Required properties:
- cell-index : QE SPI subblock index.
0: QE subblock SPI1
1: QE subblock SPI2
- compatible : should be "fsl,spi" or "aeroflexgaisler,spictrl".
- mode : the SPI operation mode, it can be "cpu" or "cpu-qe".
- reg : Offset and length of the register set for the device
- interrupts : <a b> where a is the interrupt number and b is a
field that represents an encoding of the sense and level
information for the interrupt. This should be encoded based on
the information in section 2) depending on the type of interrupt
controller you have.
- clock-frequency : input clock frequency to non FSL_SOC cores
Optional properties:
- cs-gpios : specifies the gpio pins to be used for chipselects.
The gpios will be referred to as reg = <index> in the SPI child nodes.
If unspecified, a single SPI device without a chip select can be used.
- fsl,spisel_boot : for the MPC8306 and MPC8309, specifies that the
SPISEL_BOOT signal is used as chip select for a slave device. Use
reg = <number of gpios> in the corresponding child node, i.e. 0 if
the cs-gpios property is not present.
Example:
spi@4c0 {
cell-index = <0>;
compatible = "fsl,spi";
reg = <4c0 40>;
interrupts = <82 0>;
interrupt-parent = <700>;
mode = "cpu";
cs-gpios = <&gpio 18 1 // device reg=<0>
&gpio 19 1>; // device reg=<1>
};
* eSPI (Enhanced Serial Peripheral Interface)
Required properties:
- compatible : should be "fsl,mpc8536-espi".
- reg : Offset and length of the register set for the device.
- interrupts : should contain eSPI interrupt, the device has one interrupt.
- fsl,espi-num-chipselects : the number of the chipselect signals.
Optional properties:
- fsl,csbef: chip select assertion time in bits before frame starts
- fsl,csaft: chip select negation time in bits after frame ends
Example:
spi@110000 {
#address-cells = <1>;
#size-cells = <0>;
compatible = "fsl,mpc8536-espi";
reg = <0x110000 0x1000>;
interrupts = <53 0x2>;
interrupt-parent = <&mpic>;
fsl,espi-num-chipselects = <4>;
fsl,csbef = <1>;
fsl,csaft = <1>;
};

View file

@ -35,6 +35,8 @@ properties:
- enum:
- mediatek,mt7981-spi-ipm
- mediatek,mt7986-spi-ipm
- mediatek,mt7988-spi-quad
- mediatek,mt7988-spi-single
- mediatek,mt8188-spi-ipm
- const: mediatek,spi-ipm
- items:

View file

@ -0,0 +1,83 @@
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/spi/qcom,spi-qpic-snand.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Qualcomm QPIC NAND controller
maintainers:
- Md sadre Alam <quic_mdalam@quicinc.com>
description:
The QCOM QPIC-SPI-NAND flash controller is an extended version of
the QCOM QPIC NAND flash controller. It can work both in serial
and parallel mode. It supports typical SPI-NAND page cache
operations in single, dual or quad IO mode with pipelined ECC
encoding/decoding using the QPIC ECC HW engine.
allOf:
- $ref: /schemas/spi/spi-controller.yaml#
properties:
compatible:
enum:
- qcom,ipq9574-snand
reg:
maxItems: 1
clocks:
maxItems: 3
clock-names:
items:
- const: core
- const: aon
- const: iom
dmas:
items:
- description: tx DMA channel
- description: rx DMA channel
- description: cmd DMA channel
dma-names:
items:
- const: tx
- const: rx
- const: cmd
required:
- compatible
- reg
- clocks
- clock-names
unevaluatedProperties: false
examples:
- |
#include <dt-bindings/clock/qcom,ipq9574-gcc.h>
spi@79b0000 {
compatible = "qcom,ipq9574-snand";
reg = <0x1ac00000 0x800>;
clocks = <&gcc GCC_QPIC_CLK>,
<&gcc GCC_QPIC_AHB_CLK>,
<&gcc GCC_QPIC_IO_MACRO_CLK>;
clock-names = "core", "aon", "iom";
#address-cells = <1>;
#size-cells = <0>;
flash@0 {
compatible = "spi-nand";
reg = <0>;
#address-cells = <1>;
#size-cells = <1>;
nand-ecc-engine = <&qpic_nand>;
nand-ecc-strength = <4>;
nand-ecc-step-size = <512>;
};
};

View file

@ -24,6 +24,7 @@ properties:
- enum:
- fsl,imx8ulp-spi
- fsl,imx93-spi
- fsl,imx94-spi
- fsl,imx95-spi
- const: fsl,imx7ulp-spi
reg:

View file

@ -34,6 +34,7 @@ properties:
- rockchip,rk3328-spi
- rockchip,rk3368-spi
- rockchip,rk3399-spi
- rockchip,rk3562-spi
- rockchip,rk3568-spi
- rockchip,rk3576-spi
- rockchip,rk3588-spi

View file

@ -0,0 +1,52 @@
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/spi/spi-sg2044-nor.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: SG2044 SPI NOR controller
maintainers:
- Longbin Li <looong.bin@gmail.com>
allOf:
- $ref: spi-controller.yaml#
properties:
compatible:
const: sophgo,sg2044-spifmc-nor
reg:
maxItems: 1
clocks:
maxItems: 1
interrupts:
maxItems: 1
resets:
maxItems: 1
required:
- compatible
- reg
- clocks
- interrupts
- resets
unevaluatedProperties: false
examples:
- |
#include <dt-bindings/interrupt-controller/irq.h>
spi@1000000 {
compatible = "sophgo,sg2044-spifmc-nor";
reg = <0x1000000 0x4000000>;
#address-cells = <1>;
#size-cells = <0>;
clocks = <&clk 0>;
interrupts = <37 IRQ_TYPE_LEVEL_HIGH>;
resets = <&rst 0>;
};

View file

@ -0,0 +1,105 @@
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/spi/st,stm32mp25-ospi.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: STMicroelectronics STM32 Octal Serial Peripheral Interface (OSPI)
maintainers:
- Patrice Chotard <patrice.chotard@foss.st.com>
allOf:
- $ref: spi-controller.yaml#
properties:
compatible:
const: st,stm32mp25-ospi
reg:
maxItems: 1
memory-region:
description:
Memory region to be used for memory-map read access.
In memory-mapped mode, read access are performed from the memory
device using the direct mapping.
maxItems: 1
clocks:
maxItems: 1
interrupts:
maxItems: 1
resets:
items:
- description: phandle to OSPI block reset
- description: phandle to delay block reset
dmas:
maxItems: 2
dma-names:
items:
- const: tx
- const: rx
st,syscfg-dlyb:
description: configure OCTOSPI delay block.
$ref: /schemas/types.yaml#/definitions/phandle-array
items:
- description: phandle to syscfg
- description: register offset within syscfg
access-controllers:
description: phandle to the rifsc device to check access right
and in some cases, an additional phandle to the rcc device for
secure clock control.
items:
- description: phandle to bus controller
- description: phandle to clock controller
minItems: 1
power-domains:
maxItems: 1
required:
- compatible
- reg
- clocks
- interrupts
- st,syscfg-dlyb
unevaluatedProperties: false
examples:
- |
#include <dt-bindings/clock/st,stm32mp25-rcc.h>
#include <dt-bindings/interrupt-controller/arm-gic.h>
#include <dt-bindings/reset/st,stm32mp25-rcc.h>
spi@40430000 {
compatible = "st,stm32mp25-ospi";
reg = <0x40430000 0x400>;
memory-region = <&mm_ospi1>;
interrupts = <GIC_SPI 163 IRQ_TYPE_LEVEL_HIGH>;
dmas = <&hpdma 2 0x62 0x00003121 0x0>,
<&hpdma 2 0x42 0x00003112 0x0>;
dma-names = "tx", "rx";
clocks = <&scmi_clk CK_SCMI_OSPI1>;
resets = <&scmi_reset RST_SCMI_OSPI1>, <&scmi_reset RST_SCMI_OSPI1DLL>;
access-controllers = <&rifsc 74>;
power-domains = <&CLUSTER_PD>;
st,syscfg-dlyb = <&syscfg 0x1000>;
#address-cells = <1>;
#size-cells = <0>;
flash@0 {
compatible = "jedec,spi-nor";
reg = <0>;
spi-rx-bus-width = <4>;
spi-max-frequency = <108000000>;
};
};

View file

@ -0,0 +1,37 @@
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/trigger-source/pwm-trigger.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Generic trigger source using PWM
description: Remaps a PWM channel as a trigger source.
maintainers:
- David Lechner <dlechner@baylibre.com>
properties:
compatible:
const: pwm-trigger
'#trigger-source-cells':
const: 0
pwms:
maxItems: 1
required:
- compatible
- '#trigger-source-cells'
- pwms
additionalProperties: false
examples:
- |
trigger {
compatible = "pwm-trigger";
#trigger-source-cells = <0>;
pwms = <&pwm 0 1000000 0>;
};

View file

@ -593,6 +593,8 @@ patternProperties:
description: GlobalTop Technology, Inc.
"^gmt,.*":
description: Global Mixed-mode Technology, Inc.
"^gocontroll,.*":
description: GOcontroll Modular Embedded Electronics B.V.
"^goldelico,.*":
description: Golden Delicious Computers GmbH & Co. KG
"^goodix,.*":

View file

@ -9863,6 +9863,11 @@ L: linux-media@vger.kernel.org
S: Maintained
F: drivers/media/usb/go7007/
GOCONTROLL MODULINE MODULE SLOT
M: Maud Spierings <maudspierings@gocontroll.com>
S: Maintained
F: Documentation/devicetree/bindings/connector/gocontroll,moduline-module-slot.yaml
GOODIX TOUCHSCREEN
M: Hans de Goede <hdegoede@redhat.com>
L: linux-input@vger.kernel.org
@ -22420,6 +22425,13 @@ F: Documentation/devicetree/bindings/mtd/jedec,spi-nor.yaml
F: drivers/mtd/spi-nor/
F: include/linux/mtd/spi-nor.h
SPI OFFLOAD
R: David Lechner <dlechner@baylibre.com>
F: drivers/spi/spi-offload-trigger-pwm.c
F: drivers/spi/spi-offload.c
F: include/linux/spi/offload/
K: spi_offload
SPI SUBSYSTEM
M: Mark Brown <broonie@kernel.org>
L: linux-spi@vger.kernel.org
@ -24168,6 +24180,11 @@ W: https://github.com/srcres258/linux-doc
T: git git://github.com/srcres258/linux-doc.git doc-zh-tw
F: Documentation/translations/zh_TW/
TRIGGER SOURCE - PWM
M: David Lechner <dlechner@baylibre.com>
S: Maintained
F: Documentation/devicetree/bindings/trigger-source/pwm-trigger.yaml
TRUSTED SECURITY MODULE (TSM) ATTESTATION REPORTS
M: Dan Williams <dan.j.williams@intel.com>
L: linux-coco@lists.linux.dev

View file

@ -3,7 +3,11 @@
nandcore-objs := core.o bbt.o
obj-$(CONFIG_MTD_NAND_CORE) += nandcore.o
obj-$(CONFIG_MTD_NAND_ECC_MEDIATEK) += ecc-mtk.o
ifeq ($(CONFIG_SPI_QPIC_SNAND),y)
obj-$(CONFIG_SPI_QPIC_SNAND) += qpic_common.o
else
obj-$(CONFIG_MTD_NAND_QCOM) += qpic_common.o
endif
obj-y += onenand/
obj-y += raw/
obj-y += spi/

View file

@ -55,6 +55,9 @@ config SPI_MEM
This extension is meant to simplify interaction with SPI memories
by providing a high-level interface to send memory-like commands.
config SPI_OFFLOAD
bool
comment "SPI Master Controller Drivers"
config SPI_AIROHA_SNFI
@ -176,6 +179,7 @@ config SPI_AU1550
config SPI_AXI_SPI_ENGINE
tristate "Analog Devices AXI SPI Engine controller"
depends on HAS_IOMEM
select SPI_OFFLOAD
help
This enables support for the Analog Devices AXI SPI Engine SPI controller.
It is part of the SPI Engine framework that is used in some Analog Devices
@ -932,6 +936,15 @@ config SPI_QCOM_QSPI
help
QSPI(Quad SPI) driver for Qualcomm QSPI controller.
config SPI_QPIC_SNAND
bool "QPIC SNAND controller"
depends on ARCH_QCOM || COMPILE_TEST
select MTD
help
QPIC_SNAND (QPIC SPI NAND) driver for Qualcomm QPIC controller.
QPIC controller supports both parallel nand and serial nand.
This config will enable serial nand driver for QPIC controller.
config SPI_QUP
tristate "Qualcomm SPI controller with QUP interface"
depends on ARCH_QCOM || COMPILE_TEST
@ -1021,6 +1034,15 @@ 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_SPRD
tristate "Spreadtrum SPI controller"
depends on ARCH_SPRD || COMPILE_TEST
@ -1045,6 +1067,16 @@ config SPI_STM32
is not available, the driver automatically falls back to
PIO mode.
config SPI_STM32_OSPI
tristate "STMicroelectronics STM32 OCTO SPI controller"
depends on ARCH_STM32 || COMPILE_TEST
depends on OF
depends on SPI_MEM
help
This enables support for the Octo SPI controller in master mode.
This driver does not support generic SPI. The implementation only
supports spi-mem interface.
config SPI_STM32_QSPI
tristate "STMicroelectronics STM32 QUAD SPI controller"
depends on ARCH_STM32 || COMPILE_TEST
@ -1317,4 +1349,16 @@ endif # SPI_SLAVE
config SPI_DYNAMIC
def_bool ACPI || OF_DYNAMIC || SPI_SLAVE
if SPI_OFFLOAD
comment "SPI Offload triggers"
config SPI_OFFLOAD_TRIGGER_PWM
tristate "SPI offload trigger using PWM"
depends on PWM
help
Generic SPI offload trigger implemented using PWM output.
endif # SPI_OFFLOAD
endif # SPI

View file

@ -10,6 +10,7 @@ ccflags-$(CONFIG_SPI_DEBUG) := -DDEBUG
obj-$(CONFIG_SPI_MASTER) += spi.o
obj-$(CONFIG_SPI_MEM) += spi-mem.o
obj-$(CONFIG_SPI_MUX) += spi-mux.o
obj-$(CONFIG_SPI_OFFLOAD) += spi-offload.o
obj-$(CONFIG_SPI_SPIDEV) += spidev.o
obj-$(CONFIG_SPI_LOOPBACK_TEST) += spi-loopback-test.o
@ -116,6 +117,7 @@ obj-$(CONFIG_SPI_PXA2XX) += spi-pxa2xx-platform.o
obj-$(CONFIG_SPI_PXA2XX_PCI) += spi-pxa2xx-pci.o
obj-$(CONFIG_SPI_QCOM_GENI) += spi-geni-qcom.o
obj-$(CONFIG_SPI_QCOM_QSPI) += spi-qcom-qspi.o
obj-$(CONFIG_SPI_QPIC_SNAND) += spi-qpic-snand.o
obj-$(CONFIG_SPI_QUP) += spi-qup.o
obj-$(CONFIG_SPI_ROCKCHIP) += spi-rockchip.o
obj-$(CONFIG_SPI_ROCKCHIP_SFC) += spi-rockchip-sfc.o
@ -134,9 +136,11 @@ obj-$(CONFIG_SPI_SH_SCI) += spi-sh-sci.o
obj-$(CONFIG_SPI_SIFIVE) += spi-sifive.o
obj-$(CONFIG_SPI_SLAVE_MT27XX) += spi-slave-mt27xx.o
obj-$(CONFIG_SPI_SN_F_OSPI) += spi-sn-f-ospi.o
obj-$(CONFIG_SPI_SG2044_NOR) += spi-sg2044-nor.o
obj-$(CONFIG_SPI_SPRD) += spi-sprd.o
obj-$(CONFIG_SPI_SPRD_ADI) += spi-sprd-adi.o
obj-$(CONFIG_SPI_STM32) += spi-stm32.o
obj-$(CONFIG_SPI_STM32_OSPI) += spi-stm32-ospi.o
obj-$(CONFIG_SPI_STM32_QSPI) += spi-stm32-qspi.o
obj-$(CONFIG_SPI_ST_SSC4) += spi-st-ssc4.o
obj-$(CONFIG_SPI_SUN4I) += spi-sun4i.o
@ -163,3 +167,6 @@ obj-$(CONFIG_SPI_AMD) += spi-amd.o
# SPI slave protocol handlers
obj-$(CONFIG_SPI_SLAVE_TIME) += spi-slave-time.o
obj-$(CONFIG_SPI_SLAVE_SYSTEM_CONTROL) += spi-slave-system-control.o
# SPI offload triggers
obj-$(CONFIG_SPI_OFFLOAD_TRIGGER_PWM) += spi-offload-trigger-pwm.o

View file

@ -303,13 +303,6 @@ static int do_aspeed_spi_exec_op(struct spi_mem *mem, const struct spi_mem_op *o
u32 ctl_val;
int ret = 0;
dev_dbg(aspi->dev,
"CE%d %s OP %#x mode:%d.%d.%d.%d naddr:%#x ndummies:%#x len:%#x",
chip->cs, op->data.dir == SPI_MEM_DATA_IN ? "read" : "write",
op->cmd.opcode, op->cmd.buswidth, op->addr.buswidth,
op->dummy.buswidth, op->data.buswidth,
op->addr.nbytes, op->dummy.nbytes, op->data.nbytes);
addr_mode = readl(aspi->regs + CE_CTRL_REG);
addr_mode_backup = addr_mode;

View file

@ -2,11 +2,15 @@
/*
* SPI-Engine SPI controller driver
* Copyright 2015 Analog Devices Inc.
* Copyright 2024 BayLibre, SAS
* Author: Lars-Peter Clausen <lars@metafoo.de>
*/
#include <linux/bitfield.h>
#include <linux/bitops.h>
#include <linux/clk.h>
#include <linux/completion.h>
#include <linux/dmaengine.h>
#include <linux/fpga/adi-axi-common.h>
#include <linux/interrupt.h>
#include <linux/io.h>
@ -14,9 +18,11 @@
#include <linux/module.h>
#include <linux/overflow.h>
#include <linux/platform_device.h>
#include <linux/spi/offload/provider.h>
#include <linux/spi/spi.h>
#include <trace/events/spi.h>
#define SPI_ENGINE_REG_OFFLOAD_MEM_ADDR_WIDTH 0x10
#define SPI_ENGINE_REG_RESET 0x40
#define SPI_ENGINE_REG_INT_ENABLE 0x80
@ -24,6 +30,7 @@
#define SPI_ENGINE_REG_INT_SOURCE 0x88
#define SPI_ENGINE_REG_SYNC_ID 0xc0
#define SPI_ENGINE_REG_OFFLOAD_SYNC_ID 0xc4
#define SPI_ENGINE_REG_CMD_FIFO_ROOM 0xd0
#define SPI_ENGINE_REG_SDO_FIFO_ROOM 0xd4
@ -34,10 +41,24 @@
#define SPI_ENGINE_REG_SDI_DATA_FIFO 0xe8
#define SPI_ENGINE_REG_SDI_DATA_FIFO_PEEK 0xec
#define SPI_ENGINE_MAX_NUM_OFFLOADS 32
#define SPI_ENGINE_REG_OFFLOAD_CTRL(x) (0x100 + SPI_ENGINE_MAX_NUM_OFFLOADS * (x))
#define SPI_ENGINE_REG_OFFLOAD_STATUS(x) (0x104 + SPI_ENGINE_MAX_NUM_OFFLOADS * (x))
#define SPI_ENGINE_REG_OFFLOAD_RESET(x) (0x108 + SPI_ENGINE_MAX_NUM_OFFLOADS * (x))
#define SPI_ENGINE_REG_OFFLOAD_CMD_FIFO(x) (0x110 + SPI_ENGINE_MAX_NUM_OFFLOADS * (x))
#define SPI_ENGINE_REG_OFFLOAD_SDO_FIFO(x) (0x114 + SPI_ENGINE_MAX_NUM_OFFLOADS * (x))
#define SPI_ENGINE_SPI_OFFLOAD_MEM_WIDTH_SDO GENMASK(15, 8)
#define SPI_ENGINE_SPI_OFFLOAD_MEM_WIDTH_CMD GENMASK(7, 0)
#define SPI_ENGINE_INT_CMD_ALMOST_EMPTY BIT(0)
#define SPI_ENGINE_INT_SDO_ALMOST_EMPTY BIT(1)
#define SPI_ENGINE_INT_SDI_ALMOST_FULL BIT(2)
#define SPI_ENGINE_INT_SYNC BIT(3)
#define SPI_ENGINE_INT_OFFLOAD_SYNC BIT(4)
#define SPI_ENGINE_OFFLOAD_CTRL_ENABLE BIT(0)
#define SPI_ENGINE_CONFIG_CPHA BIT(0)
#define SPI_ENGINE_CONFIG_CPOL BIT(1)
@ -79,6 +100,10 @@
#define SPI_ENGINE_CMD_CS_INV(flags) \
SPI_ENGINE_CMD(SPI_ENGINE_INST_CS_INV, 0, (flags))
/* default sizes - can be changed when SPI Engine firmware is compiled */
#define SPI_ENGINE_OFFLOAD_CMD_FIFO_SIZE 16
#define SPI_ENGINE_OFFLOAD_SDO_FIFO_SIZE 16
struct spi_engine_program {
unsigned int length;
uint16_t instructions[] __counted_by(length);
@ -106,6 +131,17 @@ struct spi_engine_message_state {
uint8_t *rx_buf;
};
enum {
SPI_ENGINE_OFFLOAD_FLAG_ASSIGNED,
SPI_ENGINE_OFFLOAD_FLAG_PREPARED,
};
struct spi_engine_offload {
struct spi_engine *spi_engine;
unsigned long flags;
unsigned int offload_num;
};
struct spi_engine {
struct clk *clk;
struct clk *ref_clk;
@ -118,6 +154,11 @@ struct spi_engine {
unsigned int int_enable;
/* shadows hardware CS inversion flag state */
u8 cs_inv;
unsigned int offload_ctrl_mem_size;
unsigned int offload_sdo_mem_size;
struct spi_offload *offload;
u32 offload_caps;
};
static void spi_engine_program_add_cmd(struct spi_engine_program *p,
@ -163,9 +204,9 @@ static void spi_engine_gen_xfer(struct spi_engine_program *p, bool dry,
unsigned int n = min(len, 256U);
unsigned int flags = 0;
if (xfer->tx_buf)
if (xfer->tx_buf || (xfer->offload_flags & SPI_OFFLOAD_XFER_TX_STREAM))
flags |= SPI_ENGINE_TRANSFER_WRITE;
if (xfer->rx_buf)
if (xfer->rx_buf || (xfer->offload_flags & SPI_OFFLOAD_XFER_RX_STREAM))
flags |= SPI_ENGINE_TRANSFER_READ;
spi_engine_program_add_cmd(p, dry,
@ -217,16 +258,24 @@ static void spi_engine_gen_cs(struct spi_engine_program *p, bool dry,
*
* NB: This is separate from spi_engine_compile_message() because the latter
* is called twice and would otherwise result in double-evaluation.
*
* Returns 0 on success, -EINVAL on failure.
*/
static void spi_engine_precompile_message(struct spi_message *msg)
static int spi_engine_precompile_message(struct spi_message *msg)
{
unsigned int clk_div, max_hz = msg->spi->controller->max_speed_hz;
struct spi_transfer *xfer;
list_for_each_entry(xfer, &msg->transfers, transfer_list) {
/* If we have an offload transfer, we can't rx to buffer */
if (msg->offload && xfer->rx_buf)
return -EINVAL;
clk_div = DIV_ROUND_UP(max_hz, xfer->speed_hz);
xfer->effective_speed_hz = max_hz / min(clk_div, 256U);
}
return 0;
}
static void spi_engine_compile_message(struct spi_message *msg, bool dry,
@ -521,11 +570,105 @@ static irqreturn_t spi_engine_irq(int irq, void *devid)
return IRQ_HANDLED;
}
static int spi_engine_offload_prepare(struct spi_message *msg)
{
struct spi_controller *host = msg->spi->controller;
struct spi_engine *spi_engine = spi_controller_get_devdata(host);
struct spi_engine_program *p = msg->opt_state;
struct spi_engine_offload *priv = msg->offload->priv;
struct spi_transfer *xfer;
void __iomem *cmd_addr;
void __iomem *sdo_addr;
size_t tx_word_count = 0;
unsigned int i;
if (p->length > spi_engine->offload_ctrl_mem_size)
return -EINVAL;
/* count total number of tx words in message */
list_for_each_entry(xfer, &msg->transfers, transfer_list) {
/* no support for reading to rx_buf */
if (xfer->rx_buf)
return -EINVAL;
if (!xfer->tx_buf)
continue;
if (xfer->bits_per_word <= 8)
tx_word_count += xfer->len;
else if (xfer->bits_per_word <= 16)
tx_word_count += xfer->len / 2;
else
tx_word_count += xfer->len / 4;
}
if (tx_word_count && !(spi_engine->offload_caps & SPI_OFFLOAD_CAP_TX_STATIC_DATA))
return -EINVAL;
if (tx_word_count > spi_engine->offload_sdo_mem_size)
return -EINVAL;
/*
* This protects against calling spi_optimize_message() with an offload
* that has already been prepared with a different message.
*/
if (test_and_set_bit_lock(SPI_ENGINE_OFFLOAD_FLAG_PREPARED, &priv->flags))
return -EBUSY;
cmd_addr = spi_engine->base +
SPI_ENGINE_REG_OFFLOAD_CMD_FIFO(priv->offload_num);
sdo_addr = spi_engine->base +
SPI_ENGINE_REG_OFFLOAD_SDO_FIFO(priv->offload_num);
list_for_each_entry(xfer, &msg->transfers, transfer_list) {
if (!xfer->tx_buf)
continue;
if (xfer->bits_per_word <= 8) {
const u8 *buf = xfer->tx_buf;
for (i = 0; i < xfer->len; i++)
writel_relaxed(buf[i], sdo_addr);
} else if (xfer->bits_per_word <= 16) {
const u16 *buf = xfer->tx_buf;
for (i = 0; i < xfer->len / 2; i++)
writel_relaxed(buf[i], sdo_addr);
} else {
const u32 *buf = xfer->tx_buf;
for (i = 0; i < xfer->len / 4; i++)
writel_relaxed(buf[i], sdo_addr);
}
}
for (i = 0; i < p->length; i++)
writel_relaxed(p->instructions[i], cmd_addr);
return 0;
}
static void spi_engine_offload_unprepare(struct spi_offload *offload)
{
struct spi_engine_offload *priv = offload->priv;
struct spi_engine *spi_engine = priv->spi_engine;
writel_relaxed(1, spi_engine->base +
SPI_ENGINE_REG_OFFLOAD_RESET(priv->offload_num));
writel_relaxed(0, spi_engine->base +
SPI_ENGINE_REG_OFFLOAD_RESET(priv->offload_num));
clear_bit_unlock(SPI_ENGINE_OFFLOAD_FLAG_PREPARED, &priv->flags);
}
static int spi_engine_optimize_message(struct spi_message *msg)
{
struct spi_engine_program p_dry, *p;
int ret;
spi_engine_precompile_message(msg);
ret = spi_engine_precompile_message(msg);
if (ret)
return ret;
p_dry.length = 0;
spi_engine_compile_message(msg, true, &p_dry);
@ -537,20 +680,61 @@ static int spi_engine_optimize_message(struct spi_message *msg)
spi_engine_compile_message(msg, false, p);
spi_engine_program_add_cmd(p, false, SPI_ENGINE_CMD_SYNC(
AXI_SPI_ENGINE_CUR_MSG_SYNC_ID));
msg->offload ? 0 : AXI_SPI_ENGINE_CUR_MSG_SYNC_ID));
msg->opt_state = p;
if (msg->offload) {
ret = spi_engine_offload_prepare(msg);
if (ret) {
msg->opt_state = NULL;
kfree(p);
return ret;
}
}
return 0;
}
static int spi_engine_unoptimize_message(struct spi_message *msg)
{
if (msg->offload)
spi_engine_offload_unprepare(msg->offload);
kfree(msg->opt_state);
return 0;
}
static struct spi_offload
*spi_engine_get_offload(struct spi_device *spi,
const struct spi_offload_config *config)
{
struct spi_controller *host = spi->controller;
struct spi_engine *spi_engine = spi_controller_get_devdata(host);
struct spi_engine_offload *priv;
if (!spi_engine->offload)
return ERR_PTR(-ENODEV);
if (config->capability_flags & ~spi_engine->offload_caps)
return ERR_PTR(-EINVAL);
priv = spi_engine->offload->priv;
if (test_and_set_bit_lock(SPI_ENGINE_OFFLOAD_FLAG_ASSIGNED, &priv->flags))
return ERR_PTR(-EBUSY);
return spi_engine->offload;
}
static void spi_engine_put_offload(struct spi_offload *offload)
{
struct spi_engine_offload *priv = offload->priv;
clear_bit_unlock(SPI_ENGINE_OFFLOAD_FLAG_ASSIGNED, &priv->flags);
}
static int spi_engine_setup(struct spi_device *device)
{
struct spi_controller *host = device->controller;
@ -583,6 +767,12 @@ static int spi_engine_transfer_one_message(struct spi_controller *host,
unsigned int int_enable = 0;
unsigned long flags;
if (msg->offload) {
dev_err(&host->dev, "Single transfer offload not supported\n");
msg->status = -EOPNOTSUPP;
goto out;
}
/* reinitialize message state for this transfer */
memset(st, 0, sizeof(*st));
st->cmd_buf = p->instructions;
@ -632,11 +822,68 @@ static int spi_engine_transfer_one_message(struct spi_controller *host,
trace_spi_transfer_stop(msg, xfer);
}
out:
spi_finalize_current_message(host);
return msg->status;
}
static int spi_engine_trigger_enable(struct spi_offload *offload)
{
struct spi_engine_offload *priv = offload->priv;
struct spi_engine *spi_engine = priv->spi_engine;
unsigned int reg;
reg = readl_relaxed(spi_engine->base +
SPI_ENGINE_REG_OFFLOAD_CTRL(priv->offload_num));
reg |= SPI_ENGINE_OFFLOAD_CTRL_ENABLE;
writel_relaxed(reg, spi_engine->base +
SPI_ENGINE_REG_OFFLOAD_CTRL(priv->offload_num));
return 0;
}
static void spi_engine_trigger_disable(struct spi_offload *offload)
{
struct spi_engine_offload *priv = offload->priv;
struct spi_engine *spi_engine = priv->spi_engine;
unsigned int reg;
reg = readl_relaxed(spi_engine->base +
SPI_ENGINE_REG_OFFLOAD_CTRL(priv->offload_num));
reg &= ~SPI_ENGINE_OFFLOAD_CTRL_ENABLE;
writel_relaxed(reg, spi_engine->base +
SPI_ENGINE_REG_OFFLOAD_CTRL(priv->offload_num));
}
static struct dma_chan
*spi_engine_tx_stream_request_dma_chan(struct spi_offload *offload)
{
struct spi_engine_offload *priv = offload->priv;
char name[16];
snprintf(name, sizeof(name), "offload%u-tx", priv->offload_num);
return dma_request_chan(offload->provider_dev, name);
}
static struct dma_chan
*spi_engine_rx_stream_request_dma_chan(struct spi_offload *offload)
{
struct spi_engine_offload *priv = offload->priv;
char name[16];
snprintf(name, sizeof(name), "offload%u-rx", priv->offload_num);
return dma_request_chan(offload->provider_dev, name);
}
static const struct spi_offload_ops spi_engine_offload_ops = {
.trigger_enable = spi_engine_trigger_enable,
.trigger_disable = spi_engine_trigger_disable,
.tx_stream_request_dma_chan = spi_engine_tx_stream_request_dma_chan,
.rx_stream_request_dma_chan = spi_engine_rx_stream_request_dma_chan,
};
static void spi_engine_release_hw(void *p)
{
struct spi_engine *spi_engine = p;
@ -651,8 +898,7 @@ static int spi_engine_probe(struct platform_device *pdev)
struct spi_engine *spi_engine;
struct spi_controller *host;
unsigned int version;
int irq;
int ret;
int irq, ret;
irq = platform_get_irq(pdev, 0);
if (irq < 0)
@ -667,6 +913,46 @@ static int spi_engine_probe(struct platform_device *pdev)
spin_lock_init(&spi_engine->lock);
init_completion(&spi_engine->msg_complete);
/*
* REVISIT: for now, all SPI Engines only have one offload. In the
* future, this should be read from a memory mapped register to
* determine the number of offloads enabled at HDL compile time. For
* now, we can tell if an offload is present if there is a trigger
* source wired up to it.
*/
if (device_property_present(&pdev->dev, "trigger-sources")) {
struct spi_engine_offload *priv;
spi_engine->offload =
devm_spi_offload_alloc(&pdev->dev,
sizeof(struct spi_engine_offload));
if (IS_ERR(spi_engine->offload))
return PTR_ERR(spi_engine->offload);
priv = spi_engine->offload->priv;
priv->spi_engine = spi_engine;
priv->offload_num = 0;
spi_engine->offload->ops = &spi_engine_offload_ops;
spi_engine->offload_caps = SPI_OFFLOAD_CAP_TRIGGER;
if (device_property_match_string(&pdev->dev, "dma-names", "offload0-rx") >= 0) {
spi_engine->offload_caps |= SPI_OFFLOAD_CAP_RX_STREAM_DMA;
spi_engine->offload->xfer_flags |= SPI_OFFLOAD_XFER_RX_STREAM;
}
if (device_property_match_string(&pdev->dev, "dma-names", "offload0-tx") >= 0) {
spi_engine->offload_caps |= SPI_OFFLOAD_CAP_TX_STREAM_DMA;
spi_engine->offload->xfer_flags |= SPI_OFFLOAD_XFER_TX_STREAM;
} else {
/*
* HDL compile option to enable TX DMA stream also disables
* the SDO memory, so can't do both at the same time.
*/
spi_engine->offload_caps |= SPI_OFFLOAD_CAP_TX_STATIC_DATA;
}
}
spi_engine->clk = devm_clk_get_enabled(&pdev->dev, "s_axi_aclk");
if (IS_ERR(spi_engine->clk))
return PTR_ERR(spi_engine->clk);
@ -688,6 +974,19 @@ static int spi_engine_probe(struct platform_device *pdev)
return -ENODEV;
}
if (ADI_AXI_PCORE_VER_MINOR(version) >= 1) {
unsigned int sizes = readl(spi_engine->base +
SPI_ENGINE_REG_OFFLOAD_MEM_ADDR_WIDTH);
spi_engine->offload_ctrl_mem_size = 1 <<
FIELD_GET(SPI_ENGINE_SPI_OFFLOAD_MEM_WIDTH_CMD, sizes);
spi_engine->offload_sdo_mem_size = 1 <<
FIELD_GET(SPI_ENGINE_SPI_OFFLOAD_MEM_WIDTH_SDO, sizes);
} else {
spi_engine->offload_ctrl_mem_size = SPI_ENGINE_OFFLOAD_CMD_FIFO_SIZE;
spi_engine->offload_sdo_mem_size = SPI_ENGINE_OFFLOAD_SDO_FIFO_SIZE;
}
writel_relaxed(0x00, spi_engine->base + SPI_ENGINE_REG_RESET);
writel_relaxed(0xff, spi_engine->base + SPI_ENGINE_REG_INT_PENDING);
writel_relaxed(0x00, spi_engine->base + SPI_ENGINE_REG_INT_ENABLE);
@ -709,6 +1008,8 @@ static int spi_engine_probe(struct platform_device *pdev)
host->transfer_one_message = spi_engine_transfer_one_message;
host->optimize_message = spi_engine_optimize_message;
host->unoptimize_message = spi_engine_unoptimize_message;
host->get_offload = spi_engine_get_offload;
host->put_offload = spi_engine_put_offload;
host->num_chipselect = 8;
/* Some features depend of the IP core version. */

View file

@ -1658,6 +1658,12 @@ static int cqspi_request_mmap_dma(struct cqspi_st *cqspi)
int ret = PTR_ERR(cqspi->rx_chan);
cqspi->rx_chan = NULL;
if (ret == -ENODEV) {
/* DMA support is not mandatory */
dev_info(&cqspi->pdev->dev, "No Rx DMA available\n");
return 0;
}
return dev_err_probe(&cqspi->pdev->dev, ret, "No Rx DMA available\n");
}
init_completion(&cqspi->rx_dma_complete);
@ -2067,7 +2073,7 @@ static const struct cqspi_driver_platdata k2g_qspi = {
static const struct cqspi_driver_platdata am654_ospi = {
.hwcaps_mask = CQSPI_SUPPORTS_OCTAL | CQSPI_SUPPORTS_QUAD,
.quirks = CQSPI_NEEDS_WR_DELAY,
.quirks = CQSPI_DISABLE_DAC_MODE | CQSPI_NEEDS_WR_DELAY,
};
static const struct cqspi_driver_platdata intel_lgm_qspi = {

View file

@ -479,6 +479,19 @@ static int fsi_spi_transfer_one_message(struct spi_controller *ctlr,
shift = SPI_FSI_SEQUENCE_SHIFT_IN(next->len);
fsi_spi_sequence_add(&seq, shift);
} else if (next->tx_buf) {
if ((next->len + transfer->len) > (SPI_FSI_MAX_TX_SIZE + 8)) {
rc = -EINVAL;
goto error;
}
len = next->len;
while (len > 8) {
fsi_spi_sequence_add(&seq,
SPI_FSI_SEQUENCE_SHIFT_OUT(8));
len -= 8;
}
fsi_spi_sequence_add(&seq, SPI_FSI_SEQUENCE_SHIFT_OUT(len));
} else {
next = NULL;
}

View file

@ -572,7 +572,7 @@ static int fsl_lpspi_calculate_timeout(struct fsl_lpspi_data *fsl_lpspi,
timeout += 1;
/* Double calculated timeout */
return msecs_to_jiffies(2 * timeout * MSEC_PER_SEC);
return secs_to_jiffies(2 * timeout);
}
static int fsl_lpspi_dma_transfer(struct spi_controller *controller,

View file

@ -39,36 +39,8 @@ struct spi_gpio {
/*----------------------------------------------------------------------*/
/*
* Because the overhead of going through four GPIO procedure calls
* per transferred bit can make performance a problem, this code
* is set up so that you can use it in either of two ways:
*
* - The slow generic way: set up platform_data to hold the GPIO
* numbers used for MISO/MOSI/SCK, and issue procedure calls for
* each of them. This driver can handle several such busses.
*
* - The quicker inlined way: only helps with platform GPIO code
* that inlines operations for constant GPIOs. This can give
* you tight (fast!) inner loops, but each such bus needs a
* new driver. You'll define a new C file, with Makefile and
* Kconfig support; the C code can be a total of six lines:
*
* #define DRIVER_NAME "myboard_spi2"
* #define SPI_MISO_GPIO 119
* #define SPI_MOSI_GPIO 120
* #define SPI_SCK_GPIO 121
* #define SPI_N_CHIPSEL 4
* #include "spi-gpio.c"
*/
#ifndef DRIVER_NAME
#define DRIVER_NAME "spi_gpio"
#define GENERIC_BITBANG /* vs tight inlines */
#endif
/*----------------------------------------------------------------------*/
static inline struct spi_gpio *__pure
@ -341,16 +313,14 @@ static int spi_gpio_probe_pdata(struct platform_device *pdev,
struct spi_gpio *spi_gpio = spi_controller_get_devdata(host);
int i;
#ifdef GENERIC_BITBANG
if (!pdata || !pdata->num_chipselect)
if (!pdata)
return -ENODEV;
#endif
/*
* The host needs to think there is a chipselect even if not
* connected
*/
host->num_chipselect = pdata->num_chipselect ?: 1;
/* It's just one always-selected device, fine to continue */
if (!pdata->num_chipselect)
return 0;
host->num_chipselect = pdata->num_chipselect;
spi_gpio->cs_gpios = devm_kcalloc(dev, host->num_chipselect,
sizeof(*spi_gpio->cs_gpios),
GFP_KERNEL);
@ -445,8 +415,6 @@ static int spi_gpio_probe(struct platform_device *pdev)
return devm_spi_register_controller(&pdev->dev, host);
}
MODULE_ALIAS("platform:" DRIVER_NAME);
static const struct of_device_id spi_gpio_dt_ids[] = {
{ .compatible = "spi-gpio" },
{}
@ -465,3 +433,4 @@ module_platform_driver(spi_gpio_driver);
MODULE_DESCRIPTION("SPI host driver using generic bitbanged GPIO ");
MODULE_AUTHOR("David Brownell");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:" DRIVER_NAME);

View file

@ -1449,7 +1449,7 @@ static int spi_imx_calculate_timeout(struct spi_imx_data *spi_imx, int size)
timeout += 1;
/* Double calculated timeout */
return msecs_to_jiffies(2 * timeout * MSEC_PER_SEC);
return secs_to_jiffies(2 * timeout);
}
static int spi_imx_dma_transfer(struct spi_imx_data *spi_imx,

View file

@ -377,6 +377,17 @@ int spi_mem_exec_op(struct spi_mem *mem, const struct spi_mem_op *op)
/* Make sure the operation frequency is correct before going futher */
spi_mem_adjust_op_freq(mem, (struct spi_mem_op *)op);
dev_vdbg(&mem->spi->dev, "[cmd: 0x%02x][%dB addr: %#8llx][%2dB dummy][%4dB data %s] %d%c-%d%c-%d%c-%d%c @ %uHz\n",
op->cmd.opcode,
op->addr.nbytes, (op->addr.nbytes ? op->addr.val : 0),
op->dummy.nbytes,
op->data.nbytes, (op->data.nbytes ? (op->data.dir == SPI_MEM_DATA_IN ? " read" : "write") : " "),
op->cmd.buswidth, op->cmd.dtr ? 'D' : 'S',
op->addr.buswidth, op->addr.dtr ? 'D' : 'S',
op->dummy.buswidth, op->dummy.dtr ? 'D' : 'S',
op->data.buswidth, op->data.dtr ? 'D' : 'S',
op->max_freq ? op->max_freq : mem->spi->max_speed_hz);
ret = spi_mem_check_op(op);
if (ret)
return ret;

View file

@ -20,6 +20,7 @@
#include <linux/spi/spi.h>
#include <linux/spi/spi-mem.h>
#include <linux/dma-mapping.h>
#include <linux/pm_qos.h>
#define SPI_CFG0_REG 0x0000
#define SPI_CFG1_REG 0x0004
@ -147,6 +148,7 @@ struct mtk_spi_compatible {
* @tx_sgl_len: Size of TX DMA transfer
* @rx_sgl_len: Size of RX DMA transfer
* @dev_comp: Device data structure
* @qos_request: QoS request
* @spi_clk_hz: Current SPI clock in Hz
* @spimem_done: SPI-MEM operation completion
* @use_spimem: Enables SPI-MEM
@ -166,6 +168,7 @@ struct mtk_spi {
struct scatterlist *tx_sgl, *rx_sgl;
u32 tx_sgl_len, rx_sgl_len;
const struct mtk_spi_compatible *dev_comp;
struct pm_qos_request qos_request;
u32 spi_clk_hz;
struct completion spimem_done;
bool use_spimem;
@ -356,6 +359,7 @@ static int mtk_spi_hw_init(struct spi_controller *host,
struct mtk_chip_config *chip_config = spi->controller_data;
struct mtk_spi *mdata = spi_controller_get_devdata(host);
cpu_latency_qos_update_request(&mdata->qos_request, 500);
cpha = spi->mode & SPI_CPHA ? 1 : 0;
cpol = spi->mode & SPI_CPOL ? 1 : 0;
@ -459,6 +463,15 @@ static int mtk_spi_prepare_message(struct spi_controller *host,
return mtk_spi_hw_init(host, msg->spi);
}
static int mtk_spi_unprepare_message(struct spi_controller *host,
struct spi_message *message)
{
struct mtk_spi *mdata = spi_controller_get_devdata(host);
cpu_latency_qos_update_request(&mdata->qos_request, PM_QOS_DEFAULT_VALUE);
return 0;
}
static void mtk_spi_set_cs(struct spi_device *spi, bool enable)
{
u32 reg_val;
@ -1143,6 +1156,7 @@ static int mtk_spi_probe(struct platform_device *pdev)
host->set_cs = mtk_spi_set_cs;
host->prepare_message = mtk_spi_prepare_message;
host->unprepare_message = mtk_spi_unprepare_message;
host->transfer_one = mtk_spi_transfer_one;
host->can_dma = mtk_spi_can_dma;
host->setup = mtk_spi_setup;
@ -1249,6 +1263,8 @@ static int mtk_spi_probe(struct platform_device *pdev)
clk_disable_unprepare(mdata->spi_hclk);
}
cpu_latency_qos_add_request(&mdata->qos_request, PM_QOS_DEFAULT_VALUE);
if (mdata->dev_comp->need_pad_sel) {
if (mdata->pad_num != host->num_chipselect)
return dev_err_probe(dev, -EINVAL,
@ -1292,6 +1308,7 @@ static void mtk_spi_remove(struct platform_device *pdev)
struct mtk_spi *mdata = spi_controller_get_devdata(host);
int ret;
cpu_latency_qos_remove_request(&mdata->qos_request);
if (mdata->use_spimem && !completion_done(&mdata->spimem_done))
complete(&mdata->spimem_done);

View file

@ -1284,9 +1284,6 @@ static int mtk_snand_exec_op(struct spi_mem *mem, const struct spi_mem_op *op)
{
struct mtk_snand *ms = spi_controller_get_devdata(mem->spi->controller);
dev_dbg(ms->dev, "OP %02x ADDR %08llX@%d:%u DATA %d:%u", op->cmd.opcode,
op->addr.val, op->addr.buswidth, op->addr.nbytes,
op->data.buswidth, op->data.nbytes);
if (mtk_snand_is_page_ops(op)) {
if (op->data.dir == SPI_MEM_DATA_IN)
return mtk_snand_read_page_cache(ms, op);

View file

@ -68,9 +68,7 @@ static int spi_mux_select(struct spi_device *spi)
priv->current_cs = spi_get_chipselect(spi, 0);
spi_setup(priv->spi);
return 0;
return spi_setup(priv->spi);
}
static int spi_mux_setup(struct spi_device *spi)

View file

@ -550,11 +550,6 @@ static int npcm_fiu_exec_op(struct spi_mem *mem, const struct spi_mem_op *op)
int ret = 0;
u8 *buf;
dev_dbg(fiu->dev, "cmd:%#x mode:%d.%d.%d.%d addr:%#llx len:%#x\n",
op->cmd.opcode, op->cmd.buswidth, op->addr.buswidth,
op->dummy.buswidth, op->data.buswidth, op->addr.val,
op->data.nbytes);
if (fiu->spix_mode || op->addr.nbytes > 4)
return -EOPNOTSUPP;

View file

@ -0,0 +1,169 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2024 Analog Devices Inc.
* Copyright (C) 2024 BayLibre, SAS
*
* Generic PWM trigger for SPI offload.
*/
#include <linux/device.h>
#include <linux/err.h>
#include <linux/math.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/property.h>
#include <linux/pwm.h>
#include <linux/spi/offload/provider.h>
#include <linux/spi/offload/types.h>
#include <linux/time.h>
#include <linux/types.h>
struct spi_offload_trigger_pwm_state {
struct device *dev;
struct pwm_device *pwm;
};
static bool spi_offload_trigger_pwm_match(struct spi_offload_trigger *trigger,
enum spi_offload_trigger_type type,
u64 *args, u32 nargs)
{
if (nargs)
return false;
return type == SPI_OFFLOAD_TRIGGER_PERIODIC;
}
static int spi_offload_trigger_pwm_validate(struct spi_offload_trigger *trigger,
struct spi_offload_trigger_config *config)
{
struct spi_offload_trigger_pwm_state *st = spi_offload_trigger_get_priv(trigger);
struct spi_offload_trigger_periodic *periodic = &config->periodic;
struct pwm_waveform wf = { };
int ret;
if (config->type != SPI_OFFLOAD_TRIGGER_PERIODIC)
return -EINVAL;
if (!periodic->frequency_hz)
return -EINVAL;
wf.period_length_ns = DIV_ROUND_UP_ULL(NSEC_PER_SEC, periodic->frequency_hz);
/* REVISIT: 50% duty-cycle for now - may add config parameter later */
wf.duty_length_ns = wf.period_length_ns / 2;
ret = pwm_round_waveform_might_sleep(st->pwm, &wf);
if (ret < 0)
return ret;
periodic->frequency_hz = DIV_ROUND_UP_ULL(NSEC_PER_SEC, wf.period_length_ns);
return 0;
}
static int spi_offload_trigger_pwm_enable(struct spi_offload_trigger *trigger,
struct spi_offload_trigger_config *config)
{
struct spi_offload_trigger_pwm_state *st = spi_offload_trigger_get_priv(trigger);
struct spi_offload_trigger_periodic *periodic = &config->periodic;
struct pwm_waveform wf = { };
if (config->type != SPI_OFFLOAD_TRIGGER_PERIODIC)
return -EINVAL;
if (!periodic->frequency_hz)
return -EINVAL;
wf.period_length_ns = DIV_ROUND_UP_ULL(NSEC_PER_SEC, periodic->frequency_hz);
/* REVISIT: 50% duty-cycle for now - may add config parameter later */
wf.duty_length_ns = wf.period_length_ns / 2;
return pwm_set_waveform_might_sleep(st->pwm, &wf, false);
}
static void spi_offload_trigger_pwm_disable(struct spi_offload_trigger *trigger)
{
struct spi_offload_trigger_pwm_state *st = spi_offload_trigger_get_priv(trigger);
struct pwm_waveform wf;
int ret;
ret = pwm_get_waveform_might_sleep(st->pwm, &wf);
if (ret < 0) {
dev_err(st->dev, "failed to get waveform: %d\n", ret);
return;
}
wf.duty_length_ns = 0;
ret = pwm_set_waveform_might_sleep(st->pwm, &wf, false);
if (ret < 0)
dev_err(st->dev, "failed to disable PWM: %d\n", ret);
}
static const struct spi_offload_trigger_ops spi_offload_trigger_pwm_ops = {
.match = spi_offload_trigger_pwm_match,
.validate = spi_offload_trigger_pwm_validate,
.enable = spi_offload_trigger_pwm_enable,
.disable = spi_offload_trigger_pwm_disable,
};
static void spi_offload_trigger_pwm_release(void *data)
{
pwm_disable(data);
}
static int spi_offload_trigger_pwm_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct spi_offload_trigger_info info = {
.fwnode = dev_fwnode(dev),
.ops = &spi_offload_trigger_pwm_ops,
};
struct spi_offload_trigger_pwm_state *st;
struct pwm_state state;
int ret;
st = devm_kzalloc(dev, sizeof(*st), GFP_KERNEL);
if (!st)
return -ENOMEM;
info.priv = st;
st->dev = dev;
st->pwm = devm_pwm_get(dev, NULL);
if (IS_ERR(st->pwm))
return dev_err_probe(dev, PTR_ERR(st->pwm), "failed to get PWM\n");
/* init with duty_cycle = 0, output enabled to ensure trigger off */
pwm_init_state(st->pwm, &state);
state.enabled = true;
ret = pwm_apply_might_sleep(st->pwm, &state);
if (ret < 0)
return dev_err_probe(dev, ret, "failed to apply PWM state\n");
ret = devm_add_action_or_reset(dev, spi_offload_trigger_pwm_release, st->pwm);
if (ret)
return ret;
return devm_spi_offload_trigger_register(dev, &info);
}
static const struct of_device_id spi_offload_trigger_pwm_of_match_table[] = {
{ .compatible = "pwm-trigger" },
{ }
};
MODULE_DEVICE_TABLE(of, spi_offload_trigger_pwm_of_match_table);
static struct platform_driver spi_offload_trigger_pwm_driver = {
.driver = {
.name = "pwm-trigger",
.of_match_table = spi_offload_trigger_pwm_of_match_table,
},
.probe = spi_offload_trigger_pwm_probe,
};
module_platform_driver(spi_offload_trigger_pwm_driver);
MODULE_AUTHOR("David Lechner <dlechner@baylibre.com>");
MODULE_DESCRIPTION("Generic PWM trigger");
MODULE_LICENSE("GPL");

468
drivers/spi/spi-offload.c Normal file
View file

@ -0,0 +1,468 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2024 Analog Devices Inc.
* Copyright (C) 2024 BayLibre, SAS
*/
/*
* SPI Offloading support.
*
* Some SPI controllers support offloading of SPI transfers. Essentially, this
* is the ability for a SPI controller to perform SPI transfers with minimal
* or even no CPU intervention, e.g. via a specialized SPI controller with a
* hardware trigger or via a conventional SPI controller using a non-Linux MCU
* processor core to offload the work.
*/
#define DEFAULT_SYMBOL_NAMESPACE "SPI_OFFLOAD"
#include <linux/cleanup.h>
#include <linux/device.h>
#include <linux/dmaengine.h>
#include <linux/export.h>
#include <linux/kref.h>
#include <linux/list.h>
#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/property.h>
#include <linux/spi/offload/consumer.h>
#include <linux/spi/offload/provider.h>
#include <linux/spi/offload/types.h>
#include <linux/spi/spi.h>
#include <linux/types.h>
struct spi_controller_and_offload {
struct spi_controller *controller;
struct spi_offload *offload;
};
struct spi_offload_trigger {
struct list_head list;
struct kref ref;
struct fwnode_handle *fwnode;
/* synchronizes calling ops and driver registration */
struct mutex lock;
/*
* If the provider goes away while the consumer still has a reference,
* ops and priv will be set to NULL and all calls will fail with -ENODEV.
*/
const struct spi_offload_trigger_ops *ops;
void *priv;
};
static LIST_HEAD(spi_offload_triggers);
static DEFINE_MUTEX(spi_offload_triggers_lock);
/**
* devm_spi_offload_alloc() - Allocate offload instance
* @dev: Device for devm purposes and assigned to &struct spi_offload.provider_dev
* @priv_size: Size of private data to allocate
*
* Offload providers should use this to allocate offload instances.
*
* Return: Pointer to new offload instance or error on failure.
*/
struct spi_offload *devm_spi_offload_alloc(struct device *dev,
size_t priv_size)
{
struct spi_offload *offload;
void *priv;
offload = devm_kzalloc(dev, sizeof(*offload), GFP_KERNEL);
if (!offload)
return ERR_PTR(-ENOMEM);
priv = devm_kzalloc(dev, priv_size, GFP_KERNEL);
if (!priv)
return ERR_PTR(-ENOMEM);
offload->provider_dev = dev;
offload->priv = priv;
return offload;
}
EXPORT_SYMBOL_GPL(devm_spi_offload_alloc);
static void spi_offload_put(void *data)
{
struct spi_controller_and_offload *resource = data;
resource->controller->put_offload(resource->offload);
kfree(resource);
}
/**
* devm_spi_offload_get() - Get an offload instance
* @dev: Device for devm purposes
* @spi: SPI device to use for the transfers
* @config: Offload configuration
*
* Peripheral drivers call this function to get an offload instance that meets
* the requirements specified in @config. If no suitable offload instance is
* available, -ENODEV is returned.
*
* Return: Offload instance or error on failure.
*/
struct spi_offload *devm_spi_offload_get(struct device *dev,
struct spi_device *spi,
const struct spi_offload_config *config)
{
struct spi_controller_and_offload *resource;
struct spi_offload *offload;
int ret;
if (!spi || !config)
return ERR_PTR(-EINVAL);
if (!spi->controller->get_offload)
return ERR_PTR(-ENODEV);
resource = kzalloc(sizeof(*resource), GFP_KERNEL);
if (!resource)
return ERR_PTR(-ENOMEM);
offload = spi->controller->get_offload(spi, config);
if (IS_ERR(offload)) {
kfree(resource);
return offload;
}
resource->controller = spi->controller;
resource->offload = offload;
ret = devm_add_action_or_reset(dev, spi_offload_put, resource);
if (ret)
return ERR_PTR(ret);
return offload;
}
EXPORT_SYMBOL_GPL(devm_spi_offload_get);
static void spi_offload_trigger_free(struct kref *ref)
{
struct spi_offload_trigger *trigger =
container_of(ref, struct spi_offload_trigger, ref);
mutex_destroy(&trigger->lock);
fwnode_handle_put(trigger->fwnode);
kfree(trigger);
}
static void spi_offload_trigger_put(void *data)
{
struct spi_offload_trigger *trigger = data;
scoped_guard(mutex, &trigger->lock)
if (trigger->ops && trigger->ops->release)
trigger->ops->release(trigger);
kref_put(&trigger->ref, spi_offload_trigger_free);
}
static struct spi_offload_trigger
*spi_offload_trigger_get(enum spi_offload_trigger_type type,
struct fwnode_reference_args *args)
{
struct spi_offload_trigger *trigger;
bool match = false;
int ret;
guard(mutex)(&spi_offload_triggers_lock);
list_for_each_entry(trigger, &spi_offload_triggers, list) {
if (trigger->fwnode != args->fwnode)
continue;
match = trigger->ops->match(trigger, type, args->args, args->nargs);
if (match)
break;
}
if (!match)
return ERR_PTR(-EPROBE_DEFER);
guard(mutex)(&trigger->lock);
if (!trigger->ops)
return ERR_PTR(-ENODEV);
if (trigger->ops->request) {
ret = trigger->ops->request(trigger, type, args->args, args->nargs);
if (ret)
return ERR_PTR(ret);
}
kref_get(&trigger->ref);
return trigger;
}
/**
* devm_spi_offload_trigger_get() - Get an offload trigger instance
* @dev: Device for devm purposes.
* @offload: Offload instance connected to a trigger.
* @type: Trigger type to get.
*
* Return: Offload trigger instance or error on failure.
*/
struct spi_offload_trigger
*devm_spi_offload_trigger_get(struct device *dev,
struct spi_offload *offload,
enum spi_offload_trigger_type type)
{
struct spi_offload_trigger *trigger;
struct fwnode_reference_args args;
int ret;
ret = fwnode_property_get_reference_args(dev_fwnode(offload->provider_dev),
"trigger-sources",
"#trigger-source-cells", 0, 0,
&args);
if (ret)
return ERR_PTR(ret);
trigger = spi_offload_trigger_get(type, &args);
fwnode_handle_put(args.fwnode);
if (IS_ERR(trigger))
return trigger;
ret = devm_add_action_or_reset(dev, spi_offload_trigger_put, trigger);
if (ret)
return ERR_PTR(ret);
return trigger;
}
EXPORT_SYMBOL_GPL(devm_spi_offload_trigger_get);
/**
* spi_offload_trigger_validate - Validate the requested trigger
* @trigger: Offload trigger instance
* @config: Trigger config to validate
*
* On success, @config may be modifed to reflect what the hardware can do.
* For example, the frequency of a periodic trigger may be adjusted to the
* nearest supported value.
*
* Callers will likely need to do additional validation of the modified trigger
* parameters.
*
* Return: 0 on success, negative error code on failure.
*/
int spi_offload_trigger_validate(struct spi_offload_trigger *trigger,
struct spi_offload_trigger_config *config)
{
guard(mutex)(&trigger->lock);
if (!trigger->ops)
return -ENODEV;
if (!trigger->ops->validate)
return -EOPNOTSUPP;
return trigger->ops->validate(trigger, config);
}
EXPORT_SYMBOL_GPL(spi_offload_trigger_validate);
/**
* spi_offload_trigger_enable - enables trigger for offload
* @offload: Offload instance
* @trigger: Offload trigger instance
* @config: Trigger config to validate
*
* There must be a prepared offload instance with the specified ID (i.e.
* spi_optimize_message() was called with the same offload assigned to the
* message). This will also reserve the bus for exclusive use by the offload
* instance until the trigger is disabled. Any other attempts to send a
* transfer or lock the bus will fail with -EBUSY during this time.
*
* Calls must be balanced with spi_offload_trigger_disable().
*
* Context: can sleep
* Return: 0 on success, else a negative error code.
*/
int spi_offload_trigger_enable(struct spi_offload *offload,
struct spi_offload_trigger *trigger,
struct spi_offload_trigger_config *config)
{
int ret;
guard(mutex)(&trigger->lock);
if (!trigger->ops)
return -ENODEV;
if (offload->ops && offload->ops->trigger_enable) {
ret = offload->ops->trigger_enable(offload);
if (ret)
return ret;
}
if (trigger->ops->enable) {
ret = trigger->ops->enable(trigger, config);
if (ret) {
if (offload->ops->trigger_disable)
offload->ops->trigger_disable(offload);
return ret;
}
}
return 0;
}
EXPORT_SYMBOL_GPL(spi_offload_trigger_enable);
/**
* spi_offload_trigger_disable - disables hardware trigger for offload
* @offload: Offload instance
* @trigger: Offload trigger instance
*
* Disables the hardware trigger for the offload instance with the specified ID
* and releases the bus for use by other clients.
*
* Context: can sleep
*/
void spi_offload_trigger_disable(struct spi_offload *offload,
struct spi_offload_trigger *trigger)
{
if (offload->ops && offload->ops->trigger_disable)
offload->ops->trigger_disable(offload);
guard(mutex)(&trigger->lock);
if (!trigger->ops)
return;
if (trigger->ops->disable)
trigger->ops->disable(trigger);
}
EXPORT_SYMBOL_GPL(spi_offload_trigger_disable);
static void spi_offload_release_dma_chan(void *chan)
{
dma_release_channel(chan);
}
/**
* devm_spi_offload_tx_stream_request_dma_chan - Get the DMA channel info for the TX stream
* @dev: Device for devm purposes.
* @offload: Offload instance
*
* This is the DMA channel that will provide data to transfers that use the
* %SPI_OFFLOAD_XFER_TX_STREAM offload flag.
*
* Return: Pointer to DMA channel info, or negative error code
*/
struct dma_chan
*devm_spi_offload_tx_stream_request_dma_chan(struct device *dev,
struct spi_offload *offload)
{
struct dma_chan *chan;
int ret;
if (!offload->ops || !offload->ops->tx_stream_request_dma_chan)
return ERR_PTR(-EOPNOTSUPP);
chan = offload->ops->tx_stream_request_dma_chan(offload);
if (IS_ERR(chan))
return chan;
ret = devm_add_action_or_reset(dev, spi_offload_release_dma_chan, chan);
if (ret)
return ERR_PTR(ret);
return chan;
}
EXPORT_SYMBOL_GPL(devm_spi_offload_tx_stream_request_dma_chan);
/**
* devm_spi_offload_rx_stream_request_dma_chan - Get the DMA channel info for the RX stream
* @dev: Device for devm purposes.
* @offload: Offload instance
*
* This is the DMA channel that will receive data from transfers that use the
* %SPI_OFFLOAD_XFER_RX_STREAM offload flag.
*
* Return: Pointer to DMA channel info, or negative error code
*/
struct dma_chan
*devm_spi_offload_rx_stream_request_dma_chan(struct device *dev,
struct spi_offload *offload)
{
struct dma_chan *chan;
int ret;
if (!offload->ops || !offload->ops->rx_stream_request_dma_chan)
return ERR_PTR(-EOPNOTSUPP);
chan = offload->ops->rx_stream_request_dma_chan(offload);
if (IS_ERR(chan))
return chan;
ret = devm_add_action_or_reset(dev, spi_offload_release_dma_chan, chan);
if (ret)
return ERR_PTR(ret);
return chan;
}
EXPORT_SYMBOL_GPL(devm_spi_offload_rx_stream_request_dma_chan);
/* Triggers providers */
static void spi_offload_trigger_unregister(void *data)
{
struct spi_offload_trigger *trigger = data;
scoped_guard(mutex, &spi_offload_triggers_lock)
list_del(&trigger->list);
scoped_guard(mutex, &trigger->lock) {
trigger->priv = NULL;
trigger->ops = NULL;
}
kref_put(&trigger->ref, spi_offload_trigger_free);
}
/**
* devm_spi_offload_trigger_register() - Allocate and register an offload trigger
* @dev: Device for devm purposes.
* @info: Provider-specific trigger info.
*
* Return: 0 on success, else a negative error code.
*/
int devm_spi_offload_trigger_register(struct device *dev,
struct spi_offload_trigger_info *info)
{
struct spi_offload_trigger *trigger;
if (!info->fwnode || !info->ops)
return -EINVAL;
trigger = kzalloc(sizeof(*trigger), GFP_KERNEL);
if (!trigger)
return -ENOMEM;
kref_init(&trigger->ref);
mutex_init(&trigger->lock);
trigger->fwnode = fwnode_handle_get(info->fwnode);
trigger->ops = info->ops;
trigger->priv = info->priv;
scoped_guard(mutex, &spi_offload_triggers_lock)
list_add_tail(&trigger->list, &spi_offload_triggers);
return devm_add_action_or_reset(dev, spi_offload_trigger_unregister, trigger);
}
EXPORT_SYMBOL_GPL(devm_spi_offload_trigger_register);
/**
* spi_offload_trigger_get_priv() - Get the private data for the trigger
*
* @trigger: Offload trigger instance.
*
* Return: Private data for the trigger.
*/
void *spi_offload_trigger_get_priv(struct spi_offload_trigger *trigger)
{
return trigger->priv;
}
EXPORT_SYMBOL_GPL(spi_offload_trigger_get_priv);

1633
drivers/spi/spi-qpic-snand.c Normal file

File diff suppressed because it is too large Load diff

View file

@ -364,7 +364,6 @@ static int rtl_snand_probe(struct platform_device *pdev)
.reg_bits = 32,
.val_bits = 32,
.reg_stride = 4,
.cache_type = REGCACHE_NONE,
};
int irq, ret;

View file

@ -139,7 +139,9 @@ struct s3c64xx_spi_dma_data {
* struct s3c64xx_spi_port_config - SPI Controller hardware info
* @fifo_lvl_mask: [DEPRECATED] use @{rx, tx}_fifomask instead.
* @rx_lvl_offset: [DEPRECATED] use @{rx,tx}_fifomask instead.
* @fifo_depth: depth of the FIFO.
* @fifo_depth: depth of the FIFOs. Used by compatibles where all the instances
* of the IP define the same FIFO depth. It has higher precedence
* than the FIFO depth specified via DT.
* @rx_fifomask: SPI_STATUS.RX_FIFO_LVL mask. Shifted mask defining the field's
* length and position.
* @tx_fifomask: SPI_STATUS.TX_FIFO_LVL mask. Shifted mask defining the field's

View file

@ -0,0 +1,488 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* SG2044 SPI NOR controller driver
*
* Copyright (c) 2025 Longbin Li <looong.bin@gmail.com>
*/
#include <linux/bitfield.h>
#include <linux/clk.h>
#include <linux/iopoll.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/spi/spi-mem.h>
/* Hardware register definitions */
#define SPIFMC_CTRL 0x00
#define SPIFMC_CTRL_CPHA BIT(12)
#define SPIFMC_CTRL_CPOL BIT(13)
#define SPIFMC_CTRL_HOLD_OL BIT(14)
#define SPIFMC_CTRL_WP_OL BIT(15)
#define SPIFMC_CTRL_LSBF BIT(20)
#define SPIFMC_CTRL_SRST BIT(21)
#define SPIFMC_CTRL_SCK_DIV_SHIFT 0
#define SPIFMC_CTRL_FRAME_LEN_SHIFT 16
#define SPIFMC_CTRL_SCK_DIV_MASK 0x7FF
#define SPIFMC_CE_CTRL 0x04
#define SPIFMC_CE_CTRL_CEMANUAL BIT(0)
#define SPIFMC_CE_CTRL_CEMANUAL_EN BIT(1)
#define SPIFMC_DLY_CTRL 0x08
#define SPIFMC_CTRL_FM_INTVL_MASK 0x000f
#define SPIFMC_CTRL_FM_INTVL BIT(0)
#define SPIFMC_CTRL_CET_MASK 0x0f00
#define SPIFMC_CTRL_CET BIT(8)
#define SPIFMC_DMMR 0x0c
#define SPIFMC_TRAN_CSR 0x10
#define SPIFMC_TRAN_CSR_TRAN_MODE_MASK GENMASK(1, 0)
#define SPIFMC_TRAN_CSR_TRAN_MODE_RX BIT(0)
#define SPIFMC_TRAN_CSR_TRAN_MODE_TX BIT(1)
#define SPIFMC_TRAN_CSR_FAST_MODE BIT(3)
#define SPIFMC_TRAN_CSR_BUS_WIDTH_1_BIT (0x00 << 4)
#define SPIFMC_TRAN_CSR_BUS_WIDTH_2_BIT (0x01 << 4)
#define SPIFMC_TRAN_CSR_BUS_WIDTH_4_BIT (0x02 << 4)
#define SPIFMC_TRAN_CSR_DMA_EN BIT(6)
#define SPIFMC_TRAN_CSR_MISO_LEVEL BIT(7)
#define SPIFMC_TRAN_CSR_ADDR_BYTES_MASK GENMASK(10, 8)
#define SPIFMC_TRAN_CSR_ADDR_BYTES_SHIFT 8
#define SPIFMC_TRAN_CSR_WITH_CMD BIT(11)
#define SPIFMC_TRAN_CSR_FIFO_TRG_LVL_MASK GENMASK(13, 12)
#define SPIFMC_TRAN_CSR_FIFO_TRG_LVL_1_BYTE (0x00 << 12)
#define SPIFMC_TRAN_CSR_FIFO_TRG_LVL_2_BYTE (0x01 << 12)
#define SPIFMC_TRAN_CSR_FIFO_TRG_LVL_4_BYTE (0x02 << 12)
#define SPIFMC_TRAN_CSR_FIFO_TRG_LVL_8_BYTE (0x03 << 12)
#define SPIFMC_TRAN_CSR_GO_BUSY BIT(15)
#define SPIFMC_TRAN_CSR_ADDR4B_SHIFT 20
#define SPIFMC_TRAN_CSR_CMD4B_SHIFT 21
#define SPIFMC_TRAN_NUM 0x14
#define SPIFMC_FIFO_PORT 0x18
#define SPIFMC_FIFO_PT 0x20
#define SPIFMC_INT_STS 0x28
#define SPIFMC_INT_TRAN_DONE BIT(0)
#define SPIFMC_INT_RD_FIFO BIT(2)
#define SPIFMC_INT_WR_FIFO BIT(3)
#define SPIFMC_INT_RX_FRAME BIT(4)
#define SPIFMC_INT_TX_FRAME BIT(5)
#define SPIFMC_INT_EN 0x2c
#define SPIFMC_INT_TRAN_DONE_EN BIT(0)
#define SPIFMC_INT_RD_FIFO_EN BIT(2)
#define SPIFMC_INT_WR_FIFO_EN BIT(3)
#define SPIFMC_INT_RX_FRAME_EN BIT(4)
#define SPIFMC_INT_TX_FRAME_EN BIT(5)
#define SPIFMC_OPT 0x030
#define SPIFMC_OPT_DISABLE_FIFO_FLUSH BIT(1)
#define SPIFMC_MAX_FIFO_DEPTH 8
#define SPIFMC_MAX_READ_SIZE 0x10000
struct sg2044_spifmc {
struct spi_controller *ctrl;
void __iomem *io_base;
struct device *dev;
struct mutex lock;
struct clk *clk;
};
static int sg2044_spifmc_wait_int(struct sg2044_spifmc *spifmc, u8 int_type)
{
u32 stat;
return readl_poll_timeout(spifmc->io_base + SPIFMC_INT_STS, stat,
(stat & int_type), 0, 1000000);
}
static int sg2044_spifmc_wait_xfer_size(struct sg2044_spifmc *spifmc,
int xfer_size)
{
u8 stat;
return readl_poll_timeout(spifmc->io_base + SPIFMC_FIFO_PT, stat,
((stat & 0xf) == xfer_size), 1, 1000000);
}
static u32 sg2044_spifmc_init_reg(struct sg2044_spifmc *spifmc)
{
u32 reg;
reg = readl(spifmc->io_base + SPIFMC_TRAN_CSR);
reg &= ~(SPIFMC_TRAN_CSR_TRAN_MODE_MASK |
SPIFMC_TRAN_CSR_FAST_MODE |
SPIFMC_TRAN_CSR_BUS_WIDTH_2_BIT |
SPIFMC_TRAN_CSR_BUS_WIDTH_4_BIT |
SPIFMC_TRAN_CSR_DMA_EN |
SPIFMC_TRAN_CSR_ADDR_BYTES_MASK |
SPIFMC_TRAN_CSR_WITH_CMD |
SPIFMC_TRAN_CSR_FIFO_TRG_LVL_MASK);
writel(reg, spifmc->io_base + SPIFMC_TRAN_CSR);
return reg;
}
static ssize_t sg2044_spifmc_read_64k(struct sg2044_spifmc *spifmc,
const struct spi_mem_op *op, loff_t from,
size_t len, u_char *buf)
{
int xfer_size, offset;
u32 reg;
int ret;
int i;
reg = sg2044_spifmc_init_reg(spifmc);
reg |= (op->addr.nbytes + op->dummy.nbytes) << SPIFMC_TRAN_CSR_ADDR_BYTES_SHIFT;
reg |= SPIFMC_TRAN_CSR_FIFO_TRG_LVL_8_BYTE;
reg |= SPIFMC_TRAN_CSR_WITH_CMD;
reg |= SPIFMC_TRAN_CSR_TRAN_MODE_RX;
writel(0, spifmc->io_base + SPIFMC_FIFO_PT);
writeb(op->cmd.opcode, spifmc->io_base + SPIFMC_FIFO_PORT);
for (i = op->addr.nbytes - 1; i >= 0; i--)
writeb((from >> i * 8) & 0xff, spifmc->io_base + SPIFMC_FIFO_PORT);
for (i = 0; i < op->dummy.nbytes; i++)
writeb(0xff, spifmc->io_base + SPIFMC_FIFO_PORT);
writel(len, spifmc->io_base + SPIFMC_TRAN_NUM);
writel(0, spifmc->io_base + SPIFMC_INT_STS);
reg |= SPIFMC_TRAN_CSR_GO_BUSY;
writel(reg, spifmc->io_base + SPIFMC_TRAN_CSR);
ret = sg2044_spifmc_wait_int(spifmc, SPIFMC_INT_RD_FIFO);
if (ret < 0)
return ret;
offset = 0;
while (offset < len) {
xfer_size = min_t(size_t, SPIFMC_MAX_FIFO_DEPTH, len - offset);
ret = sg2044_spifmc_wait_xfer_size(spifmc, xfer_size);
if (ret < 0)
return ret;
for (i = 0; i < xfer_size; i++)
buf[i + offset] = readb(spifmc->io_base + SPIFMC_FIFO_PORT);
offset += xfer_size;
}
ret = sg2044_spifmc_wait_int(spifmc, SPIFMC_INT_TRAN_DONE);
if (ret < 0)
return ret;
writel(0, spifmc->io_base + SPIFMC_FIFO_PT);
return len;
}
static ssize_t sg2044_spifmc_read(struct sg2044_spifmc *spifmc,
const struct spi_mem_op *op)
{
size_t xfer_size;
size_t offset;
loff_t from = op->addr.val;
size_t len = op->data.nbytes;
int ret;
u8 *din = op->data.buf.in;
offset = 0;
while (offset < len) {
xfer_size = min_t(size_t, SPIFMC_MAX_READ_SIZE, len - offset);
ret = sg2044_spifmc_read_64k(spifmc, op, from, xfer_size, din);
if (ret < 0)
return ret;
offset += xfer_size;
din += xfer_size;
from += xfer_size;
}
return 0;
}
static ssize_t sg2044_spifmc_write(struct sg2044_spifmc *spifmc,
const struct spi_mem_op *op)
{
size_t xfer_size;
const u8 *dout = op->data.buf.out;
int i, offset;
int ret;
u32 reg;
reg = sg2044_spifmc_init_reg(spifmc);
reg |= (op->addr.nbytes + op->dummy.nbytes) << SPIFMC_TRAN_CSR_ADDR_BYTES_SHIFT;
reg |= SPIFMC_TRAN_CSR_FIFO_TRG_LVL_8_BYTE;
reg |= SPIFMC_TRAN_CSR_WITH_CMD;
reg |= SPIFMC_TRAN_CSR_TRAN_MODE_TX;
writel(0, spifmc->io_base + SPIFMC_FIFO_PT);
writeb(op->cmd.opcode, spifmc->io_base + SPIFMC_FIFO_PORT);
for (i = op->addr.nbytes - 1; i >= 0; i--)
writeb((op->addr.val >> i * 8) & 0xff, spifmc->io_base + SPIFMC_FIFO_PORT);
for (i = 0; i < op->dummy.nbytes; i++)
writeb(0xff, spifmc->io_base + SPIFMC_FIFO_PORT);
writel(0, spifmc->io_base + SPIFMC_INT_STS);
writel(op->data.nbytes, spifmc->io_base + SPIFMC_TRAN_NUM);
reg |= SPIFMC_TRAN_CSR_GO_BUSY;
writel(reg, spifmc->io_base + SPIFMC_TRAN_CSR);
ret = sg2044_spifmc_wait_xfer_size(spifmc, 0);
if (ret < 0)
return ret;
writel(0, spifmc->io_base + SPIFMC_FIFO_PT);
offset = 0;
while (offset < op->data.nbytes) {
xfer_size = min_t(size_t, SPIFMC_MAX_FIFO_DEPTH, op->data.nbytes - offset);
ret = sg2044_spifmc_wait_xfer_size(spifmc, 0);
if (ret < 0)
return ret;
for (i = 0; i < xfer_size; i++)
writeb(dout[i + offset], spifmc->io_base + SPIFMC_FIFO_PORT);
offset += xfer_size;
}
ret = sg2044_spifmc_wait_int(spifmc, SPIFMC_INT_TRAN_DONE);
if (ret < 0)
return ret;
writel(0, spifmc->io_base + SPIFMC_FIFO_PT);
return 0;
}
static ssize_t sg2044_spifmc_tran_cmd(struct sg2044_spifmc *spifmc,
const struct spi_mem_op *op)
{
int i, ret;
u32 reg;
reg = sg2044_spifmc_init_reg(spifmc);
reg |= (op->addr.nbytes + op->dummy.nbytes) << SPIFMC_TRAN_CSR_ADDR_BYTES_SHIFT;
reg |= SPIFMC_TRAN_CSR_FIFO_TRG_LVL_1_BYTE;
reg |= SPIFMC_TRAN_CSR_WITH_CMD;
writel(0, spifmc->io_base + SPIFMC_FIFO_PT);
writeb(op->cmd.opcode, spifmc->io_base + SPIFMC_FIFO_PORT);
for (i = op->addr.nbytes - 1; i >= 0; i--)
writeb((op->addr.val >> i * 8) & 0xff, spifmc->io_base + SPIFMC_FIFO_PORT);
for (i = 0; i < op->dummy.nbytes; i++)
writeb(0xff, spifmc->io_base + SPIFMC_FIFO_PORT);
writel(0, spifmc->io_base + SPIFMC_INT_STS);
reg |= SPIFMC_TRAN_CSR_GO_BUSY;
writel(reg, spifmc->io_base + SPIFMC_TRAN_CSR);
ret = sg2044_spifmc_wait_int(spifmc, SPIFMC_INT_TRAN_DONE);
if (ret < 0)
return ret;
writel(0, spifmc->io_base + SPIFMC_FIFO_PT);
return 0;
}
static void sg2044_spifmc_trans(struct sg2044_spifmc *spifmc,
const struct spi_mem_op *op)
{
if (op->data.dir == SPI_MEM_DATA_IN)
sg2044_spifmc_read(spifmc, op);
else if (op->data.dir == SPI_MEM_DATA_OUT)
sg2044_spifmc_write(spifmc, op);
else
sg2044_spifmc_tran_cmd(spifmc, op);
}
static ssize_t sg2044_spifmc_trans_reg(struct sg2044_spifmc *spifmc,
const struct spi_mem_op *op)
{
const u8 *dout = NULL;
u8 *din = NULL;
size_t len = op->data.nbytes;
int ret, i;
u32 reg;
if (op->data.dir == SPI_MEM_DATA_IN)
din = op->data.buf.in;
else
dout = op->data.buf.out;
reg = sg2044_spifmc_init_reg(spifmc);
reg |= SPIFMC_TRAN_CSR_FIFO_TRG_LVL_1_BYTE;
reg |= SPIFMC_TRAN_CSR_WITH_CMD;
if (din) {
reg |= SPIFMC_TRAN_CSR_BUS_WIDTH_1_BIT;
reg |= SPIFMC_TRAN_CSR_TRAN_MODE_RX;
reg |= SPIFMC_TRAN_CSR_TRAN_MODE_TX;
writel(SPIFMC_OPT_DISABLE_FIFO_FLUSH, spifmc->io_base + SPIFMC_OPT);
} else {
/*
* If write values to the Status Register,
* configure TRAN_CSR register as the same as
* sg2044_spifmc_read_reg.
*/
if (op->cmd.opcode == 0x01) {
reg |= SPIFMC_TRAN_CSR_TRAN_MODE_RX;
reg |= SPIFMC_TRAN_CSR_TRAN_MODE_TX;
writel(len, spifmc->io_base + SPIFMC_TRAN_NUM);
}
}
writel(0, spifmc->io_base + SPIFMC_FIFO_PT);
writeb(op->cmd.opcode, spifmc->io_base + SPIFMC_FIFO_PORT);
for (i = 0; i < len; i++) {
if (din)
writeb(0xff, spifmc->io_base + SPIFMC_FIFO_PORT);
else
writeb(dout[i], spifmc->io_base + SPIFMC_FIFO_PORT);
}
writel(0, spifmc->io_base + SPIFMC_INT_STS);
writel(len, spifmc->io_base + SPIFMC_TRAN_NUM);
reg |= SPIFMC_TRAN_CSR_GO_BUSY;
writel(reg, spifmc->io_base + SPIFMC_TRAN_CSR);
ret = sg2044_spifmc_wait_int(spifmc, SPIFMC_INT_TRAN_DONE);
if (ret < 0)
return ret;
if (din) {
while (len--)
*din++ = readb(spifmc->io_base + SPIFMC_FIFO_PORT);
}
writel(0, spifmc->io_base + SPIFMC_FIFO_PT);
return 0;
}
static int sg2044_spifmc_exec_op(struct spi_mem *mem,
const struct spi_mem_op *op)
{
struct sg2044_spifmc *spifmc;
spifmc = spi_controller_get_devdata(mem->spi->controller);
mutex_lock(&spifmc->lock);
if (op->addr.nbytes == 0)
sg2044_spifmc_trans_reg(spifmc, op);
else
sg2044_spifmc_trans(spifmc, op);
mutex_unlock(&spifmc->lock);
return 0;
}
static const struct spi_controller_mem_ops sg2044_spifmc_mem_ops = {
.exec_op = sg2044_spifmc_exec_op,
};
static void sg2044_spifmc_init(struct sg2044_spifmc *spifmc)
{
u32 tran_csr;
u32 reg;
writel(0, spifmc->io_base + SPIFMC_DMMR);
reg = readl(spifmc->io_base + SPIFMC_CTRL);
reg |= SPIFMC_CTRL_SRST;
reg &= ~(SPIFMC_CTRL_SCK_DIV_MASK);
reg |= 1;
writel(reg, spifmc->io_base + SPIFMC_CTRL);
writel(0, spifmc->io_base + SPIFMC_CE_CTRL);
tran_csr = readl(spifmc->io_base + SPIFMC_TRAN_CSR);
tran_csr |= (0 << SPIFMC_TRAN_CSR_ADDR_BYTES_SHIFT);
tran_csr |= SPIFMC_TRAN_CSR_FIFO_TRG_LVL_4_BYTE;
tran_csr |= SPIFMC_TRAN_CSR_WITH_CMD;
writel(tran_csr, spifmc->io_base + SPIFMC_TRAN_CSR);
}
static int sg2044_spifmc_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct spi_controller *ctrl;
struct sg2044_spifmc *spifmc;
int ret;
ctrl = devm_spi_alloc_host(&pdev->dev, sizeof(*spifmc));
if (!ctrl)
return -ENOMEM;
spifmc = spi_controller_get_devdata(ctrl);
spifmc->clk = devm_clk_get_enabled(&pdev->dev, NULL);
if (IS_ERR(spifmc->clk))
return dev_err_probe(dev, PTR_ERR(spifmc->clk), "Cannot get and enable AHB clock\n");
spifmc->dev = &pdev->dev;
spifmc->ctrl = ctrl;
spifmc->io_base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(spifmc->io_base))
return PTR_ERR(spifmc->io_base);
ctrl->num_chipselect = 1;
ctrl->dev.of_node = pdev->dev.of_node;
ctrl->bits_per_word_mask = SPI_BPW_MASK(8);
ctrl->auto_runtime_pm = false;
ctrl->mem_ops = &sg2044_spifmc_mem_ops;
ctrl->mode_bits = SPI_RX_DUAL | SPI_TX_DUAL | SPI_RX_QUAD | SPI_TX_QUAD;
ret = devm_mutex_init(dev, &spifmc->lock);
if (ret)
return ret;
sg2044_spifmc_init(spifmc);
sg2044_spifmc_init_reg(spifmc);
ret = devm_spi_register_controller(&pdev->dev, ctrl);
if (ret)
return dev_err_probe(dev, ret, "spi_register_controller failed\n");
return 0;
}
static const struct of_device_id sg2044_spifmc_match[] = {
{ .compatible = "sophgo,sg2044-spifmc-nor" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, sg2044_spifmc_match);
static struct platform_driver sg2044_nor_driver = {
.driver = {
.name = "sg2044,spifmc-nor",
.of_match_table = sg2044_spifmc_match,
},
.probe = sg2044_spifmc_probe,
};
module_platform_driver(sg2044_nor_driver);
MODULE_DESCRIPTION("SG2044 SPI NOR controller driver");
MODULE_AUTHOR("Longbin Li <looong.bin@gmail.com>");
MODULE_LICENSE("GPL");

1063
drivers/spi/spi-stm32-ospi.c Normal file

File diff suppressed because it is too large Load diff

View file

@ -362,11 +362,6 @@ static int stm32_qspi_send(struct spi_device *spi, const struct spi_mem_op *op)
u32 ccr, cr;
int timeout, err = 0, err_poll_status = 0;
dev_dbg(qspi->dev, "cmd:%#x mode:%d.%d.%d.%d addr:%#llx len:%#x\n",
op->cmd.opcode, op->cmd.buswidth, op->addr.buswidth,
op->dummy.buswidth, op->data.buswidth,
op->addr.val, op->data.nbytes);
cr = readl_relaxed(qspi->io_base + QSPI_CR);
cr &= ~CR_PRESC_MASK & ~CR_FSEL;
cr |= FIELD_PREP(CR_PRESC_MASK, flash->presc);

View file

@ -540,10 +540,6 @@ static int zynq_qspi_exec_mem_op(struct spi_mem *mem,
int err = 0, i;
u8 *tmpbuf;
dev_dbg(xqspi->dev, "cmd:%#x mode:%d.%d.%d.%d\n",
op->cmd.opcode, op->cmd.buswidth, op->addr.buswidth,
op->dummy.buswidth, op->data.buswidth);
zynq_qspi_chipselect(mem->spi, true);
zynq_qspi_config_op(xqspi, mem->spi, op);

View file

@ -82,7 +82,6 @@
#define GQSPI_GENFIFO_RX 0x00020000
#define GQSPI_GENFIFO_STRIPE 0x00040000
#define GQSPI_GENFIFO_POLL 0x00080000
#define GQSPI_GENFIFO_EXP_START 0x00000100
#define GQSPI_FIFO_CTRL_RST_RX_FIFO_MASK 0x00000004
#define GQSPI_FIFO_CTRL_RST_TX_FIFO_MASK 0x00000002
#define GQSPI_FIFO_CTRL_RST_GEN_FIFO_MASK 0x00000001
@ -580,6 +579,8 @@ static int zynqmp_qspi_config_op(struct zynqmp_qspi *xqspi,
zynqmp_gqspi_write(xqspi, GQSPI_CONFIG_OFST, config_reg);
zynqmp_qspi_set_tapdelay(xqspi, baud_rate_val);
}
dev_dbg(xqspi->dev, "config speed %u\n", req_speed_hz);
return 0;
}
@ -670,69 +671,77 @@ static void zynqmp_qspi_readrxfifo(struct zynqmp_qspi *xqspi, u32 size)
static void zynqmp_qspi_fillgenfifo(struct zynqmp_qspi *xqspi, u8 nbits,
u32 genfifoentry)
{
u32 transfer_len = 0;
u32 transfer_len, tempcount, exponent;
u8 imm_data;
if (xqspi->txbuf) {
genfifoentry &= ~GQSPI_GENFIFO_RX;
genfifoentry |= GQSPI_GENFIFO_DATA_XFER;
genfifoentry |= GQSPI_GENFIFO_TX;
transfer_len = xqspi->bytes_to_transfer;
} else if (xqspi->rxbuf) {
genfifoentry &= ~GQSPI_GENFIFO_TX;
genfifoentry |= GQSPI_GENFIFO_DATA_XFER;
genfifoentry |= GQSPI_GENFIFO_DATA_XFER;
if (xqspi->rxbuf) {
genfifoentry |= GQSPI_GENFIFO_RX;
if (xqspi->mode == GQSPI_MODE_DMA)
transfer_len = xqspi->dma_rx_bytes;
else
transfer_len = xqspi->bytes_to_receive;
} else {
/* Sending dummy circles here */
genfifoentry &= ~(GQSPI_GENFIFO_TX | GQSPI_GENFIFO_RX);
genfifoentry |= GQSPI_GENFIFO_DATA_XFER;
transfer_len = xqspi->bytes_to_transfer;
}
if (xqspi->txbuf)
genfifoentry |= GQSPI_GENFIFO_TX;
genfifoentry |= zynqmp_qspi_selectspimode(xqspi, nbits);
xqspi->genfifoentry = genfifoentry;
dev_dbg(xqspi->dev, "genfifo %05x transfer_len %u\n",
genfifoentry, transfer_len);
if ((transfer_len) < GQSPI_GENFIFO_IMM_DATA_MASK) {
genfifoentry &= ~GQSPI_GENFIFO_IMM_DATA_MASK;
genfifoentry |= transfer_len;
zynqmp_gqspi_write(xqspi, GQSPI_GEN_FIFO_OFST, genfifoentry);
} else {
int tempcount = transfer_len;
u32 exponent = 8; /* 2^8 = 256 */
u8 imm_data = tempcount & 0xFF;
tempcount &= ~(tempcount & 0xFF);
/* Immediate entry */
if (tempcount != 0) {
/* Exponent entries */
genfifoentry |= GQSPI_GENFIFO_EXP;
while (tempcount != 0) {
if (tempcount & GQSPI_GENFIFO_EXP_START) {
genfifoentry &=
~GQSPI_GENFIFO_IMM_DATA_MASK;
genfifoentry |= exponent;
zynqmp_gqspi_write(xqspi,
GQSPI_GEN_FIFO_OFST,
genfifoentry);
}
tempcount = tempcount >> 1;
exponent++;
}
}
if (imm_data != 0) {
genfifoentry &= ~GQSPI_GENFIFO_EXP;
genfifoentry &= ~GQSPI_GENFIFO_IMM_DATA_MASK;
genfifoentry |= (u8)(imm_data & 0xFF);
/* Exponent entries */
imm_data = transfer_len;
tempcount = transfer_len >> 8;
exponent = 8;
genfifoentry |= GQSPI_GENFIFO_EXP;
while (tempcount) {
if (tempcount & 1)
zynqmp_gqspi_write(xqspi, GQSPI_GEN_FIFO_OFST,
genfifoentry);
}
}
if (xqspi->mode == GQSPI_MODE_IO && xqspi->rxbuf) {
/* Dummy generic FIFO entry */
zynqmp_gqspi_write(xqspi, GQSPI_GEN_FIFO_OFST, 0x0);
genfifoentry | exponent);
tempcount >>= 1;
exponent++;
}
/* Immediate entry */
genfifoentry &= ~GQSPI_GENFIFO_EXP;
if (imm_data)
zynqmp_gqspi_write(xqspi, GQSPI_GEN_FIFO_OFST,
genfifoentry | imm_data);
/* Dummy generic FIFO entry */
if (xqspi->mode == GQSPI_MODE_IO && xqspi->rxbuf)
zynqmp_gqspi_write(xqspi, GQSPI_GEN_FIFO_OFST, 0);
}
/**
* zynqmp_qspi_disable_dma() - Disable DMA mode
* @xqspi: GQSPI instance
*/
static void zynqmp_qspi_disable_dma(struct zynqmp_qspi *xqspi)
{
u32 config_reg = zynqmp_gqspi_read(xqspi, GQSPI_CONFIG_OFST);
config_reg &= ~GQSPI_CFG_MODE_EN_MASK;
zynqmp_gqspi_write(xqspi, GQSPI_CONFIG_OFST, config_reg);
xqspi->mode = GQSPI_MODE_IO;
}
/**
* zynqmp_qspi_enable_dma() - Enable DMA mode
* @xqspi: GQSPI instance
*/
static void zynqmp_qspi_enable_dma(struct zynqmp_qspi *xqspi)
{
u32 config_reg = zynqmp_gqspi_read(xqspi, GQSPI_CONFIG_OFST);
config_reg &= ~GQSPI_CFG_MODE_EN_MASK;
config_reg |= GQSPI_CFG_MODE_EN_DMA_MASK;
zynqmp_gqspi_write(xqspi, GQSPI_CONFIG_OFST, config_reg);
xqspi->mode = GQSPI_MODE_DMA;
}
/**
@ -744,7 +753,7 @@ static void zynqmp_qspi_fillgenfifo(struct zynqmp_qspi *xqspi, u8 nbits,
*/
static void zynqmp_process_dma_irq(struct zynqmp_qspi *xqspi)
{
u32 config_reg, genfifoentry;
u32 genfifoentry;
dma_unmap_single(xqspi->dev, xqspi->dma_addr,
xqspi->dma_rx_bytes, DMA_FROM_DEVICE);
@ -758,9 +767,7 @@ static void zynqmp_process_dma_irq(struct zynqmp_qspi *xqspi)
if (xqspi->bytes_to_receive > 0) {
/* Switch to IO mode,for remaining bytes to receive */
config_reg = zynqmp_gqspi_read(xqspi, GQSPI_CONFIG_OFST);
config_reg &= ~GQSPI_CFG_MODE_EN_MASK;
zynqmp_gqspi_write(xqspi, GQSPI_CONFIG_OFST, config_reg);
zynqmp_qspi_disable_dma(xqspi);
/* Initiate the transfer of remaining bytes */
genfifoentry = xqspi->genfifoentry;
@ -799,7 +806,6 @@ static void zynqmp_process_dma_irq(struct zynqmp_qspi *xqspi)
static irqreturn_t zynqmp_qspi_irq(int irq, void *dev_id)
{
struct zynqmp_qspi *xqspi = (struct zynqmp_qspi *)dev_id;
irqreturn_t ret = IRQ_NONE;
u32 status, mask, dma_status = 0;
status = zynqmp_gqspi_read(xqspi, GQSPI_ISR_OFST);
@ -814,27 +820,24 @@ static irqreturn_t zynqmp_qspi_irq(int irq, void *dev_id)
dma_status);
}
if (mask & GQSPI_ISR_TXNOT_FULL_MASK) {
zynqmp_qspi_filltxfifo(xqspi, GQSPI_TX_FIFO_FILL);
ret = IRQ_HANDLED;
}
if (!mask && !dma_status)
return IRQ_NONE;
if (dma_status & GQSPI_QSPIDMA_DST_I_STS_DONE_MASK) {
if (mask & GQSPI_ISR_TXNOT_FULL_MASK)
zynqmp_qspi_filltxfifo(xqspi, GQSPI_TX_FIFO_FILL);
if (dma_status & GQSPI_QSPIDMA_DST_I_STS_DONE_MASK)
zynqmp_process_dma_irq(xqspi);
ret = IRQ_HANDLED;
} else if (!(mask & GQSPI_IER_RXEMPTY_MASK) &&
(mask & GQSPI_IER_GENFIFOEMPTY_MASK)) {
else if (!(mask & GQSPI_IER_RXEMPTY_MASK) &&
(mask & GQSPI_IER_GENFIFOEMPTY_MASK))
zynqmp_qspi_readrxfifo(xqspi, GQSPI_RX_FIFO_FILL);
ret = IRQ_HANDLED;
}
if (xqspi->bytes_to_receive == 0 && xqspi->bytes_to_transfer == 0 &&
((status & GQSPI_IRQ_MASK) == GQSPI_IRQ_MASK)) {
zynqmp_gqspi_write(xqspi, GQSPI_IDR_OFST, GQSPI_ISR_IDR_MASK);
complete(&xqspi->data_completion);
ret = IRQ_HANDLED;
}
return ret;
return IRQ_HANDLED;
}
/**
@ -845,17 +848,14 @@ static irqreturn_t zynqmp_qspi_irq(int irq, void *dev_id)
*/
static int zynqmp_qspi_setuprxdma(struct zynqmp_qspi *xqspi)
{
u32 rx_bytes, rx_rem, config_reg;
u32 rx_bytes, rx_rem;
dma_addr_t addr;
u64 dma_align = (u64)(uintptr_t)xqspi->rxbuf;
if (xqspi->bytes_to_receive < 8 ||
((dma_align & GQSPI_DMA_UNALIGN) != 0x0)) {
/* Setting to IO mode */
config_reg = zynqmp_gqspi_read(xqspi, GQSPI_CONFIG_OFST);
config_reg &= ~GQSPI_CFG_MODE_EN_MASK;
zynqmp_gqspi_write(xqspi, GQSPI_CONFIG_OFST, config_reg);
xqspi->mode = GQSPI_MODE_IO;
zynqmp_qspi_disable_dma(xqspi);
xqspi->dma_rx_bytes = 0;
return 0;
}
@ -878,14 +878,7 @@ static int zynqmp_qspi_setuprxdma(struct zynqmp_qspi *xqspi)
zynqmp_gqspi_write(xqspi, GQSPI_QSPIDMA_DST_ADDR_MSB_OFST,
((u32)addr) & 0xfff);
/* Enabling the DMA mode */
config_reg = zynqmp_gqspi_read(xqspi, GQSPI_CONFIG_OFST);
config_reg &= ~GQSPI_CFG_MODE_EN_MASK;
config_reg |= GQSPI_CFG_MODE_EN_DMA_MASK;
zynqmp_gqspi_write(xqspi, GQSPI_CONFIG_OFST, config_reg);
/* Switch to DMA mode */
xqspi->mode = GQSPI_MODE_DMA;
zynqmp_qspi_enable_dma(xqspi);
/* Write the number of bytes to transfer */
zynqmp_gqspi_write(xqspi, GQSPI_QSPIDMA_DST_SIZE_OFST, rx_bytes);
@ -905,18 +898,10 @@ static int zynqmp_qspi_setuprxdma(struct zynqmp_qspi *xqspi)
static void zynqmp_qspi_write_op(struct zynqmp_qspi *xqspi, u8 tx_nbits,
u32 genfifoentry)
{
u32 config_reg;
zynqmp_qspi_fillgenfifo(xqspi, tx_nbits, genfifoentry);
zynqmp_qspi_filltxfifo(xqspi, GQSPI_TXD_DEPTH);
if (xqspi->mode == GQSPI_MODE_DMA) {
config_reg = zynqmp_gqspi_read(xqspi,
GQSPI_CONFIG_OFST);
config_reg &= ~GQSPI_CFG_MODE_EN_MASK;
zynqmp_gqspi_write(xqspi, GQSPI_CONFIG_OFST,
config_reg);
xqspi->mode = GQSPI_MODE_IO;
}
if (xqspi->mode == GQSPI_MODE_DMA)
zynqmp_qspi_disable_dma(xqspi);
}
/**
@ -1059,18 +1044,14 @@ static unsigned long zynqmp_qspi_timeout(struct zynqmp_qspi *xqspi, u8 bits,
static int zynqmp_qspi_exec_op(struct spi_mem *mem,
const struct spi_mem_op *op)
{
struct zynqmp_qspi *xqspi = spi_controller_get_devdata
(mem->spi->controller);
struct zynqmp_qspi *xqspi =
spi_controller_get_devdata(mem->spi->controller);
unsigned long timeout;
int err = 0, i;
u32 genfifoentry = 0;
u16 opcode = op->cmd.opcode;
u64 opaddr;
dev_dbg(xqspi->dev, "cmd:%#x mode:%d.%d.%d.%d\n",
op->cmd.opcode, op->cmd.buswidth, op->addr.buswidth,
op->dummy.buswidth, op->data.buswidth);
mutex_lock(&xqspi->op_lock);
zynqmp_qspi_config_op(xqspi, op);
zynqmp_qspi_chipselect(mem->spi, false);

View file

@ -31,6 +31,7 @@
#include <linux/ptp_clock_kernel.h>
#include <linux/sched/rt.h>
#include <linux/slab.h>
#include <linux/spi/offload/types.h>
#include <linux/spi/spi.h>
#include <linux/spi/spi-mem.h>
#include <uapi/linux/sched/types.h>
@ -42,7 +43,7 @@ EXPORT_TRACEPOINT_SYMBOL(spi_transfer_stop);
#include "internals.h"
static DEFINE_IDR(spi_master_idr);
static DEFINE_IDR(spi_controller_idr);
static void spidev_release(struct device *dev)
{
@ -305,7 +306,7 @@ static const struct attribute_group spi_controller_statistics_group = {
.attrs = spi_controller_statistics_attrs,
};
static const struct attribute_group *spi_master_groups[] = {
static const struct attribute_group *spi_controller_groups[] = {
&spi_controller_statistics_group,
NULL,
};
@ -1106,7 +1107,7 @@ static void spi_set_cs(struct spi_device *spi, bool enable, bool force)
spi_toggle_csgpiod(spi, idx, enable, activate);
}
}
/* Some SPI masters need both GPIO CS & slave_select */
/* Some SPI controllers need both GPIO CS & ->set_cs() */
if ((spi->controller->flags & SPI_CONTROLLER_GPIO_SS) &&
spi->controller->set_cs)
spi->controller->set_cs(spi, !enable);
@ -1495,10 +1496,7 @@ static void _spi_transfer_delay_ns(u32 ns)
} else {
u32 us = DIV_ROUND_UP(ns, NSEC_PER_USEC);
if (us <= 10)
udelay(us);
else
usleep_range(us, us + DIV_ROUND_UP(us, 10));
fsleep(us);
}
}
@ -2534,7 +2532,7 @@ err_out:
* @ctlr: Pointer to spi_controller device
*
* Registers an spi_device for each child node of controller node which
* represents a valid SPI slave.
* represents a valid SPI target device.
*/
static void of_register_spi_devices(struct spi_controller *ctlr)
{
@ -2819,7 +2817,7 @@ struct spi_device *acpi_spi_device_alloc(struct spi_controller *ctlr,
if (!lookup.max_speed_hz &&
ACPI_SUCCESS(acpi_get_parent(adev->handle, &parent_handle)) &&
device_match_acpi_handle(lookup.ctlr->dev.parent, parent_handle)) {
/* Apple does not use _CRS but nested devices for SPI slaves */
/* Apple does not use _CRS but nested devices for SPI target devices */
acpi_spi_parse_apple_properties(adev, &lookup);
}
@ -2911,7 +2909,7 @@ static void acpi_register_spi_devices(struct spi_controller *ctlr)
SPI_ACPI_ENUMERATE_MAX_DEPTH,
acpi_spi_add_device, NULL, ctlr, NULL);
if (ACPI_FAILURE(status))
dev_warn(&ctlr->dev, "failed to enumerate SPI slaves\n");
dev_warn(&ctlr->dev, "failed to enumerate SPI target devices\n");
}
#else
static inline void acpi_register_spi_devices(struct spi_controller *ctlr) {}
@ -2925,16 +2923,15 @@ static void spi_controller_release(struct device *dev)
kfree(ctlr);
}
static const struct class spi_master_class = {
static const struct class spi_controller_class = {
.name = "spi_master",
.dev_release = spi_controller_release,
.dev_groups = spi_master_groups,
.dev_groups = spi_controller_groups,
};
#ifdef CONFIG_SPI_SLAVE
/**
* spi_target_abort - abort the ongoing transfer request on an SPI slave
* controller
* spi_target_abort - abort the ongoing transfer request on an SPI target controller
* @spi: device used for the current transfer
*/
int spi_target_abort(struct spi_device *spi)
@ -2979,13 +2976,13 @@ static ssize_t slave_store(struct device *dev, struct device_attribute *attr,
child = device_find_any_child(&ctlr->dev);
if (child) {
/* Remove registered slave */
/* Remove registered target device */
device_unregister(child);
put_device(child);
}
if (strcmp(name, "(null)")) {
/* Register new slave */
/* Register new target device */
spi = spi_alloc_device(ctlr);
if (!spi)
return -ENOMEM;
@ -3004,40 +3001,40 @@ static ssize_t slave_store(struct device *dev, struct device_attribute *attr,
static DEVICE_ATTR_RW(slave);
static struct attribute *spi_slave_attrs[] = {
static struct attribute *spi_target_attrs[] = {
&dev_attr_slave.attr,
NULL,
};
static const struct attribute_group spi_slave_group = {
.attrs = spi_slave_attrs,
static const struct attribute_group spi_target_group = {
.attrs = spi_target_attrs,
};
static const struct attribute_group *spi_slave_groups[] = {
static const struct attribute_group *spi_target_groups[] = {
&spi_controller_statistics_group,
&spi_slave_group,
&spi_target_group,
NULL,
};
static const struct class spi_slave_class = {
static const struct class spi_target_class = {
.name = "spi_slave",
.dev_release = spi_controller_release,
.dev_groups = spi_slave_groups,
.dev_groups = spi_target_groups,
};
#else
extern struct class spi_slave_class; /* dummy */
extern struct class spi_target_class; /* dummy */
#endif
/**
* __spi_alloc_controller - allocate an SPI master or slave controller
* __spi_alloc_controller - allocate an SPI host or target controller
* @dev: the controller, possibly using the platform_bus
* @size: how much zeroed driver-private data to allocate; the pointer to this
* memory is in the driver_data field of the returned device, accessible
* with spi_controller_get_devdata(); the memory is cacheline aligned;
* drivers granting DMA access to portions of their private data need to
* round up @size using ALIGN(size, dma_get_cache_alignment()).
* @slave: flag indicating whether to allocate an SPI master (false) or SPI
* slave (true) controller
* @target: flag indicating whether to allocate an SPI host (false) or SPI target (true)
* controller
* Context: can sleep
*
* This call is used only by SPI controller drivers, which are the
@ -3054,7 +3051,7 @@ extern struct class spi_slave_class; /* dummy */
* Return: the SPI controller structure on success, else NULL.
*/
struct spi_controller *__spi_alloc_controller(struct device *dev,
unsigned int size, bool slave)
unsigned int size, bool target)
{
struct spi_controller *ctlr;
size_t ctlr_size = ALIGN(sizeof(*ctlr), dma_get_cache_alignment());
@ -3075,11 +3072,11 @@ struct spi_controller *__spi_alloc_controller(struct device *dev,
mutex_init(&ctlr->add_lock);
ctlr->bus_num = -1;
ctlr->num_chipselect = 1;
ctlr->slave = slave;
if (IS_ENABLED(CONFIG_SPI_SLAVE) && slave)
ctlr->dev.class = &spi_slave_class;
ctlr->target = target;
if (IS_ENABLED(CONFIG_SPI_SLAVE) && target)
ctlr->dev.class = &spi_target_class;
else
ctlr->dev.class = &spi_master_class;
ctlr->dev.class = &spi_controller_class;
ctlr->dev.parent = dev;
pm_suspend_ignore_children(&ctlr->dev, true);
spi_controller_set_devdata(ctlr, (void *)ctlr + ctlr_size);
@ -3097,7 +3094,7 @@ static void devm_spi_release_controller(struct device *dev, void *ctlr)
* __devm_spi_alloc_controller - resource-managed __spi_alloc_controller()
* @dev: physical device of SPI controller
* @size: how much zeroed driver-private data to allocate
* @slave: whether to allocate an SPI master (false) or SPI slave (true)
* @target: whether to allocate an SPI host (false) or SPI target (true) controller
* Context: can sleep
*
* Allocate an SPI controller and automatically release a reference on it
@ -3110,7 +3107,7 @@ static void devm_spi_release_controller(struct device *dev, void *ctlr)
*/
struct spi_controller *__devm_spi_alloc_controller(struct device *dev,
unsigned int size,
bool slave)
bool target)
{
struct spi_controller **ptr, *ctlr;
@ -3119,7 +3116,7 @@ struct spi_controller *__devm_spi_alloc_controller(struct device *dev,
if (!ptr)
return NULL;
ctlr = __spi_alloc_controller(dev, size, slave);
ctlr = __spi_alloc_controller(dev, size, target);
if (ctlr) {
ctlr->devm_allocated = true;
*ptr = ctlr;
@ -3133,8 +3130,8 @@ struct spi_controller *__devm_spi_alloc_controller(struct device *dev,
EXPORT_SYMBOL_GPL(__devm_spi_alloc_controller);
/**
* spi_get_gpio_descs() - grab chip select GPIOs for the master
* @ctlr: The SPI master to grab GPIO descriptors for
* spi_get_gpio_descs() - grab chip select GPIOs for the controller
* @ctlr: The SPI controller to grab GPIO descriptors for
*/
static int spi_get_gpio_descs(struct spi_controller *ctlr)
{
@ -3232,7 +3229,7 @@ static int spi_controller_id_alloc(struct spi_controller *ctlr, int start, int e
int id;
mutex_lock(&board_lock);
id = idr_alloc(&spi_master_idr, ctlr, start, end, GFP_KERNEL);
id = idr_alloc(&spi_controller_idr, ctlr, start, end, GFP_KERNEL);
mutex_unlock(&board_lock);
if (WARN(id < 0, "couldn't get idr"))
return id == -ENOSPC ? -EBUSY : id;
@ -3381,7 +3378,7 @@ destroy_queue:
spi_destroy_queue(ctlr);
free_bus_id:
mutex_lock(&board_lock);
idr_remove(&spi_master_idr, ctlr->bus_num);
idr_remove(&spi_controller_idr, ctlr->bus_num);
mutex_unlock(&board_lock);
return status;
}
@ -3393,8 +3390,7 @@ static void devm_spi_unregister(struct device *dev, void *res)
}
/**
* devm_spi_register_controller - register managed SPI host or target
* controller
* devm_spi_register_controller - register managed SPI host or target controller
* @dev: device managing SPI controller
* @ctlr: initialized controller, originally from spi_alloc_host() or
* spi_alloc_target()
@ -3434,7 +3430,7 @@ static int __unregister(struct device *dev, void *null)
}
/**
* spi_unregister_controller - unregister SPI master or slave controller
* spi_unregister_controller - unregister SPI host or target controller
* @ctlr: the controller being unregistered
* Context: can sleep
*
@ -3458,7 +3454,7 @@ void spi_unregister_controller(struct spi_controller *ctlr)
/* First make sure that this controller was ever added */
mutex_lock(&board_lock);
found = idr_find(&spi_master_idr, id);
found = idr_find(&spi_controller_idr, id);
mutex_unlock(&board_lock);
if (ctlr->queued) {
if (spi_destroy_queue(ctlr))
@ -3473,7 +3469,7 @@ void spi_unregister_controller(struct spi_controller *ctlr)
/* Free bus id */
mutex_lock(&board_lock);
if (found == ctlr)
idr_remove(&spi_master_idr, id);
idr_remove(&spi_controller_idr, id);
mutex_unlock(&board_lock);
if (IS_ENABLED(CONFIG_SPI_DYNAMIC))
@ -4162,6 +4158,15 @@ static int __spi_validate(struct spi_device *spi, struct spi_message *message)
if (_spi_xfer_word_delay_update(xfer, spi))
return -EINVAL;
/* Make sure controller supports required offload features. */
if (xfer->offload_flags) {
if (!message->offload)
return -EINVAL;
if (xfer->offload_flags & ~message->offload->xfer_flags)
return -EINVAL;
}
}
message->status = -EINPROGRESS;
@ -4617,7 +4622,7 @@ EXPORT_SYMBOL_GPL(spi_sync_locked);
/**
* spi_bus_lock - obtain a lock for exclusive SPI bus usage
* @ctlr: SPI bus master that should be locked for exclusive bus access
* @ctlr: SPI bus controller that should be locked for exclusive bus access
* Context: can sleep
*
* This call may only be used from a context that may sleep. The sleep
@ -4648,7 +4653,7 @@ EXPORT_SYMBOL_GPL(spi_bus_lock);
/**
* spi_bus_unlock - release the lock for exclusive SPI bus usage
* @ctlr: SPI bus master that was locked for exclusive bus access
* @ctlr: SPI bus controller that was locked for exclusive bus access
* Context: can sleep
*
* This call may only be used from a context that may sleep. The sleep
@ -4765,9 +4770,9 @@ static struct spi_controller *of_find_spi_controller_by_node(struct device_node
{
struct device *dev;
dev = class_find_device_by_of_node(&spi_master_class, node);
dev = class_find_device_by_of_node(&spi_controller_class, node);
if (!dev && IS_ENABLED(CONFIG_SPI_SLAVE))
dev = class_find_device_by_of_node(&spi_slave_class, node);
dev = class_find_device_by_of_node(&spi_target_class, node);
if (!dev)
return NULL;
@ -4847,10 +4852,10 @@ struct spi_controller *acpi_spi_find_controller_by_adev(struct acpi_device *adev
{
struct device *dev;
dev = class_find_device(&spi_master_class, NULL, adev,
dev = class_find_device(&spi_controller_class, NULL, adev,
spi_acpi_controller_match);
if (!dev && IS_ENABLED(CONFIG_SPI_SLAVE))
dev = class_find_device(&spi_slave_class, NULL, adev,
dev = class_find_device(&spi_target_class, NULL, adev,
spi_acpi_controller_match);
if (!dev)
return NULL;
@ -4920,12 +4925,12 @@ static int __init spi_init(void)
if (status < 0)
goto err1;
status = class_register(&spi_master_class);
status = class_register(&spi_controller_class);
if (status < 0)
goto err2;
if (IS_ENABLED(CONFIG_SPI_SLAVE)) {
status = class_register(&spi_slave_class);
status = class_register(&spi_target_class);
if (status < 0)
goto err3;
}
@ -4938,7 +4943,7 @@ static int __init spi_init(void)
return 0;
err3:
class_unregister(&spi_master_class);
class_unregister(&spi_controller_class);
err2:
bus_unregister(&spi_bus_type);
err1:

View file

@ -706,6 +706,7 @@ static const struct spi_device_id spidev_spi_ids[] = {
{ .name = /* cisco */ "spi-petra" },
{ .name = /* dh */ "dhcom-board" },
{ .name = /* elgin */ "jg10309-01" },
{ .name = /* gocontroll */ "moduline-module-slot"},
{ .name = /* lineartechnology */ "ltc2488" },
{ .name = /* lwn */ "bk4" },
{ .name = /* lwn */ "bk4-spi" },
@ -737,6 +738,7 @@ static const struct of_device_id spidev_dt_ids[] = {
{ .compatible = "cisco,spi-petra", .data = &spidev_of_check },
{ .compatible = "dh,dhcom-board", .data = &spidev_of_check },
{ .compatible = "elgin,jg10309-01", .data = &spidev_of_check },
{ .compatible = "gocontroll,moduline-module-slot", .data = &spidev_of_check},
{ .compatible = "lineartechnology,ltc2488", .data = &spidev_of_check },
{ .compatible = "lwn,bk4", .data = &spidev_of_check },
{ .compatible = "lwn,bk4-spi", .data = &spidev_of_check },

View file

@ -325,6 +325,10 @@ struct nandc_regs {
__le32 read_location_last1;
__le32 read_location_last2;
__le32 read_location_last3;
__le32 spi_cfg;
__le32 num_addr_cycle;
__le32 busy_wait_cnt;
__le32 flash_feature;
__le32 erased_cw_detect_cfg_clr;
__le32 erased_cw_detect_cfg_set;
@ -339,6 +343,7 @@ struct nandc_regs {
*
* @core_clk: controller clock
* @aon_clk: another controller clock
* @iomacro_clk: io macro clock
*
* @regs: a contiguous chunk of memory for DMA register
* writes. contains the register values to be
@ -348,6 +353,7 @@ struct nandc_regs {
* initialized via DT match data
*
* @controller: base controller structure
* @qspi: qpic spi structure
* @host_list: list containing all the chips attached to the
* controller
*
@ -392,6 +398,7 @@ struct qcom_nand_controller {
const struct qcom_nandc_props *props;
struct nand_controller *controller;
struct qpic_spi_nand *qspi;
struct list_head host_list;
union {

View file

@ -0,0 +1,39 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (C) 2024 Analog Devices Inc.
* Copyright (C) 2024 BayLibre, SAS
*/
#ifndef __LINUX_SPI_OFFLOAD_CONSUMER_H
#define __LINUX_SPI_OFFLOAD_CONSUMER_H
#include <linux/module.h>
#include <linux/spi/offload/types.h>
#include <linux/types.h>
MODULE_IMPORT_NS("SPI_OFFLOAD");
struct device;
struct spi_device;
struct spi_offload *devm_spi_offload_get(struct device *dev, struct spi_device *spi,
const struct spi_offload_config *config);
struct spi_offload_trigger
*devm_spi_offload_trigger_get(struct device *dev,
struct spi_offload *offload,
enum spi_offload_trigger_type type);
int spi_offload_trigger_validate(struct spi_offload_trigger *trigger,
struct spi_offload_trigger_config *config);
int spi_offload_trigger_enable(struct spi_offload *offload,
struct spi_offload_trigger *trigger,
struct spi_offload_trigger_config *config);
void spi_offload_trigger_disable(struct spi_offload *offload,
struct spi_offload_trigger *trigger);
struct dma_chan *devm_spi_offload_tx_stream_request_dma_chan(struct device *dev,
struct spi_offload *offload);
struct dma_chan *devm_spi_offload_rx_stream_request_dma_chan(struct device *dev,
struct spi_offload *offload);
#endif /* __LINUX_SPI_OFFLOAD_CONSUMER_H */

View file

@ -0,0 +1,47 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (C) 2024 Analog Devices Inc.
* Copyright (C) 2024 BayLibre, SAS
*/
#ifndef __LINUX_SPI_OFFLOAD_PROVIDER_H
#define __LINUX_SPI_OFFLOAD_PROVIDER_H
#include <linux/module.h>
#include <linux/spi/offload/types.h>
#include <linux/types.h>
MODULE_IMPORT_NS("SPI_OFFLOAD");
struct device;
struct spi_offload_trigger;
struct spi_offload *devm_spi_offload_alloc(struct device *dev, size_t priv_size);
struct spi_offload_trigger_ops {
bool (*match)(struct spi_offload_trigger *trigger,
enum spi_offload_trigger_type type, u64 *args, u32 nargs);
int (*request)(struct spi_offload_trigger *trigger,
enum spi_offload_trigger_type type, u64 *args, u32 nargs);
void (*release)(struct spi_offload_trigger *trigger);
int (*validate)(struct spi_offload_trigger *trigger,
struct spi_offload_trigger_config *config);
int (*enable)(struct spi_offload_trigger *trigger,
struct spi_offload_trigger_config *config);
void (*disable)(struct spi_offload_trigger *trigger);
};
struct spi_offload_trigger_info {
/** @fwnode: Provider fwnode, used to match to consumer. */
struct fwnode_handle *fwnode;
/** @ops: Provider-specific callbacks. */
const struct spi_offload_trigger_ops *ops;
/** Provider-specific state to be used in callbacks. */
void *priv;
};
int devm_spi_offload_trigger_register(struct device *dev,
struct spi_offload_trigger_info *info);
void *spi_offload_trigger_get_priv(struct spi_offload_trigger *trigger);
#endif /* __LINUX_SPI_OFFLOAD_PROVIDER_H */

View file

@ -0,0 +1,100 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (C) 2024 Analog Devices Inc.
* Copyright (C) 2024 BayLibre, SAS
*/
#ifndef __LINUX_SPI_OFFLOAD_TYPES_H
#define __LINUX_SPI_OFFLOAD_TYPES_H
#include <linux/bits.h>
#include <linux/types.h>
struct device;
/* This is write xfer but TX uses external data stream rather than tx_buf. */
#define SPI_OFFLOAD_XFER_TX_STREAM BIT(0)
/* This is read xfer but RX uses external data stream rather than rx_buf. */
#define SPI_OFFLOAD_XFER_RX_STREAM BIT(1)
/* Offload can be triggered by external hardware event. */
#define SPI_OFFLOAD_CAP_TRIGGER BIT(0)
/* Offload can record and then play back TX data when triggered. */
#define SPI_OFFLOAD_CAP_TX_STATIC_DATA BIT(1)
/* Offload can get TX data from an external stream source. */
#define SPI_OFFLOAD_CAP_TX_STREAM_DMA BIT(2)
/* Offload can send RX data to an external stream sink. */
#define SPI_OFFLOAD_CAP_RX_STREAM_DMA BIT(3)
/**
* struct spi_offload_config - offload configuration
*
* This is used to request an offload with specific configuration.
*/
struct spi_offload_config {
/** @capability_flags: required capabilities. See %SPI_OFFLOAD_CAP_* */
u32 capability_flags;
};
/**
* struct spi_offload - offload instance
*/
struct spi_offload {
/** @provider_dev: for get/put reference counting */
struct device *provider_dev;
/** @priv: provider driver private data */
void *priv;
/** @ops: callbacks for offload support */
const struct spi_offload_ops *ops;
/** @xfer_flags: %SPI_OFFLOAD_XFER_* flags supported by provider */
u32 xfer_flags;
};
enum spi_offload_trigger_type {
/* Indication from SPI peripheral that data is read to read. */
SPI_OFFLOAD_TRIGGER_DATA_READY,
/* Trigger comes from a periodic source such as a clock. */
SPI_OFFLOAD_TRIGGER_PERIODIC,
};
struct spi_offload_trigger_periodic {
u64 frequency_hz;
};
struct spi_offload_trigger_config {
/** @type: type discriminator for union */
enum spi_offload_trigger_type type;
union {
struct spi_offload_trigger_periodic periodic;
};
};
/**
* struct spi_offload_ops - callbacks implemented by offload providers
*/
struct spi_offload_ops {
/**
* @trigger_enable: Optional callback to enable the trigger for the
* given offload instance.
*/
int (*trigger_enable)(struct spi_offload *offload);
/**
* @trigger_disable: Optional callback to disable the trigger for the
* given offload instance.
*/
void (*trigger_disable)(struct spi_offload *offload);
/**
* @tx_stream_request_dma_chan: Optional callback for controllers that
* have an offload where the TX data stream is connected directly to a
* DMA channel.
*/
struct dma_chan *(*tx_stream_request_dma_chan)(struct spi_offload *offload);
/**
* @rx_stream_request_dma_chan: Optional callback for controllers that
* have an offload where the RX data stream is connected directly to a
* DMA channel.
*/
struct dma_chan *(*rx_stream_request_dma_chan)(struct spi_offload *offload);
};
#endif /* __LINUX_SPI_OFFLOAD_TYPES_H */

View file

@ -31,9 +31,11 @@ struct spi_transfer;
struct spi_controller_mem_ops;
struct spi_controller_mem_caps;
struct spi_message;
struct spi_offload;
struct spi_offload_config;
/*
* INTERFACES between SPI master-side drivers and SPI slave protocol handlers,
* INTERFACES between SPI controller-side drivers and SPI target protocol handlers,
* and SPI infrastructure.
*/
extern const struct bus_type spi_bus_type;
@ -128,7 +130,7 @@ extern void spi_transfer_cs_change_delay_exec(struct spi_message *msg,
struct spi_transfer *xfer);
/**
* struct spi_device - Controller side proxy for an SPI slave device
* struct spi_device - Controller side proxy for an SPI target device
* @dev: Driver model representation of the device.
* @controller: SPI controller used with the device.
* @max_speed_hz: Maximum clock rate to be used with this chip
@ -172,7 +174,7 @@ extern void spi_transfer_cs_change_delay_exec(struct spi_message *msg,
* @pcpu_statistics: statistics for the spi_device
* @cs_index_mask: Bit mask of the active chipselect(s) in the chipselect array
*
* A @spi_device is used to interchange data between an SPI slave
* A @spi_device is used to interchange data between an SPI target device
* (usually a discrete chip) and CPU memory.
*
* In @dev, the platform_data is used to hold information about this
@ -386,15 +388,15 @@ extern struct spi_device *spi_new_ancillary_device(struct spi_device *spi, u8 ch
spi_unregister_driver)
/**
* struct spi_controller - interface to SPI master or slave controller
* struct spi_controller - interface to SPI host or target controller
* @dev: device interface to this driver
* @list: link with the global spi_controller list
* @bus_num: board-specific (and often SOC-specific) identifier for a
* given SPI controller.
* @num_chipselect: chipselects are used to distinguish individual
* SPI slaves, and are numbered from zero to num_chipselects.
* each slave has a chipselect signal, but it's common that not
* every chipselect is connected to a slave.
* SPI targets, and are numbered from zero to num_chipselects.
* each target has a chipselect signal, but it's common that not
* every chipselect is connected to a target.
* @dma_alignment: SPI controller constraint on DMA buffers alignment.
* @mode_bits: flags understood by this controller driver
* @buswidth_override_bits: flags to override for this controller driver
@ -423,9 +425,9 @@ extern struct spi_device *spi_new_ancillary_device(struct spi_device *spi, u8 ch
* must fail if an unrecognized or unsupported mode is requested.
* It's always safe to call this unless transfers are pending on
* the device whose settings are being modified.
* @set_cs_timing: optional hook for SPI devices to request SPI master
* @set_cs_timing: optional hook for SPI devices to request SPI
* controller for configuring specific CS setup time, hold time and inactive
* delay interms of clock counts
* delay in terms of clock counts
* @transfer: adds a message to the controller's transfer queue.
* @cleanup: frees controller-specific state
* @can_dma: determine whether this controller supports DMA
@ -496,6 +498,10 @@ extern struct spi_device *spi_new_ancillary_device(struct spi_device *spi, u8 ch
* @mem_ops: optimized/dedicated operations for interactions with SPI memory.
* This field is optional and should only be implemented if the
* controller has native support for memory like operations.
* @get_offload: callback for controllers with offload support to get matching
* offload instance. Implementations should return -ENODEV if no match is
* found.
* @put_offload: release the offload instance acquired by @get_offload.
* @mem_caps: controller capabilities for the handling of memory operations.
* @unprepare_message: undo any work done by prepare_message().
* @target_abort: abort the ongoing transfer request on an SPI target controller
@ -541,7 +547,7 @@ extern struct spi_device *spi_new_ancillary_device(struct spi_device *spi, u8 ch
*
* The driver for an SPI controller manages access to those devices through
* a queue of spi_message transactions, copying data between CPU memory and
* an SPI slave device. For each such message it queues, it calls the
* an SPI target device. For each such message it queues, it calls the
* message's completion function when the transaction completes.
*/
struct spi_controller {
@ -591,7 +597,7 @@ struct spi_controller {
#define SPI_CONTROLLER_NO_TX BIT(2) /* Can't do buffer write */
#define SPI_CONTROLLER_MUST_RX BIT(3) /* Requires rx */
#define SPI_CONTROLLER_MUST_TX BIT(4) /* Requires tx */
#define SPI_CONTROLLER_GPIO_SS BIT(5) /* GPIO CS must select slave */
#define SPI_CONTROLLER_GPIO_SS BIT(5) /* GPIO CS must select target device */
#define SPI_CONTROLLER_SUSPENDED BIT(6) /* Currently suspended */
/*
* The spi-controller has multi chip select capability and can
@ -658,7 +664,7 @@ struct spi_controller {
* + To a given spi_device, message queueing is pure FIFO
*
* + The controller's main job is to process its message queue,
* selecting a chip (for masters), then transferring data
* selecting a chip (for controllers), then transferring data
* + If there are multiple spi_device children, the i/o queue
* arbitration algorithm is unspecified (round robin, FIFO,
* priority, reservations, preemption, etc)
@ -740,6 +746,10 @@ struct spi_controller {
const struct spi_controller_mem_ops *mem_ops;
const struct spi_controller_mem_caps *mem_caps;
struct spi_offload *(*get_offload)(struct spi_device *spi,
const struct spi_offload_config *config);
void (*put_offload)(struct spi_offload *offload);
/* GPIO chip select */
struct gpio_desc **cs_gpiods;
bool use_gpio_descriptors;
@ -822,7 +832,7 @@ void spi_take_timestamp_post(struct spi_controller *ctlr,
/* The SPI driver core manages memory for the spi_controller classdev */
extern struct spi_controller *__spi_alloc_controller(struct device *host,
unsigned int size, bool slave);
unsigned int size, bool target);
static inline struct spi_controller *spi_alloc_host(struct device *dev,
unsigned int size)
@ -841,7 +851,7 @@ static inline struct spi_controller *spi_alloc_target(struct device *dev,
struct spi_controller *__devm_spi_alloc_controller(struct device *dev,
unsigned int size,
bool slave);
bool target);
static inline struct spi_controller *devm_spi_alloc_host(struct device *dev,
unsigned int size)
@ -963,6 +973,8 @@ struct spi_res {
* @rx_sg_mapped: If true, the @rx_sg is mapped for DMA
* @tx_sg: Scatterlist for transmit, currently not for client use
* @rx_sg: Scatterlist for receive, currently not for client use
* @offload_flags: Flags that are only applicable to specialized SPI offload
* transfers. See %SPI_OFFLOAD_XFER_* in spi-offload.h.
* @ptp_sts_word_pre: The word (subject to bits_per_word semantics) offset
* within @tx_buf for which the SPI device is requesting that the time
* snapshot for this transfer begins. Upon completing the SPI transfer,
@ -977,12 +989,12 @@ struct spi_res {
* purposefully (instead of setting to spi_transfer->len - 1) to denote
* that a transfer-level snapshot taken from within the driver may still
* be of higher quality.
* @ptp_sts: Pointer to a memory location held by the SPI slave device where a
* @ptp_sts: Pointer to a memory location held by the SPI target device where a
* PTP system timestamp structure may lie. If drivers use PIO or their
* hardware has some sort of assist for retrieving exact transfer timing,
* they can (and should) assert @ptp_sts_supported and populate this
* structure using the ptp_read_system_*ts helper functions.
* The timestamp must represent the time at which the SPI slave device has
* The timestamp must represent the time at which the SPI target device has
* processed the word, i.e. the "pre" timestamp should be taken before
* transmitting the "pre" word, and the "post" timestamp after receiving
* transmit confirmation from the controller for the "post" word.
@ -1083,6 +1095,9 @@ struct spi_transfer {
u32 effective_speed_hz;
/* Use %SPI_OFFLOAD_XFER_* from spi-offload.h */
unsigned int offload_flags;
unsigned int ptp_sts_word_pre;
unsigned int ptp_sts_word_post;
@ -1108,6 +1123,7 @@ struct spi_transfer {
* @state: for use by whichever driver currently owns the message
* @opt_state: for use by whichever driver currently owns the message
* @resources: for resource management when the SPI message is processed
* @offload: (optional) offload instance used by this message
*
* A @spi_message is used to execute an atomic sequence of data transfers,
* each represented by a struct spi_transfer. The sequence is "atomic"
@ -1168,6 +1184,12 @@ struct spi_message {
*/
void *opt_state;
/*
* Optional offload instance used by this message. This must be set
* by the peripheral driver before calling spi_optimize_message().
*/
struct spi_offload *offload;
/* List of spi_res resources when the SPI message is processed */
struct list_head resources;
};
@ -1600,7 +1622,7 @@ struct spi_board_info {
* bus_num is board specific and matches the bus_num of some
* spi_controller that will probably be registered later.
*
* chip_select reflects how this chip is wired to that master;
* chip_select reflects how this chip is wired to that controller;
* it's less than num_chipselect.
*/
u16 bus_num;