mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-08-24 09:50:04 +00:00
![Amit Kumar Mahapatra via Alsa-devel](/assets/img/avatar_default.png)
Supporting multi-cs in spi drivers would require the chip_select & cs_gpiod members of struct spi_device to be an array. But changing the type of these members to array would break the spi driver functionality. To make the transition smoother introduced four new APIs to get/set the spi->chip_select & spi->cs_gpiod and replaced all spi->chip_select and spi->cs_gpiod references with get or set API calls. While adding multi-cs support in further patches the chip_select & cs_gpiod members of the spi_device structure would be converted to arrays & the "idx" parameter of the APIs would be used as array index i.e., spi->chip_select[idx] & spi->cs_gpiod[idx] respectively. Signed-off-by: Amit Kumar Mahapatra <amit.kumar-mahapatra@amd.com> Acked-by: Heiko Stuebner <heiko@sntech.de> # Rockchip drivers Reviewed-by: Michal Simek <michal.simek@amd.com> Reviewed-by: Cédric Le Goater <clg@kaod.org> # Aspeed driver Reviewed-by: Dhruva Gole <d-gole@ti.com> # SPI Cadence QSPI Reviewed-by: Patrice Chotard <patrice.chotard@foss.st.com> # spi-stm32-qspi Acked-by: William Zhang <william.zhang@broadcom.com> # bcm63xx-hsspi driver Reviewed-by: Serge Semin <fancer.lancer@gmail.com> # DW SSI part Link: https://lore.kernel.org/r/167847070432.26.15076794204368669839@mailman-core.alsa-project.org Signed-off-by: Mark Brown <broonie@kernel.org>
202 lines
5.2 KiB
C
202 lines
5.2 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
//
|
|
// General Purpose SPI multiplexer
|
|
|
|
#include <linux/err.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/mux/consumer.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/spi/spi.h>
|
|
|
|
#define SPI_MUX_NO_CS ((unsigned int)-1)
|
|
|
|
/**
|
|
* DOC: Driver description
|
|
*
|
|
* This driver supports a MUX on an SPI bus. This can be useful when you need
|
|
* more chip selects than the hardware peripherals support, or than are
|
|
* available in a particular board setup.
|
|
*
|
|
* The driver will create an additional SPI controller. Devices added under the
|
|
* mux will be handled as 'chip selects' on this controller.
|
|
*/
|
|
|
|
/**
|
|
* struct spi_mux_priv - the basic spi_mux structure
|
|
* @spi: pointer to the device struct attached to the parent
|
|
* spi controller
|
|
* @current_cs: The current chip select set in the mux
|
|
* @child_msg_complete: The mux replaces the complete callback in the child's
|
|
* message to its own callback; this field is used by the
|
|
* driver to store the child's callback during a transfer
|
|
* @child_msg_context: Used to store the child's context to the callback
|
|
* @child_msg_dev: Used to store the spi_device pointer to the child
|
|
* @mux: mux_control structure used to provide chip selects for
|
|
* downstream spi devices
|
|
*/
|
|
struct spi_mux_priv {
|
|
struct spi_device *spi;
|
|
unsigned int current_cs;
|
|
|
|
void (*child_msg_complete)(void *context);
|
|
void *child_msg_context;
|
|
struct spi_device *child_msg_dev;
|
|
struct mux_control *mux;
|
|
};
|
|
|
|
/* should not get called when the parent controller is doing a transfer */
|
|
static int spi_mux_select(struct spi_device *spi)
|
|
{
|
|
struct spi_mux_priv *priv = spi_controller_get_devdata(spi->controller);
|
|
int ret;
|
|
|
|
ret = mux_control_select(priv->mux, spi_get_chipselect(spi, 0));
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (priv->current_cs == spi_get_chipselect(spi, 0))
|
|
return 0;
|
|
|
|
dev_dbg(&priv->spi->dev, "setting up the mux for cs %d\n",
|
|
spi_get_chipselect(spi, 0));
|
|
|
|
/* copy the child device's settings except for the cs */
|
|
priv->spi->max_speed_hz = spi->max_speed_hz;
|
|
priv->spi->mode = spi->mode;
|
|
priv->spi->bits_per_word = spi->bits_per_word;
|
|
|
|
priv->current_cs = spi_get_chipselect(spi, 0);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int spi_mux_setup(struct spi_device *spi)
|
|
{
|
|
struct spi_mux_priv *priv = spi_controller_get_devdata(spi->controller);
|
|
|
|
/*
|
|
* can be called multiple times, won't do a valid setup now but we will
|
|
* change the settings when we do a transfer (necessary because we
|
|
* can't predict from which device it will be anyway)
|
|
*/
|
|
return spi_setup(priv->spi);
|
|
}
|
|
|
|
static void spi_mux_complete_cb(void *context)
|
|
{
|
|
struct spi_mux_priv *priv = (struct spi_mux_priv *)context;
|
|
struct spi_controller *ctlr = spi_get_drvdata(priv->spi);
|
|
struct spi_message *m = ctlr->cur_msg;
|
|
|
|
m->complete = priv->child_msg_complete;
|
|
m->context = priv->child_msg_context;
|
|
m->spi = priv->child_msg_dev;
|
|
spi_finalize_current_message(ctlr);
|
|
mux_control_deselect(priv->mux);
|
|
}
|
|
|
|
static int spi_mux_transfer_one_message(struct spi_controller *ctlr,
|
|
struct spi_message *m)
|
|
{
|
|
struct spi_mux_priv *priv = spi_controller_get_devdata(ctlr);
|
|
struct spi_device *spi = m->spi;
|
|
int ret;
|
|
|
|
ret = spi_mux_select(spi);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/*
|
|
* Replace the complete callback, context and spi_device with our own
|
|
* pointers. Save originals
|
|
*/
|
|
priv->child_msg_complete = m->complete;
|
|
priv->child_msg_context = m->context;
|
|
priv->child_msg_dev = m->spi;
|
|
|
|
m->complete = spi_mux_complete_cb;
|
|
m->context = priv;
|
|
m->spi = priv->spi;
|
|
|
|
/* do the transfer */
|
|
return spi_async(priv->spi, m);
|
|
}
|
|
|
|
static int spi_mux_probe(struct spi_device *spi)
|
|
{
|
|
struct spi_controller *ctlr;
|
|
struct spi_mux_priv *priv;
|
|
int ret;
|
|
|
|
ctlr = spi_alloc_master(&spi->dev, sizeof(*priv));
|
|
if (!ctlr)
|
|
return -ENOMEM;
|
|
|
|
spi_set_drvdata(spi, ctlr);
|
|
priv = spi_controller_get_devdata(ctlr);
|
|
priv->spi = spi;
|
|
|
|
/*
|
|
* Increase lockdep class as these lock are taken while the parent bus
|
|
* already holds their instance's lock.
|
|
*/
|
|
lockdep_set_subclass(&ctlr->io_mutex, 1);
|
|
lockdep_set_subclass(&ctlr->add_lock, 1);
|
|
|
|
priv->mux = devm_mux_control_get(&spi->dev, NULL);
|
|
if (IS_ERR(priv->mux)) {
|
|
ret = dev_err_probe(&spi->dev, PTR_ERR(priv->mux),
|
|
"failed to get control-mux\n");
|
|
goto err_put_ctlr;
|
|
}
|
|
|
|
priv->current_cs = SPI_MUX_NO_CS;
|
|
|
|
/* supported modes are the same as our parent's */
|
|
ctlr->mode_bits = spi->controller->mode_bits;
|
|
ctlr->flags = spi->controller->flags;
|
|
ctlr->transfer_one_message = spi_mux_transfer_one_message;
|
|
ctlr->setup = spi_mux_setup;
|
|
ctlr->num_chipselect = mux_control_states(priv->mux);
|
|
ctlr->bus_num = -1;
|
|
ctlr->dev.of_node = spi->dev.of_node;
|
|
ctlr->must_async = true;
|
|
|
|
ret = devm_spi_register_controller(&spi->dev, ctlr);
|
|
if (ret)
|
|
goto err_put_ctlr;
|
|
|
|
return 0;
|
|
|
|
err_put_ctlr:
|
|
spi_controller_put(ctlr);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static const struct spi_device_id spi_mux_id[] = {
|
|
{ "spi-mux" },
|
|
{ }
|
|
};
|
|
MODULE_DEVICE_TABLE(spi, spi_mux_id);
|
|
|
|
static const struct of_device_id spi_mux_of_match[] = {
|
|
{ .compatible = "spi-mux" },
|
|
{ }
|
|
};
|
|
MODULE_DEVICE_TABLE(of, spi_mux_of_match);
|
|
|
|
static struct spi_driver spi_mux_driver = {
|
|
.probe = spi_mux_probe,
|
|
.driver = {
|
|
.name = "spi-mux",
|
|
.of_match_table = spi_mux_of_match,
|
|
},
|
|
.id_table = spi_mux_id,
|
|
};
|
|
|
|
module_spi_driver(spi_mux_driver);
|
|
|
|
MODULE_DESCRIPTION("SPI multiplexer");
|
|
MODULE_LICENSE("GPL");
|