spi: add multi-lane support

Merge series from David Lechner <dlechner@baylibre.com>:

This series is adding support for SPI controllers and peripherals that
have multiple SPI data lanes (data lanes being independent sets of
SDI/SDO lines, each with their own serializer/deserializer).

This series covers this specific use case:

+--------------+    +---------+
| SPI          |    | SPI     |
| Controller   |    | ADC     |
|              |    |         |
|          CS0 |--->| CS      |
|         SCLK |--->| SCLK    |
|          SDO |--->| SDI     |
|         SDI0 |<---| SDOA    |
|         SDI1 |<---| SDOB    |
|         SDI2 |<---| SDOC    |
|         SDI3 |<---| SDOD    |
+--------------+     +--------+

The ADC is a simultaneous sampling ADC that can convert 4 samples at the
same time. It has 4 data output lines (SDOA-D) that each contain the
data of one of the 4 channels. So it requires a SPI controller with 4
separate deserializers in order to receive all of the information at the
same time.

This should also work for the use case in [1] as well. (Some of the
patches in this series were already submitted there). In that case the
SPI controller is used kind of like it is two separate SPI controllers,
each with its own chip select, clock, and data lines.

[1]: https://lore.kernel.org/linux-spi/20250616220054.3968946-1-sean.anderson@linux.dev/

The DT bindings are a fairly straight-forward mapping of which pins on
the peripheral are connected to which pins on the controller. The SPI
core code parses this and makes the information available to drivers.
When a peripheral driver sees that multiple data lanes are wired up, it
can chose to use them when sending messages.

The SPI message API is a bit higher-level than just specifying the
number of data lines for a SPI transfer though. I did some research on
other SPI controllers that have this feature. They tend to be the kind
meant for connecting to two flash memory chips at the same time but can
be used more generically as well. They generally have the option to
either use one lane at a time (Sean's use case), or can mirror the same
data on multiple lanes (no users of this yet) or can perform striping
of a single data FIFO/DMA stream to/from the two lanes (our use case).

For now, the API assumes that if you want to do mirror/striping, then
you want to use all available data lanes. Otherwise, it just uses the
first data lane for "normal" SPI transfers.
This commit is contained in:
Mark Brown 2026-02-02 22:03:20 +00:00
commit 8ea39d960c
No known key found for this signature in database
GPG key ID: 24D68B725D5487D0
14 changed files with 641 additions and 27 deletions

View file

@ -34,8 +34,9 @@ properties:
spi-cpol: true
spi-rx-bus-width:
minimum: 0
maximum: 1
items:
minimum: 0
maximum: 1
dc-gpios:
maxItems: 1

View file

@ -37,7 +37,15 @@ properties:
maximum: 102040816
spi-rx-bus-width:
enum: [1, 2, 4]
maxItems: 2
# all lanes must have the same width
oneOf:
- contains:
const: 1
- contains:
const: 2
- contains:
const: 4
vdd-5v-supply: true
vdd-1v8-supply: true
@ -88,6 +96,18 @@ oneOf:
unevaluatedProperties: false
allOf:
- if:
properties:
compatible:
enum:
- adi,ad4030-24
- adi,ad4032-24
then:
properties:
spi-rx-bus-width:
maxItems: 1
examples:
- |
#include <dt-bindings/gpio/gpio.h>
@ -108,3 +128,23 @@ examples:
reset-gpios = <&gpio0 1 GPIO_ACTIVE_LOW>;
};
};
- |
#include <dt-bindings/gpio/gpio.h>
spi {
#address-cells = <1>;
#size-cells = <0>;
adc@0 {
compatible = "adi,ad4630-24";
reg = <0>;
spi-max-frequency = <80000000>;
spi-rx-bus-width = <4>, <4>;
vdd-5v-supply = <&supply_5V>;
vdd-1v8-supply = <&supply_1_8V>;
vio-supply = <&supply_1_8V>;
ref-supply = <&supply_5V>;
cnv-gpios = <&gpio0 0 GPIO_ACTIVE_HIGH>;
reset-gpios = <&gpio0 1 GPIO_ACTIVE_LOW>;
};
};

View file

@ -38,8 +38,9 @@ properties:
spi-cpha: true
spi-rx-bus-width:
minimum: 1
maximum: 4
items:
minimum: 1
maximum: 4
avdd-supply:
description: Analog power supply.

View file

@ -70,6 +70,21 @@ required:
unevaluatedProperties: false
patternProperties:
"^.*@[0-9a-f]+":
type: object
properties:
spi-rx-bus-width:
maxItems: 8
items:
enum: [0, 1]
spi-tx-bus-width:
maxItems: 8
items:
enum: [0, 1]
examples:
- |
spi@44a00000 {

View file

@ -55,10 +55,12 @@ patternProperties:
maximum: 4
spi-rx-bus-width:
const: 1
items:
- const: 1
spi-tx-bus-width:
const: 1
items:
- const: 1
required:
- compatible

View file

@ -81,10 +81,12 @@ patternProperties:
maximum: 4
spi-rx-bus-width:
const: 1
items:
- const: 1
spi-tx-bus-width:
const: 1
items:
- const: 1
required:
- compatible

View file

@ -45,10 +45,12 @@ patternProperties:
properties:
spi-rx-bus-width:
enum: [1, 4]
items:
- enum: [1, 4]
spi-tx-bus-width:
enum: [1, 4]
items:
- enum: [1, 4]
allOf:
- $ref: spi-controller.yaml#

View file

@ -54,10 +54,12 @@ patternProperties:
properties:
spi-rx-bus-width:
enum: [1, 2, 4]
items:
- enum: [1, 2, 4]
spi-tx-bus-width:
enum: [1, 2, 4]
items:
- enum: [1, 2, 4]
required:
- compatible

View file

@ -64,9 +64,23 @@ properties:
description:
Bus width to the SPI bus used for read transfers.
If 0 is provided, then no RX will be possible on this device.
$ref: /schemas/types.yaml#/definitions/uint32
enum: [0, 1, 2, 4, 8]
default: 1
Some SPI peripherals and controllers may have multiple data lanes for
receiving two or more words at the same time. If this is the case, each
index in the array represents the lane on both the SPI peripheral and
controller. Additional mapping properties may be needed if a lane is
skipped on either side.
$ref: /schemas/types.yaml#/definitions/uint32-array
items:
enum: [0, 1, 2, 4, 8]
default: [1]
spi-rx-lane-map:
description: Mapping of peripheral SDO lanes to controller SDI lanes.
Each index in the array represents a peripheral SDO lane, and the value
at that index represents the corresponding controller SDI lane.
$ref: /schemas/types.yaml#/definitions/uint32-array
default: [0, 1, 2, 3, 4, 5, 6, 7]
spi-rx-delay-us:
description:
@ -81,9 +95,23 @@ properties:
description:
Bus width to the SPI bus used for write transfers.
If 0 is provided, then no TX will be possible on this device.
$ref: /schemas/types.yaml#/definitions/uint32
enum: [0, 1, 2, 4, 8]
default: 1
Some SPI peripherals and controllers may have multiple data lanes for
transmitting two or more words at the same time. If this is the case, each
index in the array represents the lane on both the SPI peripheral and
controller. Additional mapping properties may be needed if a lane is
skipped on either side.
$ref: /schemas/types.yaml#/definitions/uint32-array
items:
enum: [0, 1, 2, 4, 8]
default: [1]
spi-tx-lane-map:
description: Mapping of peripheral SDI lanes to controller SDO lanes.
Each index in the array represents a peripheral SDI lane, and the value
at that index represents the corresponding controller SDO lane.
$ref: /schemas/types.yaml#/definitions/uint32-array
default: [0, 1, 2, 3, 4, 5, 6, 7]
spi-tx-delay-us:
description:

View file

@ -9,6 +9,7 @@ Serial Peripheral Interface (SPI)
spi-summary
spidev
multiple-data-lanes
butterfly
spi-lm70llp
spi-sc18is602

View file

@ -0,0 +1,217 @@
====================================
SPI devices with multiple data lanes
====================================
Some specialized SPI controllers and peripherals support multiple data lanes
that allow reading more than one word at a time in parallel. This is different
from dual/quad/octal SPI where multiple bits of a single word are transferred
simultaneously.
For example, controllers that support parallel flash memories have this feature
as do some simultaneous-sampling ADCs where each channel has its own data lane.
---------------------
Describing the wiring
---------------------
The ``spi-tx-bus-width`` and ``spi-rx-bus-width`` properties in the devicetree
are used to describe how many data lanes are connected between the controller
and how wide each lane is. The number of items in the array indicates how many
lanes there are, and the value of each item indicates how many bits wide that
lane is.
For example, a dual-simultaneous-sampling ADC with two 4-bit lanes might be
wired up like this::
+--------------+ +----------+
| SPI | | AD4630 |
| Controller | | ADC |
| | | |
| CS0 |--->| CS |
| SCK |--->| SCK |
| SDO |--->| SDI |
| | | |
| SDIA0 |<---| SDOA0 |
| SDIA1 |<---| SDOA1 |
| SDIA2 |<---| SDOA2 |
| SDIA3 |<---| SDOA3 |
| | | |
| SDIB0 |<---| SDOB0 |
| SDIB1 |<---| SDOB1 |
| SDIB2 |<---| SDOB2 |
| SDIB3 |<---| SDOB3 |
| | | |
+--------------+ +----------+
It is described in a devicetree like this::
spi {
compatible = "my,spi-controller";
...
adc@0 {
compatible = "adi,ad4630";
reg = <0>;
...
spi-rx-bus-width = <4>, <4>; /* 2 lanes of 4 bits each */
...
};
};
In most cases, lanes will be wired up symmetrically (A to A, B to B, etc). If
this isn't the case, extra ``spi-rx-lane-map`` and ``spi-tx-lane-map``
properties are needed to provide a mapping between controller lanes and the
physical lane wires.
Here is an example where a multi-lane SPI controller has each lane wired to
separate single-lane peripherals::
+--------------+ +----------+
| SPI | | Thing 1 |
| Controller | | |
| | | |
| CS0 |--->| CS |
| SDO0 |--->| SDI |
| SDI0 |<---| SDO |
| SCLK0 |--->| SCLK |
| | | |
| | +----------+
| |
| | +----------+
| | | Thing 2 |
| | | |
| CS1 |--->| CS |
| SDO1 |--->| SDI |
| SDI1 |<---| SDO |
| SCLK1 |--->| SCLK |
| | | |
+--------------+ +----------+
This is described in a devicetree like this::
spi {
compatible = "my,spi-controller";
...
thing1@0 {
compatible = "my,thing1";
reg = <0>;
...
};
thing2@1 {
compatible = "my,thing2";
reg = <1>;
...
spi-tx-lane-map = <1>; /* lane 0 is not used, lane 1 is used for tx wire */
spi-rx-lane-map = <1>; /* lane 0 is not used, lane 1 is used for rx wire */
...
};
};
The default values of ``spi-rx-bus-width`` and ``spi-tx-bus-width`` are ``<1>``,
so these properties can still be omitted even when ``spi-rx-lane-map`` and
``spi-tx-lane-map`` are used.
----------------------------
Usage in a peripheral driver
----------------------------
These types of SPI controllers generally do not support arbitrary use of the
multiple lanes. Instead, they operate in one of a few defined modes. Peripheral
drivers should set the :c:type:`struct spi_transfer.multi_lane_mode <spi_transfer>`
field to indicate which mode they want to use for a given transfer.
The possible values for this field have the following semantics:
- :c:macro:`SPI_MULTI_BUS_MODE_SINGLE`: Only use the first lane. Other lanes are
ignored. This means that it is operating just like a conventional SPI
peripheral. This is the default, so it does not need to be explicitly set.
Example::
tx_buf[0] = 0x88;
struct spi_transfer xfer = {
.tx_buf = tx_buf,
.len = 1,
};
spi_sync_transfer(spi, &xfer, 1);
Assuming the controller is sending the MSB first, the sequence of bits
sent over the tx wire would be (right-most bit is sent first)::
controller > data bits > peripheral
---------- ---------------- ----------
SDO 0 0-0-0-1-0-0-0-1 SDI 0
- :c:macro:`SPI_MULTI_BUS_MODE_MIRROR`: Send a single data word over all of the
lanes at the same time. This only makes sense for writes and not
for reads.
Example::
tx_buf[0] = 0x88;
struct spi_transfer xfer = {
.tx_buf = tx_buf,
.len = 1,
.multi_lane_mode = SPI_MULTI_BUS_MODE_MIRROR,
};
spi_sync_transfer(spi, &xfer, 1);
The data is mirrored on each tx wire::
controller > data bits > peripheral
---------- ---------------- ----------
SDO 0 0-0-0-1-0-0-0-1 SDI 0
SDO 1 0-0-0-1-0-0-0-1 SDI 1
- :c:macro:`SPI_MULTI_BUS_MODE_STRIPE`: Send or receive two different data words
at the same time, one on each lane. This means that the buffer needs to be
sized to hold data for all lanes. Data is interleaved in the buffer, with
the first word corresponding to lane 0, the second to lane 1, and so on.
Once the last lane is used, the next word in the buffer corresponds to lane
0 again. Accordingly, the buffer size must be a multiple of the number of
lanes. This mode works for both reads and writes.
Example::
struct spi_transfer xfer = {
.rx_buf = rx_buf,
.len = 2,
.multi_lane_mode = SPI_MULTI_BUS_MODE_STRIPE,
};
spi_sync_transfer(spi, &xfer, 1);
Each rx wire has a different data word sent simultaneously::
controller < data bits < peripheral
---------- ---------------- ----------
SDI 0 0-0-0-1-0-0-0-1 SDO 0
SDI 1 1-0-0-0-1-0-0-0 SDO 1
After the transfer, ``rx_buf[0] == 0x11`` (word from SDO 0) and
``rx_buf[1] == 0x88`` (word from SDO 1).
-----------------------------
SPI controller driver support
-----------------------------
To support multiple data lanes, SPI controller drivers need to set
:c:type:`struct spi_controller.num_data_lanes <spi_controller>` to a value
greater than 1.
Then the part of the driver that handles SPI transfers needs to check the
:c:type:`struct spi_transfer.multi_lane_mode <spi_transfer>` field and implement
the appropriate behavior for each supported mode and return an error for
unsupported modes.
The core SPI code should handle the rest.

View file

@ -23,6 +23,9 @@
#include <linux/spi/spi.h>
#include <trace/events/spi.h>
#define SPI_ENGINE_REG_DATA_WIDTH 0x0C
#define SPI_ENGINE_REG_DATA_WIDTH_NUM_OF_SDIO_MASK GENMASK(23, 16)
#define SPI_ENGINE_REG_DATA_WIDTH_MASK GENMASK(15, 0)
#define SPI_ENGINE_REG_OFFLOAD_MEM_ADDR_WIDTH 0x10
#define SPI_ENGINE_REG_RESET 0x40
@ -75,6 +78,8 @@
#define SPI_ENGINE_CMD_REG_CLK_DIV 0x0
#define SPI_ENGINE_CMD_REG_CONFIG 0x1
#define SPI_ENGINE_CMD_REG_XFER_BITS 0x2
#define SPI_ENGINE_CMD_REG_SDI_MASK 0x3
#define SPI_ENGINE_CMD_REG_SDO_MASK 0x4
#define SPI_ENGINE_MISC_SYNC 0x0
#define SPI_ENGINE_MISC_SLEEP 0x1
@ -105,6 +110,10 @@
#define SPI_ENGINE_OFFLOAD_CMD_FIFO_SIZE 16
#define SPI_ENGINE_OFFLOAD_SDO_FIFO_SIZE 16
/* Extending SPI_MULTI_LANE_MODE values for optimizing messages. */
#define SPI_ENGINE_MULTI_BUS_MODE_UNKNOWN -1
#define SPI_ENGINE_MULTI_BUS_MODE_CONFLICTING -2
struct spi_engine_program {
unsigned int length;
uint16_t instructions[] __counted_by(length);
@ -142,6 +151,11 @@ struct spi_engine_offload {
unsigned long flags;
unsigned int offload_num;
unsigned int spi_mode_config;
unsigned int multi_lane_mode;
u8 rx_primary_lane_mask;
u8 tx_primary_lane_mask;
u8 rx_all_lanes_mask;
u8 tx_all_lanes_mask;
u8 bits_per_word;
};
@ -165,6 +179,25 @@ struct spi_engine {
bool offload_requires_sync;
};
static void spi_engine_primary_lane_flag(struct spi_device *spi,
u8 *rx_lane_flags, u8 *tx_lane_flags)
{
*rx_lane_flags = BIT(spi->rx_lane_map[0]);
*tx_lane_flags = BIT(spi->tx_lane_map[0]);
}
static void spi_engine_all_lanes_flags(struct spi_device *spi,
u8 *rx_lane_flags, u8 *tx_lane_flags)
{
int i;
for (i = 0; i < spi->num_rx_lanes; i++)
*rx_lane_flags |= BIT(spi->rx_lane_map[i]);
for (i = 0; i < spi->num_tx_lanes; i++)
*tx_lane_flags |= BIT(spi->tx_lane_map[i]);
}
static void spi_engine_program_add_cmd(struct spi_engine_program *p,
bool dry, uint16_t cmd)
{
@ -193,7 +226,7 @@ static unsigned int spi_engine_get_config(struct spi_device *spi)
}
static void spi_engine_gen_xfer(struct spi_engine_program *p, bool dry,
struct spi_transfer *xfer)
struct spi_transfer *xfer, u32 num_lanes)
{
unsigned int len;
@ -204,6 +237,9 @@ static void spi_engine_gen_xfer(struct spi_engine_program *p, bool dry,
else
len = xfer->len / 4;
if (xfer->multi_lane_mode == SPI_MULTI_LANE_MODE_STRIPE)
len /= num_lanes;
while (len) {
unsigned int n = min(len, 256U);
unsigned int flags = 0;
@ -269,6 +305,7 @@ static int spi_engine_precompile_message(struct spi_message *msg)
{
unsigned int clk_div, max_hz = msg->spi->controller->max_speed_hz;
struct spi_transfer *xfer;
int multi_lane_mode = SPI_ENGINE_MULTI_BUS_MODE_UNKNOWN;
u8 min_bits_per_word = U8_MAX;
u8 max_bits_per_word = 0;
@ -284,6 +321,24 @@ static int spi_engine_precompile_message(struct spi_message *msg)
min_bits_per_word = min(min_bits_per_word, xfer->bits_per_word);
max_bits_per_word = max(max_bits_per_word, xfer->bits_per_word);
}
if (xfer->rx_buf || xfer->offload_flags & SPI_OFFLOAD_XFER_RX_STREAM ||
xfer->tx_buf || xfer->offload_flags & SPI_OFFLOAD_XFER_TX_STREAM) {
switch (xfer->multi_lane_mode) {
case SPI_MULTI_LANE_MODE_SINGLE:
case SPI_MULTI_LANE_MODE_STRIPE:
break;
default:
/* Other modes, like mirror not supported */
return -EINVAL;
}
/* If all xfers have the same multi-lane mode, we can optimize. */
if (multi_lane_mode == SPI_ENGINE_MULTI_BUS_MODE_UNKNOWN)
multi_lane_mode = xfer->multi_lane_mode;
else if (multi_lane_mode != xfer->multi_lane_mode)
multi_lane_mode = SPI_ENGINE_MULTI_BUS_MODE_CONFLICTING;
}
}
/*
@ -297,6 +352,14 @@ static int spi_engine_precompile_message(struct spi_message *msg)
priv->bits_per_word = min_bits_per_word;
else
priv->bits_per_word = 0;
priv->multi_lane_mode = multi_lane_mode;
spi_engine_primary_lane_flag(msg->spi,
&priv->rx_primary_lane_mask,
&priv->tx_primary_lane_mask);
spi_engine_all_lanes_flags(msg->spi,
&priv->rx_all_lanes_mask,
&priv->tx_all_lanes_mask);
}
return 0;
@ -310,6 +373,7 @@ static void spi_engine_compile_message(struct spi_message *msg, bool dry,
struct spi_engine_offload *priv;
struct spi_transfer *xfer;
int clk_div, new_clk_div, inst_ns;
int prev_multi_lane_mode = SPI_MULTI_LANE_MODE_SINGLE;
bool keep_cs = false;
u8 bits_per_word = 0;
@ -334,6 +398,7 @@ static void spi_engine_compile_message(struct spi_message *msg, bool dry,
* in the same way.
*/
bits_per_word = priv->bits_per_word;
prev_multi_lane_mode = priv->multi_lane_mode;
} else {
spi_engine_program_add_cmd(p, dry,
SPI_ENGINE_CMD_WRITE(SPI_ENGINE_CMD_REG_CONFIG,
@ -344,6 +409,28 @@ static void spi_engine_compile_message(struct spi_message *msg, bool dry,
spi_engine_gen_cs(p, dry, spi, !xfer->cs_off);
list_for_each_entry(xfer, &msg->transfers, transfer_list) {
if (xfer->rx_buf || xfer->offload_flags & SPI_OFFLOAD_XFER_RX_STREAM ||
xfer->tx_buf || xfer->offload_flags & SPI_OFFLOAD_XFER_TX_STREAM) {
if (xfer->multi_lane_mode != prev_multi_lane_mode) {
u8 tx_lane_flags, rx_lane_flags;
if (xfer->multi_lane_mode == SPI_MULTI_LANE_MODE_STRIPE)
spi_engine_all_lanes_flags(spi, &rx_lane_flags,
&tx_lane_flags);
else
spi_engine_primary_lane_flag(spi, &rx_lane_flags,
&tx_lane_flags);
spi_engine_program_add_cmd(p, dry,
SPI_ENGINE_CMD_WRITE(SPI_ENGINE_CMD_REG_SDI_MASK,
rx_lane_flags));
spi_engine_program_add_cmd(p, dry,
SPI_ENGINE_CMD_WRITE(SPI_ENGINE_CMD_REG_SDO_MASK,
tx_lane_flags));
}
prev_multi_lane_mode = xfer->multi_lane_mode;
}
new_clk_div = host->max_speed_hz / xfer->effective_speed_hz;
if (new_clk_div != clk_div) {
clk_div = new_clk_div;
@ -360,7 +447,7 @@ static void spi_engine_compile_message(struct spi_message *msg, bool dry,
bits_per_word));
}
spi_engine_gen_xfer(p, dry, xfer);
spi_engine_gen_xfer(p, dry, xfer, spi->num_rx_lanes);
spi_engine_gen_sleep(p, dry, spi_delay_to_ns(&xfer->delay, xfer),
inst_ns, xfer->effective_speed_hz);
@ -394,6 +481,19 @@ static void spi_engine_compile_message(struct spi_message *msg, bool dry,
if (clk_div != 1)
spi_engine_program_add_cmd(p, dry,
SPI_ENGINE_CMD_WRITE(SPI_ENGINE_CMD_REG_CLK_DIV, 0));
/* Restore single lane mode unless offload disable will restore it later. */
if (prev_multi_lane_mode == SPI_MULTI_LANE_MODE_STRIPE &&
(!msg->offload || priv->multi_lane_mode != SPI_MULTI_LANE_MODE_STRIPE)) {
u8 rx_lane_flags, tx_lane_flags;
spi_engine_primary_lane_flag(spi, &rx_lane_flags, &tx_lane_flags);
spi_engine_program_add_cmd(p, dry,
SPI_ENGINE_CMD_WRITE(SPI_ENGINE_CMD_REG_SDI_MASK, rx_lane_flags));
spi_engine_program_add_cmd(p, dry,
SPI_ENGINE_CMD_WRITE(SPI_ENGINE_CMD_REG_SDO_MASK, tx_lane_flags));
}
}
static void spi_engine_xfer_next(struct spi_message *msg,
@ -799,6 +899,19 @@ static int spi_engine_setup(struct spi_device *device)
writel_relaxed(SPI_ENGINE_CMD_CS_INV(spi_engine->cs_inv),
spi_engine->base + SPI_ENGINE_REG_CMD_FIFO);
if (host->num_data_lanes > 1) {
u8 rx_lane_flags, tx_lane_flags;
spi_engine_primary_lane_flag(device, &rx_lane_flags, &tx_lane_flags);
writel_relaxed(SPI_ENGINE_CMD_WRITE(SPI_ENGINE_CMD_REG_SDI_MASK,
rx_lane_flags),
spi_engine->base + SPI_ENGINE_REG_CMD_FIFO);
writel_relaxed(SPI_ENGINE_CMD_WRITE(SPI_ENGINE_CMD_REG_SDO_MASK,
tx_lane_flags),
spi_engine->base + SPI_ENGINE_REG_CMD_FIFO);
}
/*
* In addition to setting the flags, we have to do a CS assert command
* to make the new setting actually take effect.
@ -902,6 +1015,15 @@ static int spi_engine_trigger_enable(struct spi_offload *offload)
priv->bits_per_word),
spi_engine->base + SPI_ENGINE_REG_CMD_FIFO);
if (priv->multi_lane_mode == SPI_MULTI_LANE_MODE_STRIPE) {
writel_relaxed(SPI_ENGINE_CMD_WRITE(SPI_ENGINE_CMD_REG_SDI_MASK,
priv->rx_all_lanes_mask),
spi_engine->base + SPI_ENGINE_REG_CMD_FIFO);
writel_relaxed(SPI_ENGINE_CMD_WRITE(SPI_ENGINE_CMD_REG_SDO_MASK,
priv->tx_all_lanes_mask),
spi_engine->base + SPI_ENGINE_REG_CMD_FIFO);
}
writel_relaxed(SPI_ENGINE_CMD_SYNC(1),
spi_engine->base + SPI_ENGINE_REG_CMD_FIFO);
@ -929,6 +1051,16 @@ static void spi_engine_trigger_disable(struct spi_offload *offload)
reg &= ~SPI_ENGINE_OFFLOAD_CTRL_ENABLE;
writel_relaxed(reg, spi_engine->base +
SPI_ENGINE_REG_OFFLOAD_CTRL(priv->offload_num));
/* Restore single-lane mode. */
if (priv->multi_lane_mode == SPI_MULTI_LANE_MODE_STRIPE) {
writel_relaxed(SPI_ENGINE_CMD_WRITE(SPI_ENGINE_CMD_REG_SDI_MASK,
priv->rx_primary_lane_mask),
spi_engine->base + SPI_ENGINE_REG_CMD_FIFO);
writel_relaxed(SPI_ENGINE_CMD_WRITE(SPI_ENGINE_CMD_REG_SDO_MASK,
priv->tx_primary_lane_mask),
spi_engine->base + SPI_ENGINE_REG_CMD_FIFO);
}
}
static struct dma_chan
@ -973,7 +1105,7 @@ static int spi_engine_probe(struct platform_device *pdev)
{
struct spi_engine *spi_engine;
struct spi_controller *host;
unsigned int version;
unsigned int version, data_width_reg_val;
int irq, ret;
irq = platform_get_irq(pdev, 0);
@ -1042,7 +1174,7 @@ static int spi_engine_probe(struct platform_device *pdev)
return PTR_ERR(spi_engine->base);
version = readl(spi_engine->base + ADI_AXI_REG_VERSION);
if (ADI_AXI_PCORE_VER_MAJOR(version) != 1) {
if (ADI_AXI_PCORE_VER_MAJOR(version) > 2) {
dev_err(&pdev->dev, "Unsupported peripheral version %u.%u.%u\n",
ADI_AXI_PCORE_VER_MAJOR(version),
ADI_AXI_PCORE_VER_MINOR(version),
@ -1050,6 +1182,8 @@ static int spi_engine_probe(struct platform_device *pdev)
return -ENODEV;
}
data_width_reg_val = readl(spi_engine->base + SPI_ENGINE_REG_DATA_WIDTH);
if (adi_axi_pcore_ver_gteq(version, 1, 1)) {
unsigned int sizes = readl(spi_engine->base +
SPI_ENGINE_REG_OFFLOAD_MEM_ADDR_WIDTH);
@ -1096,6 +1230,9 @@ static int spi_engine_probe(struct platform_device *pdev)
}
if (adi_axi_pcore_ver_gteq(version, 1, 3))
host->mode_bits |= SPI_MOSI_IDLE_LOW | SPI_MOSI_IDLE_HIGH;
if (adi_axi_pcore_ver_gteq(version, 2, 0))
host->num_data_lanes = FIELD_GET(SPI_ENGINE_REG_DATA_WIDTH_NUM_OF_SDIO_MASK,
data_width_reg_val);
if (host->max_speed_hz == 0)
return dev_err_probe(&pdev->dev, -EINVAL, "spi_clk rate is 0");

View file

@ -2354,8 +2354,8 @@ static void of_spi_parse_dt_cs_delay(struct device_node *nc,
static int of_spi_parse_dt(struct spi_controller *ctlr, struct spi_device *spi,
struct device_node *nc)
{
u32 value, cs[SPI_DEVICE_CS_CNT_MAX];
int rc, idx;
u32 value, cs[SPI_DEVICE_CS_CNT_MAX], map[SPI_DEVICE_DATA_LANE_CNT_MAX];
int rc, idx, max_num_data_lanes;
/* Mode (clock phase/polarity/etc.) */
if (of_property_read_bool(nc, "spi-cpha"))
@ -2370,7 +2370,65 @@ static int of_spi_parse_dt(struct spi_controller *ctlr, struct spi_device *spi,
spi->mode |= SPI_CS_HIGH;
/* Device DUAL/QUAD mode */
if (!of_property_read_u32(nc, "spi-tx-bus-width", &value)) {
rc = of_property_read_variable_u32_array(nc, "spi-tx-lane-map", map, 1,
ARRAY_SIZE(map));
if (rc >= 0) {
max_num_data_lanes = rc;
for (idx = 0; idx < max_num_data_lanes; idx++)
spi->tx_lane_map[idx] = map[idx];
} else if (rc == -EINVAL) {
/* Default lane map is identity mapping. */
max_num_data_lanes = ARRAY_SIZE(spi->tx_lane_map);
for (idx = 0; idx < max_num_data_lanes; idx++)
spi->tx_lane_map[idx] = idx;
} else {
dev_err(&ctlr->dev,
"failed to read spi-tx-lane-map property: %d\n", rc);
return rc;
}
rc = of_property_count_u32_elems(nc, "spi-tx-bus-width");
if (rc < 0 && rc != -EINVAL) {
dev_err(&ctlr->dev,
"failed to read spi-tx-bus-width property: %d\n", rc);
return rc;
}
if (rc > max_num_data_lanes) {
dev_err(&ctlr->dev,
"spi-tx-bus-width has more elements (%d) than spi-tx-lane-map (%d)\n",
rc, max_num_data_lanes);
return -EINVAL;
}
if (rc == -EINVAL) {
/* Default when property is not present. */
spi->num_tx_lanes = 1;
} else {
u32 first_value;
spi->num_tx_lanes = rc;
for (idx = 0; idx < spi->num_tx_lanes; idx++) {
rc = of_property_read_u32_index(nc, "spi-tx-bus-width",
idx, &value);
if (rc)
return rc;
/*
* For now, we only support all lanes having the same
* width so we can keep using the existing mode flags.
*/
if (!idx)
first_value = value;
else if (first_value != value) {
dev_err(&ctlr->dev,
"spi-tx-bus-width has inconsistent values: first %d vs later %d\n",
first_value, value);
return -EINVAL;
}
}
switch (value) {
case 0:
spi->mode |= SPI_NO_TX;
@ -2394,7 +2452,74 @@ static int of_spi_parse_dt(struct spi_controller *ctlr, struct spi_device *spi,
}
}
if (!of_property_read_u32(nc, "spi-rx-bus-width", &value)) {
for (idx = 0; idx < spi->num_tx_lanes; idx++) {
if (spi->tx_lane_map[idx] >= spi->controller->num_data_lanes) {
dev_err(&ctlr->dev,
"spi-tx-lane-map has invalid value %d (num_data_lanes=%d)\n",
spi->tx_lane_map[idx],
spi->controller->num_data_lanes);
return -EINVAL;
}
}
rc = of_property_read_variable_u32_array(nc, "spi-rx-lane-map", map, 1,
ARRAY_SIZE(map));
if (rc >= 0) {
max_num_data_lanes = rc;
for (idx = 0; idx < max_num_data_lanes; idx++)
spi->rx_lane_map[idx] = map[idx];
} else if (rc == -EINVAL) {
/* Default lane map is identity mapping. */
max_num_data_lanes = ARRAY_SIZE(spi->rx_lane_map);
for (idx = 0; idx < max_num_data_lanes; idx++)
spi->rx_lane_map[idx] = idx;
} else {
dev_err(&ctlr->dev,
"failed to read spi-rx-lane-map property: %d\n", rc);
return rc;
}
rc = of_property_count_u32_elems(nc, "spi-rx-bus-width");
if (rc < 0 && rc != -EINVAL) {
dev_err(&ctlr->dev,
"failed to read spi-rx-bus-width property: %d\n", rc);
return rc;
}
if (rc > max_num_data_lanes) {
dev_err(&ctlr->dev,
"spi-rx-bus-width has more elements (%d) than spi-rx-lane-map (%d)\n",
rc, max_num_data_lanes);
return -EINVAL;
}
if (rc == -EINVAL) {
/* Default when property is not present. */
spi->num_rx_lanes = 1;
} else {
u32 first_value;
spi->num_rx_lanes = rc;
for (idx = 0; idx < spi->num_rx_lanes; idx++) {
rc = of_property_read_u32_index(nc, "spi-rx-bus-width",
idx, &value);
if (rc)
return rc;
/*
* For now, we only support all lanes having the same
* width so we can keep using the existing mode flags.
*/
if (!idx)
first_value = value;
else if (first_value != value) {
dev_err(&ctlr->dev,
"spi-rx-bus-width has inconsistent values: first %d vs later %d\n",
first_value, value);
return -EINVAL;
}
}
switch (value) {
case 0:
spi->mode |= SPI_NO_RX;
@ -2418,6 +2543,16 @@ static int of_spi_parse_dt(struct spi_controller *ctlr, struct spi_device *spi,
}
}
for (idx = 0; idx < spi->num_rx_lanes; idx++) {
if (spi->rx_lane_map[idx] >= spi->controller->num_data_lanes) {
dev_err(&ctlr->dev,
"spi-rx-lane-map has invalid value %d (num_data_lanes=%d)\n",
spi->rx_lane_map[idx],
spi->controller->num_data_lanes);
return -EINVAL;
}
}
if (spi_controller_is_target(ctlr)) {
if (!of_node_name_eq(nc, "slave")) {
dev_err(&ctlr->dev, "%pOF is not called 'slave'\n",
@ -3066,6 +3201,7 @@ struct spi_controller *__spi_alloc_controller(struct device *dev,
mutex_init(&ctlr->add_lock);
ctlr->bus_num = -1;
ctlr->num_chipselect = 1;
ctlr->num_data_lanes = 1;
ctlr->target = target;
if (IS_ENABLED(CONFIG_SPI_SLAVE) && target)
ctlr->dev.class = &spi_target_class;

View file

@ -23,6 +23,9 @@
/* Max no. of CS supported per spi device */
#define SPI_DEVICE_CS_CNT_MAX 4
/* Max no. of data lanes supported per spi device */
#define SPI_DEVICE_DATA_LANE_CNT_MAX 8
struct dma_chan;
struct software_node;
struct ptp_system_timestamp;
@ -174,6 +177,10 @@ extern void spi_transfer_cs_change_delay_exec(struct spi_message *msg,
* @cs_index_mask: Bit mask of the active chipselect(s) in the chipselect array
* @cs_gpiod: Array of GPIO descriptors of the corresponding chipselect lines
* (optional, NULL when not using a GPIO line)
* @tx_lane_map: Map of peripheral lanes (index) to controller lanes (value).
* @num_tx_lanes: Number of transmit lanes wired up.
* @rx_lane_map: Map of peripheral lanes (index) to controller lanes (value).
* @num_rx_lanes: Number of receive lanes wired up.
*
* A @spi_device is used to interchange data between an SPI target device
* (usually a discrete chip) and CPU memory.
@ -242,6 +249,12 @@ struct spi_device {
struct gpio_desc *cs_gpiod[SPI_DEVICE_CS_CNT_MAX]; /* Chip select gpio desc */
/* Multi-lane SPI controller support. */
u8 tx_lane_map[SPI_DEVICE_DATA_LANE_CNT_MAX];
u8 num_tx_lanes;
u8 rx_lane_map[SPI_DEVICE_DATA_LANE_CNT_MAX];
u8 num_rx_lanes;
/*
* Likely need more hooks for more protocol options affecting how
* the controller talks to each chip, like:
@ -401,6 +414,7 @@ extern struct spi_device *spi_new_ancillary_device(struct spi_device *spi, u8 ch
* SPI targets, and are numbered from zero to num_chipselects.
* each target has a chipselect signal, but it's common that not
* every chipselect is connected to a target.
* @num_data_lanes: Number of data lanes supported by this controller. Default is 1.
* @dma_alignment: SPI controller constraint on DMA buffers alignment.
* @mode_bits: flags understood by this controller driver
* @buswidth_override_bits: flags to override for this controller driver
@ -576,6 +590,14 @@ struct spi_controller {
*/
u16 num_chipselect;
/*
* Some specialized SPI controllers can have more than one physical
* data lane interface per controller (each having it's own serializer).
* This specifies the number of data lanes in that case. Other
* controllers do not need to set this (defaults to 1).
*/
u16 num_data_lanes;
/* Some SPI controllers pose alignment requirements on DMAable
* buffers; let protocol drivers know about these requirements.
*/
@ -959,6 +981,8 @@ struct spi_res {
* (SPI_NBITS_SINGLE) is used.
* @rx_nbits: number of bits used for reading. If 0 the default
* (SPI_NBITS_SINGLE) is used.
* @multi_lane_mode: How to serialize data on multiple lanes. One of the
* SPI_MULTI_LANE_MODE_* values.
* @len: size of rx and tx buffers (in bytes)
* @speed_hz: Select a speed other than the device default for this
* transfer. If 0 the default (from @spi_device) is used.
@ -1095,6 +1119,12 @@ struct spi_transfer {
unsigned cs_change:1;
unsigned tx_nbits:4;
unsigned rx_nbits:4;
#define SPI_MULTI_LANE_MODE_SINGLE 0 /* only use single lane */
#define SPI_MULTI_LANE_MODE_STRIPE 1 /* one data word per lane */
#define SPI_MULTI_LANE_MODE_MIRROR 2 /* same word sent on all lanes */
unsigned multi_lane_mode: 2;
unsigned timestamped:1;
bool dtr_mode;
#define SPI_NBITS_SINGLE 0x01 /* 1-bit transfer */