Skip to content

Commit 002d561

Browse files
dlechbroonie
authored andcommitted
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>
1 parent 31eab84 commit 002d561

2 files changed

Lines changed: 162 additions & 4 deletions

File tree

drivers/spi/spi.c

Lines changed: 140 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2354,8 +2354,8 @@ static void of_spi_parse_dt_cs_delay(struct device_node *nc,
23542354
static int of_spi_parse_dt(struct spi_controller *ctlr, struct spi_device *spi,
23552355
struct device_node *nc)
23562356
{
2357-
u32 value, cs[SPI_DEVICE_CS_CNT_MAX];
2358-
int rc, idx;
2357+
u32 value, cs[SPI_DEVICE_CS_CNT_MAX], map[SPI_DEVICE_DATA_LANE_CNT_MAX];
2358+
int rc, idx, max_num_data_lanes;
23592359

23602360
/* Mode (clock phase/polarity/etc.) */
23612361
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,
23702370
spi->mode |= SPI_CS_HIGH;
23712371

23722372
/* Device DUAL/QUAD mode */
2373-
if (!of_property_read_u32(nc, "spi-tx-bus-width", &value)) {
2373+
2374+
rc = of_property_read_variable_u32_array(nc, "spi-tx-lane-map", map, 1,
2375+
ARRAY_SIZE(map));
2376+
if (rc >= 0) {
2377+
max_num_data_lanes = rc;
2378+
for (idx = 0; idx < max_num_data_lanes; idx++)
2379+
spi->tx_lane_map[idx] = map[idx];
2380+
} else if (rc == -EINVAL) {
2381+
/* Default lane map is identity mapping. */
2382+
max_num_data_lanes = ARRAY_SIZE(spi->tx_lane_map);
2383+
for (idx = 0; idx < max_num_data_lanes; idx++)
2384+
spi->tx_lane_map[idx] = idx;
2385+
} else {
2386+
dev_err(&ctlr->dev,
2387+
"failed to read spi-tx-lane-map property: %d\n", rc);
2388+
return rc;
2389+
}
2390+
2391+
rc = of_property_count_u32_elems(nc, "spi-tx-bus-width");
2392+
if (rc < 0 && rc != -EINVAL) {
2393+
dev_err(&ctlr->dev,
2394+
"failed to read spi-tx-bus-width property: %d\n", rc);
2395+
return rc;
2396+
}
2397+
if (rc > max_num_data_lanes) {
2398+
dev_err(&ctlr->dev,
2399+
"spi-tx-bus-width has more elements (%d) than spi-tx-lane-map (%d)\n",
2400+
rc, max_num_data_lanes);
2401+
return -EINVAL;
2402+
}
2403+
2404+
if (rc == -EINVAL) {
2405+
/* Default when property is not present. */
2406+
spi->num_tx_lanes = 1;
2407+
} else {
2408+
u32 first_value;
2409+
2410+
spi->num_tx_lanes = rc;
2411+
2412+
for (idx = 0; idx < spi->num_tx_lanes; idx++) {
2413+
rc = of_property_read_u32_index(nc, "spi-tx-bus-width",
2414+
idx, &value);
2415+
if (rc)
2416+
return rc;
2417+
2418+
/*
2419+
* For now, we only support all lanes having the same
2420+
* width so we can keep using the existing mode flags.
2421+
*/
2422+
if (!idx)
2423+
first_value = value;
2424+
else if (first_value != value) {
2425+
dev_err(&ctlr->dev,
2426+
"spi-tx-bus-width has inconsistent values: first %d vs later %d\n",
2427+
first_value, value);
2428+
return -EINVAL;
2429+
}
2430+
}
2431+
23742432
switch (value) {
23752433
case 0:
23762434
spi->mode |= SPI_NO_TX;
@@ -2394,7 +2452,74 @@ static int of_spi_parse_dt(struct spi_controller *ctlr, struct spi_device *spi,
23942452
}
23952453
}
23962454

2397-
if (!of_property_read_u32(nc, "spi-rx-bus-width", &value)) {
2455+
for (idx = 0; idx < spi->num_tx_lanes; idx++) {
2456+
if (spi->tx_lane_map[idx] >= spi->controller->num_data_lanes) {
2457+
dev_err(&ctlr->dev,
2458+
"spi-tx-lane-map has invalid value %d (num_data_lanes=%d)\n",
2459+
spi->tx_lane_map[idx],
2460+
spi->controller->num_data_lanes);
2461+
return -EINVAL;
2462+
}
2463+
}
2464+
2465+
rc = of_property_read_variable_u32_array(nc, "spi-rx-lane-map", map, 1,
2466+
ARRAY_SIZE(map));
2467+
if (rc >= 0) {
2468+
max_num_data_lanes = rc;
2469+
for (idx = 0; idx < max_num_data_lanes; idx++)
2470+
spi->rx_lane_map[idx] = map[idx];
2471+
} else if (rc == -EINVAL) {
2472+
/* Default lane map is identity mapping. */
2473+
max_num_data_lanes = ARRAY_SIZE(spi->rx_lane_map);
2474+
for (idx = 0; idx < max_num_data_lanes; idx++)
2475+
spi->rx_lane_map[idx] = idx;
2476+
} else {
2477+
dev_err(&ctlr->dev,
2478+
"failed to read spi-rx-lane-map property: %d\n", rc);
2479+
return rc;
2480+
}
2481+
2482+
rc = of_property_count_u32_elems(nc, "spi-rx-bus-width");
2483+
if (rc < 0 && rc != -EINVAL) {
2484+
dev_err(&ctlr->dev,
2485+
"failed to read spi-rx-bus-width property: %d\n", rc);
2486+
return rc;
2487+
}
2488+
if (rc > max_num_data_lanes) {
2489+
dev_err(&ctlr->dev,
2490+
"spi-rx-bus-width has more elements (%d) than spi-rx-lane-map (%d)\n",
2491+
rc, max_num_data_lanes);
2492+
return -EINVAL;
2493+
}
2494+
2495+
if (rc == -EINVAL) {
2496+
/* Default when property is not present. */
2497+
spi->num_rx_lanes = 1;
2498+
} else {
2499+
u32 first_value;
2500+
2501+
spi->num_rx_lanes = rc;
2502+
2503+
for (idx = 0; idx < spi->num_rx_lanes; idx++) {
2504+
rc = of_property_read_u32_index(nc, "spi-rx-bus-width",
2505+
idx, &value);
2506+
if (rc)
2507+
return rc;
2508+
2509+
/*
2510+
* For now, we only support all lanes having the same
2511+
* width so we can keep using the existing mode flags.
2512+
*/
2513+
if (!idx)
2514+
first_value = value;
2515+
else if (first_value != value) {
2516+
dev_err(&ctlr->dev,
2517+
"spi-rx-bus-width has inconsistent values: first %d vs later %d\n",
2518+
first_value, value);
2519+
return -EINVAL;
2520+
}
2521+
}
2522+
23982523
switch (value) {
23992524
case 0:
24002525
spi->mode |= SPI_NO_RX;
@@ -2418,6 +2543,16 @@ static int of_spi_parse_dt(struct spi_controller *ctlr, struct spi_device *spi,
24182543
}
24192544
}
24202545

2546+
for (idx = 0; idx < spi->num_rx_lanes; idx++) {
2547+
if (spi->rx_lane_map[idx] >= spi->controller->num_data_lanes) {
2548+
dev_err(&ctlr->dev,
2549+
"spi-rx-lane-map has invalid value %d (num_data_lanes=%d)\n",
2550+
spi->rx_lane_map[idx],
2551+
spi->controller->num_data_lanes);
2552+
return -EINVAL;
2553+
}
2554+
}
2555+
24212556
if (spi_controller_is_target(ctlr)) {
24222557
if (!of_node_name_eq(nc, "slave")) {
24232558
dev_err(&ctlr->dev, "%pOF is not called 'slave'\n",
@@ -3066,6 +3201,7 @@ struct spi_controller *__spi_alloc_controller(struct device *dev,
30663201
mutex_init(&ctlr->add_lock);
30673202
ctlr->bus_num = -1;
30683203
ctlr->num_chipselect = 1;
3204+
ctlr->num_data_lanes = 1;
30693205
ctlr->target = target;
30703206
if (IS_ENABLED(CONFIG_SPI_SLAVE) && target)
30713207
ctlr->dev.class = &spi_target_class;

include/linux/spi/spi.h

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@
2323
/* Max no. of CS supported per spi device */
2424
#define SPI_DEVICE_CS_CNT_MAX 4
2525

26+
/* Max no. of data lanes supported per spi device */
27+
#define SPI_DEVICE_DATA_LANE_CNT_MAX 8
28+
2629
struct dma_chan;
2730
struct software_node;
2831
struct ptp_system_timestamp;
@@ -174,6 +177,10 @@ extern void spi_transfer_cs_change_delay_exec(struct spi_message *msg,
174177
* @cs_index_mask: Bit mask of the active chipselect(s) in the chipselect array
175178
* @cs_gpiod: Array of GPIO descriptors of the corresponding chipselect lines
176179
* (optional, NULL when not using a GPIO line)
180+
* @tx_lane_map: Map of peripheral lanes (index) to controller lanes (value).
181+
* @num_tx_lanes: Number of transmit lanes wired up.
182+
* @rx_lane_map: Map of peripheral lanes (index) to controller lanes (value).
183+
* @num_rx_lanes: Number of receive lanes wired up.
177184
*
178185
* A @spi_device is used to interchange data between an SPI target device
179186
* (usually a discrete chip) and CPU memory.
@@ -242,6 +249,12 @@ struct spi_device {
242249

243250
struct gpio_desc *cs_gpiod[SPI_DEVICE_CS_CNT_MAX]; /* Chip select gpio desc */
244251

252+
/* Multi-lane SPI controller support. */
253+
u8 tx_lane_map[SPI_DEVICE_DATA_LANE_CNT_MAX];
254+
u8 num_tx_lanes;
255+
u8 rx_lane_map[SPI_DEVICE_DATA_LANE_CNT_MAX];
256+
u8 num_rx_lanes;
257+
245258
/*
246259
* Likely need more hooks for more protocol options affecting how
247260
* 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
401414
* SPI targets, and are numbered from zero to num_chipselects.
402415
* each target has a chipselect signal, but it's common that not
403416
* every chipselect is connected to a target.
417+
* @num_data_lanes: Number of data lanes supported by this controller. Default is 1.
404418
* @dma_alignment: SPI controller constraint on DMA buffers alignment.
405419
* @mode_bits: flags understood by this controller driver
406420
* @buswidth_override_bits: flags to override for this controller driver
@@ -576,6 +590,14 @@ struct spi_controller {
576590
*/
577591
u16 num_chipselect;
578592

593+
/*
594+
* Some specialized SPI controllers can have more than one physical
595+
* data lane interface per controller (each having it's own serializer).
596+
* This specifies the number of data lanes in that case. Other
597+
* controllers do not need to set this (defaults to 1).
598+
*/
599+
u16 num_data_lanes;
600+
579601
/* Some SPI controllers pose alignment requirements on DMAable
580602
* buffers; let protocol drivers know about these requirements.
581603
*/

0 commit comments

Comments
 (0)