[PATCH v2 3/5] spi: introduce SPI ancillary device with lanes
Jonathan Santos <[email protected]> Mon, 3 Aug 2026 00:02:15 -0300
| Newsgroups | org.kernel.vger.linux-spi,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <55c674601bfb3ba5495bc364cf2f7b86d0e4e65c.1785725359.git.Jonathan.Santos@analog.com> |
The existing spi_new_ancillary_device() creates an ancillary SPI device but does not consider the parent's lane map. In multi-device setups where each sub-device's chip-select is bound to a dedicated data lane, there is no way to bind an ancillary device to an arbitrary lane index. Introduce spi_new_ancillary_device_with_lane() and devm_spi_new_ancillary_device_with_lane(), which accept rx_lane_idx and tx_lane_idx parameters to select a specific lane from the parent's rx_lane_map and tx_lane_map respectively. The resulting ancillary device is registered with a single RX and TX lane, keeping it independent from the other sub-devices sharing the same controller. Signed-off-by: Jonathan Santos <[email protected]> --- Changes in v2: * New patch. --- drivers/spi/spi.c | 102 ++++++++++++++++++++++++++++++++++++++++ include/linux/spi/spi.h | 8 ++++ 2 files changed, 110 insertions(+) diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c index 61423aee1525..d41c9392c04e 100644 --- a/drivers/spi/spi.c +++ b/drivers/spi/spi.c @@ -2749,6 +2749,72 @@ struct spi_device *spi_new_ancillary_device(struct spi_device *spi, } EXPORT_SYMBOL_GPL(spi_new_ancillary_device); +/** + * spi_new_ancillary_device_with_lane() - Register ancillary SPI device bound to specific lane + * @spi: Pointer to the main SPI device registering the ancillary device + * @chip_select: Chip Select of the ancillary device + * @rx_lane_idx: Lane index within the parent's rx_lane_map + * @tx_lane_idx: Lane index within the parent's tx_lane_map + * + * Like spi_new_ancillary_device(), but additionally binds the ancillary device + * to a single lane from the parent's lane map. Use this in multi-device setups + * where each sub-device is physically wired to a dedicated CS and a dedicated + * data lane. + * + * This may only be called from main SPI device's probe routine. + * + * Return: Pointer to new ancillary device on success; ERR_PTR on failure + */ +struct spi_device *spi_new_ancillary_device_with_lane(struct spi_device *spi, + u8 chip_select, + unsigned int rx_lane_idx, + unsigned int tx_lane_idx) +{ + struct spi_controller *ctlr = spi->controller; + struct spi_device *ancillary; + int rc; + + ancillary = spi_alloc_device(ctlr); + if (!ancillary) { + rc = -ENOMEM; + goto err_out; + } + + strscpy(ancillary->modalias, "dummy", sizeof(ancillary->modalias)); + + spi_set_chipselect(ancillary, 0, chip_select); + + ancillary->max_speed_hz = spi->max_speed_hz; + ancillary->mode = spi->mode; + ancillary->cs_index_mask = BIT(0); + + if (rx_lane_idx >= spi->num_rx_lanes || tx_lane_idx >= spi->num_tx_lanes) { + rc = -EINVAL; + goto err_out; + } + + ancillary->rx_lane_map[0] = spi->rx_lane_map[rx_lane_idx]; + ancillary->num_rx_lanes = 1; + ancillary->tx_lane_map[0] = spi->tx_lane_map[tx_lane_idx]; + ancillary->num_tx_lanes = 1; + + WARN_ON(!mutex_is_locked(&ctlr->add_lock)); + + /* Register the new device, passing the parent to skip CS conflict check */ + rc = __spi_add_device(ancillary, spi); + if (rc) { + dev_err(&spi->dev, "failed to register ancillary device\n"); + goto err_out; + } + + return ancillary; + +err_out: + spi_dev_put(ancillary); + return ERR_PTR(rc); +} +EXPORT_SYMBOL_GPL(spi_new_ancillary_device_with_lane); + static void devm_spi_unregister_device(void *spi) { spi_unregister_device(spi); @@ -2789,6 +2855,42 @@ struct spi_device *devm_spi_new_ancillary_device(struct spi_device *spi, } EXPORT_SYMBOL_GPL(devm_spi_new_ancillary_device); +/** + * devm_spi_new_ancillary_device_with_lane() - Register managed ancillary SPI device bound to a lane + * @spi: Pointer to the main SPI device registering the ancillary device + * @chip_select: Chip Select of the ancillary device + * @rx_lane_idx: Per-device lane index within the parent's rx_lane_map + * @tx_lane_idx: Per-device lane index within the parent's tx_lane_map + * + * Managed version of spi_new_ancillary_device_with_lane(). The ancillary device + * will be unregistered automatically when the parent SPI device is unregistered. + * + * This may only be called from main SPI device's probe routine. + * + * Return: Pointer to new ancillary device on success; ERR_PTR on failure + */ +struct spi_device *devm_spi_new_ancillary_device_with_lane(struct spi_device *spi, + u8 chip_select, + unsigned int rx_lane_idx, + unsigned int tx_lane_idx) +{ + struct spi_device *ancillary; + int ret; + + ancillary = spi_new_ancillary_device_with_lane(spi, chip_select, + rx_lane_idx, tx_lane_idx); + if (IS_ERR(ancillary)) + return ancillary; + + ret = devm_add_action_or_reset(&spi->dev, devm_spi_unregister_device, + ancillary); + if (ret) + return ERR_PTR(ret); + + return ancillary; +} +EXPORT_SYMBOL_GPL(devm_spi_new_ancillary_device_with_lane); + #ifdef CONFIG_ACPI struct acpi_spi_lookup { struct spi_controller *ctlr; diff --git a/include/linux/spi/spi.h b/include/linux/spi/spi.h index 4c285d3ede1d..91c9e2d2e7e3 100644 --- a/include/linux/spi/spi.h +++ b/include/linux/spi/spi.h @@ -385,6 +385,14 @@ static inline void spi_unregister_driver(struct spi_driver *sdrv) extern struct spi_device *spi_new_ancillary_device(struct spi_device *spi, u8 chip_select); extern struct spi_device *devm_spi_new_ancillary_device(struct spi_device *spi, u8 chip_select); +extern struct spi_device *spi_new_ancillary_device_with_lane(struct spi_device *spi, + u8 chip_select, + unsigned int rx_lane_idx, + unsigned int tx_lane_idx); +extern struct spi_device *devm_spi_new_ancillary_device_with_lane(struct spi_device *spi, + u8 chip_select, + unsigned int rx_lane_idx, + unsigned int tx_lane_idx); /* Use a define to avoid include chaining to get THIS_MODULE */ #define spi_register_driver(driver) \ -- 2.34.1