spi: Updates for v4.8
Quite a lot of cleanup and maintainence work going on this release in various drivers, and also a fix for a nasty locking issue in the core: - A fix for locking issues when external drivers explicitly locked the bus with spi_bus_lock() - we were using the same lock to both control access to the physical bus in multi-threaded I/O operations and exclude multiple callers. Confusion between these two caused us to have scenarios where we were dropping locks. These are fixed by splitting into two separate locks like should have been done originally, making everything much clearer and correct. - Support for DMA in spi_flash_read(). - Support for instantiating spidev on ACPI systems, including some test devices used in Windows validation. - Use of the core DMA mapping functionality in the McSPI driver. - Start of support for ThunderX SPI controllers, involving a very big set of changes to the Cavium driver. - Support for Braswell, Exynos 5433, Kaby Lake, Merrifield, RK3036, RK3228, RK3368 controllers. -----BEGIN PGP SIGNATURE----- Version: GnuPG v1 iQEcBAABAgAGBQJXmPFBAAoJECTWi3JdVIfQbisH/355nT/cyqc08l9iC+a1zRDw /Bf5kN8pqmu6+y3sMjAIdptZQTlXhgR4q1ZH+oNSfowCVgvJYWF6RVCEXDBh6XHs YBQAFlYeSOO5cLTPQSDnn06oFucV/HZJppC6hM0SNclbVboeMBBS6S6aljXqMbj+ mFvtq6/iEsG6kgQcmcl3fm/SMOYF2OFDJyr66NimBXQGzjx84xJcG0eGk8kCIwEw xyiE/WmB9WT2scFSgAsfaOEE27ozaq9iANNUA/ceUibQgQYpQveBgy4XVXFjEzFo 3BVvPYGGzjebzaXbMwDV6OvSgMwnTsMxjtZGsraxIEOcMdeeMEpn1/Ze4ksWi4c= =R4Zx -----END PGP SIGNATURE----- Merge tag 'spi-v4.8' of git://git.kernel.org/pub/scm/linux/kernel/git/broonie/spi Pull spi updates from Mark Brown: "Quite a lot of cleanup and maintainence work going on this release in various drivers, and also a fix for a nasty locking issue in the core: - A fix for locking issues when external drivers explicitly locked the bus with spi_bus_lock() - we were using the same lock to both control access to the physical bus in multi-threaded I/O operations and exclude multiple callers. Confusion between these two caused us to have scenarios where we were dropping locks. These are fixed by splitting into two separate locks like should have been done originally, making everything much clearer and correct. - Support for DMA in spi_flash_read(). - Support for instantiating spidev on ACPI systems, including some test devices used in Windows validation. - Use of the core DMA mapping functionality in the McSPI driver. - Start of support for ThunderX SPI controllers, involving a very big set of changes to the Cavium driver. - Support for Braswell, Exynos 5433, Kaby Lake, Merrifield, RK3036, RK3228, RK3368 controllers" * tag 'spi-v4.8' of git://git.kernel.org/pub/scm/linux/kernel/git/broonie/spi: (64 commits) spi: Split bus and I/O locking spi: octeon: Split driver into Octeon specific and common parts spi: octeon: Move include file from arch/mips to drivers/spi spi: octeon: Put register offsets into a struct spi: octeon: Store system clock freqency in struct octeon_spi spi: octeon: Convert driver to use readq()/writeq() functions spi: pic32-sqi: fixup wait_for_completion_timeout return handling spi: pic32: fixup wait_for_completion_timeout return handling spi: rockchip: limit transfers to (64K - 1) bytes spi: xilinx: Return IRQ_NONE if no interrupts were detected spi: xilinx: Handle errors from platform_get_irq() spi: s3c64xx: restore removed comments spi: s3c64xx: add Exynos5433 compatible for ioclk handling spi: s3c64xx: use error code from clk_prepare_enable() spi: s3c64xx: rename goto labels to meaningful names spi: s3c64xx: document the clocks and the clock-name property spi: s3c64xx: add exynos5433 spi compatible spi: s3c64xx: fix reference leak to master in s3c64xx_spi_remove() spi: spi-sh: Remove deprecated create_singlethread_workqueue spi: spi-topcliff-pch: Remove deprecated create_singlethread_workqueue ...
This commit is contained in:
Коммит
7ae0ae4a02
|
@ -11,7 +11,6 @@ Required properties:
|
||||||
- "fsl,imx51-ecspi" for SPI compatible with the one integrated on i.MX51
|
- "fsl,imx51-ecspi" for SPI compatible with the one integrated on i.MX51
|
||||||
- reg : Offset and length of the register set for the device
|
- reg : Offset and length of the register set for the device
|
||||||
- interrupts : Should contain CSPI/eCSPI interrupt
|
- interrupts : Should contain CSPI/eCSPI interrupt
|
||||||
- fsl,spi-num-chipselects : Contains the number of the chipselect
|
|
||||||
- cs-gpios : Specifies the gpio pins to be used for chipselects.
|
- cs-gpios : Specifies the gpio pins to be used for chipselects.
|
||||||
- clocks : Clock specifiers for both ipg and per clocks.
|
- clocks : Clock specifiers for both ipg and per clocks.
|
||||||
- clock-names : Clock names should include both "ipg" and "per"
|
- clock-names : Clock names should include both "ipg" and "per"
|
||||||
|
@ -21,6 +20,9 @@ See the clock consumer binding,
|
||||||
Documentation/devicetree/bindings/dma/dma.txt
|
Documentation/devicetree/bindings/dma/dma.txt
|
||||||
- dma-names: DMA request names should include "tx" and "rx" if present.
|
- dma-names: DMA request names should include "tx" and "rx" if present.
|
||||||
|
|
||||||
|
Obsolete properties:
|
||||||
|
- fsl,spi-num-chipselects : Contains the number of the chipselect
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
ecspi@70010000 {
|
ecspi@70010000 {
|
||||||
|
@ -29,7 +31,6 @@ ecspi@70010000 {
|
||||||
compatible = "fsl,imx51-ecspi";
|
compatible = "fsl,imx51-ecspi";
|
||||||
reg = <0x70010000 0x4000>;
|
reg = <0x70010000 0x4000>;
|
||||||
interrupts = <36>;
|
interrupts = <36>;
|
||||||
fsl,spi-num-chipselects = <2>;
|
|
||||||
cs-gpios = <&gpio3 24 0>, /* GPIO3_24 */
|
cs-gpios = <&gpio3 24 0>, /* GPIO3_24 */
|
||||||
<&gpio3 25 0>; /* GPIO3_25 */
|
<&gpio3 25 0>; /* GPIO3_25 */
|
||||||
dmas = <&sdma 3 7 1>, <&sdma 4 7 2>;
|
dmas = <&sdma 3 7 1>, <&sdma 4 7 2>;
|
||||||
|
|
|
@ -8,11 +8,10 @@ in slave mode.
|
||||||
|
|
||||||
The SPI master node requires the following properties:
|
The SPI master node requires the following properties:
|
||||||
- #address-cells - number of cells required to define a chip select
|
- #address-cells - number of cells required to define a chip select
|
||||||
address on the SPI bus.
|
address on the SPI bus.
|
||||||
- #size-cells - should be zero.
|
- #size-cells - should be zero.
|
||||||
- compatible - name of SPI bus controller following generic names
|
- compatible - name of SPI bus controller following generic names
|
||||||
recommended practice.
|
recommended practice.
|
||||||
- cs-gpios - (optional) gpios chip select.
|
|
||||||
No other properties are required in the SPI bus node. It is assumed
|
No other properties are required in the SPI bus node. It is assumed
|
||||||
that a driver for an SPI bus device will understand that it is an SPI bus.
|
that a driver for an SPI bus device will understand that it is an SPI bus.
|
||||||
However, the binding does not attempt to define the specific method for
|
However, the binding does not attempt to define the specific method for
|
||||||
|
@ -22,11 +21,12 @@ assumption that board specific platform code will be used to manage
|
||||||
chip selects. Individual drivers can define additional properties to
|
chip selects. Individual drivers can define additional properties to
|
||||||
support describing the chip select layout.
|
support describing the chip select layout.
|
||||||
|
|
||||||
Optional property:
|
Optional properties:
|
||||||
- num-cs : total number of chipselects
|
- cs-gpios - gpios chip select.
|
||||||
|
- num-cs - total number of chipselects.
|
||||||
|
|
||||||
If cs-gpios is used the number of chip select will automatically increased
|
If cs-gpios is used the number of chip selects will be increased automatically
|
||||||
with max(cs-gpios > hw cs)
|
with max(cs-gpios > hw cs).
|
||||||
|
|
||||||
So if for example the controller has 2 CS lines, and the cs-gpios
|
So if for example the controller has 2 CS lines, and the cs-gpios
|
||||||
property looks like this:
|
property looks like this:
|
||||||
|
@ -45,29 +45,30 @@ SPI slave nodes must be children of the SPI master node and can
|
||||||
contain the following properties.
|
contain the following properties.
|
||||||
- reg - (required) chip select address of device.
|
- reg - (required) chip select address of device.
|
||||||
- compatible - (required) name of SPI device following generic names
|
- compatible - (required) name of SPI device following generic names
|
||||||
recommended practice
|
recommended practice.
|
||||||
- spi-max-frequency - (required) Maximum SPI clocking speed of device in Hz
|
- spi-max-frequency - (required) Maximum SPI clocking speed of device in Hz.
|
||||||
- spi-cpol - (optional) Empty property indicating device requires
|
- spi-cpol - (optional) Empty property indicating device requires
|
||||||
inverse clock polarity (CPOL) mode
|
inverse clock polarity (CPOL) mode.
|
||||||
- spi-cpha - (optional) Empty property indicating device requires
|
- spi-cpha - (optional) Empty property indicating device requires
|
||||||
shifted clock phase (CPHA) mode
|
shifted clock phase (CPHA) mode.
|
||||||
- spi-cs-high - (optional) Empty property indicating device requires
|
- spi-cs-high - (optional) Empty property indicating device requires
|
||||||
chip select active high
|
chip select active high.
|
||||||
- spi-3wire - (optional) Empty property indicating device requires
|
- spi-3wire - (optional) Empty property indicating device requires
|
||||||
3-wire mode.
|
3-wire mode.
|
||||||
- spi-lsb-first - (optional) Empty property indicating device requires
|
- spi-lsb-first - (optional) Empty property indicating device requires
|
||||||
LSB first mode.
|
LSB first mode.
|
||||||
- spi-tx-bus-width - (optional) The bus width(number of data wires) that
|
- spi-tx-bus-width - (optional) The bus width (number of data wires) that is
|
||||||
used for MOSI. Defaults to 1 if not present.
|
used for MOSI. Defaults to 1 if not present.
|
||||||
- spi-rx-bus-width - (optional) The bus width(number of data wires) that
|
- spi-rx-bus-width - (optional) The bus width (number of data wires) that is
|
||||||
used for MISO. Defaults to 1 if not present.
|
used for MISO. Defaults to 1 if not present.
|
||||||
- spi-rx-delay-us - (optional) Microsecond delay after a read transfer.
|
- spi-rx-delay-us - (optional) Microsecond delay after a read transfer.
|
||||||
- spi-tx-delay-us - (optional) Microsecond delay after a write transfer.
|
- spi-tx-delay-us - (optional) Microsecond delay after a write transfer.
|
||||||
|
|
||||||
Some SPI controllers and devices support Dual and Quad SPI transfer mode.
|
Some SPI controllers and devices support Dual and Quad SPI transfer mode.
|
||||||
It allows data in the SPI system to be transferred in 2 wires(DUAL) or 4 wires(QUAD).
|
It allows data in the SPI system to be transferred using 2 wires (DUAL) or 4
|
||||||
|
wires (QUAD).
|
||||||
Now the value that spi-tx-bus-width and spi-rx-bus-width can receive is
|
Now the value that spi-tx-bus-width and spi-rx-bus-width can receive is
|
||||||
only 1(SINGLE), 2(DUAL) and 4(QUAD).
|
only 1 (SINGLE), 2 (DUAL) and 4 (QUAD).
|
||||||
Dual/Quad mode is not allowed when 3-wire mode is used.
|
Dual/Quad mode is not allowed when 3-wire mode is used.
|
||||||
|
|
||||||
If a gpio chipselect is used for the SPI slave the gpio number will be passed
|
If a gpio chipselect is used for the SPI slave the gpio number will be passed
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
Serial Peripheral Interface on Cirrus Logic CL-PS71xx, EP72xx, EP73xx
|
||||||
|
|
||||||
|
Required properties
|
||||||
|
- #address-cells: must be <1>
|
||||||
|
- #size-cells: must be <0>
|
||||||
|
- compatible: should include "cirrus,ep7209-spi"
|
||||||
|
- reg: Address and length of one register range
|
||||||
|
- interrupts: one interrupt line
|
||||||
|
- clocks: One entry, refers to the SPI bus clock
|
||||||
|
- cs-gpios: Specifies the gpio pins to be used for chipselects.
|
||||||
|
See: Documentation/devicetree/bindings/spi/spi-bus.txt
|
||||||
|
|
||||||
|
An additional register is present in the system controller,
|
||||||
|
which is assumed to be in the same device tree, with and marked
|
||||||
|
as compatible with "cirrus,ep7209-syscon3".
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
spi@80000500 {
|
||||||
|
#address-cells = <1>;
|
||||||
|
#size-cells = <0>;
|
||||||
|
compatible = "cirrus,ep7209-spi";
|
||||||
|
reg = <0x80000500 0x4>;
|
||||||
|
interrupts = <15>;
|
||||||
|
clocks = <&clks CLPS711X_CLK_SPI>;
|
||||||
|
status = "disabled";
|
||||||
|
};
|
||||||
|
|
||||||
|
syscon3: syscon@80002200 {
|
||||||
|
compatible = "cirrus,ep7209-syscon3", "syscon";
|
||||||
|
reg = <0x80002200 0x40>;
|
||||||
|
};
|
||||||
|
|
|
@ -21,7 +21,7 @@ Required properties:
|
||||||
IP to the interrupt controller within the SoC. Possible values
|
IP to the interrupt controller within the SoC. Possible values
|
||||||
are 0 and 1. Manual says one of the two possible interrupt
|
are 0 and 1. Manual says one of the two possible interrupt
|
||||||
lines can be tied to the interrupt controller. Set this
|
lines can be tied to the interrupt controller. Set this
|
||||||
based on a specifc SoC configuration.
|
based on a specific SoC configuration.
|
||||||
- interrupts: interrupt number mapped to CPU.
|
- interrupts: interrupt number mapped to CPU.
|
||||||
- clocks: spi clk phandle
|
- clocks: spi clk phandle
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,15 @@ Required properties:
|
||||||
- "marvell,armada-380-spi", for the Armada 38x SoCs
|
- "marvell,armada-380-spi", for the Armada 38x SoCs
|
||||||
- "marvell,armada-390-spi", for the Armada 39x SoCs
|
- "marvell,armada-390-spi", for the Armada 39x SoCs
|
||||||
- "marvell,armada-xp-spi", for the Armada XP SoCs
|
- "marvell,armada-xp-spi", for the Armada XP SoCs
|
||||||
- reg : offset and length of the register set for the device
|
- reg : offset and length of the register set for the device.
|
||||||
|
This property can optionally have additional entries to configure
|
||||||
|
the SPI direct access mode that some of the Marvell SoCs support
|
||||||
|
additionally to the normal indirect access (PIO) mode. The values
|
||||||
|
for the MBus "target" and "attribute" are defined in the Marvell
|
||||||
|
SoC "Functional Specifications" Manual in the chapter "Marvell
|
||||||
|
Core Processor Address Decoding".
|
||||||
|
The eight register sets following the control registers refer to
|
||||||
|
chip-select lines 0 through 7 respectively.
|
||||||
- cell-index : Which of multiple SPI controllers is this.
|
- cell-index : Which of multiple SPI controllers is this.
|
||||||
Optional properties:
|
Optional properties:
|
||||||
- interrupts : Is currently not used.
|
- interrupts : Is currently not used.
|
||||||
|
@ -23,3 +31,42 @@ Example:
|
||||||
interrupts = <23>;
|
interrupts = <23>;
|
||||||
status = "disabled";
|
status = "disabled";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Example with SPI direct mode support (optionally):
|
||||||
|
spi0: spi@10600 {
|
||||||
|
compatible = "marvell,orion-spi";
|
||||||
|
#address-cells = <1>;
|
||||||
|
#size-cells = <0>;
|
||||||
|
cell-index = <0>;
|
||||||
|
reg = <MBUS_ID(0xf0, 0x01) 0x10600 0x28>, /* control */
|
||||||
|
<MBUS_ID(0x01, 0x1e) 0 0xffffffff>, /* CS0 */
|
||||||
|
<MBUS_ID(0x01, 0x5e) 0 0xffffffff>, /* CS1 */
|
||||||
|
<MBUS_ID(0x01, 0x9e) 0 0xffffffff>, /* CS2 */
|
||||||
|
<MBUS_ID(0x01, 0xde) 0 0xffffffff>, /* CS3 */
|
||||||
|
<MBUS_ID(0x01, 0x1f) 0 0xffffffff>, /* CS4 */
|
||||||
|
<MBUS_ID(0x01, 0x5f) 0 0xffffffff>, /* CS5 */
|
||||||
|
<MBUS_ID(0x01, 0x9f) 0 0xffffffff>, /* CS6 */
|
||||||
|
<MBUS_ID(0x01, 0xdf) 0 0xffffffff>; /* CS7 */
|
||||||
|
interrupts = <23>;
|
||||||
|
status = "disabled";
|
||||||
|
};
|
||||||
|
|
||||||
|
To enable the direct mode, the board specific 'ranges' property in the
|
||||||
|
'soc' node needs to add the entries for the desired SPI controllers
|
||||||
|
and its chip-selects that are used in the direct mode instead of PIO
|
||||||
|
mode. Here an example for this (SPI controller 0, device 1 and SPI
|
||||||
|
controller 1, device 2 are used in direct mode. All other SPI device
|
||||||
|
are used in the default indirect (PIO) mode):
|
||||||
|
soc {
|
||||||
|
/*
|
||||||
|
* Enable the SPI direct access by configuring an entry
|
||||||
|
* here in the board-specific ranges property
|
||||||
|
*/
|
||||||
|
ranges = <MBUS_ID(0xf0, 0x01) 0 0 0xf1000000 0x100000>, /* internal regs */
|
||||||
|
<MBUS_ID(0x01, 0x1d) 0 0 0xfff00000 0x100000>, /* BootROM */
|
||||||
|
<MBUS_ID(0x01, 0x5e) 0 0 0xf1100000 0x10000>, /* SPI0-DEV1 */
|
||||||
|
<MBUS_ID(0x01, 0x9a) 0 0 0xf1110000 0x10000>; /* SPI1-DEV2 */
|
||||||
|
|
||||||
|
For further information on the MBus bindings, please see the MBus
|
||||||
|
DT documentation:
|
||||||
|
Documentation/devicetree/bindings/bus/mvebu-mbus.txt
|
||||||
|
|
|
@ -6,10 +6,13 @@ and display controllers using the SPI communication interface.
|
||||||
Required Properties:
|
Required Properties:
|
||||||
|
|
||||||
- compatible: should be one of the following.
|
- compatible: should be one of the following.
|
||||||
"rockchip,rk3066-spi" for rk3066.
|
"rockchip,rk3036-spi" for rk3036 SoCS.
|
||||||
"rockchip,rk3188-spi", "rockchip,rk3066-spi" for rk3188.
|
"rockchip,rk3066-spi" for rk3066 SoCs.
|
||||||
"rockchip,rk3288-spi", "rockchip,rk3066-spi" for rk3288.
|
"rockchip,rk3188-spi" for rk3188 SoCs.
|
||||||
"rockchip,rk3399-spi", "rockchip,rk3066-spi" for rk3399.
|
"rockchip,rk3228-spi" for rk3228 SoCS.
|
||||||
|
"rockchip,rk3288-spi" for rk3288 SoCs.
|
||||||
|
"rockchip,rk3368-spi" for rk3368 SoCs.
|
||||||
|
"rockchip,rk3399-spi" for rk3399 SoCs.
|
||||||
- reg: physical base address of the controller and length of memory mapped
|
- reg: physical base address of the controller and length of memory mapped
|
||||||
region.
|
region.
|
||||||
- interrupts: The interrupt number to the cpu. The interrupt specifier format
|
- interrupts: The interrupt number to the cpu. The interrupt specifier format
|
||||||
|
|
|
@ -9,7 +9,8 @@ Required SoC Specific Properties:
|
||||||
- samsung,s3c2443-spi: for s3c2443, s3c2416 and s3c2450 platforms
|
- samsung,s3c2443-spi: for s3c2443, s3c2416 and s3c2450 platforms
|
||||||
- samsung,s3c6410-spi: for s3c6410 platforms
|
- samsung,s3c6410-spi: for s3c6410 platforms
|
||||||
- samsung,s5pv210-spi: for s5pv210 and s5pc110 platforms
|
- samsung,s5pv210-spi: for s5pv210 and s5pc110 platforms
|
||||||
- samsung,exynos7-spi: for exynos7 platforms
|
- samsung,exynos5433-spi: for exynos5433 compatible controllers
|
||||||
|
- samsung,exynos7-spi: for exynos7 platforms <DEPRECATED>
|
||||||
|
|
||||||
- reg: physical base address of the controller and length of memory mapped
|
- reg: physical base address of the controller and length of memory mapped
|
||||||
region.
|
region.
|
||||||
|
@ -23,6 +24,15 @@ Required SoC Specific Properties:
|
||||||
- dma-names: Names for the dma channels. There must be at least one channel
|
- dma-names: Names for the dma channels. There must be at least one channel
|
||||||
named "tx" for transmit and named "rx" for receive.
|
named "tx" for transmit and named "rx" for receive.
|
||||||
|
|
||||||
|
- clocks: specifies the clock IDs provided to the SPI controller; they are
|
||||||
|
required for interacting with the controller itself, for synchronizing the bus
|
||||||
|
and as I/O clock (the latter is required by exynos5433 and exynos7).
|
||||||
|
|
||||||
|
- clock-names: string names of the clocks in the 'clocks' property; for all the
|
||||||
|
the devices the names must be "spi", "spi_busclkN" (where N is determined by
|
||||||
|
"samsung,spi-src-clk"), while Exynos5433 should specify a third clock
|
||||||
|
"spi_ioclk" for the I/O clock.
|
||||||
|
|
||||||
Required Board Specific Properties:
|
Required Board Specific Properties:
|
||||||
|
|
||||||
- #address-cells: should be 1.
|
- #address-cells: should be 1.
|
||||||
|
@ -40,6 +50,9 @@ Optional Board Specific Properties:
|
||||||
|
|
||||||
- cs-gpios: should specify GPIOs used for chipselects (see spi-bus.txt)
|
- cs-gpios: should specify GPIOs used for chipselects (see spi-bus.txt)
|
||||||
|
|
||||||
|
- no-cs-readback: the CS line is disconnected, therefore the device should not
|
||||||
|
operate based on CS signalling.
|
||||||
|
|
||||||
SPI Controller specific data in SPI slave nodes:
|
SPI Controller specific data in SPI slave nodes:
|
||||||
|
|
||||||
- The spi slave nodes should provide the following information which is required
|
- The spi slave nodes should provide the following information which is required
|
||||||
|
|
|
@ -20,7 +20,7 @@ Optional properties:
|
||||||
chipselect register and offset of that register.
|
chipselect register and offset of that register.
|
||||||
|
|
||||||
NOTE: TI QSPI controller requires different pinmux and IODelay
|
NOTE: TI QSPI controller requires different pinmux and IODelay
|
||||||
paramaters for Mode-0 and Mode-3 operations, which needs to be set up by
|
parameters for Mode-0 and Mode-3 operations, which needs to be set up by
|
||||||
the bootloader (U-Boot). Default configuration only supports Mode-0
|
the bootloader (U-Boot). Default configuration only supports Mode-0
|
||||||
operation. Hence, "spi-cpol" and "spi-cpha" DT properties cannot be
|
operation. Hence, "spi-cpol" and "spi-cpha" DT properties cannot be
|
||||||
specified in the slave nodes of TI QSPI controller without appropriate
|
specified in the slave nodes of TI QSPI controller without appropriate
|
||||||
|
|
|
@ -10921,6 +10921,7 @@ L: linux-spi@vger.kernel.org
|
||||||
T: git git://git.kernel.org/pub/scm/linux/kernel/git/broonie/spi.git
|
T: git git://git.kernel.org/pub/scm/linux/kernel/git/broonie/spi.git
|
||||||
Q: http://patchwork.kernel.org/project/spi-devel-general/list/
|
Q: http://patchwork.kernel.org/project/spi-devel-general/list/
|
||||||
S: Maintained
|
S: Maintained
|
||||||
|
F: Documentation/devicetree/bindings/spi/
|
||||||
F: Documentation/spi/
|
F: Documentation/spi/
|
||||||
F: drivers/spi/
|
F: drivers/spi/
|
||||||
F: include/linux/spi/
|
F: include/linux/spi/
|
||||||
|
|
|
@ -411,6 +411,7 @@ config SPI_OMAP24XX
|
||||||
tristate "McSPI driver for OMAP"
|
tristate "McSPI driver for OMAP"
|
||||||
depends on HAS_DMA
|
depends on HAS_DMA
|
||||||
depends on ARCH_OMAP2PLUS || COMPILE_TEST
|
depends on ARCH_OMAP2PLUS || COMPILE_TEST
|
||||||
|
select SG_SPLIT
|
||||||
help
|
help
|
||||||
SPI master controller for OMAP24XX and later Multichannel SPI
|
SPI master controller for OMAP24XX and later Multichannel SPI
|
||||||
(McSPI) modules.
|
(McSPI) modules.
|
||||||
|
|
|
@ -56,6 +56,7 @@ obj-$(CONFIG_SPI_MT65XX) += spi-mt65xx.o
|
||||||
obj-$(CONFIG_SPI_MXS) += spi-mxs.o
|
obj-$(CONFIG_SPI_MXS) += spi-mxs.o
|
||||||
obj-$(CONFIG_SPI_NUC900) += spi-nuc900.o
|
obj-$(CONFIG_SPI_NUC900) += spi-nuc900.o
|
||||||
obj-$(CONFIG_SPI_OC_TINY) += spi-oc-tiny.o
|
obj-$(CONFIG_SPI_OC_TINY) += spi-oc-tiny.o
|
||||||
|
spi-octeon-objs := spi-cavium.o spi-cavium-octeon.o
|
||||||
obj-$(CONFIG_SPI_OCTEON) += spi-octeon.o
|
obj-$(CONFIG_SPI_OCTEON) += spi-octeon.o
|
||||||
obj-$(CONFIG_SPI_OMAP_UWIRE) += spi-omap-uwire.o
|
obj-$(CONFIG_SPI_OMAP_UWIRE) += spi-omap-uwire.o
|
||||||
obj-$(CONFIG_SPI_OMAP_100K) += spi-omap-100k.o
|
obj-$(CONFIG_SPI_OMAP_100K) += spi-omap-100k.o
|
||||||
|
|
|
@ -64,8 +64,6 @@ struct bfin_sport_spi_master_data {
|
||||||
/* Pin request list */
|
/* Pin request list */
|
||||||
u16 *pin_req;
|
u16 *pin_req;
|
||||||
|
|
||||||
/* Driver message queue */
|
|
||||||
struct workqueue_struct *workqueue;
|
|
||||||
struct work_struct pump_messages;
|
struct work_struct pump_messages;
|
||||||
spinlock_t lock;
|
spinlock_t lock;
|
||||||
struct list_head queue;
|
struct list_head queue;
|
||||||
|
@ -300,7 +298,7 @@ bfin_sport_spi_giveback(struct bfin_sport_spi_master_data *drv_data)
|
||||||
drv_data->cur_msg = NULL;
|
drv_data->cur_msg = NULL;
|
||||||
drv_data->cur_transfer = NULL;
|
drv_data->cur_transfer = NULL;
|
||||||
drv_data->cur_chip = NULL;
|
drv_data->cur_chip = NULL;
|
||||||
queue_work(drv_data->workqueue, &drv_data->pump_messages);
|
schedule_work(&drv_data->pump_messages);
|
||||||
spin_unlock_irqrestore(&drv_data->lock, flags);
|
spin_unlock_irqrestore(&drv_data->lock, flags);
|
||||||
|
|
||||||
if (!drv_data->cs_change)
|
if (!drv_data->cs_change)
|
||||||
|
@ -556,7 +554,7 @@ bfin_sport_spi_transfer(struct spi_device *spi, struct spi_message *msg)
|
||||||
list_add_tail(&msg->queue, &drv_data->queue);
|
list_add_tail(&msg->queue, &drv_data->queue);
|
||||||
|
|
||||||
if (drv_data->run && !drv_data->busy)
|
if (drv_data->run && !drv_data->busy)
|
||||||
queue_work(drv_data->workqueue, &drv_data->pump_messages);
|
schedule_work(&drv_data->pump_messages);
|
||||||
|
|
||||||
spin_unlock_irqrestore(&drv_data->lock, flags);
|
spin_unlock_irqrestore(&drv_data->lock, flags);
|
||||||
|
|
||||||
|
@ -666,12 +664,7 @@ bfin_sport_spi_init_queue(struct bfin_sport_spi_master_data *drv_data)
|
||||||
tasklet_init(&drv_data->pump_transfers,
|
tasklet_init(&drv_data->pump_transfers,
|
||||||
bfin_sport_spi_pump_transfers, (unsigned long)drv_data);
|
bfin_sport_spi_pump_transfers, (unsigned long)drv_data);
|
||||||
|
|
||||||
/* init messages workqueue */
|
|
||||||
INIT_WORK(&drv_data->pump_messages, bfin_sport_spi_pump_messages);
|
INIT_WORK(&drv_data->pump_messages, bfin_sport_spi_pump_messages);
|
||||||
drv_data->workqueue =
|
|
||||||
create_singlethread_workqueue(dev_name(drv_data->master->dev.parent));
|
|
||||||
if (drv_data->workqueue == NULL)
|
|
||||||
return -EBUSY;
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -694,7 +687,7 @@ bfin_sport_spi_start_queue(struct bfin_sport_spi_master_data *drv_data)
|
||||||
drv_data->cur_chip = NULL;
|
drv_data->cur_chip = NULL;
|
||||||
spin_unlock_irqrestore(&drv_data->lock, flags);
|
spin_unlock_irqrestore(&drv_data->lock, flags);
|
||||||
|
|
||||||
queue_work(drv_data->workqueue, &drv_data->pump_messages);
|
schedule_work(&drv_data->pump_messages);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -738,7 +731,7 @@ bfin_sport_spi_destroy_queue(struct bfin_sport_spi_master_data *drv_data)
|
||||||
if (status)
|
if (status)
|
||||||
return status;
|
return status;
|
||||||
|
|
||||||
destroy_workqueue(drv_data->workqueue);
|
flush_work(&drv_data->pump_messages);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,8 +67,6 @@ struct bfin_spi_master_data {
|
||||||
/* BFIN hookup */
|
/* BFIN hookup */
|
||||||
struct bfin5xx_spi_master *master_info;
|
struct bfin5xx_spi_master *master_info;
|
||||||
|
|
||||||
/* Driver message queue */
|
|
||||||
struct workqueue_struct *workqueue;
|
|
||||||
struct work_struct pump_messages;
|
struct work_struct pump_messages;
|
||||||
spinlock_t lock;
|
spinlock_t lock;
|
||||||
struct list_head queue;
|
struct list_head queue;
|
||||||
|
@ -359,7 +357,7 @@ static void bfin_spi_giveback(struct bfin_spi_master_data *drv_data)
|
||||||
drv_data->cur_msg = NULL;
|
drv_data->cur_msg = NULL;
|
||||||
drv_data->cur_transfer = NULL;
|
drv_data->cur_transfer = NULL;
|
||||||
drv_data->cur_chip = NULL;
|
drv_data->cur_chip = NULL;
|
||||||
queue_work(drv_data->workqueue, &drv_data->pump_messages);
|
schedule_work(&drv_data->pump_messages);
|
||||||
spin_unlock_irqrestore(&drv_data->lock, flags);
|
spin_unlock_irqrestore(&drv_data->lock, flags);
|
||||||
|
|
||||||
msg->state = NULL;
|
msg->state = NULL;
|
||||||
|
@ -946,7 +944,7 @@ static int bfin_spi_transfer(struct spi_device *spi, struct spi_message *msg)
|
||||||
list_add_tail(&msg->queue, &drv_data->queue);
|
list_add_tail(&msg->queue, &drv_data->queue);
|
||||||
|
|
||||||
if (drv_data->running && !drv_data->busy)
|
if (drv_data->running && !drv_data->busy)
|
||||||
queue_work(drv_data->workqueue, &drv_data->pump_messages);
|
schedule_work(&drv_data->pump_messages);
|
||||||
|
|
||||||
spin_unlock_irqrestore(&drv_data->lock, flags);
|
spin_unlock_irqrestore(&drv_data->lock, flags);
|
||||||
|
|
||||||
|
@ -1177,12 +1175,7 @@ static int bfin_spi_init_queue(struct bfin_spi_master_data *drv_data)
|
||||||
tasklet_init(&drv_data->pump_transfers,
|
tasklet_init(&drv_data->pump_transfers,
|
||||||
bfin_spi_pump_transfers, (unsigned long)drv_data);
|
bfin_spi_pump_transfers, (unsigned long)drv_data);
|
||||||
|
|
||||||
/* init messages workqueue */
|
|
||||||
INIT_WORK(&drv_data->pump_messages, bfin_spi_pump_messages);
|
INIT_WORK(&drv_data->pump_messages, bfin_spi_pump_messages);
|
||||||
drv_data->workqueue = create_singlethread_workqueue(
|
|
||||||
dev_name(drv_data->master->dev.parent));
|
|
||||||
if (drv_data->workqueue == NULL)
|
|
||||||
return -EBUSY;
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -1204,7 +1197,7 @@ static int bfin_spi_start_queue(struct bfin_spi_master_data *drv_data)
|
||||||
drv_data->cur_chip = NULL;
|
drv_data->cur_chip = NULL;
|
||||||
spin_unlock_irqrestore(&drv_data->lock, flags);
|
spin_unlock_irqrestore(&drv_data->lock, flags);
|
||||||
|
|
||||||
queue_work(drv_data->workqueue, &drv_data->pump_messages);
|
schedule_work(&drv_data->pump_messages);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -1246,7 +1239,7 @@ static int bfin_spi_destroy_queue(struct bfin_spi_master_data *drv_data)
|
||||||
if (status != 0)
|
if (status != 0)
|
||||||
return status;
|
return status;
|
||||||
|
|
||||||
destroy_workqueue(drv_data->workqueue);
|
flush_work(&drv_data->pump_messages);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,104 @@
|
||||||
|
/*
|
||||||
|
* This file is subject to the terms and conditions of the GNU General Public
|
||||||
|
* License. See the file "COPYING" in the main directory of this archive
|
||||||
|
* for more details.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2011, 2012 Cavium, Inc.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/platform_device.h>
|
||||||
|
#include <linux/spi/spi.h>
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/io.h>
|
||||||
|
#include <linux/of.h>
|
||||||
|
|
||||||
|
#include <asm/octeon/octeon.h>
|
||||||
|
|
||||||
|
#include "spi-cavium.h"
|
||||||
|
|
||||||
|
static int octeon_spi_probe(struct platform_device *pdev)
|
||||||
|
{
|
||||||
|
struct resource *res_mem;
|
||||||
|
void __iomem *reg_base;
|
||||||
|
struct spi_master *master;
|
||||||
|
struct octeon_spi *p;
|
||||||
|
int err = -ENOENT;
|
||||||
|
|
||||||
|
master = spi_alloc_master(&pdev->dev, sizeof(struct octeon_spi));
|
||||||
|
if (!master)
|
||||||
|
return -ENOMEM;
|
||||||
|
p = spi_master_get_devdata(master);
|
||||||
|
platform_set_drvdata(pdev, master);
|
||||||
|
|
||||||
|
res_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||||
|
reg_base = devm_ioremap_resource(&pdev->dev, res_mem);
|
||||||
|
if (IS_ERR(reg_base)) {
|
||||||
|
err = PTR_ERR(reg_base);
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
p->register_base = reg_base;
|
||||||
|
p->sys_freq = octeon_get_io_clock_rate();
|
||||||
|
|
||||||
|
p->regs.config = 0;
|
||||||
|
p->regs.status = 0x08;
|
||||||
|
p->regs.tx = 0x10;
|
||||||
|
p->regs.data = 0x80;
|
||||||
|
|
||||||
|
master->num_chipselect = 4;
|
||||||
|
master->mode_bits = SPI_CPHA |
|
||||||
|
SPI_CPOL |
|
||||||
|
SPI_CS_HIGH |
|
||||||
|
SPI_LSB_FIRST |
|
||||||
|
SPI_3WIRE;
|
||||||
|
|
||||||
|
master->transfer_one_message = octeon_spi_transfer_one_message;
|
||||||
|
master->bits_per_word_mask = SPI_BPW_MASK(8);
|
||||||
|
master->max_speed_hz = OCTEON_SPI_MAX_CLOCK_HZ;
|
||||||
|
|
||||||
|
master->dev.of_node = pdev->dev.of_node;
|
||||||
|
err = devm_spi_register_master(&pdev->dev, master);
|
||||||
|
if (err) {
|
||||||
|
dev_err(&pdev->dev, "register master failed: %d\n", err);
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
dev_info(&pdev->dev, "OCTEON SPI bus driver\n");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
fail:
|
||||||
|
spi_master_put(master);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int octeon_spi_remove(struct platform_device *pdev)
|
||||||
|
{
|
||||||
|
struct spi_master *master = platform_get_drvdata(pdev);
|
||||||
|
struct octeon_spi *p = spi_master_get_devdata(master);
|
||||||
|
|
||||||
|
/* Clear the CSENA* and put everything in a known state. */
|
||||||
|
writeq(0, p->register_base + OCTEON_SPI_CFG(p));
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct of_device_id octeon_spi_match[] = {
|
||||||
|
{ .compatible = "cavium,octeon-3010-spi", },
|
||||||
|
{},
|
||||||
|
};
|
||||||
|
MODULE_DEVICE_TABLE(of, octeon_spi_match);
|
||||||
|
|
||||||
|
static struct platform_driver octeon_spi_driver = {
|
||||||
|
.driver = {
|
||||||
|
.name = "spi-octeon",
|
||||||
|
.of_match_table = octeon_spi_match,
|
||||||
|
},
|
||||||
|
.probe = octeon_spi_probe,
|
||||||
|
.remove = octeon_spi_remove,
|
||||||
|
};
|
||||||
|
|
||||||
|
module_platform_driver(octeon_spi_driver);
|
||||||
|
|
||||||
|
MODULE_DESCRIPTION("Cavium, Inc. OCTEON SPI bus driver");
|
||||||
|
MODULE_AUTHOR("David Daney");
|
||||||
|
MODULE_LICENSE("GPL");
|
|
@ -0,0 +1,151 @@
|
||||||
|
/*
|
||||||
|
* This file is subject to the terms and conditions of the GNU General Public
|
||||||
|
* License. See the file "COPYING" in the main directory of this archive
|
||||||
|
* for more details.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2011, 2012 Cavium, Inc.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/spi/spi.h>
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/delay.h>
|
||||||
|
#include <linux/io.h>
|
||||||
|
|
||||||
|
#include "spi-cavium.h"
|
||||||
|
|
||||||
|
static void octeon_spi_wait_ready(struct octeon_spi *p)
|
||||||
|
{
|
||||||
|
union cvmx_mpi_sts mpi_sts;
|
||||||
|
unsigned int loops = 0;
|
||||||
|
|
||||||
|
do {
|
||||||
|
if (loops++)
|
||||||
|
__delay(500);
|
||||||
|
mpi_sts.u64 = readq(p->register_base + OCTEON_SPI_STS(p));
|
||||||
|
} while (mpi_sts.s.busy);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int octeon_spi_do_transfer(struct octeon_spi *p,
|
||||||
|
struct spi_message *msg,
|
||||||
|
struct spi_transfer *xfer,
|
||||||
|
bool last_xfer)
|
||||||
|
{
|
||||||
|
struct spi_device *spi = msg->spi;
|
||||||
|
union cvmx_mpi_cfg mpi_cfg;
|
||||||
|
union cvmx_mpi_tx mpi_tx;
|
||||||
|
unsigned int clkdiv;
|
||||||
|
int mode;
|
||||||
|
bool cpha, cpol;
|
||||||
|
const u8 *tx_buf;
|
||||||
|
u8 *rx_buf;
|
||||||
|
int len;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
mode = spi->mode;
|
||||||
|
cpha = mode & SPI_CPHA;
|
||||||
|
cpol = mode & SPI_CPOL;
|
||||||
|
|
||||||
|
clkdiv = p->sys_freq / (2 * xfer->speed_hz);
|
||||||
|
|
||||||
|
mpi_cfg.u64 = 0;
|
||||||
|
|
||||||
|
mpi_cfg.s.clkdiv = clkdiv;
|
||||||
|
mpi_cfg.s.cshi = (mode & SPI_CS_HIGH) ? 1 : 0;
|
||||||
|
mpi_cfg.s.lsbfirst = (mode & SPI_LSB_FIRST) ? 1 : 0;
|
||||||
|
mpi_cfg.s.wireor = (mode & SPI_3WIRE) ? 1 : 0;
|
||||||
|
mpi_cfg.s.idlelo = cpha != cpol;
|
||||||
|
mpi_cfg.s.cslate = cpha ? 1 : 0;
|
||||||
|
mpi_cfg.s.enable = 1;
|
||||||
|
|
||||||
|
if (spi->chip_select < 4)
|
||||||
|
p->cs_enax |= 1ull << (12 + spi->chip_select);
|
||||||
|
mpi_cfg.u64 |= p->cs_enax;
|
||||||
|
|
||||||
|
if (mpi_cfg.u64 != p->last_cfg) {
|
||||||
|
p->last_cfg = mpi_cfg.u64;
|
||||||
|
writeq(mpi_cfg.u64, p->register_base + OCTEON_SPI_CFG(p));
|
||||||
|
}
|
||||||
|
tx_buf = xfer->tx_buf;
|
||||||
|
rx_buf = xfer->rx_buf;
|
||||||
|
len = xfer->len;
|
||||||
|
while (len > OCTEON_SPI_MAX_BYTES) {
|
||||||
|
for (i = 0; i < OCTEON_SPI_MAX_BYTES; i++) {
|
||||||
|
u8 d;
|
||||||
|
if (tx_buf)
|
||||||
|
d = *tx_buf++;
|
||||||
|
else
|
||||||
|
d = 0;
|
||||||
|
writeq(d, p->register_base + OCTEON_SPI_DAT0(p) + (8 * i));
|
||||||
|
}
|
||||||
|
mpi_tx.u64 = 0;
|
||||||
|
mpi_tx.s.csid = spi->chip_select;
|
||||||
|
mpi_tx.s.leavecs = 1;
|
||||||
|
mpi_tx.s.txnum = tx_buf ? OCTEON_SPI_MAX_BYTES : 0;
|
||||||
|
mpi_tx.s.totnum = OCTEON_SPI_MAX_BYTES;
|
||||||
|
writeq(mpi_tx.u64, p->register_base + OCTEON_SPI_TX(p));
|
||||||
|
|
||||||
|
octeon_spi_wait_ready(p);
|
||||||
|
if (rx_buf)
|
||||||
|
for (i = 0; i < OCTEON_SPI_MAX_BYTES; i++) {
|
||||||
|
u64 v = readq(p->register_base + OCTEON_SPI_DAT0(p) + (8 * i));
|
||||||
|
*rx_buf++ = (u8)v;
|
||||||
|
}
|
||||||
|
len -= OCTEON_SPI_MAX_BYTES;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < len; i++) {
|
||||||
|
u8 d;
|
||||||
|
if (tx_buf)
|
||||||
|
d = *tx_buf++;
|
||||||
|
else
|
||||||
|
d = 0;
|
||||||
|
writeq(d, p->register_base + OCTEON_SPI_DAT0(p) + (8 * i));
|
||||||
|
}
|
||||||
|
|
||||||
|
mpi_tx.u64 = 0;
|
||||||
|
mpi_tx.s.csid = spi->chip_select;
|
||||||
|
if (last_xfer)
|
||||||
|
mpi_tx.s.leavecs = xfer->cs_change;
|
||||||
|
else
|
||||||
|
mpi_tx.s.leavecs = !xfer->cs_change;
|
||||||
|
mpi_tx.s.txnum = tx_buf ? len : 0;
|
||||||
|
mpi_tx.s.totnum = len;
|
||||||
|
writeq(mpi_tx.u64, p->register_base + OCTEON_SPI_TX(p));
|
||||||
|
|
||||||
|
octeon_spi_wait_ready(p);
|
||||||
|
if (rx_buf)
|
||||||
|
for (i = 0; i < len; i++) {
|
||||||
|
u64 v = readq(p->register_base + OCTEON_SPI_DAT0(p) + (8 * i));
|
||||||
|
*rx_buf++ = (u8)v;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (xfer->delay_usecs)
|
||||||
|
udelay(xfer->delay_usecs);
|
||||||
|
|
||||||
|
return xfer->len;
|
||||||
|
}
|
||||||
|
|
||||||
|
int octeon_spi_transfer_one_message(struct spi_master *master,
|
||||||
|
struct spi_message *msg)
|
||||||
|
{
|
||||||
|
struct octeon_spi *p = spi_master_get_devdata(master);
|
||||||
|
unsigned int total_len = 0;
|
||||||
|
int status = 0;
|
||||||
|
struct spi_transfer *xfer;
|
||||||
|
|
||||||
|
list_for_each_entry(xfer, &msg->transfers, transfer_list) {
|
||||||
|
bool last_xfer = list_is_last(&xfer->transfer_list,
|
||||||
|
&msg->transfers);
|
||||||
|
int r = octeon_spi_do_transfer(p, msg, xfer, last_xfer);
|
||||||
|
if (r < 0) {
|
||||||
|
status = r;
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
total_len += r;
|
||||||
|
}
|
||||||
|
err:
|
||||||
|
msg->status = status;
|
||||||
|
msg->actual_length = total_len;
|
||||||
|
spi_finalize_current_message(master);
|
||||||
|
return status;
|
||||||
|
}
|
|
@ -1,32 +1,33 @@
|
||||||
/***********************license start***************
|
#ifndef __SPI_CAVIUM_H
|
||||||
* Author: Cavium Networks
|
#define __SPI_CAVIUM_H
|
||||||
*
|
|
||||||
* Contact: support@caviumnetworks.com
|
|
||||||
* This file is part of the OCTEON SDK
|
|
||||||
*
|
|
||||||
* Copyright (c) 2003-2012 Cavium Networks
|
|
||||||
*
|
|
||||||
* This file is free software; you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License, Version 2, as
|
|
||||||
* published by the Free Software Foundation.
|
|
||||||
*
|
|
||||||
* This file is distributed in the hope that it will be useful, but
|
|
||||||
* AS-IS and WITHOUT ANY WARRANTY; without even the implied warranty
|
|
||||||
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, TITLE, or
|
|
||||||
* NONINFRINGEMENT. See the GNU General Public License for more
|
|
||||||
* details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this file; if not, write to the Free Software
|
|
||||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
||||||
* or visit http://www.gnu.org/licenses/.
|
|
||||||
*
|
|
||||||
* This file may also be available under a different license from Cavium.
|
|
||||||
* Contact Cavium Networks for more information
|
|
||||||
***********************license end**************************************/
|
|
||||||
|
|
||||||
#ifndef __CVMX_MPI_DEFS_H__
|
#define OCTEON_SPI_MAX_BYTES 9
|
||||||
#define __CVMX_MPI_DEFS_H__
|
#define OCTEON_SPI_MAX_CLOCK_HZ 16000000
|
||||||
|
|
||||||
|
struct octeon_spi_regs {
|
||||||
|
int config;
|
||||||
|
int status;
|
||||||
|
int tx;
|
||||||
|
int data;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct octeon_spi {
|
||||||
|
void __iomem *register_base;
|
||||||
|
u64 last_cfg;
|
||||||
|
u64 cs_enax;
|
||||||
|
int sys_freq;
|
||||||
|
struct octeon_spi_regs regs;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define OCTEON_SPI_CFG(x) (x->regs.config)
|
||||||
|
#define OCTEON_SPI_STS(x) (x->regs.status)
|
||||||
|
#define OCTEON_SPI_TX(x) (x->regs.tx)
|
||||||
|
#define OCTEON_SPI_DAT0(x) (x->regs.data)
|
||||||
|
|
||||||
|
int octeon_spi_transfer_one_message(struct spi_master *master,
|
||||||
|
struct spi_message *msg);
|
||||||
|
|
||||||
|
/* MPI register descriptions */
|
||||||
|
|
||||||
#define CVMX_MPI_CFG (CVMX_ADD_IO_SEG(0x0001070000001000ull))
|
#define CVMX_MPI_CFG (CVMX_ADD_IO_SEG(0x0001070000001000ull))
|
||||||
#define CVMX_MPI_DATX(offset) (CVMX_ADD_IO_SEG(0x0001070000001080ull) + ((offset) & 15) * 8)
|
#define CVMX_MPI_DATX(offset) (CVMX_ADD_IO_SEG(0x0001070000001080ull) + ((offset) & 15) * 8)
|
||||||
|
@ -325,4 +326,4 @@ union cvmx_mpi_tx {
|
||||||
struct cvmx_mpi_tx_cn61xx cnf71xx;
|
struct cvmx_mpi_tx_cn61xx cnf71xx;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif /* __SPI_CAVIUM_H */
|
|
@ -1,7 +1,7 @@
|
||||||
/*
|
/*
|
||||||
* CLPS711X SPI bus driver
|
* CLPS711X SPI bus driver
|
||||||
*
|
*
|
||||||
* Copyright (C) 2012-2014 Alexander Shiyan <shc_work@mail.ru>
|
* Copyright (C) 2012-2016 Alexander Shiyan <shc_work@mail.ru>
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* This program is free software; you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -12,7 +12,6 @@
|
||||||
#include <linux/io.h>
|
#include <linux/io.h>
|
||||||
#include <linux/clk.h>
|
#include <linux/clk.h>
|
||||||
#include <linux/gpio.h>
|
#include <linux/gpio.h>
|
||||||
#include <linux/delay.h>
|
|
||||||
#include <linux/module.h>
|
#include <linux/module.h>
|
||||||
#include <linux/interrupt.h>
|
#include <linux/interrupt.h>
|
||||||
#include <linux/platform_device.h>
|
#include <linux/platform_device.h>
|
||||||
|
@ -20,9 +19,8 @@
|
||||||
#include <linux/mfd/syscon.h>
|
#include <linux/mfd/syscon.h>
|
||||||
#include <linux/mfd/syscon/clps711x.h>
|
#include <linux/mfd/syscon/clps711x.h>
|
||||||
#include <linux/spi/spi.h>
|
#include <linux/spi/spi.h>
|
||||||
#include <linux/platform_data/spi-clps711x.h>
|
|
||||||
|
|
||||||
#define DRIVER_NAME "spi-clps711x"
|
#define DRIVER_NAME "clps711x-spi"
|
||||||
|
|
||||||
#define SYNCIO_FRMLEN(x) ((x) << 8)
|
#define SYNCIO_FRMLEN(x) ((x) << 8)
|
||||||
#define SYNCIO_TXFRMEN (1 << 14)
|
#define SYNCIO_TXFRMEN (1 << 14)
|
||||||
|
@ -40,6 +38,17 @@ struct spi_clps711x_data {
|
||||||
|
|
||||||
static int spi_clps711x_setup(struct spi_device *spi)
|
static int spi_clps711x_setup(struct spi_device *spi)
|
||||||
{
|
{
|
||||||
|
if (!spi->controller_state) {
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = devm_gpio_request(&spi->master->dev, spi->cs_gpio,
|
||||||
|
dev_name(&spi->master->dev));
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
spi->controller_state = spi;
|
||||||
|
}
|
||||||
|
|
||||||
/* We are expect that SPI-device is not selected */
|
/* We are expect that SPI-device is not selected */
|
||||||
gpio_direction_output(spi->cs_gpio, !(spi->mode & SPI_CS_HIGH));
|
gpio_direction_output(spi->cs_gpio, !(spi->mode & SPI_CS_HIGH));
|
||||||
|
|
||||||
|
@ -104,20 +113,9 @@ static irqreturn_t spi_clps711x_isr(int irq, void *dev_id)
|
||||||
static int spi_clps711x_probe(struct platform_device *pdev)
|
static int spi_clps711x_probe(struct platform_device *pdev)
|
||||||
{
|
{
|
||||||
struct spi_clps711x_data *hw;
|
struct spi_clps711x_data *hw;
|
||||||
struct spi_clps711x_pdata *pdata = dev_get_platdata(&pdev->dev);
|
|
||||||
struct spi_master *master;
|
struct spi_master *master;
|
||||||
struct resource *res;
|
struct resource *res;
|
||||||
int i, irq, ret;
|
int irq, ret;
|
||||||
|
|
||||||
if (!pdata) {
|
|
||||||
dev_err(&pdev->dev, "No platform data supplied\n");
|
|
||||||
return -EINVAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pdata->num_chipselect < 1) {
|
|
||||||
dev_err(&pdev->dev, "At least one CS must be defined\n");
|
|
||||||
return -EINVAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
irq = platform_get_irq(pdev, 0);
|
irq = platform_get_irq(pdev, 0);
|
||||||
if (irq < 0)
|
if (irq < 0)
|
||||||
|
@ -127,40 +125,24 @@ static int spi_clps711x_probe(struct platform_device *pdev)
|
||||||
if (!master)
|
if (!master)
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
|
|
||||||
master->cs_gpios = devm_kzalloc(&pdev->dev, sizeof(int) *
|
master->bus_num = -1;
|
||||||
pdata->num_chipselect, GFP_KERNEL);
|
|
||||||
if (!master->cs_gpios) {
|
|
||||||
ret = -ENOMEM;
|
|
||||||
goto err_out;
|
|
||||||
}
|
|
||||||
|
|
||||||
master->bus_num = pdev->id;
|
|
||||||
master->mode_bits = SPI_CPHA | SPI_CS_HIGH;
|
master->mode_bits = SPI_CPHA | SPI_CS_HIGH;
|
||||||
master->bits_per_word_mask = SPI_BPW_RANGE_MASK(1, 8);
|
master->bits_per_word_mask = SPI_BPW_RANGE_MASK(1, 8);
|
||||||
master->num_chipselect = pdata->num_chipselect;
|
master->dev.of_node = pdev->dev.of_node;
|
||||||
master->setup = spi_clps711x_setup;
|
master->setup = spi_clps711x_setup;
|
||||||
master->prepare_message = spi_clps711x_prepare_message;
|
master->prepare_message = spi_clps711x_prepare_message;
|
||||||
master->transfer_one = spi_clps711x_transfer_one;
|
master->transfer_one = spi_clps711x_transfer_one;
|
||||||
|
|
||||||
hw = spi_master_get_devdata(master);
|
hw = spi_master_get_devdata(master);
|
||||||
|
|
||||||
for (i = 0; i < master->num_chipselect; i++) {
|
|
||||||
master->cs_gpios[i] = pdata->chipselect[i];
|
|
||||||
ret = devm_gpio_request(&pdev->dev, master->cs_gpios[i],
|
|
||||||
DRIVER_NAME);
|
|
||||||
if (ret) {
|
|
||||||
dev_err(&pdev->dev, "Can't get CS GPIO %i\n", i);
|
|
||||||
goto err_out;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
hw->spi_clk = devm_clk_get(&pdev->dev, NULL);
|
hw->spi_clk = devm_clk_get(&pdev->dev, NULL);
|
||||||
if (IS_ERR(hw->spi_clk)) {
|
if (IS_ERR(hw->spi_clk)) {
|
||||||
ret = PTR_ERR(hw->spi_clk);
|
ret = PTR_ERR(hw->spi_clk);
|
||||||
goto err_out;
|
goto err_out;
|
||||||
}
|
}
|
||||||
|
|
||||||
hw->syscon = syscon_regmap_lookup_by_pdevname("syscon.3");
|
hw->syscon =
|
||||||
|
syscon_regmap_lookup_by_compatible("cirrus,ep7209-syscon3");
|
||||||
if (IS_ERR(hw->syscon)) {
|
if (IS_ERR(hw->syscon)) {
|
||||||
ret = PTR_ERR(hw->syscon);
|
ret = PTR_ERR(hw->syscon);
|
||||||
goto err_out;
|
goto err_out;
|
||||||
|
@ -185,14 +167,8 @@ static int spi_clps711x_probe(struct platform_device *pdev)
|
||||||
goto err_out;
|
goto err_out;
|
||||||
|
|
||||||
ret = devm_spi_register_master(&pdev->dev, master);
|
ret = devm_spi_register_master(&pdev->dev, master);
|
||||||
if (!ret) {
|
if (!ret)
|
||||||
dev_info(&pdev->dev,
|
|
||||||
"SPI bus driver initialized. Master clock %u Hz\n",
|
|
||||||
master->max_speed_hz);
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
|
||||||
|
|
||||||
dev_err(&pdev->dev, "Failed to register master\n");
|
|
||||||
|
|
||||||
err_out:
|
err_out:
|
||||||
spi_master_put(master);
|
spi_master_put(master);
|
||||||
|
@ -200,9 +176,16 @@ err_out:
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const struct of_device_id clps711x_spi_dt_ids[] = {
|
||||||
|
{ .compatible = "cirrus,ep7209-spi", },
|
||||||
|
{ }
|
||||||
|
};
|
||||||
|
MODULE_DEVICE_TABLE(of, clps711x_spi_dt_ids);
|
||||||
|
|
||||||
static struct platform_driver clps711x_spi_driver = {
|
static struct platform_driver clps711x_spi_driver = {
|
||||||
.driver = {
|
.driver = {
|
||||||
.name = DRIVER_NAME,
|
.name = DRIVER_NAME,
|
||||||
|
.of_match_table = clps711x_spi_dt_ids,
|
||||||
},
|
},
|
||||||
.probe = spi_clps711x_probe,
|
.probe = spi_clps711x_probe,
|
||||||
};
|
};
|
||||||
|
|
|
@ -59,8 +59,6 @@
|
||||||
struct spi_imx_config {
|
struct spi_imx_config {
|
||||||
unsigned int speed_hz;
|
unsigned int speed_hz;
|
||||||
unsigned int bpw;
|
unsigned int bpw;
|
||||||
unsigned int mode;
|
|
||||||
u8 cs;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
enum spi_imx_devtype {
|
enum spi_imx_devtype {
|
||||||
|
@ -76,7 +74,7 @@ struct spi_imx_data;
|
||||||
|
|
||||||
struct spi_imx_devtype_data {
|
struct spi_imx_devtype_data {
|
||||||
void (*intctrl)(struct spi_imx_data *, int);
|
void (*intctrl)(struct spi_imx_data *, int);
|
||||||
int (*config)(struct spi_imx_data *, struct spi_imx_config *);
|
int (*config)(struct spi_device *, struct spi_imx_config *);
|
||||||
void (*trigger)(struct spi_imx_data *);
|
void (*trigger)(struct spi_imx_data *);
|
||||||
int (*rx_available)(struct spi_imx_data *);
|
int (*rx_available)(struct spi_imx_data *);
|
||||||
void (*reset)(struct spi_imx_data *);
|
void (*reset)(struct spi_imx_data *);
|
||||||
|
@ -112,7 +110,6 @@ struct spi_imx_data {
|
||||||
struct completion dma_tx_completion;
|
struct completion dma_tx_completion;
|
||||||
|
|
||||||
const struct spi_imx_devtype_data *devtype_data;
|
const struct spi_imx_devtype_data *devtype_data;
|
||||||
int chipselect[0];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static inline int is_imx27_cspi(struct spi_imx_data *d)
|
static inline int is_imx27_cspi(struct spi_imx_data *d)
|
||||||
|
@ -312,7 +309,7 @@ static unsigned int mx51_ecspi_clkdiv(struct spi_imx_data *spi_imx,
|
||||||
(post << MX51_ECSPI_CTRL_POSTDIV_OFFSET);
|
(post << MX51_ECSPI_CTRL_POSTDIV_OFFSET);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void __maybe_unused mx51_ecspi_intctrl(struct spi_imx_data *spi_imx, int enable)
|
static void mx51_ecspi_intctrl(struct spi_imx_data *spi_imx, int enable)
|
||||||
{
|
{
|
||||||
unsigned val = 0;
|
unsigned val = 0;
|
||||||
|
|
||||||
|
@ -325,7 +322,7 @@ static void __maybe_unused mx51_ecspi_intctrl(struct spi_imx_data *spi_imx, int
|
||||||
writel(val, spi_imx->base + MX51_ECSPI_INT);
|
writel(val, spi_imx->base + MX51_ECSPI_INT);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void __maybe_unused mx51_ecspi_trigger(struct spi_imx_data *spi_imx)
|
static void mx51_ecspi_trigger(struct spi_imx_data *spi_imx)
|
||||||
{
|
{
|
||||||
u32 reg;
|
u32 reg;
|
||||||
|
|
||||||
|
@ -334,9 +331,10 @@ static void __maybe_unused mx51_ecspi_trigger(struct spi_imx_data *spi_imx)
|
||||||
writel(reg, spi_imx->base + MX51_ECSPI_CTRL);
|
writel(reg, spi_imx->base + MX51_ECSPI_CTRL);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int __maybe_unused mx51_ecspi_config(struct spi_imx_data *spi_imx,
|
static int mx51_ecspi_config(struct spi_device *spi,
|
||||||
struct spi_imx_config *config)
|
struct spi_imx_config *config)
|
||||||
{
|
{
|
||||||
|
struct spi_imx_data *spi_imx = spi_master_get_devdata(spi->master);
|
||||||
u32 ctrl = MX51_ECSPI_CTRL_ENABLE;
|
u32 ctrl = MX51_ECSPI_CTRL_ENABLE;
|
||||||
u32 clk = config->speed_hz, delay, reg;
|
u32 clk = config->speed_hz, delay, reg;
|
||||||
u32 cfg = readl(spi_imx->base + MX51_ECSPI_CONFIG);
|
u32 cfg = readl(spi_imx->base + MX51_ECSPI_CONFIG);
|
||||||
|
@ -355,28 +353,28 @@ static int __maybe_unused mx51_ecspi_config(struct spi_imx_data *spi_imx,
|
||||||
spi_imx->spi_bus_clk = clk;
|
spi_imx->spi_bus_clk = clk;
|
||||||
|
|
||||||
/* set chip select to use */
|
/* set chip select to use */
|
||||||
ctrl |= MX51_ECSPI_CTRL_CS(config->cs);
|
ctrl |= MX51_ECSPI_CTRL_CS(spi->chip_select);
|
||||||
|
|
||||||
ctrl |= (config->bpw - 1) << MX51_ECSPI_CTRL_BL_OFFSET;
|
ctrl |= (config->bpw - 1) << MX51_ECSPI_CTRL_BL_OFFSET;
|
||||||
|
|
||||||
cfg |= MX51_ECSPI_CONFIG_SBBCTRL(config->cs);
|
cfg |= MX51_ECSPI_CONFIG_SBBCTRL(spi->chip_select);
|
||||||
|
|
||||||
if (config->mode & SPI_CPHA)
|
if (spi->mode & SPI_CPHA)
|
||||||
cfg |= MX51_ECSPI_CONFIG_SCLKPHA(config->cs);
|
cfg |= MX51_ECSPI_CONFIG_SCLKPHA(spi->chip_select);
|
||||||
else
|
else
|
||||||
cfg &= ~MX51_ECSPI_CONFIG_SCLKPHA(config->cs);
|
cfg &= ~MX51_ECSPI_CONFIG_SCLKPHA(spi->chip_select);
|
||||||
|
|
||||||
if (config->mode & SPI_CPOL) {
|
if (spi->mode & SPI_CPOL) {
|
||||||
cfg |= MX51_ECSPI_CONFIG_SCLKPOL(config->cs);
|
cfg |= MX51_ECSPI_CONFIG_SCLKPOL(spi->chip_select);
|
||||||
cfg |= MX51_ECSPI_CONFIG_SCLKCTL(config->cs);
|
cfg |= MX51_ECSPI_CONFIG_SCLKCTL(spi->chip_select);
|
||||||
} else {
|
} else {
|
||||||
cfg &= ~MX51_ECSPI_CONFIG_SCLKPOL(config->cs);
|
cfg &= ~MX51_ECSPI_CONFIG_SCLKPOL(spi->chip_select);
|
||||||
cfg &= ~MX51_ECSPI_CONFIG_SCLKCTL(config->cs);
|
cfg &= ~MX51_ECSPI_CONFIG_SCLKCTL(spi->chip_select);
|
||||||
}
|
}
|
||||||
if (config->mode & SPI_CS_HIGH)
|
if (spi->mode & SPI_CS_HIGH)
|
||||||
cfg |= MX51_ECSPI_CONFIG_SSBPOL(config->cs);
|
cfg |= MX51_ECSPI_CONFIG_SSBPOL(spi->chip_select);
|
||||||
else
|
else
|
||||||
cfg &= ~MX51_ECSPI_CONFIG_SSBPOL(config->cs);
|
cfg &= ~MX51_ECSPI_CONFIG_SSBPOL(spi->chip_select);
|
||||||
|
|
||||||
if (spi_imx->usedma)
|
if (spi_imx->usedma)
|
||||||
ctrl |= MX51_ECSPI_CTRL_SMC;
|
ctrl |= MX51_ECSPI_CTRL_SMC;
|
||||||
|
@ -385,7 +383,7 @@ static int __maybe_unused mx51_ecspi_config(struct spi_imx_data *spi_imx,
|
||||||
writel(ctrl, spi_imx->base + MX51_ECSPI_CTRL);
|
writel(ctrl, spi_imx->base + MX51_ECSPI_CTRL);
|
||||||
|
|
||||||
reg = readl(spi_imx->base + MX51_ECSPI_TESTREG);
|
reg = readl(spi_imx->base + MX51_ECSPI_TESTREG);
|
||||||
if (config->mode & SPI_LOOP)
|
if (spi->mode & SPI_LOOP)
|
||||||
reg |= MX51_ECSPI_TESTREG_LBC;
|
reg |= MX51_ECSPI_TESTREG_LBC;
|
||||||
else
|
else
|
||||||
reg &= ~MX51_ECSPI_TESTREG_LBC;
|
reg &= ~MX51_ECSPI_TESTREG_LBC;
|
||||||
|
@ -424,12 +422,12 @@ static int __maybe_unused mx51_ecspi_config(struct spi_imx_data *spi_imx,
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int __maybe_unused mx51_ecspi_rx_available(struct spi_imx_data *spi_imx)
|
static int mx51_ecspi_rx_available(struct spi_imx_data *spi_imx)
|
||||||
{
|
{
|
||||||
return readl(spi_imx->base + MX51_ECSPI_STAT) & MX51_ECSPI_STAT_RR;
|
return readl(spi_imx->base + MX51_ECSPI_STAT) & MX51_ECSPI_STAT_RR;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void __maybe_unused mx51_ecspi_reset(struct spi_imx_data *spi_imx)
|
static void mx51_ecspi_reset(struct spi_imx_data *spi_imx)
|
||||||
{
|
{
|
||||||
/* drain receive buffer */
|
/* drain receive buffer */
|
||||||
while (mx51_ecspi_rx_available(spi_imx))
|
while (mx51_ecspi_rx_available(spi_imx))
|
||||||
|
@ -459,7 +457,7 @@ static void __maybe_unused mx51_ecspi_reset(struct spi_imx_data *spi_imx)
|
||||||
* the i.MX35 has a slightly different register layout for bits
|
* the i.MX35 has a slightly different register layout for bits
|
||||||
* we do not use here.
|
* we do not use here.
|
||||||
*/
|
*/
|
||||||
static void __maybe_unused mx31_intctrl(struct spi_imx_data *spi_imx, int enable)
|
static void mx31_intctrl(struct spi_imx_data *spi_imx, int enable)
|
||||||
{
|
{
|
||||||
unsigned int val = 0;
|
unsigned int val = 0;
|
||||||
|
|
||||||
|
@ -471,7 +469,7 @@ static void __maybe_unused mx31_intctrl(struct spi_imx_data *spi_imx, int enable
|
||||||
writel(val, spi_imx->base + MXC_CSPIINT);
|
writel(val, spi_imx->base + MXC_CSPIINT);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void __maybe_unused mx31_trigger(struct spi_imx_data *spi_imx)
|
static void mx31_trigger(struct spi_imx_data *spi_imx)
|
||||||
{
|
{
|
||||||
unsigned int reg;
|
unsigned int reg;
|
||||||
|
|
||||||
|
@ -480,11 +478,10 @@ static void __maybe_unused mx31_trigger(struct spi_imx_data *spi_imx)
|
||||||
writel(reg, spi_imx->base + MXC_CSPICTRL);
|
writel(reg, spi_imx->base + MXC_CSPICTRL);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int __maybe_unused mx31_config(struct spi_imx_data *spi_imx,
|
static int mx31_config(struct spi_device *spi, struct spi_imx_config *config)
|
||||||
struct spi_imx_config *config)
|
|
||||||
{
|
{
|
||||||
|
struct spi_imx_data *spi_imx = spi_master_get_devdata(spi->master);
|
||||||
unsigned int reg = MX31_CSPICTRL_ENABLE | MX31_CSPICTRL_MASTER;
|
unsigned int reg = MX31_CSPICTRL_ENABLE | MX31_CSPICTRL_MASTER;
|
||||||
int cs = spi_imx->chipselect[config->cs];
|
|
||||||
|
|
||||||
reg |= spi_imx_clkdiv_2(spi_imx->spi_clk, config->speed_hz) <<
|
reg |= spi_imx_clkdiv_2(spi_imx->spi_clk, config->speed_hz) <<
|
||||||
MX31_CSPICTRL_DR_SHIFT;
|
MX31_CSPICTRL_DR_SHIFT;
|
||||||
|
@ -496,14 +493,14 @@ static int __maybe_unused mx31_config(struct spi_imx_data *spi_imx,
|
||||||
reg |= (config->bpw - 1) << MX31_CSPICTRL_BC_SHIFT;
|
reg |= (config->bpw - 1) << MX31_CSPICTRL_BC_SHIFT;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config->mode & SPI_CPHA)
|
if (spi->mode & SPI_CPHA)
|
||||||
reg |= MX31_CSPICTRL_PHA;
|
reg |= MX31_CSPICTRL_PHA;
|
||||||
if (config->mode & SPI_CPOL)
|
if (spi->mode & SPI_CPOL)
|
||||||
reg |= MX31_CSPICTRL_POL;
|
reg |= MX31_CSPICTRL_POL;
|
||||||
if (config->mode & SPI_CS_HIGH)
|
if (spi->mode & SPI_CS_HIGH)
|
||||||
reg |= MX31_CSPICTRL_SSPOL;
|
reg |= MX31_CSPICTRL_SSPOL;
|
||||||
if (cs < 0)
|
if (spi->cs_gpio < 0)
|
||||||
reg |= (cs + 32) <<
|
reg |= (spi->cs_gpio + 32) <<
|
||||||
(is_imx35_cspi(spi_imx) ? MX35_CSPICTRL_CS_SHIFT :
|
(is_imx35_cspi(spi_imx) ? MX35_CSPICTRL_CS_SHIFT :
|
||||||
MX31_CSPICTRL_CS_SHIFT);
|
MX31_CSPICTRL_CS_SHIFT);
|
||||||
|
|
||||||
|
@ -512,12 +509,12 @@ static int __maybe_unused mx31_config(struct spi_imx_data *spi_imx,
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int __maybe_unused mx31_rx_available(struct spi_imx_data *spi_imx)
|
static int mx31_rx_available(struct spi_imx_data *spi_imx)
|
||||||
{
|
{
|
||||||
return readl(spi_imx->base + MX31_CSPISTATUS) & MX31_STATUS_RR;
|
return readl(spi_imx->base + MX31_CSPISTATUS) & MX31_STATUS_RR;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void __maybe_unused mx31_reset(struct spi_imx_data *spi_imx)
|
static void mx31_reset(struct spi_imx_data *spi_imx)
|
||||||
{
|
{
|
||||||
/* drain receive buffer */
|
/* drain receive buffer */
|
||||||
while (readl(spi_imx->base + MX31_CSPISTATUS) & MX31_STATUS_RR)
|
while (readl(spi_imx->base + MX31_CSPISTATUS) & MX31_STATUS_RR)
|
||||||
|
@ -537,7 +534,7 @@ static void __maybe_unused mx31_reset(struct spi_imx_data *spi_imx)
|
||||||
#define MX21_CSPICTRL_DR_SHIFT 14
|
#define MX21_CSPICTRL_DR_SHIFT 14
|
||||||
#define MX21_CSPICTRL_CS_SHIFT 19
|
#define MX21_CSPICTRL_CS_SHIFT 19
|
||||||
|
|
||||||
static void __maybe_unused mx21_intctrl(struct spi_imx_data *spi_imx, int enable)
|
static void mx21_intctrl(struct spi_imx_data *spi_imx, int enable)
|
||||||
{
|
{
|
||||||
unsigned int val = 0;
|
unsigned int val = 0;
|
||||||
|
|
||||||
|
@ -549,7 +546,7 @@ static void __maybe_unused mx21_intctrl(struct spi_imx_data *spi_imx, int enable
|
||||||
writel(val, spi_imx->base + MXC_CSPIINT);
|
writel(val, spi_imx->base + MXC_CSPIINT);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void __maybe_unused mx21_trigger(struct spi_imx_data *spi_imx)
|
static void mx21_trigger(struct spi_imx_data *spi_imx)
|
||||||
{
|
{
|
||||||
unsigned int reg;
|
unsigned int reg;
|
||||||
|
|
||||||
|
@ -558,37 +555,36 @@ static void __maybe_unused mx21_trigger(struct spi_imx_data *spi_imx)
|
||||||
writel(reg, spi_imx->base + MXC_CSPICTRL);
|
writel(reg, spi_imx->base + MXC_CSPICTRL);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int __maybe_unused mx21_config(struct spi_imx_data *spi_imx,
|
static int mx21_config(struct spi_device *spi, struct spi_imx_config *config)
|
||||||
struct spi_imx_config *config)
|
|
||||||
{
|
{
|
||||||
|
struct spi_imx_data *spi_imx = spi_master_get_devdata(spi->master);
|
||||||
unsigned int reg = MX21_CSPICTRL_ENABLE | MX21_CSPICTRL_MASTER;
|
unsigned int reg = MX21_CSPICTRL_ENABLE | MX21_CSPICTRL_MASTER;
|
||||||
int cs = spi_imx->chipselect[config->cs];
|
|
||||||
unsigned int max = is_imx27_cspi(spi_imx) ? 16 : 18;
|
unsigned int max = is_imx27_cspi(spi_imx) ? 16 : 18;
|
||||||
|
|
||||||
reg |= spi_imx_clkdiv_1(spi_imx->spi_clk, config->speed_hz, max) <<
|
reg |= spi_imx_clkdiv_1(spi_imx->spi_clk, config->speed_hz, max) <<
|
||||||
MX21_CSPICTRL_DR_SHIFT;
|
MX21_CSPICTRL_DR_SHIFT;
|
||||||
reg |= config->bpw - 1;
|
reg |= config->bpw - 1;
|
||||||
|
|
||||||
if (config->mode & SPI_CPHA)
|
if (spi->mode & SPI_CPHA)
|
||||||
reg |= MX21_CSPICTRL_PHA;
|
reg |= MX21_CSPICTRL_PHA;
|
||||||
if (config->mode & SPI_CPOL)
|
if (spi->mode & SPI_CPOL)
|
||||||
reg |= MX21_CSPICTRL_POL;
|
reg |= MX21_CSPICTRL_POL;
|
||||||
if (config->mode & SPI_CS_HIGH)
|
if (spi->mode & SPI_CS_HIGH)
|
||||||
reg |= MX21_CSPICTRL_SSPOL;
|
reg |= MX21_CSPICTRL_SSPOL;
|
||||||
if (cs < 0)
|
if (spi->cs_gpio < 0)
|
||||||
reg |= (cs + 32) << MX21_CSPICTRL_CS_SHIFT;
|
reg |= (spi->cs_gpio + 32) << MX21_CSPICTRL_CS_SHIFT;
|
||||||
|
|
||||||
writel(reg, spi_imx->base + MXC_CSPICTRL);
|
writel(reg, spi_imx->base + MXC_CSPICTRL);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int __maybe_unused mx21_rx_available(struct spi_imx_data *spi_imx)
|
static int mx21_rx_available(struct spi_imx_data *spi_imx)
|
||||||
{
|
{
|
||||||
return readl(spi_imx->base + MXC_CSPIINT) & MX21_INTREG_RR;
|
return readl(spi_imx->base + MXC_CSPIINT) & MX21_INTREG_RR;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void __maybe_unused mx21_reset(struct spi_imx_data *spi_imx)
|
static void mx21_reset(struct spi_imx_data *spi_imx)
|
||||||
{
|
{
|
||||||
writel(1, spi_imx->base + MXC_RESET);
|
writel(1, spi_imx->base + MXC_RESET);
|
||||||
}
|
}
|
||||||
|
@ -604,7 +600,7 @@ static void __maybe_unused mx21_reset(struct spi_imx_data *spi_imx)
|
||||||
#define MX1_CSPICTRL_MASTER (1 << 10)
|
#define MX1_CSPICTRL_MASTER (1 << 10)
|
||||||
#define MX1_CSPICTRL_DR_SHIFT 13
|
#define MX1_CSPICTRL_DR_SHIFT 13
|
||||||
|
|
||||||
static void __maybe_unused mx1_intctrl(struct spi_imx_data *spi_imx, int enable)
|
static void mx1_intctrl(struct spi_imx_data *spi_imx, int enable)
|
||||||
{
|
{
|
||||||
unsigned int val = 0;
|
unsigned int val = 0;
|
||||||
|
|
||||||
|
@ -616,7 +612,7 @@ static void __maybe_unused mx1_intctrl(struct spi_imx_data *spi_imx, int enable)
|
||||||
writel(val, spi_imx->base + MXC_CSPIINT);
|
writel(val, spi_imx->base + MXC_CSPIINT);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void __maybe_unused mx1_trigger(struct spi_imx_data *spi_imx)
|
static void mx1_trigger(struct spi_imx_data *spi_imx)
|
||||||
{
|
{
|
||||||
unsigned int reg;
|
unsigned int reg;
|
||||||
|
|
||||||
|
@ -625,18 +621,18 @@ static void __maybe_unused mx1_trigger(struct spi_imx_data *spi_imx)
|
||||||
writel(reg, spi_imx->base + MXC_CSPICTRL);
|
writel(reg, spi_imx->base + MXC_CSPICTRL);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int __maybe_unused mx1_config(struct spi_imx_data *spi_imx,
|
static int mx1_config(struct spi_device *spi, struct spi_imx_config *config)
|
||||||
struct spi_imx_config *config)
|
|
||||||
{
|
{
|
||||||
|
struct spi_imx_data *spi_imx = spi_master_get_devdata(spi->master);
|
||||||
unsigned int reg = MX1_CSPICTRL_ENABLE | MX1_CSPICTRL_MASTER;
|
unsigned int reg = MX1_CSPICTRL_ENABLE | MX1_CSPICTRL_MASTER;
|
||||||
|
|
||||||
reg |= spi_imx_clkdiv_2(spi_imx->spi_clk, config->speed_hz) <<
|
reg |= spi_imx_clkdiv_2(spi_imx->spi_clk, config->speed_hz) <<
|
||||||
MX1_CSPICTRL_DR_SHIFT;
|
MX1_CSPICTRL_DR_SHIFT;
|
||||||
reg |= config->bpw - 1;
|
reg |= config->bpw - 1;
|
||||||
|
|
||||||
if (config->mode & SPI_CPHA)
|
if (spi->mode & SPI_CPHA)
|
||||||
reg |= MX1_CSPICTRL_PHA;
|
reg |= MX1_CSPICTRL_PHA;
|
||||||
if (config->mode & SPI_CPOL)
|
if (spi->mode & SPI_CPOL)
|
||||||
reg |= MX1_CSPICTRL_POL;
|
reg |= MX1_CSPICTRL_POL;
|
||||||
|
|
||||||
writel(reg, spi_imx->base + MXC_CSPICTRL);
|
writel(reg, spi_imx->base + MXC_CSPICTRL);
|
||||||
|
@ -644,12 +640,12 @@ static int __maybe_unused mx1_config(struct spi_imx_data *spi_imx,
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int __maybe_unused mx1_rx_available(struct spi_imx_data *spi_imx)
|
static int mx1_rx_available(struct spi_imx_data *spi_imx)
|
||||||
{
|
{
|
||||||
return readl(spi_imx->base + MXC_CSPIINT) & MX1_INTREG_RR;
|
return readl(spi_imx->base + MXC_CSPIINT) & MX1_INTREG_RR;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void __maybe_unused mx1_reset(struct spi_imx_data *spi_imx)
|
static void mx1_reset(struct spi_imx_data *spi_imx)
|
||||||
{
|
{
|
||||||
writel(1, spi_imx->base + MXC_RESET);
|
writel(1, spi_imx->base + MXC_RESET);
|
||||||
}
|
}
|
||||||
|
@ -747,15 +743,13 @@ MODULE_DEVICE_TABLE(of, spi_imx_dt_ids);
|
||||||
|
|
||||||
static void spi_imx_chipselect(struct spi_device *spi, int is_active)
|
static void spi_imx_chipselect(struct spi_device *spi, int is_active)
|
||||||
{
|
{
|
||||||
struct spi_imx_data *spi_imx = spi_master_get_devdata(spi->master);
|
|
||||||
int gpio = spi_imx->chipselect[spi->chip_select];
|
|
||||||
int active = is_active != BITBANG_CS_INACTIVE;
|
int active = is_active != BITBANG_CS_INACTIVE;
|
||||||
int dev_is_lowactive = !(spi->mode & SPI_CS_HIGH);
|
int dev_is_lowactive = !(spi->mode & SPI_CS_HIGH);
|
||||||
|
|
||||||
if (!gpio_is_valid(gpio))
|
if (!gpio_is_valid(spi->cs_gpio))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
gpio_set_value(gpio, dev_is_lowactive ^ active);
|
gpio_set_value(spi->cs_gpio, dev_is_lowactive ^ active);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void spi_imx_push(struct spi_imx_data *spi_imx)
|
static void spi_imx_push(struct spi_imx_data *spi_imx)
|
||||||
|
@ -859,8 +853,6 @@ static int spi_imx_setupxfer(struct spi_device *spi,
|
||||||
|
|
||||||
config.bpw = t ? t->bits_per_word : spi->bits_per_word;
|
config.bpw = t ? t->bits_per_word : spi->bits_per_word;
|
||||||
config.speed_hz = t ? t->speed_hz : spi->max_speed_hz;
|
config.speed_hz = t ? t->speed_hz : spi->max_speed_hz;
|
||||||
config.mode = spi->mode;
|
|
||||||
config.cs = spi->chip_select;
|
|
||||||
|
|
||||||
if (!config.speed_hz)
|
if (!config.speed_hz)
|
||||||
config.speed_hz = spi->max_speed_hz;
|
config.speed_hz = spi->max_speed_hz;
|
||||||
|
@ -891,7 +883,7 @@ static int spi_imx_setupxfer(struct spi_device *spi,
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
spi_imx->devtype_data->config(spi_imx, &config);
|
spi_imx->devtype_data->config(spi, &config);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -1050,6 +1042,8 @@ static int spi_imx_pio_transfer(struct spi_device *spi,
|
||||||
struct spi_transfer *transfer)
|
struct spi_transfer *transfer)
|
||||||
{
|
{
|
||||||
struct spi_imx_data *spi_imx = spi_master_get_devdata(spi->master);
|
struct spi_imx_data *spi_imx = spi_master_get_devdata(spi->master);
|
||||||
|
unsigned long transfer_timeout;
|
||||||
|
unsigned long timeout;
|
||||||
|
|
||||||
spi_imx->tx_buf = transfer->tx_buf;
|
spi_imx->tx_buf = transfer->tx_buf;
|
||||||
spi_imx->rx_buf = transfer->rx_buf;
|
spi_imx->rx_buf = transfer->rx_buf;
|
||||||
|
@ -1062,7 +1056,15 @@ static int spi_imx_pio_transfer(struct spi_device *spi,
|
||||||
|
|
||||||
spi_imx->devtype_data->intctrl(spi_imx, MXC_INT_TE);
|
spi_imx->devtype_data->intctrl(spi_imx, MXC_INT_TE);
|
||||||
|
|
||||||
wait_for_completion(&spi_imx->xfer_done);
|
transfer_timeout = spi_imx_calculate_timeout(spi_imx, transfer->len);
|
||||||
|
|
||||||
|
timeout = wait_for_completion_timeout(&spi_imx->xfer_done,
|
||||||
|
transfer_timeout);
|
||||||
|
if (!timeout) {
|
||||||
|
dev_err(&spi->dev, "I/O Error in PIO\n");
|
||||||
|
spi_imx->devtype_data->reset(spi_imx);
|
||||||
|
return -ETIMEDOUT;
|
||||||
|
}
|
||||||
|
|
||||||
return transfer->len;
|
return transfer->len;
|
||||||
}
|
}
|
||||||
|
@ -1080,14 +1082,12 @@ static int spi_imx_transfer(struct spi_device *spi,
|
||||||
|
|
||||||
static int spi_imx_setup(struct spi_device *spi)
|
static int spi_imx_setup(struct spi_device *spi)
|
||||||
{
|
{
|
||||||
struct spi_imx_data *spi_imx = spi_master_get_devdata(spi->master);
|
|
||||||
int gpio = spi_imx->chipselect[spi->chip_select];
|
|
||||||
|
|
||||||
dev_dbg(&spi->dev, "%s: mode %d, %u bpw, %d hz\n", __func__,
|
dev_dbg(&spi->dev, "%s: mode %d, %u bpw, %d hz\n", __func__,
|
||||||
spi->mode, spi->bits_per_word, spi->max_speed_hz);
|
spi->mode, spi->bits_per_word, spi->max_speed_hz);
|
||||||
|
|
||||||
if (gpio_is_valid(gpio))
|
if (gpio_is_valid(spi->cs_gpio))
|
||||||
gpio_direction_output(gpio, spi->mode & SPI_CS_HIGH ? 0 : 1);
|
gpio_direction_output(spi->cs_gpio,
|
||||||
|
spi->mode & SPI_CS_HIGH ? 0 : 1);
|
||||||
|
|
||||||
spi_imx_chipselect(spi, BITBANG_CS_INACTIVE);
|
spi_imx_chipselect(spi, BITBANG_CS_INACTIVE);
|
||||||
|
|
||||||
|
@ -1137,31 +1137,21 @@ static int spi_imx_probe(struct platform_device *pdev)
|
||||||
struct spi_master *master;
|
struct spi_master *master;
|
||||||
struct spi_imx_data *spi_imx;
|
struct spi_imx_data *spi_imx;
|
||||||
struct resource *res;
|
struct resource *res;
|
||||||
int i, ret, num_cs, irq;
|
int i, ret, irq;
|
||||||
|
|
||||||
if (!np && !mxc_platform_info) {
|
if (!np && !mxc_platform_info) {
|
||||||
dev_err(&pdev->dev, "can't get the platform data\n");
|
dev_err(&pdev->dev, "can't get the platform data\n");
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = of_property_read_u32(np, "fsl,spi-num-chipselects", &num_cs);
|
master = spi_alloc_master(&pdev->dev, sizeof(struct spi_imx_data));
|
||||||
if (ret < 0) {
|
|
||||||
if (mxc_platform_info)
|
|
||||||
num_cs = mxc_platform_info->num_chipselect;
|
|
||||||
else
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
master = spi_alloc_master(&pdev->dev,
|
|
||||||
sizeof(struct spi_imx_data) + sizeof(int) * num_cs);
|
|
||||||
if (!master)
|
if (!master)
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
|
|
||||||
platform_set_drvdata(pdev, master);
|
platform_set_drvdata(pdev, master);
|
||||||
|
|
||||||
master->bits_per_word_mask = SPI_BPW_RANGE_MASK(1, 32);
|
master->bits_per_word_mask = SPI_BPW_RANGE_MASK(1, 32);
|
||||||
master->bus_num = pdev->id;
|
master->bus_num = np ? -1 : pdev->id;
|
||||||
master->num_chipselect = num_cs;
|
|
||||||
|
|
||||||
spi_imx = spi_master_get_devdata(master);
|
spi_imx = spi_master_get_devdata(master);
|
||||||
spi_imx->bitbang.master = master;
|
spi_imx->bitbang.master = master;
|
||||||
|
@ -1170,22 +1160,16 @@ static int spi_imx_probe(struct platform_device *pdev)
|
||||||
spi_imx->devtype_data = of_id ? of_id->data :
|
spi_imx->devtype_data = of_id ? of_id->data :
|
||||||
(struct spi_imx_devtype_data *)pdev->id_entry->driver_data;
|
(struct spi_imx_devtype_data *)pdev->id_entry->driver_data;
|
||||||
|
|
||||||
for (i = 0; i < master->num_chipselect; i++) {
|
if (mxc_platform_info) {
|
||||||
int cs_gpio = of_get_named_gpio(np, "cs-gpios", i);
|
master->num_chipselect = mxc_platform_info->num_chipselect;
|
||||||
if (!gpio_is_valid(cs_gpio) && mxc_platform_info)
|
master->cs_gpios = devm_kzalloc(&master->dev,
|
||||||
cs_gpio = mxc_platform_info->chipselect[i];
|
sizeof(int) * master->num_chipselect, GFP_KERNEL);
|
||||||
|
if (!master->cs_gpios)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
spi_imx->chipselect[i] = cs_gpio;
|
for (i = 0; i < master->num_chipselect; i++)
|
||||||
if (!gpio_is_valid(cs_gpio))
|
master->cs_gpios[i] = mxc_platform_info->chipselect[i];
|
||||||
continue;
|
}
|
||||||
|
|
||||||
ret = devm_gpio_request(&pdev->dev, spi_imx->chipselect[i],
|
|
||||||
DRIVER_NAME);
|
|
||||||
if (ret) {
|
|
||||||
dev_err(&pdev->dev, "can't get cs gpios\n");
|
|
||||||
goto out_master_put;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
spi_imx->bitbang.chipselect = spi_imx_chipselect;
|
spi_imx->bitbang.chipselect = spi_imx_chipselect;
|
||||||
spi_imx->bitbang.setup_transfer = spi_imx_setupxfer;
|
spi_imx->bitbang.setup_transfer = spi_imx_setupxfer;
|
||||||
|
@ -1267,6 +1251,19 @@ static int spi_imx_probe(struct platform_device *pdev)
|
||||||
goto out_clk_put;
|
goto out_clk_put;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < master->num_chipselect; i++) {
|
||||||
|
if (!gpio_is_valid(master->cs_gpios[i]))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
ret = devm_gpio_request(&pdev->dev, master->cs_gpios[i],
|
||||||
|
DRIVER_NAME);
|
||||||
|
if (ret) {
|
||||||
|
dev_err(&pdev->dev, "Can't get CS GPIO %i\n",
|
||||||
|
master->cs_gpios[i]);
|
||||||
|
goto out_clk_put;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
dev_info(&pdev->dev, "probed\n");
|
dev_info(&pdev->dev, "probed\n");
|
||||||
|
|
||||||
clk_disable(spi_imx->clk_ipg);
|
clk_disable(spi_imx->clk_ipg);
|
||||||
|
|
|
@ -536,7 +536,7 @@ static int spi_test_check_loopback_result(struct spi_device *spi,
|
||||||
|
|
||||||
mismatch_error:
|
mismatch_error:
|
||||||
dev_err(&spi->dev,
|
dev_err(&spi->dev,
|
||||||
"loopback strangeness - transfer missmatch on byte %04zx - expected 0x%02x, but got 0x%02x\n",
|
"loopback strangeness - transfer mismatch on byte %04zx - expected 0x%02x, but got 0x%02x\n",
|
||||||
i, txb, rxb);
|
i, txb, rxb);
|
||||||
|
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
|
|
|
@ -42,7 +42,6 @@ struct mpc52xx_psc_spi {
|
||||||
u8 bits_per_word;
|
u8 bits_per_word;
|
||||||
u8 busy;
|
u8 busy;
|
||||||
|
|
||||||
struct workqueue_struct *workqueue;
|
|
||||||
struct work_struct work;
|
struct work_struct work;
|
||||||
|
|
||||||
struct list_head queue;
|
struct list_head queue;
|
||||||
|
@ -299,7 +298,7 @@ static int mpc52xx_psc_spi_transfer(struct spi_device *spi,
|
||||||
|
|
||||||
spin_lock_irqsave(&mps->lock, flags);
|
spin_lock_irqsave(&mps->lock, flags);
|
||||||
list_add_tail(&m->queue, &mps->queue);
|
list_add_tail(&m->queue, &mps->queue);
|
||||||
queue_work(mps->workqueue, &mps->work);
|
schedule_work(&mps->work);
|
||||||
spin_unlock_irqrestore(&mps->lock, flags);
|
spin_unlock_irqrestore(&mps->lock, flags);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -425,21 +424,12 @@ static int mpc52xx_psc_spi_do_probe(struct device *dev, u32 regaddr,
|
||||||
INIT_WORK(&mps->work, mpc52xx_psc_spi_work);
|
INIT_WORK(&mps->work, mpc52xx_psc_spi_work);
|
||||||
INIT_LIST_HEAD(&mps->queue);
|
INIT_LIST_HEAD(&mps->queue);
|
||||||
|
|
||||||
mps->workqueue = create_singlethread_workqueue(
|
|
||||||
dev_name(master->dev.parent));
|
|
||||||
if (mps->workqueue == NULL) {
|
|
||||||
ret = -EBUSY;
|
|
||||||
goto free_irq;
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = spi_register_master(master);
|
ret = spi_register_master(master);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
goto unreg_master;
|
goto free_irq;
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
unreg_master:
|
|
||||||
destroy_workqueue(mps->workqueue);
|
|
||||||
free_irq:
|
free_irq:
|
||||||
free_irq(mps->irq, mps);
|
free_irq(mps->irq, mps);
|
||||||
free_master:
|
free_master:
|
||||||
|
@ -484,8 +474,7 @@ static int mpc52xx_psc_spi_of_remove(struct platform_device *op)
|
||||||
struct spi_master *master = spi_master_get(platform_get_drvdata(op));
|
struct spi_master *master = spi_master_get(platform_get_drvdata(op));
|
||||||
struct mpc52xx_psc_spi *mps = spi_master_get_devdata(master);
|
struct mpc52xx_psc_spi *mps = spi_master_get_devdata(master);
|
||||||
|
|
||||||
flush_workqueue(mps->workqueue);
|
flush_work(&mps->work);
|
||||||
destroy_workqueue(mps->workqueue);
|
|
||||||
spi_unregister_master(master);
|
spi_unregister_master(master);
|
||||||
free_irq(mps->irq, mps);
|
free_irq(mps->irq, mps);
|
||||||
if (mps->psc)
|
if (mps->psc)
|
||||||
|
|
|
@ -1,255 +0,0 @@
|
||||||
/*
|
|
||||||
* This file is subject to the terms and conditions of the GNU General Public
|
|
||||||
* License. See the file "COPYING" in the main directory of this archive
|
|
||||||
* for more details.
|
|
||||||
*
|
|
||||||
* Copyright (C) 2011, 2012 Cavium, Inc.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <linux/platform_device.h>
|
|
||||||
#include <linux/interrupt.h>
|
|
||||||
#include <linux/spi/spi.h>
|
|
||||||
#include <linux/module.h>
|
|
||||||
#include <linux/delay.h>
|
|
||||||
#include <linux/io.h>
|
|
||||||
#include <linux/of.h>
|
|
||||||
|
|
||||||
#include <asm/octeon/octeon.h>
|
|
||||||
#include <asm/octeon/cvmx-mpi-defs.h>
|
|
||||||
|
|
||||||
#define OCTEON_SPI_CFG 0
|
|
||||||
#define OCTEON_SPI_STS 0x08
|
|
||||||
#define OCTEON_SPI_TX 0x10
|
|
||||||
#define OCTEON_SPI_DAT0 0x80
|
|
||||||
|
|
||||||
#define OCTEON_SPI_MAX_BYTES 9
|
|
||||||
|
|
||||||
#define OCTEON_SPI_MAX_CLOCK_HZ 16000000
|
|
||||||
|
|
||||||
struct octeon_spi {
|
|
||||||
u64 register_base;
|
|
||||||
u64 last_cfg;
|
|
||||||
u64 cs_enax;
|
|
||||||
};
|
|
||||||
|
|
||||||
static void octeon_spi_wait_ready(struct octeon_spi *p)
|
|
||||||
{
|
|
||||||
union cvmx_mpi_sts mpi_sts;
|
|
||||||
unsigned int loops = 0;
|
|
||||||
|
|
||||||
do {
|
|
||||||
if (loops++)
|
|
||||||
__delay(500);
|
|
||||||
mpi_sts.u64 = cvmx_read_csr(p->register_base + OCTEON_SPI_STS);
|
|
||||||
} while (mpi_sts.s.busy);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int octeon_spi_do_transfer(struct octeon_spi *p,
|
|
||||||
struct spi_message *msg,
|
|
||||||
struct spi_transfer *xfer,
|
|
||||||
bool last_xfer)
|
|
||||||
{
|
|
||||||
struct spi_device *spi = msg->spi;
|
|
||||||
union cvmx_mpi_cfg mpi_cfg;
|
|
||||||
union cvmx_mpi_tx mpi_tx;
|
|
||||||
unsigned int clkdiv;
|
|
||||||
unsigned int speed_hz;
|
|
||||||
int mode;
|
|
||||||
bool cpha, cpol;
|
|
||||||
const u8 *tx_buf;
|
|
||||||
u8 *rx_buf;
|
|
||||||
int len;
|
|
||||||
int i;
|
|
||||||
|
|
||||||
mode = spi->mode;
|
|
||||||
cpha = mode & SPI_CPHA;
|
|
||||||
cpol = mode & SPI_CPOL;
|
|
||||||
|
|
||||||
speed_hz = xfer->speed_hz;
|
|
||||||
|
|
||||||
clkdiv = octeon_get_io_clock_rate() / (2 * speed_hz);
|
|
||||||
|
|
||||||
mpi_cfg.u64 = 0;
|
|
||||||
|
|
||||||
mpi_cfg.s.clkdiv = clkdiv;
|
|
||||||
mpi_cfg.s.cshi = (mode & SPI_CS_HIGH) ? 1 : 0;
|
|
||||||
mpi_cfg.s.lsbfirst = (mode & SPI_LSB_FIRST) ? 1 : 0;
|
|
||||||
mpi_cfg.s.wireor = (mode & SPI_3WIRE) ? 1 : 0;
|
|
||||||
mpi_cfg.s.idlelo = cpha != cpol;
|
|
||||||
mpi_cfg.s.cslate = cpha ? 1 : 0;
|
|
||||||
mpi_cfg.s.enable = 1;
|
|
||||||
|
|
||||||
if (spi->chip_select < 4)
|
|
||||||
p->cs_enax |= 1ull << (12 + spi->chip_select);
|
|
||||||
mpi_cfg.u64 |= p->cs_enax;
|
|
||||||
|
|
||||||
if (mpi_cfg.u64 != p->last_cfg) {
|
|
||||||
p->last_cfg = mpi_cfg.u64;
|
|
||||||
cvmx_write_csr(p->register_base + OCTEON_SPI_CFG, mpi_cfg.u64);
|
|
||||||
}
|
|
||||||
tx_buf = xfer->tx_buf;
|
|
||||||
rx_buf = xfer->rx_buf;
|
|
||||||
len = xfer->len;
|
|
||||||
while (len > OCTEON_SPI_MAX_BYTES) {
|
|
||||||
for (i = 0; i < OCTEON_SPI_MAX_BYTES; i++) {
|
|
||||||
u8 d;
|
|
||||||
if (tx_buf)
|
|
||||||
d = *tx_buf++;
|
|
||||||
else
|
|
||||||
d = 0;
|
|
||||||
cvmx_write_csr(p->register_base + OCTEON_SPI_DAT0 + (8 * i), d);
|
|
||||||
}
|
|
||||||
mpi_tx.u64 = 0;
|
|
||||||
mpi_tx.s.csid = spi->chip_select;
|
|
||||||
mpi_tx.s.leavecs = 1;
|
|
||||||
mpi_tx.s.txnum = tx_buf ? OCTEON_SPI_MAX_BYTES : 0;
|
|
||||||
mpi_tx.s.totnum = OCTEON_SPI_MAX_BYTES;
|
|
||||||
cvmx_write_csr(p->register_base + OCTEON_SPI_TX, mpi_tx.u64);
|
|
||||||
|
|
||||||
octeon_spi_wait_ready(p);
|
|
||||||
if (rx_buf)
|
|
||||||
for (i = 0; i < OCTEON_SPI_MAX_BYTES; i++) {
|
|
||||||
u64 v = cvmx_read_csr(p->register_base + OCTEON_SPI_DAT0 + (8 * i));
|
|
||||||
*rx_buf++ = (u8)v;
|
|
||||||
}
|
|
||||||
len -= OCTEON_SPI_MAX_BYTES;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (i = 0; i < len; i++) {
|
|
||||||
u8 d;
|
|
||||||
if (tx_buf)
|
|
||||||
d = *tx_buf++;
|
|
||||||
else
|
|
||||||
d = 0;
|
|
||||||
cvmx_write_csr(p->register_base + OCTEON_SPI_DAT0 + (8 * i), d);
|
|
||||||
}
|
|
||||||
|
|
||||||
mpi_tx.u64 = 0;
|
|
||||||
mpi_tx.s.csid = spi->chip_select;
|
|
||||||
if (last_xfer)
|
|
||||||
mpi_tx.s.leavecs = xfer->cs_change;
|
|
||||||
else
|
|
||||||
mpi_tx.s.leavecs = !xfer->cs_change;
|
|
||||||
mpi_tx.s.txnum = tx_buf ? len : 0;
|
|
||||||
mpi_tx.s.totnum = len;
|
|
||||||
cvmx_write_csr(p->register_base + OCTEON_SPI_TX, mpi_tx.u64);
|
|
||||||
|
|
||||||
octeon_spi_wait_ready(p);
|
|
||||||
if (rx_buf)
|
|
||||||
for (i = 0; i < len; i++) {
|
|
||||||
u64 v = cvmx_read_csr(p->register_base + OCTEON_SPI_DAT0 + (8 * i));
|
|
||||||
*rx_buf++ = (u8)v;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (xfer->delay_usecs)
|
|
||||||
udelay(xfer->delay_usecs);
|
|
||||||
|
|
||||||
return xfer->len;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int octeon_spi_transfer_one_message(struct spi_master *master,
|
|
||||||
struct spi_message *msg)
|
|
||||||
{
|
|
||||||
struct octeon_spi *p = spi_master_get_devdata(master);
|
|
||||||
unsigned int total_len = 0;
|
|
||||||
int status = 0;
|
|
||||||
struct spi_transfer *xfer;
|
|
||||||
|
|
||||||
list_for_each_entry(xfer, &msg->transfers, transfer_list) {
|
|
||||||
bool last_xfer = list_is_last(&xfer->transfer_list,
|
|
||||||
&msg->transfers);
|
|
||||||
int r = octeon_spi_do_transfer(p, msg, xfer, last_xfer);
|
|
||||||
if (r < 0) {
|
|
||||||
status = r;
|
|
||||||
goto err;
|
|
||||||
}
|
|
||||||
total_len += r;
|
|
||||||
}
|
|
||||||
err:
|
|
||||||
msg->status = status;
|
|
||||||
msg->actual_length = total_len;
|
|
||||||
spi_finalize_current_message(master);
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int octeon_spi_probe(struct platform_device *pdev)
|
|
||||||
{
|
|
||||||
struct resource *res_mem;
|
|
||||||
void __iomem *reg_base;
|
|
||||||
struct spi_master *master;
|
|
||||||
struct octeon_spi *p;
|
|
||||||
int err = -ENOENT;
|
|
||||||
|
|
||||||
master = spi_alloc_master(&pdev->dev, sizeof(struct octeon_spi));
|
|
||||||
if (!master)
|
|
||||||
return -ENOMEM;
|
|
||||||
p = spi_master_get_devdata(master);
|
|
||||||
platform_set_drvdata(pdev, master);
|
|
||||||
|
|
||||||
res_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
||||||
reg_base = devm_ioremap_resource(&pdev->dev, res_mem);
|
|
||||||
if (IS_ERR(reg_base)) {
|
|
||||||
err = PTR_ERR(reg_base);
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
p->register_base = (u64)reg_base;
|
|
||||||
|
|
||||||
master->num_chipselect = 4;
|
|
||||||
master->mode_bits = SPI_CPHA |
|
|
||||||
SPI_CPOL |
|
|
||||||
SPI_CS_HIGH |
|
|
||||||
SPI_LSB_FIRST |
|
|
||||||
SPI_3WIRE;
|
|
||||||
|
|
||||||
master->transfer_one_message = octeon_spi_transfer_one_message;
|
|
||||||
master->bits_per_word_mask = SPI_BPW_MASK(8);
|
|
||||||
master->max_speed_hz = OCTEON_SPI_MAX_CLOCK_HZ;
|
|
||||||
|
|
||||||
master->dev.of_node = pdev->dev.of_node;
|
|
||||||
err = devm_spi_register_master(&pdev->dev, master);
|
|
||||||
if (err) {
|
|
||||||
dev_err(&pdev->dev, "register master failed: %d\n", err);
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
dev_info(&pdev->dev, "OCTEON SPI bus driver\n");
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
fail:
|
|
||||||
spi_master_put(master);
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int octeon_spi_remove(struct platform_device *pdev)
|
|
||||||
{
|
|
||||||
struct spi_master *master = platform_get_drvdata(pdev);
|
|
||||||
struct octeon_spi *p = spi_master_get_devdata(master);
|
|
||||||
u64 register_base = p->register_base;
|
|
||||||
|
|
||||||
/* Clear the CSENA* and put everything in a known state. */
|
|
||||||
cvmx_write_csr(register_base + OCTEON_SPI_CFG, 0);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static const struct of_device_id octeon_spi_match[] = {
|
|
||||||
{ .compatible = "cavium,octeon-3010-spi", },
|
|
||||||
{},
|
|
||||||
};
|
|
||||||
MODULE_DEVICE_TABLE(of, octeon_spi_match);
|
|
||||||
|
|
||||||
static struct platform_driver octeon_spi_driver = {
|
|
||||||
.driver = {
|
|
||||||
.name = "spi-octeon",
|
|
||||||
.of_match_table = octeon_spi_match,
|
|
||||||
},
|
|
||||||
.probe = octeon_spi_probe,
|
|
||||||
.remove = octeon_spi_remove,
|
|
||||||
};
|
|
||||||
|
|
||||||
module_platform_driver(octeon_spi_driver);
|
|
||||||
|
|
||||||
MODULE_DESCRIPTION("Cavium, Inc. OCTEON SPI bus driver");
|
|
||||||
MODULE_AUTHOR("David Daney");
|
|
||||||
MODULE_LICENSE("GPL");
|
|
|
@ -419,16 +419,13 @@ static void omap2_mcspi_tx_dma(struct spi_device *spi,
|
||||||
|
|
||||||
if (mcspi_dma->dma_tx) {
|
if (mcspi_dma->dma_tx) {
|
||||||
struct dma_async_tx_descriptor *tx;
|
struct dma_async_tx_descriptor *tx;
|
||||||
struct scatterlist sg;
|
|
||||||
|
|
||||||
dmaengine_slave_config(mcspi_dma->dma_tx, &cfg);
|
dmaengine_slave_config(mcspi_dma->dma_tx, &cfg);
|
||||||
|
|
||||||
sg_init_table(&sg, 1);
|
tx = dmaengine_prep_slave_sg(mcspi_dma->dma_tx, xfer->tx_sg.sgl,
|
||||||
sg_dma_address(&sg) = xfer->tx_dma;
|
xfer->tx_sg.nents,
|
||||||
sg_dma_len(&sg) = xfer->len;
|
DMA_MEM_TO_DEV,
|
||||||
|
DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
|
||||||
tx = dmaengine_prep_slave_sg(mcspi_dma->dma_tx, &sg, 1,
|
|
||||||
DMA_MEM_TO_DEV, DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
|
|
||||||
if (tx) {
|
if (tx) {
|
||||||
tx->callback = omap2_mcspi_tx_callback;
|
tx->callback = omap2_mcspi_tx_callback;
|
||||||
tx->callback_param = spi;
|
tx->callback_param = spi;
|
||||||
|
@ -449,7 +446,10 @@ omap2_mcspi_rx_dma(struct spi_device *spi, struct spi_transfer *xfer,
|
||||||
{
|
{
|
||||||
struct omap2_mcspi *mcspi;
|
struct omap2_mcspi *mcspi;
|
||||||
struct omap2_mcspi_dma *mcspi_dma;
|
struct omap2_mcspi_dma *mcspi_dma;
|
||||||
unsigned int count, dma_count;
|
unsigned int count, transfer_reduction = 0;
|
||||||
|
struct scatterlist *sg_out[2];
|
||||||
|
int nb_sizes = 0, out_mapped_nents[2], ret, x;
|
||||||
|
size_t sizes[2];
|
||||||
u32 l;
|
u32 l;
|
||||||
int elements = 0;
|
int elements = 0;
|
||||||
int word_len, element_count;
|
int word_len, element_count;
|
||||||
|
@ -457,10 +457,14 @@ omap2_mcspi_rx_dma(struct spi_device *spi, struct spi_transfer *xfer,
|
||||||
mcspi = spi_master_get_devdata(spi->master);
|
mcspi = spi_master_get_devdata(spi->master);
|
||||||
mcspi_dma = &mcspi->dma_channels[spi->chip_select];
|
mcspi_dma = &mcspi->dma_channels[spi->chip_select];
|
||||||
count = xfer->len;
|
count = xfer->len;
|
||||||
dma_count = xfer->len;
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* In the "End-of-Transfer Procedure" section for DMA RX in OMAP35x TRM
|
||||||
|
* it mentions reducing DMA transfer length by one element in master
|
||||||
|
* normal mode.
|
||||||
|
*/
|
||||||
if (mcspi->fifo_depth == 0)
|
if (mcspi->fifo_depth == 0)
|
||||||
dma_count -= es;
|
transfer_reduction = es;
|
||||||
|
|
||||||
word_len = cs->word_len;
|
word_len = cs->word_len;
|
||||||
l = mcspi_cached_chconf0(spi);
|
l = mcspi_cached_chconf0(spi);
|
||||||
|
@ -474,20 +478,46 @@ omap2_mcspi_rx_dma(struct spi_device *spi, struct spi_transfer *xfer,
|
||||||
|
|
||||||
if (mcspi_dma->dma_rx) {
|
if (mcspi_dma->dma_rx) {
|
||||||
struct dma_async_tx_descriptor *tx;
|
struct dma_async_tx_descriptor *tx;
|
||||||
struct scatterlist sg;
|
|
||||||
|
|
||||||
dmaengine_slave_config(mcspi_dma->dma_rx, &cfg);
|
dmaengine_slave_config(mcspi_dma->dma_rx, &cfg);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Reduce DMA transfer length by one more if McSPI is
|
||||||
|
* configured in turbo mode.
|
||||||
|
*/
|
||||||
if ((l & OMAP2_MCSPI_CHCONF_TURBO) && mcspi->fifo_depth == 0)
|
if ((l & OMAP2_MCSPI_CHCONF_TURBO) && mcspi->fifo_depth == 0)
|
||||||
dma_count -= es;
|
transfer_reduction += es;
|
||||||
|
|
||||||
sg_init_table(&sg, 1);
|
if (transfer_reduction) {
|
||||||
sg_dma_address(&sg) = xfer->rx_dma;
|
/* Split sgl into two. The second sgl won't be used. */
|
||||||
sg_dma_len(&sg) = dma_count;
|
sizes[0] = count - transfer_reduction;
|
||||||
|
sizes[1] = transfer_reduction;
|
||||||
|
nb_sizes = 2;
|
||||||
|
} else {
|
||||||
|
/*
|
||||||
|
* Don't bother splitting the sgl. This essentially
|
||||||
|
* clones the original sgl.
|
||||||
|
*/
|
||||||
|
sizes[0] = count;
|
||||||
|
nb_sizes = 1;
|
||||||
|
}
|
||||||
|
|
||||||
tx = dmaengine_prep_slave_sg(mcspi_dma->dma_rx, &sg, 1,
|
ret = sg_split(xfer->rx_sg.sgl, xfer->rx_sg.nents,
|
||||||
DMA_DEV_TO_MEM, DMA_PREP_INTERRUPT |
|
0, nb_sizes,
|
||||||
DMA_CTRL_ACK);
|
sizes,
|
||||||
|
sg_out, out_mapped_nents,
|
||||||
|
GFP_KERNEL);
|
||||||
|
|
||||||
|
if (ret < 0) {
|
||||||
|
dev_err(&spi->dev, "sg_split failed\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
tx = dmaengine_prep_slave_sg(mcspi_dma->dma_rx,
|
||||||
|
sg_out[0],
|
||||||
|
out_mapped_nents[0],
|
||||||
|
DMA_DEV_TO_MEM,
|
||||||
|
DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
|
||||||
if (tx) {
|
if (tx) {
|
||||||
tx->callback = omap2_mcspi_rx_callback;
|
tx->callback = omap2_mcspi_rx_callback;
|
||||||
tx->callback_param = spi;
|
tx->callback_param = spi;
|
||||||
|
@ -501,12 +531,17 @@ omap2_mcspi_rx_dma(struct spi_device *spi, struct spi_transfer *xfer,
|
||||||
omap2_mcspi_set_dma_req(spi, 1, 1);
|
omap2_mcspi_set_dma_req(spi, 1, 1);
|
||||||
|
|
||||||
wait_for_completion(&mcspi_dma->dma_rx_completion);
|
wait_for_completion(&mcspi_dma->dma_rx_completion);
|
||||||
dma_unmap_single(mcspi->dev, xfer->rx_dma, count,
|
|
||||||
DMA_FROM_DEVICE);
|
for (x = 0; x < nb_sizes; x++)
|
||||||
|
kfree(sg_out[x]);
|
||||||
|
|
||||||
if (mcspi->fifo_depth > 0)
|
if (mcspi->fifo_depth > 0)
|
||||||
return count;
|
return count;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Due to the DMA transfer length reduction the missing bytes must
|
||||||
|
* be read manually to receive all of the expected data.
|
||||||
|
*/
|
||||||
omap2_mcspi_set_enable(spi, 0);
|
omap2_mcspi_set_enable(spi, 0);
|
||||||
|
|
||||||
elements = element_count - 1;
|
elements = element_count - 1;
|
||||||
|
@ -615,8 +650,6 @@ omap2_mcspi_txrx_dma(struct spi_device *spi, struct spi_transfer *xfer)
|
||||||
|
|
||||||
if (tx != NULL) {
|
if (tx != NULL) {
|
||||||
wait_for_completion(&mcspi_dma->dma_tx_completion);
|
wait_for_completion(&mcspi_dma->dma_tx_completion);
|
||||||
dma_unmap_single(mcspi->dev, xfer->tx_dma, xfer->len,
|
|
||||||
DMA_TO_DEVICE);
|
|
||||||
|
|
||||||
if (mcspi->fifo_depth > 0) {
|
if (mcspi->fifo_depth > 0) {
|
||||||
irqstat_reg = mcspi->base + OMAP2_MCSPI_IRQSTATUS;
|
irqstat_reg = mcspi->base + OMAP2_MCSPI_IRQSTATUS;
|
||||||
|
@ -1074,8 +1107,9 @@ static void omap2_mcspi_cleanup(struct spi_device *spi)
|
||||||
gpio_free(spi->cs_gpio);
|
gpio_free(spi->cs_gpio);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int omap2_mcspi_work_one(struct omap2_mcspi *mcspi,
|
static int omap2_mcspi_transfer_one(struct spi_master *master,
|
||||||
struct spi_device *spi, struct spi_transfer *t)
|
struct spi_device *spi,
|
||||||
|
struct spi_transfer *t)
|
||||||
{
|
{
|
||||||
|
|
||||||
/* We only enable one channel at a time -- the one whose message is
|
/* We only enable one channel at a time -- the one whose message is
|
||||||
|
@ -1085,7 +1119,7 @@ static int omap2_mcspi_work_one(struct omap2_mcspi *mcspi,
|
||||||
* chipselect with the FORCE bit ... CS != channel enable.
|
* chipselect with the FORCE bit ... CS != channel enable.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
struct spi_master *master;
|
struct omap2_mcspi *mcspi;
|
||||||
struct omap2_mcspi_dma *mcspi_dma;
|
struct omap2_mcspi_dma *mcspi_dma;
|
||||||
struct omap2_mcspi_cs *cs;
|
struct omap2_mcspi_cs *cs;
|
||||||
struct omap2_mcspi_device_config *cd;
|
struct omap2_mcspi_device_config *cd;
|
||||||
|
@ -1093,7 +1127,7 @@ static int omap2_mcspi_work_one(struct omap2_mcspi *mcspi,
|
||||||
int status = 0;
|
int status = 0;
|
||||||
u32 chconf;
|
u32 chconf;
|
||||||
|
|
||||||
master = spi->master;
|
mcspi = spi_master_get_devdata(master);
|
||||||
mcspi_dma = mcspi->dma_channels + spi->chip_select;
|
mcspi_dma = mcspi->dma_channels + spi->chip_select;
|
||||||
cs = spi->controller_state;
|
cs = spi->controller_state;
|
||||||
cd = spi->controller_data;
|
cd = spi->controller_data;
|
||||||
|
@ -1153,7 +1187,8 @@ static int omap2_mcspi_work_one(struct omap2_mcspi *mcspi,
|
||||||
unsigned count;
|
unsigned count;
|
||||||
|
|
||||||
if ((mcspi_dma->dma_rx && mcspi_dma->dma_tx) &&
|
if ((mcspi_dma->dma_rx && mcspi_dma->dma_tx) &&
|
||||||
(t->len >= DMA_MIN_BYTES))
|
master->cur_msg_mapped &&
|
||||||
|
master->can_dma(master, spi, t))
|
||||||
omap2_mcspi_set_fifo(spi, t, 1);
|
omap2_mcspi_set_fifo(spi, t, 1);
|
||||||
|
|
||||||
omap2_mcspi_set_enable(spi, 1);
|
omap2_mcspi_set_enable(spi, 1);
|
||||||
|
@ -1164,7 +1199,8 @@ static int omap2_mcspi_work_one(struct omap2_mcspi *mcspi,
|
||||||
+ OMAP2_MCSPI_TX0);
|
+ OMAP2_MCSPI_TX0);
|
||||||
|
|
||||||
if ((mcspi_dma->dma_rx && mcspi_dma->dma_tx) &&
|
if ((mcspi_dma->dma_rx && mcspi_dma->dma_tx) &&
|
||||||
(t->len >= DMA_MIN_BYTES))
|
master->cur_msg_mapped &&
|
||||||
|
master->can_dma(master, spi, t))
|
||||||
count = omap2_mcspi_txrx_dma(spi, t);
|
count = omap2_mcspi_txrx_dma(spi, t);
|
||||||
else
|
else
|
||||||
count = omap2_mcspi_txrx_pio(spi, t);
|
count = omap2_mcspi_txrx_pio(spi, t);
|
||||||
|
@ -1233,55 +1269,11 @@ static int omap2_mcspi_prepare_message(struct spi_master *master,
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int omap2_mcspi_transfer_one(struct spi_master *master,
|
static bool omap2_mcspi_can_dma(struct spi_master *master,
|
||||||
struct spi_device *spi, struct spi_transfer *t)
|
struct spi_device *spi,
|
||||||
|
struct spi_transfer *xfer)
|
||||||
{
|
{
|
||||||
struct omap2_mcspi *mcspi;
|
return (xfer->len >= DMA_MIN_BYTES);
|
||||||
struct omap2_mcspi_dma *mcspi_dma;
|
|
||||||
const void *tx_buf = t->tx_buf;
|
|
||||||
void *rx_buf = t->rx_buf;
|
|
||||||
unsigned len = t->len;
|
|
||||||
|
|
||||||
mcspi = spi_master_get_devdata(master);
|
|
||||||
mcspi_dma = mcspi->dma_channels + spi->chip_select;
|
|
||||||
|
|
||||||
if ((len && !(rx_buf || tx_buf))) {
|
|
||||||
dev_dbg(mcspi->dev, "transfer: %d Hz, %d %s%s, %d bpw\n",
|
|
||||||
t->speed_hz,
|
|
||||||
len,
|
|
||||||
tx_buf ? "tx" : "",
|
|
||||||
rx_buf ? "rx" : "",
|
|
||||||
t->bits_per_word);
|
|
||||||
return -EINVAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (len < DMA_MIN_BYTES)
|
|
||||||
goto skip_dma_map;
|
|
||||||
|
|
||||||
if (mcspi_dma->dma_tx && tx_buf != NULL) {
|
|
||||||
t->tx_dma = dma_map_single(mcspi->dev, (void *) tx_buf,
|
|
||||||
len, DMA_TO_DEVICE);
|
|
||||||
if (dma_mapping_error(mcspi->dev, t->tx_dma)) {
|
|
||||||
dev_dbg(mcspi->dev, "dma %cX %d bytes error\n",
|
|
||||||
'T', len);
|
|
||||||
return -EINVAL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (mcspi_dma->dma_rx && rx_buf != NULL) {
|
|
||||||
t->rx_dma = dma_map_single(mcspi->dev, rx_buf, t->len,
|
|
||||||
DMA_FROM_DEVICE);
|
|
||||||
if (dma_mapping_error(mcspi->dev, t->rx_dma)) {
|
|
||||||
dev_dbg(mcspi->dev, "dma %cX %d bytes error\n",
|
|
||||||
'R', len);
|
|
||||||
if (tx_buf != NULL)
|
|
||||||
dma_unmap_single(mcspi->dev, t->tx_dma,
|
|
||||||
len, DMA_TO_DEVICE);
|
|
||||||
return -EINVAL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
skip_dma_map:
|
|
||||||
return omap2_mcspi_work_one(mcspi, spi, t);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int omap2_mcspi_master_setup(struct omap2_mcspi *mcspi)
|
static int omap2_mcspi_master_setup(struct omap2_mcspi *mcspi)
|
||||||
|
@ -1361,6 +1353,7 @@ static int omap2_mcspi_probe(struct platform_device *pdev)
|
||||||
master->setup = omap2_mcspi_setup;
|
master->setup = omap2_mcspi_setup;
|
||||||
master->auto_runtime_pm = true;
|
master->auto_runtime_pm = true;
|
||||||
master->prepare_message = omap2_mcspi_prepare_message;
|
master->prepare_message = omap2_mcspi_prepare_message;
|
||||||
|
master->can_dma = omap2_mcspi_can_dma;
|
||||||
master->transfer_one = omap2_mcspi_transfer_one;
|
master->transfer_one = omap2_mcspi_transfer_one;
|
||||||
master->set_cs = omap2_mcspi_set_cs;
|
master->set_cs = omap2_mcspi_set_cs;
|
||||||
master->cleanup = omap2_mcspi_cleanup;
|
master->cleanup = omap2_mcspi_cleanup;
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
#include <linux/module.h>
|
#include <linux/module.h>
|
||||||
#include <linux/pm_runtime.h>
|
#include <linux/pm_runtime.h>
|
||||||
#include <linux/of.h>
|
#include <linux/of.h>
|
||||||
|
#include <linux/of_address.h>
|
||||||
#include <linux/of_device.h>
|
#include <linux/of_device.h>
|
||||||
#include <linux/clk.h>
|
#include <linux/clk.h>
|
||||||
#include <linux/sizes.h>
|
#include <linux/sizes.h>
|
||||||
|
@ -43,6 +44,9 @@
|
||||||
#define ORION_SPI_INT_CAUSE_REG 0x10
|
#define ORION_SPI_INT_CAUSE_REG 0x10
|
||||||
#define ORION_SPI_TIMING_PARAMS_REG 0x18
|
#define ORION_SPI_TIMING_PARAMS_REG 0x18
|
||||||
|
|
||||||
|
/* Register for the "Direct Mode" */
|
||||||
|
#define SPI_DIRECT_WRITE_CONFIG_REG 0x20
|
||||||
|
|
||||||
#define ORION_SPI_TMISO_SAMPLE_MASK (0x3 << 6)
|
#define ORION_SPI_TMISO_SAMPLE_MASK (0x3 << 6)
|
||||||
#define ORION_SPI_TMISO_SAMPLE_1 (1 << 6)
|
#define ORION_SPI_TMISO_SAMPLE_1 (1 << 6)
|
||||||
#define ORION_SPI_TMISO_SAMPLE_2 (2 << 6)
|
#define ORION_SPI_TMISO_SAMPLE_2 (2 << 6)
|
||||||
|
@ -78,11 +82,18 @@ struct orion_spi_dev {
|
||||||
bool is_errata_50mhz_ac;
|
bool is_errata_50mhz_ac;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct orion_direct_acc {
|
||||||
|
void __iomem *vaddr;
|
||||||
|
u32 size;
|
||||||
|
};
|
||||||
|
|
||||||
struct orion_spi {
|
struct orion_spi {
|
||||||
struct spi_master *master;
|
struct spi_master *master;
|
||||||
void __iomem *base;
|
void __iomem *base;
|
||||||
struct clk *clk;
|
struct clk *clk;
|
||||||
const struct orion_spi_dev *devdata;
|
const struct orion_spi_dev *devdata;
|
||||||
|
|
||||||
|
struct orion_direct_acc direct_access[ORION_NUM_CHIPSELECTS];
|
||||||
};
|
};
|
||||||
|
|
||||||
static inline void __iomem *spi_reg(struct orion_spi *orion_spi, u32 reg)
|
static inline void __iomem *spi_reg(struct orion_spi *orion_spi, u32 reg)
|
||||||
|
@ -372,10 +383,39 @@ orion_spi_write_read(struct spi_device *spi, struct spi_transfer *xfer)
|
||||||
{
|
{
|
||||||
unsigned int count;
|
unsigned int count;
|
||||||
int word_len;
|
int word_len;
|
||||||
|
struct orion_spi *orion_spi;
|
||||||
|
int cs = spi->chip_select;
|
||||||
|
|
||||||
word_len = spi->bits_per_word;
|
word_len = spi->bits_per_word;
|
||||||
count = xfer->len;
|
count = xfer->len;
|
||||||
|
|
||||||
|
orion_spi = spi_master_get_devdata(spi->master);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Use SPI direct write mode if base address is available. Otherwise
|
||||||
|
* fall back to PIO mode for this transfer.
|
||||||
|
*/
|
||||||
|
if ((orion_spi->direct_access[cs].vaddr) && (xfer->tx_buf) &&
|
||||||
|
(word_len == 8)) {
|
||||||
|
unsigned int cnt = count / 4;
|
||||||
|
unsigned int rem = count % 4;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Send the TX-data to the SPI device via the direct
|
||||||
|
* mapped address window
|
||||||
|
*/
|
||||||
|
iowrite32_rep(orion_spi->direct_access[cs].vaddr,
|
||||||
|
xfer->tx_buf, cnt);
|
||||||
|
if (rem) {
|
||||||
|
u32 *buf = (u32 *)xfer->tx_buf;
|
||||||
|
|
||||||
|
iowrite8_rep(orion_spi->direct_access[cs].vaddr,
|
||||||
|
&buf[cnt], rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
if (word_len == 8) {
|
if (word_len == 8) {
|
||||||
const u8 *tx = xfer->tx_buf;
|
const u8 *tx = xfer->tx_buf;
|
||||||
u8 *rx = xfer->rx_buf;
|
u8 *rx = xfer->rx_buf;
|
||||||
|
@ -425,6 +465,10 @@ static int orion_spi_reset(struct orion_spi *orion_spi)
|
||||||
{
|
{
|
||||||
/* Verify that the CS is deasserted */
|
/* Verify that the CS is deasserted */
|
||||||
orion_spi_clrbits(orion_spi, ORION_SPI_IF_CTRL_REG, 0x1);
|
orion_spi_clrbits(orion_spi, ORION_SPI_IF_CTRL_REG, 0x1);
|
||||||
|
|
||||||
|
/* Don't deassert CS between the direct mapped SPI transfers */
|
||||||
|
writel(0, spi_reg(orion_spi, SPI_DIRECT_WRITE_CONFIG_REG));
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -504,6 +548,7 @@ static int orion_spi_probe(struct platform_device *pdev)
|
||||||
struct resource *r;
|
struct resource *r;
|
||||||
unsigned long tclk_hz;
|
unsigned long tclk_hz;
|
||||||
int status = 0;
|
int status = 0;
|
||||||
|
struct device_node *np;
|
||||||
|
|
||||||
master = spi_alloc_master(&pdev->dev, sizeof(*spi));
|
master = spi_alloc_master(&pdev->dev, sizeof(*spi));
|
||||||
if (master == NULL) {
|
if (master == NULL) {
|
||||||
|
@ -576,6 +621,49 @@ static int orion_spi_probe(struct platform_device *pdev)
|
||||||
goto out_rel_clk;
|
goto out_rel_clk;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Scan all SPI devices of this controller for direct mapped devices */
|
||||||
|
for_each_available_child_of_node(pdev->dev.of_node, np) {
|
||||||
|
u32 cs;
|
||||||
|
|
||||||
|
/* Get chip-select number from the "reg" property */
|
||||||
|
status = of_property_read_u32(np, "reg", &cs);
|
||||||
|
if (status) {
|
||||||
|
dev_err(&pdev->dev,
|
||||||
|
"%s has no valid 'reg' property (%d)\n",
|
||||||
|
np->full_name, status);
|
||||||
|
status = 0;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Check if an address is configured for this SPI device. If
|
||||||
|
* not, the MBus mapping via the 'ranges' property in the 'soc'
|
||||||
|
* node is not configured and this device should not use the
|
||||||
|
* direct mode. In this case, just continue with the next
|
||||||
|
* device.
|
||||||
|
*/
|
||||||
|
status = of_address_to_resource(pdev->dev.of_node, cs + 1, r);
|
||||||
|
if (status)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Only map one page for direct access. This is enough for the
|
||||||
|
* simple TX transfer which only writes to the first word.
|
||||||
|
* This needs to get extended for the direct SPI-NOR / SPI-NAND
|
||||||
|
* support, once this gets implemented.
|
||||||
|
*/
|
||||||
|
spi->direct_access[cs].vaddr = devm_ioremap(&pdev->dev,
|
||||||
|
r->start,
|
||||||
|
PAGE_SIZE);
|
||||||
|
if (!spi->direct_access[cs].vaddr) {
|
||||||
|
status = -ENOMEM;
|
||||||
|
goto out_rel_clk;
|
||||||
|
}
|
||||||
|
spi->direct_access[cs].size = PAGE_SIZE;
|
||||||
|
|
||||||
|
dev_info(&pdev->dev, "CS%d configured for direct access\n", cs);
|
||||||
|
}
|
||||||
|
|
||||||
pm_runtime_set_active(&pdev->dev);
|
pm_runtime_set_active(&pdev->dev);
|
||||||
pm_runtime_use_autosuspend(&pdev->dev);
|
pm_runtime_use_autosuspend(&pdev->dev);
|
||||||
pm_runtime_set_autosuspend_delay(&pdev->dev, SPI_AUTOSUSPEND_TIMEOUT);
|
pm_runtime_set_autosuspend_delay(&pdev->dev, SPI_AUTOSUSPEND_TIMEOUT);
|
||||||
|
|
|
@ -354,6 +354,7 @@ static int pic32_sqi_one_message(struct spi_master *master,
|
||||||
struct spi_transfer *xfer;
|
struct spi_transfer *xfer;
|
||||||
struct pic32_sqi *sqi;
|
struct pic32_sqi *sqi;
|
||||||
int ret = 0, mode;
|
int ret = 0, mode;
|
||||||
|
unsigned long timeout;
|
||||||
u32 val;
|
u32 val;
|
||||||
|
|
||||||
sqi = spi_master_get_devdata(master);
|
sqi = spi_master_get_devdata(master);
|
||||||
|
@ -419,10 +420,10 @@ static int pic32_sqi_one_message(struct spi_master *master,
|
||||||
writel(val, sqi->regs + PESQI_BD_CTRL_REG);
|
writel(val, sqi->regs + PESQI_BD_CTRL_REG);
|
||||||
|
|
||||||
/* wait for xfer completion */
|
/* wait for xfer completion */
|
||||||
ret = wait_for_completion_timeout(&sqi->xfer_done, 5 * HZ);
|
timeout = wait_for_completion_timeout(&sqi->xfer_done, 5 * HZ);
|
||||||
if (ret <= 0) {
|
if (timeout == 0) {
|
||||||
dev_err(&sqi->master->dev, "wait timedout/interrupted\n");
|
dev_err(&sqi->master->dev, "wait timedout/interrupted\n");
|
||||||
ret = -EIO;
|
ret = -ETIMEDOUT;
|
||||||
msg->status = ret;
|
msg->status = ret;
|
||||||
} else {
|
} else {
|
||||||
/* success */
|
/* success */
|
||||||
|
|
|
@ -507,6 +507,7 @@ static int pic32_spi_one_transfer(struct spi_master *master,
|
||||||
{
|
{
|
||||||
struct pic32_spi *pic32s;
|
struct pic32_spi *pic32s;
|
||||||
bool dma_issued = false;
|
bool dma_issued = false;
|
||||||
|
unsigned long timeout;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
pic32s = spi_master_get_devdata(master);
|
pic32s = spi_master_get_devdata(master);
|
||||||
|
@ -553,8 +554,8 @@ static int pic32_spi_one_transfer(struct spi_master *master,
|
||||||
}
|
}
|
||||||
|
|
||||||
/* wait for completion */
|
/* wait for completion */
|
||||||
ret = wait_for_completion_timeout(&pic32s->xfer_done, 2 * HZ);
|
timeout = wait_for_completion_timeout(&pic32s->xfer_done, 2 * HZ);
|
||||||
if (ret <= 0) {
|
if (timeout == 0) {
|
||||||
dev_err(&spi->dev, "wait error/timedout\n");
|
dev_err(&spi->dev, "wait error/timedout\n");
|
||||||
if (dma_issued) {
|
if (dma_issued) {
|
||||||
dmaengine_terminate_all(master->dma_rx);
|
dmaengine_terminate_all(master->dma_rx);
|
||||||
|
|
|
@ -20,79 +20,6 @@
|
||||||
|
|
||||||
#include "spi-pxa2xx.h"
|
#include "spi-pxa2xx.h"
|
||||||
|
|
||||||
static int pxa2xx_spi_map_dma_buffer(struct driver_data *drv_data,
|
|
||||||
enum dma_data_direction dir)
|
|
||||||
{
|
|
||||||
int i, nents, len = drv_data->len;
|
|
||||||
struct scatterlist *sg;
|
|
||||||
struct device *dmadev;
|
|
||||||
struct sg_table *sgt;
|
|
||||||
void *buf, *pbuf;
|
|
||||||
|
|
||||||
if (dir == DMA_TO_DEVICE) {
|
|
||||||
dmadev = drv_data->tx_chan->device->dev;
|
|
||||||
sgt = &drv_data->tx_sgt;
|
|
||||||
buf = drv_data->tx;
|
|
||||||
} else {
|
|
||||||
dmadev = drv_data->rx_chan->device->dev;
|
|
||||||
sgt = &drv_data->rx_sgt;
|
|
||||||
buf = drv_data->rx;
|
|
||||||
}
|
|
||||||
|
|
||||||
nents = DIV_ROUND_UP(len, SZ_2K);
|
|
||||||
if (nents != sgt->nents) {
|
|
||||||
int ret;
|
|
||||||
|
|
||||||
sg_free_table(sgt);
|
|
||||||
ret = sg_alloc_table(sgt, nents, GFP_ATOMIC);
|
|
||||||
if (ret)
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
pbuf = buf;
|
|
||||||
for_each_sg(sgt->sgl, sg, sgt->nents, i) {
|
|
||||||
size_t bytes = min_t(size_t, len, SZ_2K);
|
|
||||||
|
|
||||||
sg_set_buf(sg, pbuf, bytes);
|
|
||||||
pbuf += bytes;
|
|
||||||
len -= bytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
nents = dma_map_sg(dmadev, sgt->sgl, sgt->nents, dir);
|
|
||||||
if (!nents)
|
|
||||||
return -ENOMEM;
|
|
||||||
|
|
||||||
return nents;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void pxa2xx_spi_unmap_dma_buffer(struct driver_data *drv_data,
|
|
||||||
enum dma_data_direction dir)
|
|
||||||
{
|
|
||||||
struct device *dmadev;
|
|
||||||
struct sg_table *sgt;
|
|
||||||
|
|
||||||
if (dir == DMA_TO_DEVICE) {
|
|
||||||
dmadev = drv_data->tx_chan->device->dev;
|
|
||||||
sgt = &drv_data->tx_sgt;
|
|
||||||
} else {
|
|
||||||
dmadev = drv_data->rx_chan->device->dev;
|
|
||||||
sgt = &drv_data->rx_sgt;
|
|
||||||
}
|
|
||||||
|
|
||||||
dma_unmap_sg(dmadev, sgt->sgl, sgt->nents, dir);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void pxa2xx_spi_unmap_dma_buffers(struct driver_data *drv_data)
|
|
||||||
{
|
|
||||||
if (!drv_data->dma_mapped)
|
|
||||||
return;
|
|
||||||
|
|
||||||
pxa2xx_spi_unmap_dma_buffer(drv_data, DMA_FROM_DEVICE);
|
|
||||||
pxa2xx_spi_unmap_dma_buffer(drv_data, DMA_TO_DEVICE);
|
|
||||||
|
|
||||||
drv_data->dma_mapped = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void pxa2xx_spi_dma_transfer_complete(struct driver_data *drv_data,
|
static void pxa2xx_spi_dma_transfer_complete(struct driver_data *drv_data,
|
||||||
bool error)
|
bool error)
|
||||||
{
|
{
|
||||||
|
@ -125,8 +52,6 @@ static void pxa2xx_spi_dma_transfer_complete(struct driver_data *drv_data,
|
||||||
pxa2xx_spi_write(drv_data, SSTO, 0);
|
pxa2xx_spi_write(drv_data, SSTO, 0);
|
||||||
|
|
||||||
if (!error) {
|
if (!error) {
|
||||||
pxa2xx_spi_unmap_dma_buffers(drv_data);
|
|
||||||
|
|
||||||
msg->actual_length += drv_data->len;
|
msg->actual_length += drv_data->len;
|
||||||
msg->state = pxa2xx_spi_next_transfer(drv_data);
|
msg->state = pxa2xx_spi_next_transfer(drv_data);
|
||||||
} else {
|
} else {
|
||||||
|
@ -152,11 +77,12 @@ pxa2xx_spi_dma_prepare_one(struct driver_data *drv_data,
|
||||||
enum dma_transfer_direction dir)
|
enum dma_transfer_direction dir)
|
||||||
{
|
{
|
||||||
struct chip_data *chip = drv_data->cur_chip;
|
struct chip_data *chip = drv_data->cur_chip;
|
||||||
|
struct spi_transfer *xfer = drv_data->cur_transfer;
|
||||||
enum dma_slave_buswidth width;
|
enum dma_slave_buswidth width;
|
||||||
struct dma_slave_config cfg;
|
struct dma_slave_config cfg;
|
||||||
struct dma_chan *chan;
|
struct dma_chan *chan;
|
||||||
struct sg_table *sgt;
|
struct sg_table *sgt;
|
||||||
int nents, ret;
|
int ret;
|
||||||
|
|
||||||
switch (drv_data->n_bytes) {
|
switch (drv_data->n_bytes) {
|
||||||
case 1:
|
case 1:
|
||||||
|
@ -178,17 +104,15 @@ pxa2xx_spi_dma_prepare_one(struct driver_data *drv_data,
|
||||||
cfg.dst_addr_width = width;
|
cfg.dst_addr_width = width;
|
||||||
cfg.dst_maxburst = chip->dma_burst_size;
|
cfg.dst_maxburst = chip->dma_burst_size;
|
||||||
|
|
||||||
sgt = &drv_data->tx_sgt;
|
sgt = &xfer->tx_sg;
|
||||||
nents = drv_data->tx_nents;
|
chan = drv_data->master->dma_tx;
|
||||||
chan = drv_data->tx_chan;
|
|
||||||
} else {
|
} else {
|
||||||
cfg.src_addr = drv_data->ssdr_physical;
|
cfg.src_addr = drv_data->ssdr_physical;
|
||||||
cfg.src_addr_width = width;
|
cfg.src_addr_width = width;
|
||||||
cfg.src_maxburst = chip->dma_burst_size;
|
cfg.src_maxburst = chip->dma_burst_size;
|
||||||
|
|
||||||
sgt = &drv_data->rx_sgt;
|
sgt = &xfer->rx_sg;
|
||||||
nents = drv_data->rx_nents;
|
chan = drv_data->master->dma_rx;
|
||||||
chan = drv_data->rx_chan;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = dmaengine_slave_config(chan, &cfg);
|
ret = dmaengine_slave_config(chan, &cfg);
|
||||||
|
@ -197,46 +121,10 @@ pxa2xx_spi_dma_prepare_one(struct driver_data *drv_data,
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
return dmaengine_prep_slave_sg(chan, sgt->sgl, nents, dir,
|
return dmaengine_prep_slave_sg(chan, sgt->sgl, sgt->nents, dir,
|
||||||
DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
|
DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool pxa2xx_spi_dma_is_possible(size_t len)
|
|
||||||
{
|
|
||||||
return len <= MAX_DMA_LEN;
|
|
||||||
}
|
|
||||||
|
|
||||||
int pxa2xx_spi_map_dma_buffers(struct driver_data *drv_data)
|
|
||||||
{
|
|
||||||
const struct chip_data *chip = drv_data->cur_chip;
|
|
||||||
int ret;
|
|
||||||
|
|
||||||
if (!chip->enable_dma)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
/* Don't bother with DMA if we can't do even a single burst */
|
|
||||||
if (drv_data->len < chip->dma_burst_size)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
ret = pxa2xx_spi_map_dma_buffer(drv_data, DMA_TO_DEVICE);
|
|
||||||
if (ret <= 0) {
|
|
||||||
dev_warn(&drv_data->pdev->dev, "failed to DMA map TX\n");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
drv_data->tx_nents = ret;
|
|
||||||
|
|
||||||
ret = pxa2xx_spi_map_dma_buffer(drv_data, DMA_FROM_DEVICE);
|
|
||||||
if (ret <= 0) {
|
|
||||||
pxa2xx_spi_unmap_dma_buffer(drv_data, DMA_TO_DEVICE);
|
|
||||||
dev_warn(&drv_data->pdev->dev, "failed to DMA map RX\n");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
drv_data->rx_nents = ret;
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
irqreturn_t pxa2xx_spi_dma_transfer(struct driver_data *drv_data)
|
irqreturn_t pxa2xx_spi_dma_transfer(struct driver_data *drv_data)
|
||||||
{
|
{
|
||||||
u32 status;
|
u32 status;
|
||||||
|
@ -245,8 +133,8 @@ irqreturn_t pxa2xx_spi_dma_transfer(struct driver_data *drv_data)
|
||||||
if (status & SSSR_ROR) {
|
if (status & SSSR_ROR) {
|
||||||
dev_err(&drv_data->pdev->dev, "FIFO overrun\n");
|
dev_err(&drv_data->pdev->dev, "FIFO overrun\n");
|
||||||
|
|
||||||
dmaengine_terminate_async(drv_data->rx_chan);
|
dmaengine_terminate_async(drv_data->master->dma_rx);
|
||||||
dmaengine_terminate_async(drv_data->tx_chan);
|
dmaengine_terminate_async(drv_data->master->dma_tx);
|
||||||
|
|
||||||
pxa2xx_spi_dma_transfer_complete(drv_data, true);
|
pxa2xx_spi_dma_transfer_complete(drv_data, true);
|
||||||
return IRQ_HANDLED;
|
return IRQ_HANDLED;
|
||||||
|
@ -285,16 +173,15 @@ int pxa2xx_spi_dma_prepare(struct driver_data *drv_data, u32 dma_burst)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
err_rx:
|
err_rx:
|
||||||
dmaengine_terminate_async(drv_data->tx_chan);
|
dmaengine_terminate_async(drv_data->master->dma_tx);
|
||||||
err_tx:
|
err_tx:
|
||||||
pxa2xx_spi_unmap_dma_buffers(drv_data);
|
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
void pxa2xx_spi_dma_start(struct driver_data *drv_data)
|
void pxa2xx_spi_dma_start(struct driver_data *drv_data)
|
||||||
{
|
{
|
||||||
dma_async_issue_pending(drv_data->rx_chan);
|
dma_async_issue_pending(drv_data->master->dma_rx);
|
||||||
dma_async_issue_pending(drv_data->tx_chan);
|
dma_async_issue_pending(drv_data->master->dma_tx);
|
||||||
|
|
||||||
atomic_set(&drv_data->dma_running, 1);
|
atomic_set(&drv_data->dma_running, 1);
|
||||||
}
|
}
|
||||||
|
@ -303,21 +190,22 @@ int pxa2xx_spi_dma_setup(struct driver_data *drv_data)
|
||||||
{
|
{
|
||||||
struct pxa2xx_spi_master *pdata = drv_data->master_info;
|
struct pxa2xx_spi_master *pdata = drv_data->master_info;
|
||||||
struct device *dev = &drv_data->pdev->dev;
|
struct device *dev = &drv_data->pdev->dev;
|
||||||
|
struct spi_master *master = drv_data->master;
|
||||||
dma_cap_mask_t mask;
|
dma_cap_mask_t mask;
|
||||||
|
|
||||||
dma_cap_zero(mask);
|
dma_cap_zero(mask);
|
||||||
dma_cap_set(DMA_SLAVE, mask);
|
dma_cap_set(DMA_SLAVE, mask);
|
||||||
|
|
||||||
drv_data->tx_chan = dma_request_slave_channel_compat(mask,
|
master->dma_tx = dma_request_slave_channel_compat(mask,
|
||||||
pdata->dma_filter, pdata->tx_param, dev, "tx");
|
pdata->dma_filter, pdata->tx_param, dev, "tx");
|
||||||
if (!drv_data->tx_chan)
|
if (!master->dma_tx)
|
||||||
return -ENODEV;
|
return -ENODEV;
|
||||||
|
|
||||||
drv_data->rx_chan = dma_request_slave_channel_compat(mask,
|
master->dma_rx = dma_request_slave_channel_compat(mask,
|
||||||
pdata->dma_filter, pdata->rx_param, dev, "rx");
|
pdata->dma_filter, pdata->rx_param, dev, "rx");
|
||||||
if (!drv_data->rx_chan) {
|
if (!master->dma_rx) {
|
||||||
dma_release_channel(drv_data->tx_chan);
|
dma_release_channel(master->dma_tx);
|
||||||
drv_data->tx_chan = NULL;
|
master->dma_tx = NULL;
|
||||||
return -ENODEV;
|
return -ENODEV;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -326,17 +214,17 @@ int pxa2xx_spi_dma_setup(struct driver_data *drv_data)
|
||||||
|
|
||||||
void pxa2xx_spi_dma_release(struct driver_data *drv_data)
|
void pxa2xx_spi_dma_release(struct driver_data *drv_data)
|
||||||
{
|
{
|
||||||
if (drv_data->rx_chan) {
|
struct spi_master *master = drv_data->master;
|
||||||
dmaengine_terminate_sync(drv_data->rx_chan);
|
|
||||||
dma_release_channel(drv_data->rx_chan);
|
if (master->dma_rx) {
|
||||||
sg_free_table(&drv_data->rx_sgt);
|
dmaengine_terminate_sync(master->dma_rx);
|
||||||
drv_data->rx_chan = NULL;
|
dma_release_channel(master->dma_rx);
|
||||||
|
master->dma_rx = NULL;
|
||||||
}
|
}
|
||||||
if (drv_data->tx_chan) {
|
if (master->dma_tx) {
|
||||||
dmaengine_terminate_sync(drv_data->tx_chan);
|
dmaengine_terminate_sync(master->dma_tx);
|
||||||
dma_release_channel(drv_data->tx_chan);
|
dma_release_channel(master->dma_tx);
|
||||||
sg_free_table(&drv_data->tx_sgt);
|
master->dma_tx = NULL;
|
||||||
drv_data->tx_chan = NULL;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,24 +1,26 @@
|
||||||
/*
|
/*
|
||||||
* CE4100's SPI device is more or less the same one as found on PXA
|
* CE4100's SPI device is more or less the same one as found on PXA
|
||||||
*
|
*
|
||||||
|
* Copyright (C) 2016, Intel Corporation
|
||||||
*/
|
*/
|
||||||
|
#include <linux/clk-provider.h>
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/of_device.h>
|
||||||
#include <linux/pci.h>
|
#include <linux/pci.h>
|
||||||
#include <linux/platform_device.h>
|
#include <linux/platform_device.h>
|
||||||
#include <linux/of_device.h>
|
|
||||||
#include <linux/module.h>
|
|
||||||
#include <linux/spi/pxa2xx_spi.h>
|
#include <linux/spi/pxa2xx_spi.h>
|
||||||
#include <linux/clk-provider.h>
|
|
||||||
|
|
||||||
#include <linux/dmaengine.h>
|
#include <linux/dmaengine.h>
|
||||||
#include <linux/platform_data/dma-dw.h>
|
#include <linux/platform_data/dma-dw.h>
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
PORT_CE4100,
|
PORT_QUARK_X1000,
|
||||||
PORT_BYT,
|
PORT_BYT,
|
||||||
|
PORT_MRFLD,
|
||||||
PORT_BSW0,
|
PORT_BSW0,
|
||||||
PORT_BSW1,
|
PORT_BSW1,
|
||||||
PORT_BSW2,
|
PORT_BSW2,
|
||||||
PORT_QUARK_X1000,
|
PORT_CE4100,
|
||||||
PORT_LPT,
|
PORT_LPT,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -29,8 +31,11 @@ struct pxa_spi_info {
|
||||||
unsigned long max_clk_rate;
|
unsigned long max_clk_rate;
|
||||||
|
|
||||||
/* DMA channel request parameters */
|
/* DMA channel request parameters */
|
||||||
|
bool (*dma_filter)(struct dma_chan *chan, void *param);
|
||||||
void *tx_param;
|
void *tx_param;
|
||||||
void *rx_param;
|
void *rx_param;
|
||||||
|
|
||||||
|
int (*setup)(struct pci_dev *pdev, struct pxa_spi_info *c);
|
||||||
};
|
};
|
||||||
|
|
||||||
static struct dw_dma_slave byt_tx_param = { .dst_id = 0 };
|
static struct dw_dma_slave byt_tx_param = { .dst_id = 0 };
|
||||||
|
@ -57,86 +62,12 @@ static bool lpss_dma_filter(struct dma_chan *chan, void *param)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct pxa_spi_info spi_info_configs[] = {
|
static int lpss_spi_setup(struct pci_dev *dev, struct pxa_spi_info *c)
|
||||||
[PORT_CE4100] = {
|
|
||||||
.type = PXA25x_SSP,
|
|
||||||
.port_id = -1,
|
|
||||||
.num_chipselect = -1,
|
|
||||||
.max_clk_rate = 3686400,
|
|
||||||
},
|
|
||||||
[PORT_BYT] = {
|
|
||||||
.type = LPSS_BYT_SSP,
|
|
||||||
.port_id = 0,
|
|
||||||
.num_chipselect = 1,
|
|
||||||
.max_clk_rate = 50000000,
|
|
||||||
.tx_param = &byt_tx_param,
|
|
||||||
.rx_param = &byt_rx_param,
|
|
||||||
},
|
|
||||||
[PORT_BSW0] = {
|
|
||||||
.type = LPSS_BYT_SSP,
|
|
||||||
.port_id = 0,
|
|
||||||
.num_chipselect = 1,
|
|
||||||
.max_clk_rate = 50000000,
|
|
||||||
.tx_param = &bsw0_tx_param,
|
|
||||||
.rx_param = &bsw0_rx_param,
|
|
||||||
},
|
|
||||||
[PORT_BSW1] = {
|
|
||||||
.type = LPSS_BYT_SSP,
|
|
||||||
.port_id = 1,
|
|
||||||
.num_chipselect = 1,
|
|
||||||
.max_clk_rate = 50000000,
|
|
||||||
.tx_param = &bsw1_tx_param,
|
|
||||||
.rx_param = &bsw1_rx_param,
|
|
||||||
},
|
|
||||||
[PORT_BSW2] = {
|
|
||||||
.type = LPSS_BYT_SSP,
|
|
||||||
.port_id = 2,
|
|
||||||
.num_chipselect = 1,
|
|
||||||
.max_clk_rate = 50000000,
|
|
||||||
.tx_param = &bsw2_tx_param,
|
|
||||||
.rx_param = &bsw2_rx_param,
|
|
||||||
},
|
|
||||||
[PORT_QUARK_X1000] = {
|
|
||||||
.type = QUARK_X1000_SSP,
|
|
||||||
.port_id = -1,
|
|
||||||
.num_chipselect = 1,
|
|
||||||
.max_clk_rate = 50000000,
|
|
||||||
},
|
|
||||||
[PORT_LPT] = {
|
|
||||||
.type = LPSS_LPT_SSP,
|
|
||||||
.port_id = 0,
|
|
||||||
.num_chipselect = 1,
|
|
||||||
.max_clk_rate = 50000000,
|
|
||||||
.tx_param = &lpt_tx_param,
|
|
||||||
.rx_param = &lpt_rx_param,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
static int pxa2xx_spi_pci_probe(struct pci_dev *dev,
|
|
||||||
const struct pci_device_id *ent)
|
|
||||||
{
|
{
|
||||||
struct platform_device_info pi;
|
|
||||||
int ret;
|
|
||||||
struct platform_device *pdev;
|
|
||||||
struct pxa2xx_spi_master spi_pdata;
|
|
||||||
struct ssp_device *ssp;
|
|
||||||
struct pxa_spi_info *c;
|
|
||||||
char buf[40];
|
|
||||||
struct pci_dev *dma_dev;
|
struct pci_dev *dma_dev;
|
||||||
|
|
||||||
ret = pcim_enable_device(dev);
|
c->num_chipselect = 1;
|
||||||
if (ret)
|
c->max_clk_rate = 50000000;
|
||||||
return ret;
|
|
||||||
|
|
||||||
ret = pcim_iomap_regions(dev, 1 << 0, "PXA2xx SPI");
|
|
||||||
if (ret)
|
|
||||||
return ret;
|
|
||||||
|
|
||||||
c = &spi_info_configs[ent->driver_data];
|
|
||||||
|
|
||||||
memset(&spi_pdata, 0, sizeof(spi_pdata));
|
|
||||||
spi_pdata.num_chipselect = (c->num_chipselect > 0) ?
|
|
||||||
c->num_chipselect : dev->devfn;
|
|
||||||
|
|
||||||
dma_dev = pci_get_slot(dev->bus, PCI_DEVFN(PCI_SLOT(dev->devfn), 0));
|
dma_dev = pci_get_slot(dev->bus, PCI_DEVFN(PCI_SLOT(dev->devfn), 0));
|
||||||
|
|
||||||
|
@ -156,7 +87,115 @@ static int pxa2xx_spi_pci_probe(struct pci_dev *dev,
|
||||||
slave->p_master = 1;
|
slave->p_master = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
spi_pdata.dma_filter = lpss_dma_filter;
|
c->dma_filter = lpss_dma_filter;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int mrfld_spi_setup(struct pci_dev *dev, struct pxa_spi_info *c)
|
||||||
|
{
|
||||||
|
switch (PCI_FUNC(dev->devfn)) {
|
||||||
|
case 0:
|
||||||
|
c->port_id = 3;
|
||||||
|
c->num_chipselect = 1;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
c->port_id = 5;
|
||||||
|
c->num_chipselect = 4;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
c->port_id = 6;
|
||||||
|
c->num_chipselect = 1;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return -ENODEV;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct pxa_spi_info spi_info_configs[] = {
|
||||||
|
[PORT_CE4100] = {
|
||||||
|
.type = PXA25x_SSP,
|
||||||
|
.port_id = -1,
|
||||||
|
.num_chipselect = -1,
|
||||||
|
.max_clk_rate = 3686400,
|
||||||
|
},
|
||||||
|
[PORT_BYT] = {
|
||||||
|
.type = LPSS_BYT_SSP,
|
||||||
|
.port_id = 0,
|
||||||
|
.setup = lpss_spi_setup,
|
||||||
|
.tx_param = &byt_tx_param,
|
||||||
|
.rx_param = &byt_rx_param,
|
||||||
|
},
|
||||||
|
[PORT_BSW0] = {
|
||||||
|
.type = LPSS_BSW_SSP,
|
||||||
|
.port_id = 0,
|
||||||
|
.setup = lpss_spi_setup,
|
||||||
|
.tx_param = &bsw0_tx_param,
|
||||||
|
.rx_param = &bsw0_rx_param,
|
||||||
|
},
|
||||||
|
[PORT_BSW1] = {
|
||||||
|
.type = LPSS_BSW_SSP,
|
||||||
|
.port_id = 1,
|
||||||
|
.setup = lpss_spi_setup,
|
||||||
|
.tx_param = &bsw1_tx_param,
|
||||||
|
.rx_param = &bsw1_rx_param,
|
||||||
|
},
|
||||||
|
[PORT_BSW2] = {
|
||||||
|
.type = LPSS_BSW_SSP,
|
||||||
|
.port_id = 2,
|
||||||
|
.setup = lpss_spi_setup,
|
||||||
|
.tx_param = &bsw2_tx_param,
|
||||||
|
.rx_param = &bsw2_rx_param,
|
||||||
|
},
|
||||||
|
[PORT_MRFLD] = {
|
||||||
|
.type = PXA27x_SSP,
|
||||||
|
.max_clk_rate = 25000000,
|
||||||
|
.setup = mrfld_spi_setup,
|
||||||
|
},
|
||||||
|
[PORT_QUARK_X1000] = {
|
||||||
|
.type = QUARK_X1000_SSP,
|
||||||
|
.port_id = -1,
|
||||||
|
.num_chipselect = 1,
|
||||||
|
.max_clk_rate = 50000000,
|
||||||
|
},
|
||||||
|
[PORT_LPT] = {
|
||||||
|
.type = LPSS_LPT_SSP,
|
||||||
|
.port_id = 0,
|
||||||
|
.setup = lpss_spi_setup,
|
||||||
|
.tx_param = &lpt_tx_param,
|
||||||
|
.rx_param = &lpt_rx_param,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
static int pxa2xx_spi_pci_probe(struct pci_dev *dev,
|
||||||
|
const struct pci_device_id *ent)
|
||||||
|
{
|
||||||
|
struct platform_device_info pi;
|
||||||
|
int ret;
|
||||||
|
struct platform_device *pdev;
|
||||||
|
struct pxa2xx_spi_master spi_pdata;
|
||||||
|
struct ssp_device *ssp;
|
||||||
|
struct pxa_spi_info *c;
|
||||||
|
char buf[40];
|
||||||
|
|
||||||
|
ret = pcim_enable_device(dev);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
ret = pcim_iomap_regions(dev, 1 << 0, "PXA2xx SPI");
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
c = &spi_info_configs[ent->driver_data];
|
||||||
|
if (c->setup) {
|
||||||
|
ret = c->setup(dev, c);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
memset(&spi_pdata, 0, sizeof(spi_pdata));
|
||||||
|
spi_pdata.num_chipselect = (c->num_chipselect > 0) ? c->num_chipselect : dev->devfn;
|
||||||
|
spi_pdata.dma_filter = c->dma_filter;
|
||||||
spi_pdata.tx_param = c->tx_param;
|
spi_pdata.tx_param = c->tx_param;
|
||||||
spi_pdata.rx_param = c->rx_param;
|
spi_pdata.rx_param = c->rx_param;
|
||||||
spi_pdata.enable_dma = c->rx_param && c->tx_param;
|
spi_pdata.enable_dma = c->rx_param && c->tx_param;
|
||||||
|
@ -164,10 +203,6 @@ static int pxa2xx_spi_pci_probe(struct pci_dev *dev,
|
||||||
ssp = &spi_pdata.ssp;
|
ssp = &spi_pdata.ssp;
|
||||||
ssp->phys_base = pci_resource_start(dev, 0);
|
ssp->phys_base = pci_resource_start(dev, 0);
|
||||||
ssp->mmio_base = pcim_iomap_table(dev)[0];
|
ssp->mmio_base = pcim_iomap_table(dev)[0];
|
||||||
if (!ssp->mmio_base) {
|
|
||||||
dev_err(&dev->dev, "failed to ioremap() registers\n");
|
|
||||||
return -EIO;
|
|
||||||
}
|
|
||||||
ssp->irq = dev->irq;
|
ssp->irq = dev->irq;
|
||||||
ssp->port_id = (c->port_id >= 0) ? c->port_id : dev->devfn;
|
ssp->port_id = (c->port_id >= 0) ? c->port_id : dev->devfn;
|
||||||
ssp->type = c->type;
|
ssp->type = c->type;
|
||||||
|
@ -208,12 +243,13 @@ static void pxa2xx_spi_pci_remove(struct pci_dev *dev)
|
||||||
}
|
}
|
||||||
|
|
||||||
static const struct pci_device_id pxa2xx_spi_pci_devices[] = {
|
static const struct pci_device_id pxa2xx_spi_pci_devices[] = {
|
||||||
{ PCI_VDEVICE(INTEL, 0x2e6a), PORT_CE4100 },
|
|
||||||
{ PCI_VDEVICE(INTEL, 0x0935), PORT_QUARK_X1000 },
|
{ PCI_VDEVICE(INTEL, 0x0935), PORT_QUARK_X1000 },
|
||||||
{ PCI_VDEVICE(INTEL, 0x0f0e), PORT_BYT },
|
{ PCI_VDEVICE(INTEL, 0x0f0e), PORT_BYT },
|
||||||
|
{ PCI_VDEVICE(INTEL, 0x1194), PORT_MRFLD },
|
||||||
{ PCI_VDEVICE(INTEL, 0x228e), PORT_BSW0 },
|
{ PCI_VDEVICE(INTEL, 0x228e), PORT_BSW0 },
|
||||||
{ PCI_VDEVICE(INTEL, 0x2290), PORT_BSW1 },
|
{ PCI_VDEVICE(INTEL, 0x2290), PORT_BSW1 },
|
||||||
{ PCI_VDEVICE(INTEL, 0x22ac), PORT_BSW2 },
|
{ PCI_VDEVICE(INTEL, 0x22ac), PORT_BSW2 },
|
||||||
|
{ PCI_VDEVICE(INTEL, 0x2e6a), PORT_CE4100 },
|
||||||
{ PCI_VDEVICE(INTEL, 0x9ce6), PORT_LPT },
|
{ PCI_VDEVICE(INTEL, 0x9ce6), PORT_LPT },
|
||||||
{ },
|
{ },
|
||||||
};
|
};
|
||||||
|
|
|
@ -585,7 +585,14 @@ static void reset_sccr1(struct driver_data *drv_data)
|
||||||
u32 sccr1_reg;
|
u32 sccr1_reg;
|
||||||
|
|
||||||
sccr1_reg = pxa2xx_spi_read(drv_data, SSCR1) & ~drv_data->int_cr1;
|
sccr1_reg = pxa2xx_spi_read(drv_data, SSCR1) & ~drv_data->int_cr1;
|
||||||
sccr1_reg &= ~SSCR1_RFT;
|
switch (drv_data->ssp_type) {
|
||||||
|
case QUARK_X1000_SSP:
|
||||||
|
sccr1_reg &= ~QUARK_X1000_SSCR1_RFT;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
sccr1_reg &= ~SSCR1_RFT;
|
||||||
|
break;
|
||||||
|
}
|
||||||
sccr1_reg |= chip->threshold;
|
sccr1_reg |= chip->threshold;
|
||||||
pxa2xx_spi_write(drv_data, SSCR1, sccr1_reg);
|
pxa2xx_spi_write(drv_data, SSCR1, sccr1_reg);
|
||||||
}
|
}
|
||||||
|
@ -912,9 +919,21 @@ static unsigned int pxa2xx_ssp_get_clk_div(struct driver_data *drv_data,
|
||||||
return clk_div << 8;
|
return clk_div << 8;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool pxa2xx_spi_can_dma(struct spi_master *master,
|
||||||
|
struct spi_device *spi,
|
||||||
|
struct spi_transfer *xfer)
|
||||||
|
{
|
||||||
|
struct chip_data *chip = spi_get_ctldata(spi);
|
||||||
|
|
||||||
|
return chip->enable_dma &&
|
||||||
|
xfer->len <= MAX_DMA_LEN &&
|
||||||
|
xfer->len >= chip->dma_burst_size;
|
||||||
|
}
|
||||||
|
|
||||||
static void pump_transfers(unsigned long data)
|
static void pump_transfers(unsigned long data)
|
||||||
{
|
{
|
||||||
struct driver_data *drv_data = (struct driver_data *)data;
|
struct driver_data *drv_data = (struct driver_data *)data;
|
||||||
|
struct spi_master *master = drv_data->master;
|
||||||
struct spi_message *message = NULL;
|
struct spi_message *message = NULL;
|
||||||
struct spi_transfer *transfer = NULL;
|
struct spi_transfer *transfer = NULL;
|
||||||
struct spi_transfer *previous = NULL;
|
struct spi_transfer *previous = NULL;
|
||||||
|
@ -928,6 +947,7 @@ static void pump_transfers(unsigned long data)
|
||||||
u32 dma_burst = drv_data->cur_chip->dma_burst_size;
|
u32 dma_burst = drv_data->cur_chip->dma_burst_size;
|
||||||
u32 change_mask = pxa2xx_spi_get_ssrc1_change_mask(drv_data);
|
u32 change_mask = pxa2xx_spi_get_ssrc1_change_mask(drv_data);
|
||||||
int err;
|
int err;
|
||||||
|
int dma_mapped;
|
||||||
|
|
||||||
/* Get current state information */
|
/* Get current state information */
|
||||||
message = drv_data->cur_msg;
|
message = drv_data->cur_msg;
|
||||||
|
@ -962,7 +982,7 @@ static void pump_transfers(unsigned long data)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Check if we can DMA this transfer */
|
/* Check if we can DMA this transfer */
|
||||||
if (!pxa2xx_spi_dma_is_possible(transfer->len) && chip->enable_dma) {
|
if (transfer->len > MAX_DMA_LEN && chip->enable_dma) {
|
||||||
|
|
||||||
/* reject already-mapped transfers; PIO won't always work */
|
/* reject already-mapped transfers; PIO won't always work */
|
||||||
if (message->is_dma_mapped
|
if (message->is_dma_mapped
|
||||||
|
@ -1039,10 +1059,10 @@ static void pump_transfers(unsigned long data)
|
||||||
|
|
||||||
message->state = RUNNING_STATE;
|
message->state = RUNNING_STATE;
|
||||||
|
|
||||||
drv_data->dma_mapped = 0;
|
dma_mapped = master->can_dma &&
|
||||||
if (pxa2xx_spi_dma_is_possible(drv_data->len))
|
master->can_dma(master, message->spi, transfer) &&
|
||||||
drv_data->dma_mapped = pxa2xx_spi_map_dma_buffers(drv_data);
|
master->cur_msg_mapped;
|
||||||
if (drv_data->dma_mapped) {
|
if (dma_mapped) {
|
||||||
|
|
||||||
/* Ensure we have the correct interrupt handler */
|
/* Ensure we have the correct interrupt handler */
|
||||||
drv_data->transfer_handler = pxa2xx_spi_dma_transfer;
|
drv_data->transfer_handler = pxa2xx_spi_dma_transfer;
|
||||||
|
@ -1072,14 +1092,14 @@ static void pump_transfers(unsigned long data)
|
||||||
cr0 = pxa2xx_configure_sscr0(drv_data, clk_div, bits);
|
cr0 = pxa2xx_configure_sscr0(drv_data, clk_div, bits);
|
||||||
if (!pxa25x_ssp_comp(drv_data))
|
if (!pxa25x_ssp_comp(drv_data))
|
||||||
dev_dbg(&message->spi->dev, "%u Hz actual, %s\n",
|
dev_dbg(&message->spi->dev, "%u Hz actual, %s\n",
|
||||||
drv_data->master->max_speed_hz
|
master->max_speed_hz
|
||||||
/ (1 + ((cr0 & SSCR0_SCR(0xfff)) >> 8)),
|
/ (1 + ((cr0 & SSCR0_SCR(0xfff)) >> 8)),
|
||||||
drv_data->dma_mapped ? "DMA" : "PIO");
|
dma_mapped ? "DMA" : "PIO");
|
||||||
else
|
else
|
||||||
dev_dbg(&message->spi->dev, "%u Hz actual, %s\n",
|
dev_dbg(&message->spi->dev, "%u Hz actual, %s\n",
|
||||||
drv_data->master->max_speed_hz / 2
|
master->max_speed_hz / 2
|
||||||
/ (1 + ((cr0 & SSCR0_SCR(0x0ff)) >> 8)),
|
/ (1 + ((cr0 & SSCR0_SCR(0x0ff)) >> 8)),
|
||||||
drv_data->dma_mapped ? "DMA" : "PIO");
|
dma_mapped ? "DMA" : "PIO");
|
||||||
|
|
||||||
if (is_lpss_ssp(drv_data)) {
|
if (is_lpss_ssp(drv_data)) {
|
||||||
if ((pxa2xx_spi_read(drv_data, SSIRF) & 0xff)
|
if ((pxa2xx_spi_read(drv_data, SSIRF) & 0xff)
|
||||||
|
@ -1240,7 +1260,7 @@ static int setup(struct spi_device *spi)
|
||||||
chip->frm = spi->chip_select;
|
chip->frm = spi->chip_select;
|
||||||
} else
|
} else
|
||||||
chip->gpio_cs = -1;
|
chip->gpio_cs = -1;
|
||||||
chip->enable_dma = 0;
|
chip->enable_dma = drv_data->master_info->enable_dma;
|
||||||
chip->timeout = TIMOUT_DFLT;
|
chip->timeout = TIMOUT_DFLT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1259,17 +1279,9 @@ static int setup(struct spi_device *spi)
|
||||||
tx_hi_thres = chip_info->tx_hi_threshold;
|
tx_hi_thres = chip_info->tx_hi_threshold;
|
||||||
if (chip_info->rx_threshold)
|
if (chip_info->rx_threshold)
|
||||||
rx_thres = chip_info->rx_threshold;
|
rx_thres = chip_info->rx_threshold;
|
||||||
chip->enable_dma = drv_data->master_info->enable_dma;
|
|
||||||
chip->dma_threshold = 0;
|
chip->dma_threshold = 0;
|
||||||
if (chip_info->enable_loopback)
|
if (chip_info->enable_loopback)
|
||||||
chip->cr1 = SSCR1_LBM;
|
chip->cr1 = SSCR1_LBM;
|
||||||
} else if (ACPI_HANDLE(&spi->dev)) {
|
|
||||||
/*
|
|
||||||
* Slave devices enumerated from ACPI namespace don't
|
|
||||||
* usually have chip_info but we still might want to use
|
|
||||||
* DMA with them.
|
|
||||||
*/
|
|
||||||
chip->enable_dma = drv_data->master_info->enable_dma;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
chip->lpss_rx_threshold = SSIRF_RxThresh(rx_thres);
|
chip->lpss_rx_threshold = SSIRF_RxThresh(rx_thres);
|
||||||
|
@ -1389,6 +1401,9 @@ static const struct pci_device_id pxa2xx_spi_pci_compound_match[] = {
|
||||||
/* SPT-H */
|
/* SPT-H */
|
||||||
{ PCI_VDEVICE(INTEL, 0xa129), LPSS_SPT_SSP },
|
{ PCI_VDEVICE(INTEL, 0xa129), LPSS_SPT_SSP },
|
||||||
{ PCI_VDEVICE(INTEL, 0xa12a), LPSS_SPT_SSP },
|
{ PCI_VDEVICE(INTEL, 0xa12a), LPSS_SPT_SSP },
|
||||||
|
/* KBL-H */
|
||||||
|
{ PCI_VDEVICE(INTEL, 0xa2a9), LPSS_SPT_SSP },
|
||||||
|
{ PCI_VDEVICE(INTEL, 0xa2aa), LPSS_SPT_SSP },
|
||||||
/* BXT A-Step */
|
/* BXT A-Step */
|
||||||
{ PCI_VDEVICE(INTEL, 0x0ac2), LPSS_BXT_SSP },
|
{ PCI_VDEVICE(INTEL, 0x0ac2), LPSS_BXT_SSP },
|
||||||
{ PCI_VDEVICE(INTEL, 0x0ac4), LPSS_BXT_SSP },
|
{ PCI_VDEVICE(INTEL, 0x0ac4), LPSS_BXT_SSP },
|
||||||
|
@ -1601,6 +1616,8 @@ static int pxa2xx_spi_probe(struct platform_device *pdev)
|
||||||
if (status) {
|
if (status) {
|
||||||
dev_dbg(dev, "no DMA channels available, using PIO\n");
|
dev_dbg(dev, "no DMA channels available, using PIO\n");
|
||||||
platform_info->enable_dma = false;
|
platform_info->enable_dma = false;
|
||||||
|
} else {
|
||||||
|
master->can_dma = pxa2xx_spi_can_dma;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -50,12 +50,6 @@ struct driver_data {
|
||||||
struct tasklet_struct pump_transfers;
|
struct tasklet_struct pump_transfers;
|
||||||
|
|
||||||
/* DMA engine support */
|
/* DMA engine support */
|
||||||
struct dma_chan *rx_chan;
|
|
||||||
struct dma_chan *tx_chan;
|
|
||||||
struct sg_table rx_sgt;
|
|
||||||
struct sg_table tx_sgt;
|
|
||||||
int rx_nents;
|
|
||||||
int tx_nents;
|
|
||||||
atomic_t dma_running;
|
atomic_t dma_running;
|
||||||
|
|
||||||
/* Current message transfer state info */
|
/* Current message transfer state info */
|
||||||
|
@ -67,7 +61,6 @@ struct driver_data {
|
||||||
void *tx_end;
|
void *tx_end;
|
||||||
void *rx;
|
void *rx;
|
||||||
void *rx_end;
|
void *rx_end;
|
||||||
int dma_mapped;
|
|
||||||
u8 n_bytes;
|
u8 n_bytes;
|
||||||
int (*write)(struct driver_data *drv_data);
|
int (*write)(struct driver_data *drv_data);
|
||||||
int (*read)(struct driver_data *drv_data);
|
int (*read)(struct driver_data *drv_data);
|
||||||
|
@ -145,8 +138,6 @@ extern void *pxa2xx_spi_next_transfer(struct driver_data *drv_data);
|
||||||
#define MAX_DMA_LEN SZ_64K
|
#define MAX_DMA_LEN SZ_64K
|
||||||
#define DEFAULT_DMA_CR1 (SSCR1_TSRE | SSCR1_RSRE | SSCR1_TRAIL)
|
#define DEFAULT_DMA_CR1 (SSCR1_TSRE | SSCR1_RSRE | SSCR1_TRAIL)
|
||||||
|
|
||||||
extern bool pxa2xx_spi_dma_is_possible(size_t len);
|
|
||||||
extern int pxa2xx_spi_map_dma_buffers(struct driver_data *drv_data);
|
|
||||||
extern irqreturn_t pxa2xx_spi_dma_transfer(struct driver_data *drv_data);
|
extern irqreturn_t pxa2xx_spi_dma_transfer(struct driver_data *drv_data);
|
||||||
extern int pxa2xx_spi_dma_prepare(struct driver_data *drv_data, u32 dma_burst);
|
extern int pxa2xx_spi_dma_prepare(struct driver_data *drv_data, u32 dma_burst);
|
||||||
extern void pxa2xx_spi_dma_start(struct driver_data *drv_data);
|
extern void pxa2xx_spi_dma_start(struct driver_data *drv_data);
|
||||||
|
|
|
@ -142,6 +142,12 @@
|
||||||
/* sclk_out: spi master internal logic in rk3x can support 50Mhz */
|
/* sclk_out: spi master internal logic in rk3x can support 50Mhz */
|
||||||
#define MAX_SCLK_OUT 50000000
|
#define MAX_SCLK_OUT 50000000
|
||||||
|
|
||||||
|
/*
|
||||||
|
* SPI_CTRLR1 is 16-bits, so we should support lengths of 0xffff + 1. However,
|
||||||
|
* the controller seems to hang when given 0x10000, so stick with this for now.
|
||||||
|
*/
|
||||||
|
#define ROCKCHIP_SPI_MAX_TRANLEN 0xffff
|
||||||
|
|
||||||
enum rockchip_ssi_type {
|
enum rockchip_ssi_type {
|
||||||
SSI_MOTO_SPI = 0,
|
SSI_MOTO_SPI = 0,
|
||||||
SSI_TI_SSP,
|
SSI_TI_SSP,
|
||||||
|
@ -573,6 +579,11 @@ static void rockchip_spi_config(struct rockchip_spi *rs)
|
||||||
dev_dbg(rs->dev, "cr0 0x%x, div %d\n", cr0, div);
|
dev_dbg(rs->dev, "cr0 0x%x, div %d\n", cr0, div);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static size_t rockchip_spi_max_transfer_size(struct spi_device *spi)
|
||||||
|
{
|
||||||
|
return ROCKCHIP_SPI_MAX_TRANLEN;
|
||||||
|
}
|
||||||
|
|
||||||
static int rockchip_spi_transfer_one(
|
static int rockchip_spi_transfer_one(
|
||||||
struct spi_master *master,
|
struct spi_master *master,
|
||||||
struct spi_device *spi,
|
struct spi_device *spi,
|
||||||
|
@ -589,6 +600,11 @@ static int rockchip_spi_transfer_one(
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (xfer->len > ROCKCHIP_SPI_MAX_TRANLEN) {
|
||||||
|
dev_err(rs->dev, "Transfer is too long (%d)\n", xfer->len);
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
rs->speed = xfer->speed_hz;
|
rs->speed = xfer->speed_hz;
|
||||||
rs->bpw = xfer->bits_per_word;
|
rs->bpw = xfer->bits_per_word;
|
||||||
rs->n_bytes = rs->bpw >> 3;
|
rs->n_bytes = rs->bpw >> 3;
|
||||||
|
@ -730,6 +746,7 @@ static int rockchip_spi_probe(struct platform_device *pdev)
|
||||||
master->prepare_message = rockchip_spi_prepare_message;
|
master->prepare_message = rockchip_spi_prepare_message;
|
||||||
master->unprepare_message = rockchip_spi_unprepare_message;
|
master->unprepare_message = rockchip_spi_unprepare_message;
|
||||||
master->transfer_one = rockchip_spi_transfer_one;
|
master->transfer_one = rockchip_spi_transfer_one;
|
||||||
|
master->max_transfer_size = rockchip_spi_max_transfer_size;
|
||||||
master->handle_err = rockchip_spi_handle_err;
|
master->handle_err = rockchip_spi_handle_err;
|
||||||
|
|
||||||
rs->dma_tx.ch = dma_request_chan(rs->dev, "tx");
|
rs->dma_tx.ch = dma_request_chan(rs->dev, "tx");
|
||||||
|
@ -894,9 +911,12 @@ static const struct dev_pm_ops rockchip_spi_pm = {
|
||||||
};
|
};
|
||||||
|
|
||||||
static const struct of_device_id rockchip_spi_dt_match[] = {
|
static const struct of_device_id rockchip_spi_dt_match[] = {
|
||||||
|
{ .compatible = "rockchip,rk3036-spi", },
|
||||||
{ .compatible = "rockchip,rk3066-spi", },
|
{ .compatible = "rockchip,rk3066-spi", },
|
||||||
{ .compatible = "rockchip,rk3188-spi", },
|
{ .compatible = "rockchip,rk3188-spi", },
|
||||||
|
{ .compatible = "rockchip,rk3228-spi", },
|
||||||
{ .compatible = "rockchip,rk3288-spi", },
|
{ .compatible = "rockchip,rk3288-spi", },
|
||||||
|
{ .compatible = "rockchip,rk3368-spi", },
|
||||||
{ .compatible = "rockchip,rk3399-spi", },
|
{ .compatible = "rockchip,rk3399-spi", },
|
||||||
{ },
|
{ },
|
||||||
};
|
};
|
||||||
|
|
|
@ -156,12 +156,14 @@ struct s3c64xx_spi_port_config {
|
||||||
int quirks;
|
int quirks;
|
||||||
bool high_speed;
|
bool high_speed;
|
||||||
bool clk_from_cmu;
|
bool clk_from_cmu;
|
||||||
|
bool clk_ioclk;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* struct s3c64xx_spi_driver_data - Runtime info holder for SPI driver.
|
* struct s3c64xx_spi_driver_data - Runtime info holder for SPI driver.
|
||||||
* @clk: Pointer to the spi clock.
|
* @clk: Pointer to the spi clock.
|
||||||
* @src_clk: Pointer to the clock used to generate SPI signals.
|
* @src_clk: Pointer to the clock used to generate SPI signals.
|
||||||
|
* @ioclk: Pointer to the i/o clock between master and slave
|
||||||
* @master: Pointer to the SPI Protocol master.
|
* @master: Pointer to the SPI Protocol master.
|
||||||
* @cntrlr_info: Platform specific data for the controller this driver manages.
|
* @cntrlr_info: Platform specific data for the controller this driver manages.
|
||||||
* @tgl_spi: Pointer to the last CS left untoggled by the cs_change hint.
|
* @tgl_spi: Pointer to the last CS left untoggled by the cs_change hint.
|
||||||
|
@ -181,6 +183,7 @@ struct s3c64xx_spi_driver_data {
|
||||||
void __iomem *regs;
|
void __iomem *regs;
|
||||||
struct clk *clk;
|
struct clk *clk;
|
||||||
struct clk *src_clk;
|
struct clk *src_clk;
|
||||||
|
struct clk *ioclk;
|
||||||
struct platform_device *pdev;
|
struct platform_device *pdev;
|
||||||
struct spi_master *master;
|
struct spi_master *master;
|
||||||
struct s3c64xx_spi_info *cntrlr_info;
|
struct s3c64xx_spi_info *cntrlr_info;
|
||||||
|
@ -310,44 +313,63 @@ static void prepare_dma(struct s3c64xx_spi_dma_data *dma,
|
||||||
dma_async_issue_pending(dma->ch);
|
dma_async_issue_pending(dma->ch);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void s3c64xx_spi_set_cs(struct spi_device *spi, bool enable)
|
||||||
|
{
|
||||||
|
struct s3c64xx_spi_driver_data *sdd =
|
||||||
|
spi_master_get_devdata(spi->master);
|
||||||
|
|
||||||
|
if (sdd->cntrlr_info->no_cs)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (enable) {
|
||||||
|
if (!(sdd->port_conf->quirks & S3C64XX_SPI_QUIRK_CS_AUTO)) {
|
||||||
|
writel(0, sdd->regs + S3C64XX_SPI_SLAVE_SEL);
|
||||||
|
} else {
|
||||||
|
u32 ssel = readl(sdd->regs + S3C64XX_SPI_SLAVE_SEL);
|
||||||
|
|
||||||
|
ssel |= (S3C64XX_SPI_SLAVE_AUTO |
|
||||||
|
S3C64XX_SPI_SLAVE_NSC_CNT_2);
|
||||||
|
writel(ssel, sdd->regs + S3C64XX_SPI_SLAVE_SEL);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!(sdd->port_conf->quirks & S3C64XX_SPI_QUIRK_CS_AUTO))
|
||||||
|
writel(S3C64XX_SPI_SLAVE_SIG_INACT,
|
||||||
|
sdd->regs + S3C64XX_SPI_SLAVE_SEL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static int s3c64xx_spi_prepare_transfer(struct spi_master *spi)
|
static int s3c64xx_spi_prepare_transfer(struct spi_master *spi)
|
||||||
{
|
{
|
||||||
struct s3c64xx_spi_driver_data *sdd = spi_master_get_devdata(spi);
|
struct s3c64xx_spi_driver_data *sdd = spi_master_get_devdata(spi);
|
||||||
dma_filter_fn filter = sdd->cntrlr_info->filter;
|
dma_filter_fn filter = sdd->cntrlr_info->filter;
|
||||||
struct device *dev = &sdd->pdev->dev;
|
struct device *dev = &sdd->pdev->dev;
|
||||||
dma_cap_mask_t mask;
|
dma_cap_mask_t mask;
|
||||||
int ret;
|
|
||||||
|
|
||||||
if (!is_polling(sdd)) {
|
if (is_polling(sdd))
|
||||||
dma_cap_zero(mask);
|
return 0;
|
||||||
dma_cap_set(DMA_SLAVE, mask);
|
|
||||||
|
|
||||||
/* Acquire DMA channels */
|
dma_cap_zero(mask);
|
||||||
sdd->rx_dma.ch = dma_request_slave_channel_compat(mask, filter,
|
dma_cap_set(DMA_SLAVE, mask);
|
||||||
sdd->cntrlr_info->dma_rx, dev, "rx");
|
|
||||||
if (!sdd->rx_dma.ch) {
|
|
||||||
dev_err(dev, "Failed to get RX DMA channel\n");
|
|
||||||
ret = -EBUSY;
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
spi->dma_rx = sdd->rx_dma.ch;
|
|
||||||
|
|
||||||
sdd->tx_dma.ch = dma_request_slave_channel_compat(mask, filter,
|
/* Acquire DMA channels */
|
||||||
sdd->cntrlr_info->dma_tx, dev, "tx");
|
sdd->rx_dma.ch = dma_request_slave_channel_compat(mask, filter,
|
||||||
if (!sdd->tx_dma.ch) {
|
sdd->cntrlr_info->dma_rx, dev, "rx");
|
||||||
dev_err(dev, "Failed to get TX DMA channel\n");
|
if (!sdd->rx_dma.ch) {
|
||||||
ret = -EBUSY;
|
dev_err(dev, "Failed to get RX DMA channel\n");
|
||||||
goto out_rx;
|
return -EBUSY;
|
||||||
}
|
|
||||||
spi->dma_tx = sdd->tx_dma.ch;
|
|
||||||
}
|
}
|
||||||
|
spi->dma_rx = sdd->rx_dma.ch;
|
||||||
|
|
||||||
|
sdd->tx_dma.ch = dma_request_slave_channel_compat(mask, filter,
|
||||||
|
sdd->cntrlr_info->dma_tx, dev, "tx");
|
||||||
|
if (!sdd->tx_dma.ch) {
|
||||||
|
dev_err(dev, "Failed to get TX DMA channel\n");
|
||||||
|
dma_release_channel(sdd->rx_dma.ch);
|
||||||
|
return -EBUSY;
|
||||||
|
}
|
||||||
|
spi->dma_tx = sdd->tx_dma.ch;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
out_rx:
|
|
||||||
dma_release_channel(sdd->rx_dma.ch);
|
|
||||||
out:
|
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int s3c64xx_spi_unprepare_transfer(struct spi_master *spi)
|
static int s3c64xx_spi_unprepare_transfer(struct spi_master *spi)
|
||||||
|
@ -577,9 +599,7 @@ static void s3c64xx_spi_config(struct s3c64xx_spi_driver_data *sdd)
|
||||||
u32 val;
|
u32 val;
|
||||||
|
|
||||||
/* Disable Clock */
|
/* Disable Clock */
|
||||||
if (sdd->port_conf->clk_from_cmu) {
|
if (!sdd->port_conf->clk_from_cmu) {
|
||||||
clk_disable_unprepare(sdd->src_clk);
|
|
||||||
} else {
|
|
||||||
val = readl(regs + S3C64XX_SPI_CLK_CFG);
|
val = readl(regs + S3C64XX_SPI_CLK_CFG);
|
||||||
val &= ~S3C64XX_SPI_ENCLK_ENABLE;
|
val &= ~S3C64XX_SPI_ENCLK_ENABLE;
|
||||||
writel(val, regs + S3C64XX_SPI_CLK_CFG);
|
writel(val, regs + S3C64XX_SPI_CLK_CFG);
|
||||||
|
@ -622,11 +642,8 @@ static void s3c64xx_spi_config(struct s3c64xx_spi_driver_data *sdd)
|
||||||
writel(val, regs + S3C64XX_SPI_MODE_CFG);
|
writel(val, regs + S3C64XX_SPI_MODE_CFG);
|
||||||
|
|
||||||
if (sdd->port_conf->clk_from_cmu) {
|
if (sdd->port_conf->clk_from_cmu) {
|
||||||
/* Configure Clock */
|
/* The src_clk clock is divided internally by 2 */
|
||||||
/* There is half-multiplier before the SPI */
|
|
||||||
clk_set_rate(sdd->src_clk, sdd->cur_speed * 2);
|
clk_set_rate(sdd->src_clk, sdd->cur_speed * 2);
|
||||||
/* Enable Clock */
|
|
||||||
clk_prepare_enable(sdd->src_clk);
|
|
||||||
} else {
|
} else {
|
||||||
/* Configure Clock */
|
/* Configure Clock */
|
||||||
val = readl(regs + S3C64XX_SPI_CLK_CFG);
|
val = readl(regs + S3C64XX_SPI_CLK_CFG);
|
||||||
|
@ -651,16 +668,6 @@ static int s3c64xx_spi_prepare_message(struct spi_master *master,
|
||||||
struct spi_device *spi = msg->spi;
|
struct spi_device *spi = msg->spi;
|
||||||
struct s3c64xx_spi_csinfo *cs = spi->controller_data;
|
struct s3c64xx_spi_csinfo *cs = spi->controller_data;
|
||||||
|
|
||||||
/* If Master's(controller) state differs from that needed by Slave */
|
|
||||||
if (sdd->cur_speed != spi->max_speed_hz
|
|
||||||
|| sdd->cur_mode != spi->mode
|
|
||||||
|| sdd->cur_bpw != spi->bits_per_word) {
|
|
||||||
sdd->cur_bpw = spi->bits_per_word;
|
|
||||||
sdd->cur_speed = spi->max_speed_hz;
|
|
||||||
sdd->cur_mode = spi->mode;
|
|
||||||
s3c64xx_spi_config(sdd);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Configure feedback delay */
|
/* Configure feedback delay */
|
||||||
writel(cs->fb_delay & 0x3, sdd->regs + S3C64XX_SPI_FB_CLK);
|
writel(cs->fb_delay & 0x3, sdd->regs + S3C64XX_SPI_FB_CLK);
|
||||||
|
|
||||||
|
@ -687,6 +694,7 @@ static int s3c64xx_spi_transfer_one(struct spi_master *master,
|
||||||
if (bpw != sdd->cur_bpw || speed != sdd->cur_speed) {
|
if (bpw != sdd->cur_bpw || speed != sdd->cur_speed) {
|
||||||
sdd->cur_bpw = bpw;
|
sdd->cur_bpw = bpw;
|
||||||
sdd->cur_speed = speed;
|
sdd->cur_speed = speed;
|
||||||
|
sdd->cur_mode = spi->mode;
|
||||||
s3c64xx_spi_config(sdd);
|
s3c64xx_spi_config(sdd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -706,12 +714,7 @@ static int s3c64xx_spi_transfer_one(struct spi_master *master,
|
||||||
enable_datapath(sdd, spi, xfer, use_dma);
|
enable_datapath(sdd, spi, xfer, use_dma);
|
||||||
|
|
||||||
/* Start the signals */
|
/* Start the signals */
|
||||||
if (!(sdd->port_conf->quirks & S3C64XX_SPI_QUIRK_CS_AUTO))
|
s3c64xx_spi_set_cs(spi, true);
|
||||||
writel(0, sdd->regs + S3C64XX_SPI_SLAVE_SEL);
|
|
||||||
else
|
|
||||||
writel(readl(sdd->regs + S3C64XX_SPI_SLAVE_SEL)
|
|
||||||
| S3C64XX_SPI_SLAVE_AUTO | S3C64XX_SPI_SLAVE_NSC_CNT_2,
|
|
||||||
sdd->regs + S3C64XX_SPI_SLAVE_SEL);
|
|
||||||
|
|
||||||
spin_unlock_irqrestore(&sdd->lock, flags);
|
spin_unlock_irqrestore(&sdd->lock, flags);
|
||||||
|
|
||||||
|
@ -861,16 +864,15 @@ static int s3c64xx_spi_setup(struct spi_device *spi)
|
||||||
|
|
||||||
pm_runtime_mark_last_busy(&sdd->pdev->dev);
|
pm_runtime_mark_last_busy(&sdd->pdev->dev);
|
||||||
pm_runtime_put_autosuspend(&sdd->pdev->dev);
|
pm_runtime_put_autosuspend(&sdd->pdev->dev);
|
||||||
if (!(sdd->port_conf->quirks & S3C64XX_SPI_QUIRK_CS_AUTO))
|
s3c64xx_spi_set_cs(spi, false);
|
||||||
writel(S3C64XX_SPI_SLAVE_SIG_INACT, sdd->regs + S3C64XX_SPI_SLAVE_SEL);
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
setup_exit:
|
setup_exit:
|
||||||
pm_runtime_mark_last_busy(&sdd->pdev->dev);
|
pm_runtime_mark_last_busy(&sdd->pdev->dev);
|
||||||
pm_runtime_put_autosuspend(&sdd->pdev->dev);
|
pm_runtime_put_autosuspend(&sdd->pdev->dev);
|
||||||
/* setup() returns with device de-selected */
|
/* setup() returns with device de-selected */
|
||||||
if (!(sdd->port_conf->quirks & S3C64XX_SPI_QUIRK_CS_AUTO))
|
s3c64xx_spi_set_cs(spi, false);
|
||||||
writel(S3C64XX_SPI_SLAVE_SIG_INACT, sdd->regs + S3C64XX_SPI_SLAVE_SEL);
|
|
||||||
|
|
||||||
if (gpio_is_valid(spi->cs_gpio))
|
if (gpio_is_valid(spi->cs_gpio))
|
||||||
gpio_free(spi->cs_gpio);
|
gpio_free(spi->cs_gpio);
|
||||||
|
@ -944,7 +946,9 @@ static void s3c64xx_spi_hwinit(struct s3c64xx_spi_driver_data *sdd, int channel)
|
||||||
|
|
||||||
sdd->cur_speed = 0;
|
sdd->cur_speed = 0;
|
||||||
|
|
||||||
if (!(sdd->port_conf->quirks & S3C64XX_SPI_QUIRK_CS_AUTO))
|
if (sci->no_cs)
|
||||||
|
writel(0, sdd->regs + S3C64XX_SPI_SLAVE_SEL);
|
||||||
|
else if (!(sdd->port_conf->quirks & S3C64XX_SPI_QUIRK_CS_AUTO))
|
||||||
writel(S3C64XX_SPI_SLAVE_SIG_INACT, sdd->regs + S3C64XX_SPI_SLAVE_SEL);
|
writel(S3C64XX_SPI_SLAVE_SIG_INACT, sdd->regs + S3C64XX_SPI_SLAVE_SEL);
|
||||||
|
|
||||||
/* Disable Interrupts - we use Polling if not DMA mode */
|
/* Disable Interrupts - we use Polling if not DMA mode */
|
||||||
|
@ -999,6 +1003,8 @@ static struct s3c64xx_spi_info *s3c64xx_spi_parse_dt(struct device *dev)
|
||||||
sci->num_cs = temp;
|
sci->num_cs = temp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sci->no_cs = of_property_read_bool(dev->of_node, "broken-cs");
|
||||||
|
|
||||||
return sci;
|
return sci;
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
|
@ -1076,7 +1082,7 @@ static int s3c64xx_spi_probe(struct platform_device *pdev)
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
dev_err(&pdev->dev, "failed to get alias id, errno %d\n",
|
dev_err(&pdev->dev, "failed to get alias id, errno %d\n",
|
||||||
ret);
|
ret);
|
||||||
goto err0;
|
goto err_deref_master;
|
||||||
}
|
}
|
||||||
sdd->port_id = ret;
|
sdd->port_id = ret;
|
||||||
} else {
|
} else {
|
||||||
|
@ -1114,13 +1120,13 @@ static int s3c64xx_spi_probe(struct platform_device *pdev)
|
||||||
sdd->regs = devm_ioremap_resource(&pdev->dev, mem_res);
|
sdd->regs = devm_ioremap_resource(&pdev->dev, mem_res);
|
||||||
if (IS_ERR(sdd->regs)) {
|
if (IS_ERR(sdd->regs)) {
|
||||||
ret = PTR_ERR(sdd->regs);
|
ret = PTR_ERR(sdd->regs);
|
||||||
goto err0;
|
goto err_deref_master;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sci->cfg_gpio && sci->cfg_gpio()) {
|
if (sci->cfg_gpio && sci->cfg_gpio()) {
|
||||||
dev_err(&pdev->dev, "Unable to config gpio\n");
|
dev_err(&pdev->dev, "Unable to config gpio\n");
|
||||||
ret = -EBUSY;
|
ret = -EBUSY;
|
||||||
goto err0;
|
goto err_deref_master;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Setup clocks */
|
/* Setup clocks */
|
||||||
|
@ -1128,13 +1134,13 @@ static int s3c64xx_spi_probe(struct platform_device *pdev)
|
||||||
if (IS_ERR(sdd->clk)) {
|
if (IS_ERR(sdd->clk)) {
|
||||||
dev_err(&pdev->dev, "Unable to acquire clock 'spi'\n");
|
dev_err(&pdev->dev, "Unable to acquire clock 'spi'\n");
|
||||||
ret = PTR_ERR(sdd->clk);
|
ret = PTR_ERR(sdd->clk);
|
||||||
goto err0;
|
goto err_deref_master;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (clk_prepare_enable(sdd->clk)) {
|
ret = clk_prepare_enable(sdd->clk);
|
||||||
|
if (ret) {
|
||||||
dev_err(&pdev->dev, "Couldn't enable clock 'spi'\n");
|
dev_err(&pdev->dev, "Couldn't enable clock 'spi'\n");
|
||||||
ret = -EBUSY;
|
goto err_deref_master;
|
||||||
goto err0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sprintf(clk_name, "spi_busclk%d", sci->src_clk_nr);
|
sprintf(clk_name, "spi_busclk%d", sci->src_clk_nr);
|
||||||
|
@ -1143,13 +1149,28 @@ static int s3c64xx_spi_probe(struct platform_device *pdev)
|
||||||
dev_err(&pdev->dev,
|
dev_err(&pdev->dev,
|
||||||
"Unable to acquire clock '%s'\n", clk_name);
|
"Unable to acquire clock '%s'\n", clk_name);
|
||||||
ret = PTR_ERR(sdd->src_clk);
|
ret = PTR_ERR(sdd->src_clk);
|
||||||
goto err2;
|
goto err_disable_clk;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (clk_prepare_enable(sdd->src_clk)) {
|
ret = clk_prepare_enable(sdd->src_clk);
|
||||||
|
if (ret) {
|
||||||
dev_err(&pdev->dev, "Couldn't enable clock '%s'\n", clk_name);
|
dev_err(&pdev->dev, "Couldn't enable clock '%s'\n", clk_name);
|
||||||
ret = -EBUSY;
|
goto err_disable_clk;
|
||||||
goto err2;
|
}
|
||||||
|
|
||||||
|
if (sdd->port_conf->clk_ioclk) {
|
||||||
|
sdd->ioclk = devm_clk_get(&pdev->dev, "spi_ioclk");
|
||||||
|
if (IS_ERR(sdd->ioclk)) {
|
||||||
|
dev_err(&pdev->dev, "Unable to acquire 'ioclk'\n");
|
||||||
|
ret = PTR_ERR(sdd->ioclk);
|
||||||
|
goto err_disable_src_clk;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = clk_prepare_enable(sdd->ioclk);
|
||||||
|
if (ret) {
|
||||||
|
dev_err(&pdev->dev, "Couldn't enable clock 'ioclk'\n");
|
||||||
|
goto err_disable_src_clk;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pm_runtime_set_autosuspend_delay(&pdev->dev, AUTOSUSPEND_TIMEOUT);
|
pm_runtime_set_autosuspend_delay(&pdev->dev, AUTOSUSPEND_TIMEOUT);
|
||||||
|
@ -1169,7 +1190,7 @@ static int s3c64xx_spi_probe(struct platform_device *pdev)
|
||||||
if (ret != 0) {
|
if (ret != 0) {
|
||||||
dev_err(&pdev->dev, "Failed to request IRQ %d: %d\n",
|
dev_err(&pdev->dev, "Failed to request IRQ %d: %d\n",
|
||||||
irq, ret);
|
irq, ret);
|
||||||
goto err3;
|
goto err_pm_put;
|
||||||
}
|
}
|
||||||
|
|
||||||
writel(S3C64XX_SPI_INT_RX_OVERRUN_EN | S3C64XX_SPI_INT_RX_UNDERRUN_EN |
|
writel(S3C64XX_SPI_INT_RX_OVERRUN_EN | S3C64XX_SPI_INT_RX_UNDERRUN_EN |
|
||||||
|
@ -1179,7 +1200,7 @@ static int s3c64xx_spi_probe(struct platform_device *pdev)
|
||||||
ret = devm_spi_register_master(&pdev->dev, master);
|
ret = devm_spi_register_master(&pdev->dev, master);
|
||||||
if (ret != 0) {
|
if (ret != 0) {
|
||||||
dev_err(&pdev->dev, "cannot register SPI master: %d\n", ret);
|
dev_err(&pdev->dev, "cannot register SPI master: %d\n", ret);
|
||||||
goto err3;
|
goto err_pm_put;
|
||||||
}
|
}
|
||||||
|
|
||||||
dev_dbg(&pdev->dev, "Samsung SoC SPI Driver loaded for Bus SPI-%d with %d Slaves attached\n",
|
dev_dbg(&pdev->dev, "Samsung SoC SPI Driver loaded for Bus SPI-%d with %d Slaves attached\n",
|
||||||
|
@ -1193,15 +1214,17 @@ static int s3c64xx_spi_probe(struct platform_device *pdev)
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
err3:
|
err_pm_put:
|
||||||
pm_runtime_put_noidle(&pdev->dev);
|
pm_runtime_put_noidle(&pdev->dev);
|
||||||
pm_runtime_disable(&pdev->dev);
|
pm_runtime_disable(&pdev->dev);
|
||||||
pm_runtime_set_suspended(&pdev->dev);
|
pm_runtime_set_suspended(&pdev->dev);
|
||||||
|
|
||||||
|
clk_disable_unprepare(sdd->ioclk);
|
||||||
|
err_disable_src_clk:
|
||||||
clk_disable_unprepare(sdd->src_clk);
|
clk_disable_unprepare(sdd->src_clk);
|
||||||
err2:
|
err_disable_clk:
|
||||||
clk_disable_unprepare(sdd->clk);
|
clk_disable_unprepare(sdd->clk);
|
||||||
err0:
|
err_deref_master:
|
||||||
spi_master_put(master);
|
spi_master_put(master);
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
|
@ -1209,13 +1232,15 @@ err0:
|
||||||
|
|
||||||
static int s3c64xx_spi_remove(struct platform_device *pdev)
|
static int s3c64xx_spi_remove(struct platform_device *pdev)
|
||||||
{
|
{
|
||||||
struct spi_master *master = spi_master_get(platform_get_drvdata(pdev));
|
struct spi_master *master = platform_get_drvdata(pdev);
|
||||||
struct s3c64xx_spi_driver_data *sdd = spi_master_get_devdata(master);
|
struct s3c64xx_spi_driver_data *sdd = spi_master_get_devdata(master);
|
||||||
|
|
||||||
pm_runtime_get_sync(&pdev->dev);
|
pm_runtime_get_sync(&pdev->dev);
|
||||||
|
|
||||||
writel(0, sdd->regs + S3C64XX_SPI_INT_EN);
|
writel(0, sdd->regs + S3C64XX_SPI_INT_EN);
|
||||||
|
|
||||||
|
clk_disable_unprepare(sdd->ioclk);
|
||||||
|
|
||||||
clk_disable_unprepare(sdd->src_clk);
|
clk_disable_unprepare(sdd->src_clk);
|
||||||
|
|
||||||
clk_disable_unprepare(sdd->clk);
|
clk_disable_unprepare(sdd->clk);
|
||||||
|
@ -1274,6 +1299,7 @@ static int s3c64xx_spi_runtime_suspend(struct device *dev)
|
||||||
|
|
||||||
clk_disable_unprepare(sdd->clk);
|
clk_disable_unprepare(sdd->clk);
|
||||||
clk_disable_unprepare(sdd->src_clk);
|
clk_disable_unprepare(sdd->src_clk);
|
||||||
|
clk_disable_unprepare(sdd->ioclk);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -1284,17 +1310,28 @@ static int s3c64xx_spi_runtime_resume(struct device *dev)
|
||||||
struct s3c64xx_spi_driver_data *sdd = spi_master_get_devdata(master);
|
struct s3c64xx_spi_driver_data *sdd = spi_master_get_devdata(master);
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
ret = clk_prepare_enable(sdd->src_clk);
|
if (sdd->port_conf->clk_ioclk) {
|
||||||
if (ret != 0)
|
ret = clk_prepare_enable(sdd->ioclk);
|
||||||
return ret;
|
if (ret != 0)
|
||||||
|
return ret;
|
||||||
ret = clk_prepare_enable(sdd->clk);
|
|
||||||
if (ret != 0) {
|
|
||||||
clk_disable_unprepare(sdd->src_clk);
|
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ret = clk_prepare_enable(sdd->src_clk);
|
||||||
|
if (ret != 0)
|
||||||
|
goto err_disable_ioclk;
|
||||||
|
|
||||||
|
ret = clk_prepare_enable(sdd->clk);
|
||||||
|
if (ret != 0)
|
||||||
|
goto err_disable_src_clk;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
err_disable_src_clk:
|
||||||
|
clk_disable_unprepare(sdd->src_clk);
|
||||||
|
err_disable_ioclk:
|
||||||
|
clk_disable_unprepare(sdd->ioclk);
|
||||||
|
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
#endif /* CONFIG_PM */
|
#endif /* CONFIG_PM */
|
||||||
|
|
||||||
|
@ -1350,6 +1387,16 @@ static struct s3c64xx_spi_port_config exynos7_spi_port_config = {
|
||||||
.quirks = S3C64XX_SPI_QUIRK_CS_AUTO,
|
.quirks = S3C64XX_SPI_QUIRK_CS_AUTO,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static struct s3c64xx_spi_port_config exynos5433_spi_port_config = {
|
||||||
|
.fifo_lvl_mask = { 0x1ff, 0x7f, 0x7f, 0x7f, 0x7f, 0x1ff},
|
||||||
|
.rx_lvl_offset = 15,
|
||||||
|
.tx_st_done = 25,
|
||||||
|
.high_speed = true,
|
||||||
|
.clk_from_cmu = true,
|
||||||
|
.clk_ioclk = true,
|
||||||
|
.quirks = S3C64XX_SPI_QUIRK_CS_AUTO,
|
||||||
|
};
|
||||||
|
|
||||||
static const struct platform_device_id s3c64xx_spi_driver_ids[] = {
|
static const struct platform_device_id s3c64xx_spi_driver_ids[] = {
|
||||||
{
|
{
|
||||||
.name = "s3c2443-spi",
|
.name = "s3c2443-spi",
|
||||||
|
@ -1380,6 +1427,9 @@ static const struct of_device_id s3c64xx_spi_dt_match[] = {
|
||||||
{ .compatible = "samsung,exynos7-spi",
|
{ .compatible = "samsung,exynos7-spi",
|
||||||
.data = (void *)&exynos7_spi_port_config,
|
.data = (void *)&exynos7_spi_port_config,
|
||||||
},
|
},
|
||||||
|
{ .compatible = "samsung,exynos5433-spi",
|
||||||
|
.data = (void *)&exynos5433_spi_port_config,
|
||||||
|
},
|
||||||
{ },
|
{ },
|
||||||
};
|
};
|
||||||
MODULE_DEVICE_TABLE(of, s3c64xx_spi_dt_match);
|
MODULE_DEVICE_TABLE(of, s3c64xx_spi_dt_match);
|
||||||
|
|
|
@ -45,7 +45,6 @@ struct sh_msiof_spi_priv {
|
||||||
void __iomem *mapbase;
|
void __iomem *mapbase;
|
||||||
struct clk *clk;
|
struct clk *clk;
|
||||||
struct platform_device *pdev;
|
struct platform_device *pdev;
|
||||||
const struct sh_msiof_chipdata *chipdata;
|
|
||||||
struct sh_msiof_spi_info *info;
|
struct sh_msiof_spi_info *info;
|
||||||
struct completion done;
|
struct completion done;
|
||||||
unsigned int tx_fifo_size;
|
unsigned int tx_fifo_size;
|
||||||
|
@ -271,7 +270,7 @@ static void sh_msiof_spi_set_clk_regs(struct sh_msiof_spi_priv *p,
|
||||||
|
|
||||||
scr = sh_msiof_spi_div_table[k].brdv | SCR_BRPS(brps);
|
scr = sh_msiof_spi_div_table[k].brdv | SCR_BRPS(brps);
|
||||||
sh_msiof_write(p, TSCR, scr);
|
sh_msiof_write(p, TSCR, scr);
|
||||||
if (!(p->chipdata->master_flags & SPI_MASTER_MUST_TX))
|
if (!(p->master->flags & SPI_MASTER_MUST_TX))
|
||||||
sh_msiof_write(p, RSCR, scr);
|
sh_msiof_write(p, RSCR, scr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -336,7 +335,7 @@ static void sh_msiof_spi_set_pin_regs(struct sh_msiof_spi_priv *p,
|
||||||
tmp |= lsb_first << MDR1_BITLSB_SHIFT;
|
tmp |= lsb_first << MDR1_BITLSB_SHIFT;
|
||||||
tmp |= sh_msiof_spi_get_dtdl_and_syncdl(p);
|
tmp |= sh_msiof_spi_get_dtdl_and_syncdl(p);
|
||||||
sh_msiof_write(p, TMDR1, tmp | MDR1_TRMD | TMDR1_PCON);
|
sh_msiof_write(p, TMDR1, tmp | MDR1_TRMD | TMDR1_PCON);
|
||||||
if (p->chipdata->master_flags & SPI_MASTER_MUST_TX) {
|
if (p->master->flags & SPI_MASTER_MUST_TX) {
|
||||||
/* These bits are reserved if RX needs TX */
|
/* These bits are reserved if RX needs TX */
|
||||||
tmp &= ~0x0000ffff;
|
tmp &= ~0x0000ffff;
|
||||||
}
|
}
|
||||||
|
@ -360,7 +359,7 @@ static void sh_msiof_spi_set_mode_regs(struct sh_msiof_spi_priv *p,
|
||||||
{
|
{
|
||||||
u32 dr2 = MDR2_BITLEN1(bits) | MDR2_WDLEN1(words);
|
u32 dr2 = MDR2_BITLEN1(bits) | MDR2_WDLEN1(words);
|
||||||
|
|
||||||
if (tx_buf || (p->chipdata->master_flags & SPI_MASTER_MUST_TX))
|
if (tx_buf || (p->master->flags & SPI_MASTER_MUST_TX))
|
||||||
sh_msiof_write(p, TMDR2, dr2);
|
sh_msiof_write(p, TMDR2, dr2);
|
||||||
else
|
else
|
||||||
sh_msiof_write(p, TMDR2, dr2 | MDR2_GRPMASK1);
|
sh_msiof_write(p, TMDR2, dr2 | MDR2_GRPMASK1);
|
||||||
|
@ -1152,6 +1151,7 @@ static int sh_msiof_spi_probe(struct platform_device *pdev)
|
||||||
{
|
{
|
||||||
struct resource *r;
|
struct resource *r;
|
||||||
struct spi_master *master;
|
struct spi_master *master;
|
||||||
|
const struct sh_msiof_chipdata *chipdata;
|
||||||
const struct of_device_id *of_id;
|
const struct of_device_id *of_id;
|
||||||
struct sh_msiof_spi_priv *p;
|
struct sh_msiof_spi_priv *p;
|
||||||
int i;
|
int i;
|
||||||
|
@ -1170,10 +1170,10 @@ static int sh_msiof_spi_probe(struct platform_device *pdev)
|
||||||
|
|
||||||
of_id = of_match_device(sh_msiof_match, &pdev->dev);
|
of_id = of_match_device(sh_msiof_match, &pdev->dev);
|
||||||
if (of_id) {
|
if (of_id) {
|
||||||
p->chipdata = of_id->data;
|
chipdata = of_id->data;
|
||||||
p->info = sh_msiof_spi_parse_dt(&pdev->dev);
|
p->info = sh_msiof_spi_parse_dt(&pdev->dev);
|
||||||
} else {
|
} else {
|
||||||
p->chipdata = (const void *)pdev->id_entry->driver_data;
|
chipdata = (const void *)pdev->id_entry->driver_data;
|
||||||
p->info = dev_get_platdata(&pdev->dev);
|
p->info = dev_get_platdata(&pdev->dev);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1217,8 +1217,8 @@ static int sh_msiof_spi_probe(struct platform_device *pdev)
|
||||||
pm_runtime_enable(&pdev->dev);
|
pm_runtime_enable(&pdev->dev);
|
||||||
|
|
||||||
/* Platform data may override FIFO sizes */
|
/* Platform data may override FIFO sizes */
|
||||||
p->tx_fifo_size = p->chipdata->tx_fifo_size;
|
p->tx_fifo_size = chipdata->tx_fifo_size;
|
||||||
p->rx_fifo_size = p->chipdata->rx_fifo_size;
|
p->rx_fifo_size = chipdata->rx_fifo_size;
|
||||||
if (p->info->tx_fifo_override)
|
if (p->info->tx_fifo_override)
|
||||||
p->tx_fifo_size = p->info->tx_fifo_override;
|
p->tx_fifo_size = p->info->tx_fifo_override;
|
||||||
if (p->info->rx_fifo_override)
|
if (p->info->rx_fifo_override)
|
||||||
|
@ -1227,7 +1227,7 @@ static int sh_msiof_spi_probe(struct platform_device *pdev)
|
||||||
/* init master code */
|
/* init master code */
|
||||||
master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH;
|
master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH;
|
||||||
master->mode_bits |= SPI_LSB_FIRST | SPI_3WIRE;
|
master->mode_bits |= SPI_LSB_FIRST | SPI_3WIRE;
|
||||||
master->flags = p->chipdata->master_flags;
|
master->flags = chipdata->master_flags;
|
||||||
master->bus_num = pdev->id;
|
master->bus_num = pdev->id;
|
||||||
master->dev.of_node = pdev->dev.of_node;
|
master->dev.of_node = pdev->dev.of_node;
|
||||||
master->num_chipselect = p->info->num_chipselect;
|
master->num_chipselect = p->info->num_chipselect;
|
||||||
|
|
|
@ -82,7 +82,6 @@ struct spi_sh_data {
|
||||||
int irq;
|
int irq;
|
||||||
struct spi_master *master;
|
struct spi_master *master;
|
||||||
struct list_head queue;
|
struct list_head queue;
|
||||||
struct workqueue_struct *workqueue;
|
|
||||||
struct work_struct ws;
|
struct work_struct ws;
|
||||||
unsigned long cr1;
|
unsigned long cr1;
|
||||||
wait_queue_head_t wait;
|
wait_queue_head_t wait;
|
||||||
|
@ -380,7 +379,7 @@ static int spi_sh_transfer(struct spi_device *spi, struct spi_message *mesg)
|
||||||
spi_sh_clear_bit(ss, SPI_SH_SSA, SPI_SH_CR1);
|
spi_sh_clear_bit(ss, SPI_SH_SSA, SPI_SH_CR1);
|
||||||
|
|
||||||
list_add_tail(&mesg->queue, &ss->queue);
|
list_add_tail(&mesg->queue, &ss->queue);
|
||||||
queue_work(ss->workqueue, &ss->ws);
|
schedule_work(&ss->ws);
|
||||||
|
|
||||||
spin_unlock_irqrestore(&ss->lock, flags);
|
spin_unlock_irqrestore(&ss->lock, flags);
|
||||||
|
|
||||||
|
@ -425,7 +424,7 @@ static int spi_sh_remove(struct platform_device *pdev)
|
||||||
struct spi_sh_data *ss = platform_get_drvdata(pdev);
|
struct spi_sh_data *ss = platform_get_drvdata(pdev);
|
||||||
|
|
||||||
spi_unregister_master(ss->master);
|
spi_unregister_master(ss->master);
|
||||||
destroy_workqueue(ss->workqueue);
|
flush_work(&ss->ws);
|
||||||
free_irq(ss->irq, ss);
|
free_irq(ss->irq, ss);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -484,18 +483,11 @@ static int spi_sh_probe(struct platform_device *pdev)
|
||||||
spin_lock_init(&ss->lock);
|
spin_lock_init(&ss->lock);
|
||||||
INIT_WORK(&ss->ws, spi_sh_work);
|
INIT_WORK(&ss->ws, spi_sh_work);
|
||||||
init_waitqueue_head(&ss->wait);
|
init_waitqueue_head(&ss->wait);
|
||||||
ss->workqueue = create_singlethread_workqueue(
|
|
||||||
dev_name(master->dev.parent));
|
|
||||||
if (ss->workqueue == NULL) {
|
|
||||||
dev_err(&pdev->dev, "create workqueue error\n");
|
|
||||||
ret = -EBUSY;
|
|
||||||
goto error1;
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = request_irq(irq, spi_sh_irq, 0, "spi_sh", ss);
|
ret = request_irq(irq, spi_sh_irq, 0, "spi_sh", ss);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
dev_err(&pdev->dev, "request_irq error\n");
|
dev_err(&pdev->dev, "request_irq error\n");
|
||||||
goto error2;
|
goto error1;
|
||||||
}
|
}
|
||||||
|
|
||||||
master->num_chipselect = 2;
|
master->num_chipselect = 2;
|
||||||
|
@ -514,8 +506,6 @@ static int spi_sh_probe(struct platform_device *pdev)
|
||||||
|
|
||||||
error3:
|
error3:
|
||||||
free_irq(irq, ss);
|
free_irq(irq, ss);
|
||||||
error2:
|
|
||||||
destroy_workqueue(ss->workqueue);
|
|
||||||
error1:
|
error1:
|
||||||
spi_master_put(master);
|
spi_master_put(master);
|
||||||
|
|
||||||
|
|
|
@ -167,6 +167,11 @@ static void sun4i_spi_set_cs(struct spi_device *spi, bool enable)
|
||||||
sun4i_spi_write(sspi, SUN4I_CTL_REG, reg);
|
sun4i_spi_write(sspi, SUN4I_CTL_REG, reg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static size_t sun4i_spi_max_transfer_size(struct spi_device *spi)
|
||||||
|
{
|
||||||
|
return SUN4I_FIFO_DEPTH - 1;
|
||||||
|
}
|
||||||
|
|
||||||
static int sun4i_spi_transfer_one(struct spi_master *master,
|
static int sun4i_spi_transfer_one(struct spi_master *master,
|
||||||
struct spi_device *spi,
|
struct spi_device *spi,
|
||||||
struct spi_transfer *tfr)
|
struct spi_transfer *tfr)
|
||||||
|
@ -402,6 +407,8 @@ static int sun4i_spi_probe(struct platform_device *pdev)
|
||||||
}
|
}
|
||||||
|
|
||||||
sspi->master = master;
|
sspi->master = master;
|
||||||
|
master->max_speed_hz = 100 * 1000 * 1000;
|
||||||
|
master->min_speed_hz = 3 * 1000;
|
||||||
master->set_cs = sun4i_spi_set_cs;
|
master->set_cs = sun4i_spi_set_cs;
|
||||||
master->transfer_one = sun4i_spi_transfer_one;
|
master->transfer_one = sun4i_spi_transfer_one;
|
||||||
master->num_chipselect = 4;
|
master->num_chipselect = 4;
|
||||||
|
@ -409,6 +416,7 @@ static int sun4i_spi_probe(struct platform_device *pdev)
|
||||||
master->bits_per_word_mask = SPI_BPW_MASK(8);
|
master->bits_per_word_mask = SPI_BPW_MASK(8);
|
||||||
master->dev.of_node = pdev->dev.of_node;
|
master->dev.of_node = pdev->dev.of_node;
|
||||||
master->auto_runtime_pm = true;
|
master->auto_runtime_pm = true;
|
||||||
|
master->max_transfer_size = sun4i_spi_max_transfer_size;
|
||||||
|
|
||||||
sspi->hclk = devm_clk_get(&pdev->dev, "ahb");
|
sspi->hclk = devm_clk_get(&pdev->dev, "ahb");
|
||||||
if (IS_ERR(sspi->hclk)) {
|
if (IS_ERR(sspi->hclk)) {
|
||||||
|
|
|
@ -153,6 +153,10 @@ static void sun6i_spi_set_cs(struct spi_device *spi, bool enable)
|
||||||
sun6i_spi_write(sspi, SUN6I_TFR_CTL_REG, reg);
|
sun6i_spi_write(sspi, SUN6I_TFR_CTL_REG, reg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static size_t sun6i_spi_max_transfer_size(struct spi_device *spi)
|
||||||
|
{
|
||||||
|
return SUN6I_FIFO_DEPTH - 1;
|
||||||
|
}
|
||||||
|
|
||||||
static int sun6i_spi_transfer_one(struct spi_master *master,
|
static int sun6i_spi_transfer_one(struct spi_master *master,
|
||||||
struct spi_device *spi,
|
struct spi_device *spi,
|
||||||
|
@ -394,6 +398,8 @@ static int sun6i_spi_probe(struct platform_device *pdev)
|
||||||
}
|
}
|
||||||
|
|
||||||
sspi->master = master;
|
sspi->master = master;
|
||||||
|
master->max_speed_hz = 100 * 1000 * 1000;
|
||||||
|
master->min_speed_hz = 3 * 1000;
|
||||||
master->set_cs = sun6i_spi_set_cs;
|
master->set_cs = sun6i_spi_set_cs;
|
||||||
master->transfer_one = sun6i_spi_transfer_one;
|
master->transfer_one = sun6i_spi_transfer_one;
|
||||||
master->num_chipselect = 4;
|
master->num_chipselect = 4;
|
||||||
|
@ -401,6 +407,7 @@ static int sun6i_spi_probe(struct platform_device *pdev)
|
||||||
master->bits_per_word_mask = SPI_BPW_MASK(8);
|
master->bits_per_word_mask = SPI_BPW_MASK(8);
|
||||||
master->dev.of_node = pdev->dev.of_node;
|
master->dev.of_node = pdev->dev.of_node;
|
||||||
master->auto_runtime_pm = true;
|
master->auto_runtime_pm = true;
|
||||||
|
master->max_transfer_size = sun6i_spi_max_transfer_size;
|
||||||
|
|
||||||
sspi->hclk = devm_clk_get(&pdev->dev, "ahb");
|
sspi->hclk = devm_clk_get(&pdev->dev, "ahb");
|
||||||
if (IS_ERR(sspi->hclk)) {
|
if (IS_ERR(sspi->hclk)) {
|
||||||
|
|
|
@ -141,7 +141,7 @@ static int ti_qspi_setup(struct spi_device *spi)
|
||||||
u32 clk_ctrl_reg, clk_rate, clk_mask;
|
u32 clk_ctrl_reg, clk_rate, clk_mask;
|
||||||
|
|
||||||
if (spi->master->busy) {
|
if (spi->master->busy) {
|
||||||
dev_dbg(qspi->dev, "master busy doing other trasnfers\n");
|
dev_dbg(qspi->dev, "master busy doing other transfers\n");
|
||||||
return -EBUSY;
|
return -EBUSY;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -133,8 +133,6 @@ struct pch_spi_dma_ctrl {
|
||||||
* @io_remap_addr: The remapped PCI base address
|
* @io_remap_addr: The remapped PCI base address
|
||||||
* @master: Pointer to the SPI master structure
|
* @master: Pointer to the SPI master structure
|
||||||
* @work: Reference to work queue handler
|
* @work: Reference to work queue handler
|
||||||
* @wk: Workqueue for carrying out execution of the
|
|
||||||
* requests
|
|
||||||
* @wait: Wait queue for waking up upon receiving an
|
* @wait: Wait queue for waking up upon receiving an
|
||||||
* interrupt.
|
* interrupt.
|
||||||
* @transfer_complete: Status of SPI Transfer
|
* @transfer_complete: Status of SPI Transfer
|
||||||
|
@ -169,7 +167,6 @@ struct pch_spi_data {
|
||||||
unsigned long io_base_addr;
|
unsigned long io_base_addr;
|
||||||
struct spi_master *master;
|
struct spi_master *master;
|
||||||
struct work_struct work;
|
struct work_struct work;
|
||||||
struct workqueue_struct *wk;
|
|
||||||
wait_queue_head_t wait;
|
wait_queue_head_t wait;
|
||||||
u8 transfer_complete;
|
u8 transfer_complete;
|
||||||
u8 bcurrent_msg_processing;
|
u8 bcurrent_msg_processing;
|
||||||
|
@ -517,8 +514,7 @@ static int pch_spi_transfer(struct spi_device *pspi, struct spi_message *pmsg)
|
||||||
|
|
||||||
dev_dbg(&pspi->dev, "%s - Invoked list_add_tail\n", __func__);
|
dev_dbg(&pspi->dev, "%s - Invoked list_add_tail\n", __func__);
|
||||||
|
|
||||||
/* schedule work queue to run */
|
schedule_work(&data->work);
|
||||||
queue_work(data->wk, &data->work);
|
|
||||||
dev_dbg(&pspi->dev, "%s - Invoked queue work\n", __func__);
|
dev_dbg(&pspi->dev, "%s - Invoked queue work\n", __func__);
|
||||||
|
|
||||||
retval = 0;
|
retval = 0;
|
||||||
|
@ -674,7 +670,7 @@ static void pch_spi_nomore_transfer(struct pch_spi_data *data)
|
||||||
*more messages)
|
*more messages)
|
||||||
*/
|
*/
|
||||||
dev_dbg(&data->master->dev, "%s:Invoke queue_work\n", __func__);
|
dev_dbg(&data->master->dev, "%s:Invoke queue_work\n", __func__);
|
||||||
queue_work(data->wk, &data->work);
|
schedule_work(&data->work);
|
||||||
} else if (data->board_dat->suspend_sts ||
|
} else if (data->board_dat->suspend_sts ||
|
||||||
data->status == STATUS_EXITING) {
|
data->status == STATUS_EXITING) {
|
||||||
dev_dbg(&data->master->dev,
|
dev_dbg(&data->master->dev,
|
||||||
|
@ -1266,14 +1262,7 @@ static void pch_spi_free_resources(struct pch_spi_board_data *board_dat,
|
||||||
{
|
{
|
||||||
dev_dbg(&board_dat->pdev->dev, "%s ENTRY\n", __func__);
|
dev_dbg(&board_dat->pdev->dev, "%s ENTRY\n", __func__);
|
||||||
|
|
||||||
/* free workqueue */
|
flush_work(&data->work);
|
||||||
if (data->wk != NULL) {
|
|
||||||
destroy_workqueue(data->wk);
|
|
||||||
data->wk = NULL;
|
|
||||||
dev_dbg(&board_dat->pdev->dev,
|
|
||||||
"%s destroy_workqueue invoked successfully\n",
|
|
||||||
__func__);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int pch_spi_get_resources(struct pch_spi_board_data *board_dat,
|
static int pch_spi_get_resources(struct pch_spi_board_data *board_dat,
|
||||||
|
@ -1283,14 +1272,6 @@ static int pch_spi_get_resources(struct pch_spi_board_data *board_dat,
|
||||||
|
|
||||||
dev_dbg(&board_dat->pdev->dev, "%s ENTRY\n", __func__);
|
dev_dbg(&board_dat->pdev->dev, "%s ENTRY\n", __func__);
|
||||||
|
|
||||||
/* create workqueue */
|
|
||||||
data->wk = create_singlethread_workqueue(KBUILD_MODNAME);
|
|
||||||
if (!data->wk) {
|
|
||||||
dev_err(&board_dat->pdev->dev,
|
|
||||||
"%s create_singlet hread_workqueue failed\n", __func__);
|
|
||||||
retval = -EBUSY;
|
|
||||||
goto err_return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* reset PCH SPI h/w */
|
/* reset PCH SPI h/w */
|
||||||
pch_spi_reset(data->master);
|
pch_spi_reset(data->master);
|
||||||
|
@ -1299,7 +1280,6 @@ static int pch_spi_get_resources(struct pch_spi_board_data *board_dat,
|
||||||
|
|
||||||
dev_dbg(&board_dat->pdev->dev, "%s data->irq_reg_sts=true\n", __func__);
|
dev_dbg(&board_dat->pdev->dev, "%s data->irq_reg_sts=true\n", __func__);
|
||||||
|
|
||||||
err_return:
|
|
||||||
if (retval != 0) {
|
if (retval != 0) {
|
||||||
dev_err(&board_dat->pdev->dev,
|
dev_err(&board_dat->pdev->dev,
|
||||||
"%s FAIL:invoking pch_spi_free_resources\n", __func__);
|
"%s FAIL:invoking pch_spi_free_resources\n", __func__);
|
||||||
|
|
|
@ -72,7 +72,6 @@
|
||||||
|
|
||||||
|
|
||||||
struct txx9spi {
|
struct txx9spi {
|
||||||
struct workqueue_struct *workqueue;
|
|
||||||
struct work_struct work;
|
struct work_struct work;
|
||||||
spinlock_t lock; /* protect 'queue' */
|
spinlock_t lock; /* protect 'queue' */
|
||||||
struct list_head queue;
|
struct list_head queue;
|
||||||
|
@ -315,7 +314,7 @@ static int txx9spi_transfer(struct spi_device *spi, struct spi_message *m)
|
||||||
|
|
||||||
spin_lock_irqsave(&c->lock, flags);
|
spin_lock_irqsave(&c->lock, flags);
|
||||||
list_add_tail(&m->queue, &c->queue);
|
list_add_tail(&m->queue, &c->queue);
|
||||||
queue_work(c->workqueue, &c->work);
|
schedule_work(&c->work);
|
||||||
spin_unlock_irqrestore(&c->lock, flags);
|
spin_unlock_irqrestore(&c->lock, flags);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -374,10 +373,6 @@ static int txx9spi_probe(struct platform_device *dev)
|
||||||
if (ret)
|
if (ret)
|
||||||
goto exit;
|
goto exit;
|
||||||
|
|
||||||
c->workqueue = create_singlethread_workqueue(
|
|
||||||
dev_name(master->dev.parent));
|
|
||||||
if (!c->workqueue)
|
|
||||||
goto exit_busy;
|
|
||||||
c->last_chipselect = -1;
|
c->last_chipselect = -1;
|
||||||
|
|
||||||
dev_info(&dev->dev, "at %#llx, irq %d, %dMHz\n",
|
dev_info(&dev->dev, "at %#llx, irq %d, %dMHz\n",
|
||||||
|
@ -400,8 +395,6 @@ static int txx9spi_probe(struct platform_device *dev)
|
||||||
exit_busy:
|
exit_busy:
|
||||||
ret = -EBUSY;
|
ret = -EBUSY;
|
||||||
exit:
|
exit:
|
||||||
if (c->workqueue)
|
|
||||||
destroy_workqueue(c->workqueue);
|
|
||||||
clk_disable(c->clk);
|
clk_disable(c->clk);
|
||||||
spi_master_put(master);
|
spi_master_put(master);
|
||||||
return ret;
|
return ret;
|
||||||
|
@ -412,7 +405,7 @@ static int txx9spi_remove(struct platform_device *dev)
|
||||||
struct spi_master *master = platform_get_drvdata(dev);
|
struct spi_master *master = platform_get_drvdata(dev);
|
||||||
struct txx9spi *c = spi_master_get_devdata(master);
|
struct txx9spi *c = spi_master_get_devdata(master);
|
||||||
|
|
||||||
destroy_workqueue(c->workqueue);
|
flush_work(&c->work);
|
||||||
clk_disable(c->clk);
|
clk_disable(c->clk);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -341,9 +341,10 @@ static irqreturn_t xilinx_spi_irq(int irq, void *dev_id)
|
||||||
|
|
||||||
if (ipif_isr & XSPI_INTR_TX_EMPTY) { /* Transmission completed */
|
if (ipif_isr & XSPI_INTR_TX_EMPTY) { /* Transmission completed */
|
||||||
complete(&xspi->done);
|
complete(&xspi->done);
|
||||||
|
return IRQ_HANDLED;
|
||||||
}
|
}
|
||||||
|
|
||||||
return IRQ_HANDLED;
|
return IRQ_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int xilinx_spi_find_buffer_size(struct xilinx_spi *xspi)
|
static int xilinx_spi_find_buffer_size(struct xilinx_spi *xspi)
|
||||||
|
@ -455,7 +456,10 @@ static int xilinx_spi_probe(struct platform_device *pdev)
|
||||||
xspi->buffer_size = xilinx_spi_find_buffer_size(xspi);
|
xspi->buffer_size = xilinx_spi_find_buffer_size(xspi);
|
||||||
|
|
||||||
xspi->irq = platform_get_irq(pdev, 0);
|
xspi->irq = platform_get_irq(pdev, 0);
|
||||||
if (xspi->irq >= 0) {
|
if (xspi->irq < 0 && xspi->irq != -ENXIO) {
|
||||||
|
ret = xspi->irq;
|
||||||
|
goto put_master;
|
||||||
|
} else if (xspi->irq >= 0) {
|
||||||
/* Register for SPI Interrupt */
|
/* Register for SPI Interrupt */
|
||||||
ret = devm_request_irq(&pdev->dev, xspi->irq, xilinx_spi_irq, 0,
|
ret = devm_request_irq(&pdev->dev, xspi->irq, xilinx_spi_irq, 0,
|
||||||
dev_name(&pdev->dev), xspi);
|
dev_name(&pdev->dev), xspi);
|
||||||
|
|
|
@ -851,6 +851,20 @@ static int __spi_unmap_msg(struct spi_master *master, struct spi_message *msg)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
#else /* !CONFIG_HAS_DMA */
|
#else /* !CONFIG_HAS_DMA */
|
||||||
|
static inline int spi_map_buf(struct spi_master *master,
|
||||||
|
struct device *dev, struct sg_table *sgt,
|
||||||
|
void *buf, size_t len,
|
||||||
|
enum dma_data_direction dir)
|
||||||
|
{
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void spi_unmap_buf(struct spi_master *master,
|
||||||
|
struct device *dev, struct sg_table *sgt,
|
||||||
|
enum dma_data_direction dir)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
static inline int __spi_map_msg(struct spi_master *master,
|
static inline int __spi_map_msg(struct spi_master *master,
|
||||||
struct spi_message *msg)
|
struct spi_message *msg)
|
||||||
{
|
{
|
||||||
|
@ -1057,7 +1071,6 @@ EXPORT_SYMBOL_GPL(spi_finalize_current_transfer);
|
||||||
* __spi_pump_messages - function which processes spi message queue
|
* __spi_pump_messages - function which processes spi message queue
|
||||||
* @master: master to process queue for
|
* @master: master to process queue for
|
||||||
* @in_kthread: true if we are in the context of the message pump thread
|
* @in_kthread: true if we are in the context of the message pump thread
|
||||||
* @bus_locked: true if the bus mutex is held when calling this function
|
|
||||||
*
|
*
|
||||||
* This function checks if there is any spi message in the queue that
|
* This function checks if there is any spi message in the queue that
|
||||||
* needs processing and if so call out to the driver to initialize hardware
|
* needs processing and if so call out to the driver to initialize hardware
|
||||||
|
@ -1067,8 +1080,7 @@ EXPORT_SYMBOL_GPL(spi_finalize_current_transfer);
|
||||||
* inside spi_sync(); the queue extraction handling at the top of the
|
* inside spi_sync(); the queue extraction handling at the top of the
|
||||||
* function should deal with this safely.
|
* function should deal with this safely.
|
||||||
*/
|
*/
|
||||||
static void __spi_pump_messages(struct spi_master *master, bool in_kthread,
|
static void __spi_pump_messages(struct spi_master *master, bool in_kthread)
|
||||||
bool bus_locked)
|
|
||||||
{
|
{
|
||||||
unsigned long flags;
|
unsigned long flags;
|
||||||
bool was_busy = false;
|
bool was_busy = false;
|
||||||
|
@ -1140,6 +1152,8 @@ static void __spi_pump_messages(struct spi_master *master, bool in_kthread,
|
||||||
master->busy = true;
|
master->busy = true;
|
||||||
spin_unlock_irqrestore(&master->queue_lock, flags);
|
spin_unlock_irqrestore(&master->queue_lock, flags);
|
||||||
|
|
||||||
|
mutex_lock(&master->io_mutex);
|
||||||
|
|
||||||
if (!was_busy && master->auto_runtime_pm) {
|
if (!was_busy && master->auto_runtime_pm) {
|
||||||
ret = pm_runtime_get_sync(master->dev.parent);
|
ret = pm_runtime_get_sync(master->dev.parent);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
|
@ -1164,9 +1178,6 @@ static void __spi_pump_messages(struct spi_master *master, bool in_kthread,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!bus_locked)
|
|
||||||
mutex_lock(&master->bus_lock_mutex);
|
|
||||||
|
|
||||||
trace_spi_message_start(master->cur_msg);
|
trace_spi_message_start(master->cur_msg);
|
||||||
|
|
||||||
if (master->prepare_message) {
|
if (master->prepare_message) {
|
||||||
|
@ -1196,8 +1207,7 @@ static void __spi_pump_messages(struct spi_master *master, bool in_kthread,
|
||||||
}
|
}
|
||||||
|
|
||||||
out:
|
out:
|
||||||
if (!bus_locked)
|
mutex_unlock(&master->io_mutex);
|
||||||
mutex_unlock(&master->bus_lock_mutex);
|
|
||||||
|
|
||||||
/* Prod the scheduler in case transfer_one() was busy waiting */
|
/* Prod the scheduler in case transfer_one() was busy waiting */
|
||||||
if (!ret)
|
if (!ret)
|
||||||
|
@ -1213,7 +1223,7 @@ static void spi_pump_messages(struct kthread_work *work)
|
||||||
struct spi_master *master =
|
struct spi_master *master =
|
||||||
container_of(work, struct spi_master, pump_messages);
|
container_of(work, struct spi_master, pump_messages);
|
||||||
|
|
||||||
__spi_pump_messages(master, true, master->bus_lock_flag);
|
__spi_pump_messages(master, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int spi_init_queue(struct spi_master *master)
|
static int spi_init_queue(struct spi_master *master)
|
||||||
|
@ -1886,6 +1896,7 @@ int spi_register_master(struct spi_master *master)
|
||||||
spin_lock_init(&master->queue_lock);
|
spin_lock_init(&master->queue_lock);
|
||||||
spin_lock_init(&master->bus_lock_spinlock);
|
spin_lock_init(&master->bus_lock_spinlock);
|
||||||
mutex_init(&master->bus_lock_mutex);
|
mutex_init(&master->bus_lock_mutex);
|
||||||
|
mutex_init(&master->io_mutex);
|
||||||
master->bus_lock_flag = 0;
|
master->bus_lock_flag = 0;
|
||||||
init_completion(&master->xfer_completion);
|
init_completion(&master->xfer_completion);
|
||||||
if (!master->max_dma_len)
|
if (!master->max_dma_len)
|
||||||
|
@ -2738,6 +2749,7 @@ int spi_flash_read(struct spi_device *spi,
|
||||||
|
|
||||||
{
|
{
|
||||||
struct spi_master *master = spi->master;
|
struct spi_master *master = spi->master;
|
||||||
|
struct device *rx_dev = NULL;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
if ((msg->opcode_nbits == SPI_NBITS_DUAL ||
|
if ((msg->opcode_nbits == SPI_NBITS_DUAL ||
|
||||||
|
@ -2763,9 +2775,24 @@ int spi_flash_read(struct spi_device *spi,
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mutex_lock(&master->bus_lock_mutex);
|
mutex_lock(&master->bus_lock_mutex);
|
||||||
|
mutex_lock(&master->io_mutex);
|
||||||
|
if (master->dma_rx) {
|
||||||
|
rx_dev = master->dma_rx->device->dev;
|
||||||
|
ret = spi_map_buf(master, rx_dev, &msg->rx_sg,
|
||||||
|
msg->buf, msg->len,
|
||||||
|
DMA_FROM_DEVICE);
|
||||||
|
if (!ret)
|
||||||
|
msg->cur_msg_mapped = true;
|
||||||
|
}
|
||||||
ret = master->spi_flash_read(spi, msg);
|
ret = master->spi_flash_read(spi, msg);
|
||||||
|
if (msg->cur_msg_mapped)
|
||||||
|
spi_unmap_buf(master, rx_dev, &msg->rx_sg,
|
||||||
|
DMA_FROM_DEVICE);
|
||||||
|
mutex_unlock(&master->io_mutex);
|
||||||
mutex_unlock(&master->bus_lock_mutex);
|
mutex_unlock(&master->bus_lock_mutex);
|
||||||
|
|
||||||
if (master->auto_runtime_pm)
|
if (master->auto_runtime_pm)
|
||||||
pm_runtime_put(master->dev.parent);
|
pm_runtime_put(master->dev.parent);
|
||||||
|
|
||||||
|
@ -2785,8 +2812,7 @@ static void spi_complete(void *arg)
|
||||||
complete(arg);
|
complete(arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int __spi_sync(struct spi_device *spi, struct spi_message *message,
|
static int __spi_sync(struct spi_device *spi, struct spi_message *message)
|
||||||
int bus_locked)
|
|
||||||
{
|
{
|
||||||
DECLARE_COMPLETION_ONSTACK(done);
|
DECLARE_COMPLETION_ONSTACK(done);
|
||||||
int status;
|
int status;
|
||||||
|
@ -2804,9 +2830,6 @@ static int __spi_sync(struct spi_device *spi, struct spi_message *message,
|
||||||
SPI_STATISTICS_INCREMENT_FIELD(&master->statistics, spi_sync);
|
SPI_STATISTICS_INCREMENT_FIELD(&master->statistics, spi_sync);
|
||||||
SPI_STATISTICS_INCREMENT_FIELD(&spi->statistics, spi_sync);
|
SPI_STATISTICS_INCREMENT_FIELD(&spi->statistics, spi_sync);
|
||||||
|
|
||||||
if (!bus_locked)
|
|
||||||
mutex_lock(&master->bus_lock_mutex);
|
|
||||||
|
|
||||||
/* If we're not using the legacy transfer method then we will
|
/* If we're not using the legacy transfer method then we will
|
||||||
* try to transfer in the calling context so special case.
|
* try to transfer in the calling context so special case.
|
||||||
* This code would be less tricky if we could remove the
|
* This code would be less tricky if we could remove the
|
||||||
|
@ -2824,9 +2847,6 @@ static int __spi_sync(struct spi_device *spi, struct spi_message *message,
|
||||||
status = spi_async_locked(spi, message);
|
status = spi_async_locked(spi, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!bus_locked)
|
|
||||||
mutex_unlock(&master->bus_lock_mutex);
|
|
||||||
|
|
||||||
if (status == 0) {
|
if (status == 0) {
|
||||||
/* Push out the messages in the calling context if we
|
/* Push out the messages in the calling context if we
|
||||||
* can.
|
* can.
|
||||||
|
@ -2836,7 +2856,7 @@ static int __spi_sync(struct spi_device *spi, struct spi_message *message,
|
||||||
spi_sync_immediate);
|
spi_sync_immediate);
|
||||||
SPI_STATISTICS_INCREMENT_FIELD(&spi->statistics,
|
SPI_STATISTICS_INCREMENT_FIELD(&spi->statistics,
|
||||||
spi_sync_immediate);
|
spi_sync_immediate);
|
||||||
__spi_pump_messages(master, false, bus_locked);
|
__spi_pump_messages(master, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
wait_for_completion(&done);
|
wait_for_completion(&done);
|
||||||
|
@ -2869,7 +2889,13 @@ static int __spi_sync(struct spi_device *spi, struct spi_message *message,
|
||||||
*/
|
*/
|
||||||
int spi_sync(struct spi_device *spi, struct spi_message *message)
|
int spi_sync(struct spi_device *spi, struct spi_message *message)
|
||||||
{
|
{
|
||||||
return __spi_sync(spi, message, spi->master->bus_lock_flag);
|
int ret;
|
||||||
|
|
||||||
|
mutex_lock(&spi->master->bus_lock_mutex);
|
||||||
|
ret = __spi_sync(spi, message);
|
||||||
|
mutex_unlock(&spi->master->bus_lock_mutex);
|
||||||
|
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(spi_sync);
|
EXPORT_SYMBOL_GPL(spi_sync);
|
||||||
|
|
||||||
|
@ -2891,7 +2917,7 @@ EXPORT_SYMBOL_GPL(spi_sync);
|
||||||
*/
|
*/
|
||||||
int spi_sync_locked(struct spi_device *spi, struct spi_message *message)
|
int spi_sync_locked(struct spi_device *spi, struct spi_message *message)
|
||||||
{
|
{
|
||||||
return __spi_sync(spi, message, 1);
|
return __spi_sync(spi, message);
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(spi_sync_locked);
|
EXPORT_SYMBOL_GPL(spi_sync_locked);
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,7 @@
|
||||||
#include <linux/compat.h>
|
#include <linux/compat.h>
|
||||||
#include <linux/of.h>
|
#include <linux/of.h>
|
||||||
#include <linux/of_device.h>
|
#include <linux/of_device.h>
|
||||||
|
#include <linux/acpi.h>
|
||||||
|
|
||||||
#include <linux/spi/spi.h>
|
#include <linux/spi/spi.h>
|
||||||
#include <linux/spi/spidev.h>
|
#include <linux/spi/spidev.h>
|
||||||
|
@ -700,6 +701,43 @@ static const struct of_device_id spidev_dt_ids[] = {
|
||||||
MODULE_DEVICE_TABLE(of, spidev_dt_ids);
|
MODULE_DEVICE_TABLE(of, spidev_dt_ids);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef CONFIG_ACPI
|
||||||
|
|
||||||
|
/* Dummy SPI devices not to be used in production systems */
|
||||||
|
#define SPIDEV_ACPI_DUMMY 1
|
||||||
|
|
||||||
|
static const struct acpi_device_id spidev_acpi_ids[] = {
|
||||||
|
/*
|
||||||
|
* The ACPI SPT000* devices are only meant for development and
|
||||||
|
* testing. Systems used in production should have a proper ACPI
|
||||||
|
* description of the connected peripheral and they should also use
|
||||||
|
* a proper driver instead of poking directly to the SPI bus.
|
||||||
|
*/
|
||||||
|
{ "SPT0001", SPIDEV_ACPI_DUMMY },
|
||||||
|
{ "SPT0002", SPIDEV_ACPI_DUMMY },
|
||||||
|
{ "SPT0003", SPIDEV_ACPI_DUMMY },
|
||||||
|
{},
|
||||||
|
};
|
||||||
|
MODULE_DEVICE_TABLE(acpi, spidev_acpi_ids);
|
||||||
|
|
||||||
|
static void spidev_probe_acpi(struct spi_device *spi)
|
||||||
|
{
|
||||||
|
const struct acpi_device_id *id;
|
||||||
|
|
||||||
|
if (!has_acpi_companion(&spi->dev))
|
||||||
|
return;
|
||||||
|
|
||||||
|
id = acpi_match_device(spidev_acpi_ids, &spi->dev);
|
||||||
|
if (WARN_ON(!id))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (id->driver_data == SPIDEV_ACPI_DUMMY)
|
||||||
|
dev_warn(&spi->dev, "do not use this driver in production systems!\n");
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
static inline void spidev_probe_acpi(struct spi_device *spi) {}
|
||||||
|
#endif
|
||||||
|
|
||||||
/*-------------------------------------------------------------------------*/
|
/*-------------------------------------------------------------------------*/
|
||||||
|
|
||||||
static int spidev_probe(struct spi_device *spi)
|
static int spidev_probe(struct spi_device *spi)
|
||||||
|
@ -719,6 +757,8 @@ static int spidev_probe(struct spi_device *spi)
|
||||||
!of_match_device(spidev_dt_ids, &spi->dev));
|
!of_match_device(spidev_dt_ids, &spi->dev));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
spidev_probe_acpi(spi);
|
||||||
|
|
||||||
/* Allocate driver data */
|
/* Allocate driver data */
|
||||||
spidev = kzalloc(sizeof(*spidev), GFP_KERNEL);
|
spidev = kzalloc(sizeof(*spidev), GFP_KERNEL);
|
||||||
if (!spidev)
|
if (!spidev)
|
||||||
|
@ -789,6 +829,7 @@ static struct spi_driver spidev_spi_driver = {
|
||||||
.driver = {
|
.driver = {
|
||||||
.name = "spidev",
|
.name = "spidev",
|
||||||
.of_match_table = of_match_ptr(spidev_dt_ids),
|
.of_match_table = of_match_ptr(spidev_dt_ids),
|
||||||
|
.acpi_match_table = ACPI_PTR(spidev_acpi_ids),
|
||||||
},
|
},
|
||||||
.probe = spidev_probe,
|
.probe = spidev_probe,
|
||||||
.remove = spidev_remove,
|
.remove = spidev_remove,
|
||||||
|
|
|
@ -38,6 +38,7 @@ struct s3c64xx_spi_csinfo {
|
||||||
struct s3c64xx_spi_info {
|
struct s3c64xx_spi_info {
|
||||||
int src_clk_nr;
|
int src_clk_nr;
|
||||||
int num_cs;
|
int num_cs;
|
||||||
|
bool no_cs;
|
||||||
int (*cfg_gpio)(void);
|
int (*cfg_gpio)(void);
|
||||||
dma_filter_fn filter;
|
dma_filter_fn filter;
|
||||||
void *dma_tx;
|
void *dma_tx;
|
||||||
|
|
|
@ -312,8 +312,9 @@ static inline void spi_unregister_driver(struct spi_driver *sdrv)
|
||||||
* @flags: other constraints relevant to this driver
|
* @flags: other constraints relevant to this driver
|
||||||
* @max_transfer_size: function that returns the max transfer size for
|
* @max_transfer_size: function that returns the max transfer size for
|
||||||
* a &spi_device; may be %NULL, so the default %SIZE_MAX will be used.
|
* a &spi_device; may be %NULL, so the default %SIZE_MAX will be used.
|
||||||
|
* @io_mutex: mutex for physical bus access
|
||||||
* @bus_lock_spinlock: spinlock for SPI bus locking
|
* @bus_lock_spinlock: spinlock for SPI bus locking
|
||||||
* @bus_lock_mutex: mutex for SPI bus locking
|
* @bus_lock_mutex: mutex for exclusion of multiple callers
|
||||||
* @bus_lock_flag: indicates that the SPI bus is locked for exclusive use
|
* @bus_lock_flag: indicates that the SPI bus is locked for exclusive use
|
||||||
* @setup: updates the device mode and clocking records used by a
|
* @setup: updates the device mode and clocking records used by a
|
||||||
* device's SPI controller; protocol code may call this. This
|
* device's SPI controller; protocol code may call this. This
|
||||||
|
@ -446,6 +447,9 @@ struct spi_master {
|
||||||
*/
|
*/
|
||||||
size_t (*max_transfer_size)(struct spi_device *spi);
|
size_t (*max_transfer_size)(struct spi_device *spi);
|
||||||
|
|
||||||
|
/* I/O mutex */
|
||||||
|
struct mutex io_mutex;
|
||||||
|
|
||||||
/* lock and mutex for SPI bus locking */
|
/* lock and mutex for SPI bus locking */
|
||||||
spinlock_t bus_lock_spinlock;
|
spinlock_t bus_lock_spinlock;
|
||||||
struct mutex bus_lock_mutex;
|
struct mutex bus_lock_mutex;
|
||||||
|
@ -1143,6 +1147,8 @@ static inline ssize_t spi_w8r16be(struct spi_device *spi, u8 cmd)
|
||||||
* @opcode_nbits: number of lines to send opcode
|
* @opcode_nbits: number of lines to send opcode
|
||||||
* @addr_nbits: number of lines to send address
|
* @addr_nbits: number of lines to send address
|
||||||
* @data_nbits: number of lines for data
|
* @data_nbits: number of lines for data
|
||||||
|
* @rx_sg: Scatterlist for receive data read from flash
|
||||||
|
* @cur_msg_mapped: message has been mapped for DMA
|
||||||
*/
|
*/
|
||||||
struct spi_flash_read_message {
|
struct spi_flash_read_message {
|
||||||
void *buf;
|
void *buf;
|
||||||
|
@ -1155,6 +1161,8 @@ struct spi_flash_read_message {
|
||||||
u8 opcode_nbits;
|
u8 opcode_nbits;
|
||||||
u8 addr_nbits;
|
u8 addr_nbits;
|
||||||
u8 data_nbits;
|
u8 data_nbits;
|
||||||
|
struct sg_table rx_sg;
|
||||||
|
bool cur_msg_mapped;
|
||||||
};
|
};
|
||||||
|
|
||||||
/* SPI core interface for flash read support */
|
/* SPI core interface for flash read support */
|
||||||
|
|
Загрузка…
Ссылка в новой задаче