summaryrefslogtreecommitdiff
path: root/drivers/spi
diff options
context:
space:
mode:
authorMark Brown <broonie@kernel.org>2026-02-02 22:03:20 +0000
committerMark Brown <broonie@kernel.org>2026-02-02 22:03:20 +0000
commit8ea39d960c9f890e9213cdcfcbe4b3f281acd12f (patch)
tree77d4a0e9bb75252fc1dd847ded1d70b31d8dc2cd /drivers/spi
parentda0a672268b34279aa999664860e6becc38f3f51 (diff)
parent0ec5ed7c95d1ba6a74491928ff38abb351dbed36 (diff)
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.
Diffstat (limited to 'drivers/spi')
-rw-r--r--drivers/spi/spi-axi-spi-engine.c145
-rw-r--r--drivers/spi/spi.c144
2 files changed, 281 insertions, 8 deletions
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;