summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/devicetree/bindings/display/panel/sitronix,st7789v.yaml5
-rw-r--r--Documentation/devicetree/bindings/iio/adc/adi,ad4030.yaml42
-rw-r--r--Documentation/devicetree/bindings/iio/adc/adi,ad4695.yaml5
-rw-r--r--Documentation/devicetree/bindings/spi/adi,axi-spi-engine.yaml15
-rw-r--r--Documentation/devicetree/bindings/spi/allwinner,sun4i-a10-spi.yaml6
-rw-r--r--Documentation/devicetree/bindings/spi/allwinner,sun6i-a31-spi.yaml6
-rw-r--r--Documentation/devicetree/bindings/spi/andestech,ae350-spi.yaml6
-rw-r--r--Documentation/devicetree/bindings/spi/nvidia,tegra210-quad.yaml6
-rw-r--r--Documentation/devicetree/bindings/spi/spi-peripheral-props.yaml40
-rw-r--r--Documentation/spi/index.rst1
-rw-r--r--Documentation/spi/multiple-data-lanes.rst217
-rw-r--r--drivers/spi/spi-axi-spi-engine.c145
-rw-r--r--drivers/spi/spi.c144
-rw-r--r--include/linux/spi/spi.h30
14 files changed, 641 insertions, 27 deletions
diff --git a/Documentation/devicetree/bindings/display/panel/sitronix,st7789v.yaml b/Documentation/devicetree/bindings/display/panel/sitronix,st7789v.yaml
index 0ce2ea13583d..c35d4f2ab9a4 100644
--- a/Documentation/devicetree/bindings/display/panel/sitronix,st7789v.yaml
+++ b/Documentation/devicetree/bindings/display/panel/sitronix,st7789v.yaml
@@ -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
diff --git a/Documentation/devicetree/bindings/iio/adc/adi,ad4030.yaml b/Documentation/devicetree/bindings/iio/adc/adi,ad4030.yaml
index 54e7349317b7..e22d518135f2 100644
--- a/Documentation/devicetree/bindings/iio/adc/adi,ad4030.yaml
+++ b/Documentation/devicetree/bindings/iio/adc/adi,ad4030.yaml
@@ -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>;
+ };
+ };
diff --git a/Documentation/devicetree/bindings/iio/adc/adi,ad4695.yaml b/Documentation/devicetree/bindings/iio/adc/adi,ad4695.yaml
index cbde7a0505d2..ae8d0b5f328b 100644
--- a/Documentation/devicetree/bindings/iio/adc/adi,ad4695.yaml
+++ b/Documentation/devicetree/bindings/iio/adc/adi,ad4695.yaml
@@ -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.
diff --git a/Documentation/devicetree/bindings/spi/adi,axi-spi-engine.yaml b/Documentation/devicetree/bindings/spi/adi,axi-spi-engine.yaml
index 4b3828eda6cb..0f2448371f17 100644
--- a/Documentation/devicetree/bindings/spi/adi,axi-spi-engine.yaml
+++ b/Documentation/devicetree/bindings/spi/adi,axi-spi-engine.yaml
@@ -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 {
diff --git a/Documentation/devicetree/bindings/spi/allwinner,sun4i-a10-spi.yaml b/Documentation/devicetree/bindings/spi/allwinner,sun4i-a10-spi.yaml
index e1ab3f523ad6..a34e6471dbe8 100644
--- a/Documentation/devicetree/bindings/spi/allwinner,sun4i-a10-spi.yaml
+++ b/Documentation/devicetree/bindings/spi/allwinner,sun4i-a10-spi.yaml
@@ -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
diff --git a/Documentation/devicetree/bindings/spi/allwinner,sun6i-a31-spi.yaml b/Documentation/devicetree/bindings/spi/allwinner,sun6i-a31-spi.yaml
index 1b91d1566c95..a6067030c5ed 100644
--- a/Documentation/devicetree/bindings/spi/allwinner,sun6i-a31-spi.yaml
+++ b/Documentation/devicetree/bindings/spi/allwinner,sun6i-a31-spi.yaml
@@ -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
diff --git a/Documentation/devicetree/bindings/spi/andestech,ae350-spi.yaml b/Documentation/devicetree/bindings/spi/andestech,ae350-spi.yaml
index 78093468dd5e..8e441742cee6 100644
--- a/Documentation/devicetree/bindings/spi/andestech,ae350-spi.yaml
+++ b/Documentation/devicetree/bindings/spi/andestech,ae350-spi.yaml
@@ -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#
diff --git a/Documentation/devicetree/bindings/spi/nvidia,tegra210-quad.yaml b/Documentation/devicetree/bindings/spi/nvidia,tegra210-quad.yaml
index 8b3640280559..909c204b8adf 100644
--- a/Documentation/devicetree/bindings/spi/nvidia,tegra210-quad.yaml
+++ b/Documentation/devicetree/bindings/spi/nvidia,tegra210-quad.yaml
@@ -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
diff --git a/Documentation/devicetree/bindings/spi/spi-peripheral-props.yaml b/Documentation/devicetree/bindings/spi/spi-peripheral-props.yaml
index 8b6e8fc009db..880a9f624566 100644
--- a/Documentation/devicetree/bindings/spi/spi-peripheral-props.yaml
+++ b/Documentation/devicetree/bindings/spi/spi-peripheral-props.yaml
@@ -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:
diff --git a/Documentation/spi/index.rst b/Documentation/spi/index.rst
index 824ce42ed4f0..2c89b1ee39e2 100644
--- a/Documentation/spi/index.rst
+++ b/Documentation/spi/index.rst
@@ -9,6 +9,7 @@ Serial Peripheral Interface (SPI)
spi-summary
spidev
+ multiple-data-lanes
butterfly
spi-lm70llp
spi-sc18is602
diff --git a/Documentation/spi/multiple-data-lanes.rst b/Documentation/spi/multiple-data-lanes.rst
new file mode 100644
index 000000000000..69cb532d052f
--- /dev/null
+++ b/Documentation/spi/multiple-data-lanes.rst
@@ -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.
diff --git a/drivers/spi/spi-axi-spi-engine.c b/drivers/spi/spi-axi-spi-engine.c
index 91805eae9263..c75e8da049f7 100644
--- a/drivers/spi/spi-axi-spi-engine.c
+++ b/drivers/spi/spi-axi-spi-engine.c
@@ -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");
diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c
index b773c297f8b1..a24de5c98399 100644
--- a/drivers/spi/spi.c
+++ b/drivers/spi/spi.c
@@ -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;
diff --git a/include/linux/spi/spi.h b/include/linux/spi/spi.h
index cb2c2df31089..39681f7e063b 100644
--- a/include/linux/spi/spi.h
+++ b/include/linux/spi/spi.h
@@ -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 */