diff options
| author | David Lechner <dlechner@baylibre.com> | 2026-01-23 14:37:26 -0600 |
|---|---|---|
| committer | Mark Brown <broonie@kernel.org> | 2026-02-02 12:12:43 +0000 |
| commit | 002d561f89c3a61ee17d38070e48ea4eb1243732 (patch) | |
| tree | aca823a29a2c4ada2eb1594b5e9d801d40fa3970 /drivers/spi | |
| parent | 31eab8425110b933dd7c818809cb4ffa3b2c6d82 (diff) | |
spi: support controllers with multiple data lanes
Add support for SPI controllers with multiple physical SPI data lanes.
(A data lane in this context means lines connected to a serializer, so a
controller with two data lanes would have two serializers in a single
controller).
This is common in the type of controller that can be used with parallel
flash memories, but can be used for general purpose SPI as well.
To indicate support, a controller just needs to set ctlr->num_data_lanes
to something greater than 1. Peripherals indicate which lane they are
connected to via device tree (ACPI support can be added if needed).
The spi-{tx,rx}-bus-width DT properties can now be arrays. The length of
the array indicates the number of data lanes, and each element indicates
the bus width of that lane. For now, we restrict all lanes to have the
same bus width to keep things simple. Support for an optional controller
lane mapping property is also implemented.
Signed-off-by: David Lechner <dlechner@baylibre.com>
Link: https://patch.msgid.link/20260123-spi-add-multi-bus-support-v6-3-12af183c06eb@baylibre.com
Signed-off-by: Mark Brown <broonie@kernel.org>
Diffstat (limited to 'drivers/spi')
| -rw-r--r-- | drivers/spi/spi.c | 144 |
1 files changed, 140 insertions, 4 deletions
diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c index e25df9990f82..3d9fba0fe915 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; |
