spi: add ancillary device support
Introduce support for ancillary devices, similar to existing implementation for I2C. This is useful for devices having multiple chip-selects, for example some microcontrollers provide a normal SPI interface and a flashing SPI interface. Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com> Link: https://lore.kernel.org/r/20210621175359.126729-2-sebastian.reichel@collabora.com Signed-off-by: Mark Brown <broonie@kernel.org>
This commit is contained in:
parent
476ad3ff89
commit
0c79378c01
@ -564,6 +564,55 @@ static void spi_cleanup(struct spi_device *spi)
|
|||||||
spi->controller->cleanup(spi);
|
spi->controller->cleanup(spi);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int __spi_add_device(struct spi_device *spi)
|
||||||
|
{
|
||||||
|
struct spi_controller *ctlr = spi->controller;
|
||||||
|
struct device *dev = ctlr->dev.parent;
|
||||||
|
int status;
|
||||||
|
|
||||||
|
status = bus_for_each_dev(&spi_bus_type, NULL, spi, spi_dev_check);
|
||||||
|
if (status) {
|
||||||
|
dev_err(dev, "chipselect %d already in use\n",
|
||||||
|
spi->chip_select);
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Controller may unregister concurrently */
|
||||||
|
if (IS_ENABLED(CONFIG_SPI_DYNAMIC) &&
|
||||||
|
!device_is_registered(&ctlr->dev)) {
|
||||||
|
return -ENODEV;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Descriptors take precedence */
|
||||||
|
if (ctlr->cs_gpiods)
|
||||||
|
spi->cs_gpiod = ctlr->cs_gpiods[spi->chip_select];
|
||||||
|
else if (ctlr->cs_gpios)
|
||||||
|
spi->cs_gpio = ctlr->cs_gpios[spi->chip_select];
|
||||||
|
|
||||||
|
/* Drivers may modify this initial i/o setup, but will
|
||||||
|
* normally rely on the device being setup. Devices
|
||||||
|
* using SPI_CS_HIGH can't coexist well otherwise...
|
||||||
|
*/
|
||||||
|
status = spi_setup(spi);
|
||||||
|
if (status < 0) {
|
||||||
|
dev_err(dev, "can't setup %s, status %d\n",
|
||||||
|
dev_name(&spi->dev), status);
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Device may be bound to an active driver when this returns */
|
||||||
|
status = device_add(&spi->dev);
|
||||||
|
if (status < 0) {
|
||||||
|
dev_err(dev, "can't add %s, status %d\n",
|
||||||
|
dev_name(&spi->dev), status);
|
||||||
|
spi_cleanup(spi);
|
||||||
|
} else {
|
||||||
|
dev_dbg(dev, "registered child %s\n", dev_name(&spi->dev));
|
||||||
|
}
|
||||||
|
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* spi_add_device - Add spi_device allocated with spi_alloc_device
|
* spi_add_device - Add spi_device allocated with spi_alloc_device
|
||||||
* @spi: spi_device to register
|
* @spi: spi_device to register
|
||||||
@ -594,54 +643,31 @@ int spi_add_device(struct spi_device *spi)
|
|||||||
* its configuration. Lock against concurrent add() calls.
|
* its configuration. Lock against concurrent add() calls.
|
||||||
*/
|
*/
|
||||||
mutex_lock(&spi_add_lock);
|
mutex_lock(&spi_add_lock);
|
||||||
|
status = __spi_add_device(spi);
|
||||||
status = bus_for_each_dev(&spi_bus_type, NULL, spi, spi_dev_check);
|
|
||||||
if (status) {
|
|
||||||
dev_err(dev, "chipselect %d already in use\n",
|
|
||||||
spi->chip_select);
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Controller may unregister concurrently */
|
|
||||||
if (IS_ENABLED(CONFIG_SPI_DYNAMIC) &&
|
|
||||||
!device_is_registered(&ctlr->dev)) {
|
|
||||||
status = -ENODEV;
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Descriptors take precedence */
|
|
||||||
if (ctlr->cs_gpiods)
|
|
||||||
spi->cs_gpiod = ctlr->cs_gpiods[spi->chip_select];
|
|
||||||
else if (ctlr->cs_gpios)
|
|
||||||
spi->cs_gpio = ctlr->cs_gpios[spi->chip_select];
|
|
||||||
|
|
||||||
/* Drivers may modify this initial i/o setup, but will
|
|
||||||
* normally rely on the device being setup. Devices
|
|
||||||
* using SPI_CS_HIGH can't coexist well otherwise...
|
|
||||||
*/
|
|
||||||
status = spi_setup(spi);
|
|
||||||
if (status < 0) {
|
|
||||||
dev_err(dev, "can't setup %s, status %d\n",
|
|
||||||
dev_name(&spi->dev), status);
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Device may be bound to an active driver when this returns */
|
|
||||||
status = device_add(&spi->dev);
|
|
||||||
if (status < 0) {
|
|
||||||
dev_err(dev, "can't add %s, status %d\n",
|
|
||||||
dev_name(&spi->dev), status);
|
|
||||||
spi_cleanup(spi);
|
|
||||||
} else {
|
|
||||||
dev_dbg(dev, "registered child %s\n", dev_name(&spi->dev));
|
|
||||||
}
|
|
||||||
|
|
||||||
done:
|
|
||||||
mutex_unlock(&spi_add_lock);
|
mutex_unlock(&spi_add_lock);
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(spi_add_device);
|
EXPORT_SYMBOL_GPL(spi_add_device);
|
||||||
|
|
||||||
|
static int spi_add_device_locked(struct spi_device *spi)
|
||||||
|
{
|
||||||
|
struct spi_controller *ctlr = spi->controller;
|
||||||
|
struct device *dev = ctlr->dev.parent;
|
||||||
|
|
||||||
|
/* Chipselects are numbered 0..max; validate. */
|
||||||
|
if (spi->chip_select >= ctlr->num_chipselect) {
|
||||||
|
dev_err(dev, "cs%d >= max %d\n", spi->chip_select,
|
||||||
|
ctlr->num_chipselect);
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set the bus ID string */
|
||||||
|
spi_dev_set_name(spi);
|
||||||
|
|
||||||
|
WARN_ON(!mutex_is_locked(&spi_add_lock));
|
||||||
|
return __spi_add_device(spi);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* spi_new_device - instantiate one new SPI device
|
* spi_new_device - instantiate one new SPI device
|
||||||
* @ctlr: Controller to which device is connected
|
* @ctlr: Controller to which device is connected
|
||||||
@ -2125,6 +2151,55 @@ static void of_register_spi_devices(struct spi_controller *ctlr)
|
|||||||
static void of_register_spi_devices(struct spi_controller *ctlr) { }
|
static void of_register_spi_devices(struct spi_controller *ctlr) { }
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* spi_new_ancillary_device() - Register ancillary SPI device
|
||||||
|
* @spi: Pointer to the main SPI device registering the ancillary device
|
||||||
|
* @chip_select: Chip Select of the ancillary device
|
||||||
|
*
|
||||||
|
* Register an ancillary SPI device; for example some chips have a chip-select
|
||||||
|
* for normal device usage and another one for setup/firmware upload.
|
||||||
|
*
|
||||||
|
* This may only be called from main SPI device's probe routine.
|
||||||
|
*
|
||||||
|
* Return: 0 on success; negative errno on failure
|
||||||
|
*/
|
||||||
|
struct spi_device *spi_new_ancillary_device(struct spi_device *spi,
|
||||||
|
u8 chip_select)
|
||||||
|
{
|
||||||
|
struct spi_device *ancillary;
|
||||||
|
int rc = 0;
|
||||||
|
|
||||||
|
/* Alloc an spi_device */
|
||||||
|
ancillary = spi_alloc_device(spi->controller);
|
||||||
|
if (!ancillary) {
|
||||||
|
rc = -ENOMEM;
|
||||||
|
goto err_out;
|
||||||
|
}
|
||||||
|
|
||||||
|
strlcpy(ancillary->modalias, "dummy", sizeof(ancillary->modalias));
|
||||||
|
|
||||||
|
/* Use provided chip-select for ancillary device */
|
||||||
|
ancillary->chip_select = chip_select;
|
||||||
|
|
||||||
|
/* Take over SPI mode/speed from SPI main device */
|
||||||
|
ancillary->max_speed_hz = spi->max_speed_hz;
|
||||||
|
ancillary->mode = ancillary->mode;
|
||||||
|
|
||||||
|
/* Register the new device */
|
||||||
|
rc = spi_add_device_locked(ancillary);
|
||||||
|
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);
|
||||||
|
|
||||||
#ifdef CONFIG_ACPI
|
#ifdef CONFIG_ACPI
|
||||||
struct acpi_spi_lookup {
|
struct acpi_spi_lookup {
|
||||||
struct spi_controller *ctlr;
|
struct spi_controller *ctlr;
|
||||||
|
@ -299,6 +299,8 @@ static inline void spi_unregister_driver(struct spi_driver *sdrv)
|
|||||||
driver_unregister(&sdrv->driver);
|
driver_unregister(&sdrv->driver);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extern struct spi_device *spi_new_ancillary_device(struct spi_device *spi, u8 chip_select);
|
||||||
|
|
||||||
/* use a define to avoid include chaining to get THIS_MODULE */
|
/* use a define to avoid include chaining to get THIS_MODULE */
|
||||||
#define spi_register_driver(driver) \
|
#define spi_register_driver(driver) \
|
||||||
__spi_register_driver(THIS_MODULE, driver)
|
__spi_register_driver(THIS_MODULE, driver)
|
||||||
|
Loading…
Reference in New Issue
Block a user