Commit 0c79378c authored by Sebastian Reichel's avatar Sebastian Reichel Committed by Mark Brown

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: default avatarSebastian Reichel <sebastian.reichel@collabora.com>
Link: https://lore.kernel.org/r/20210621175359.126729-2-sebastian.reichel@collabora.comSigned-off-by: default avatarMark Brown <broonie@kernel.org>
parent 476ad3ff
...@@ -564,49 +564,23 @@ static void spi_cleanup(struct spi_device *spi) ...@@ -564,49 +564,23 @@ 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)
* spi_add_device - Add spi_device allocated with spi_alloc_device
* @spi: spi_device to register
*
* Companion function to spi_alloc_device. Devices allocated with
* spi_alloc_device can be added onto the spi bus with this function.
*
* Return: 0 on success; negative errno on failure
*/
int spi_add_device(struct spi_device *spi)
{ {
struct spi_controller *ctlr = spi->controller; struct spi_controller *ctlr = spi->controller;
struct device *dev = ctlr->dev.parent; struct device *dev = ctlr->dev.parent;
int status; int status;
/* 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);
/* We need to make sure there's no other device with this
* chipselect **BEFORE** we call setup(), else we'll trash
* its configuration. Lock against concurrent add() calls.
*/
mutex_lock(&spi_add_lock);
status = bus_for_each_dev(&spi_bus_type, NULL, spi, spi_dev_check); status = bus_for_each_dev(&spi_bus_type, NULL, spi, spi_dev_check);
if (status) { if (status) {
dev_err(dev, "chipselect %d already in use\n", dev_err(dev, "chipselect %d already in use\n",
spi->chip_select); spi->chip_select);
goto done; return status;
} }
/* Controller may unregister concurrently */ /* Controller may unregister concurrently */
if (IS_ENABLED(CONFIG_SPI_DYNAMIC) && if (IS_ENABLED(CONFIG_SPI_DYNAMIC) &&
!device_is_registered(&ctlr->dev)) { !device_is_registered(&ctlr->dev)) {
status = -ENODEV; return -ENODEV;
goto done;
} }
/* Descriptors take precedence */ /* Descriptors take precedence */
...@@ -623,7 +597,7 @@ int spi_add_device(struct spi_device *spi) ...@@ -623,7 +597,7 @@ int spi_add_device(struct spi_device *spi)
if (status < 0) { if (status < 0) {
dev_err(dev, "can't setup %s, status %d\n", dev_err(dev, "can't setup %s, status %d\n",
dev_name(&spi->dev), status); dev_name(&spi->dev), status);
goto done; return status;
} }
/* Device may be bound to an active driver when this returns */ /* Device may be bound to an active driver when this returns */
...@@ -636,12 +610,64 @@ int spi_add_device(struct spi_device *spi) ...@@ -636,12 +610,64 @@ int spi_add_device(struct spi_device *spi)
dev_dbg(dev, "registered child %s\n", dev_name(&spi->dev)); dev_dbg(dev, "registered child %s\n", dev_name(&spi->dev));
} }
done: return status;
}
/**
* spi_add_device - Add spi_device allocated with spi_alloc_device
* @spi: spi_device to register
*
* Companion function to spi_alloc_device. Devices allocated with
* spi_alloc_device can be added onto the spi bus with this function.
*
* Return: 0 on success; negative errno on failure
*/
int spi_add_device(struct spi_device *spi)
{
struct spi_controller *ctlr = spi->controller;
struct device *dev = ctlr->dev.parent;
int status;
/* 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);
/* We need to make sure there's no other device with this
* chipselect **BEFORE** we call setup(), else we'll trash
* its configuration. Lock against concurrent add() calls.
*/
mutex_lock(&spi_add_lock);
status = __spi_add_device(spi);
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) ...@@ -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) ...@@ -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)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment