linux/drivers/spi/spi-mux.c

207 lines
5.4 KiB
C
Raw Normal View History

// 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);
spi_setup(priv->spi);
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_host(&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->bits_per_word_mask = spi->controller->bits_per_word_mask;
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;
spi: mux: Fix mux interaction with fast path optimisations The spi-mux driver is rather too clever and attempts to resubmit any message that is submitted to it to the parent controller with some adjusted callbacks. This does not play at all nicely with the fast path which now sets flags on the message indicating that it's being handled through the fast path, we see async messages flagged as being on the fast path. Ideally the spi-mux code would duplicate the message but that's rather invasive and a bit fragile in that it relies on the mux knowing which fields in the message to copy. Instead teach the core that there are controllers which can't cope with the fast path and have the mux flag itself as being such a controller, ensuring that messages going via the mux don't get partially handled via the fast path. This will reduce the performance of any spi-mux connected device since we'll now always use the thread for both the actual controller and the mux controller instead of just the actual controller but given that we were always hitting the slow path anyway it's hopefully not too much of an additional cost and it allows us to keep the fast path. Fixes: ae7d2346dc89 ("spi: Don't use the message queue if possible in spi_sync") Reported-by: Casper Andersson <casper.casan@gmail.com> Tested-by: Casper Andersson <casper.casan@gmail.com> Signed-off-by: Mark Brown <broonie@kernel.org> Link: https://lore.kernel.org/r/20220901120732.49245-1-broonie@kernel.org Signed-off-by: Mark Brown <broonie@kernel.org>
2022-09-01 12:07:32 +00:00
ctlr->must_async = true;
spi: add defer_optimize_message controller flag Adding spi_optimize_message() broke the spi-mux driver because it calls spi_async() from it's transfer_one_message() callback. This resulted in passing an incorrectly optimized message to the controller. For example, if the underlying controller has an optimize_message() callback, this would have not been called and can cause a crash when the underlying controller driver tries to transfer the message. Also, since the spi-mux driver swaps out the controller pointer by replacing msg->spi, __spi_unoptimize_message() was being called with a different controller than the one used in __spi_optimize_message(). This could cause a crash when attempting to free the message resources when __spi_unoptimize_message() is called in spi_finalize_current_message() since it is being called with a controller that did not allocate the resources. This is fixed by adding a defer_optimize_message flag for controllers. This flag causes all of the spi_[maybe_][un]optimize_message() calls to be a no-op (other than attaching a pointer to the spi device to the message). This allows the spi-mux driver to pass an unmodified message to spi_async() in spi_mux_transfer_one_message() after the spi device has been swapped out. This causes __spi_optimize_message() and __spi_unoptimize_message() to be called only once per message and with the correct/same controller in each case. Reported-by: Oleksij Rempel <o.rempel@pengutronix.de> Closes: https://lore.kernel.org/linux-spi/Zn6HMrYG2b7epUxT@pengutronix.de/ Reported-by: Marc Kleine-Budde <mkl@pengutronix.de> Closes: https://lore.kernel.org/linux-spi/20240628-awesome-discerning-bear-1621f9-mkl@pengutronix.de/ Fixes: 7b1d87af14d9 ("spi: add spi_optimize_message() APIs") Signed-off-by: David Lechner <dlechner@baylibre.com> Link: https://patch.msgid.link/20240708-spi-mux-fix-v1-2-6c8845193128@baylibre.com Signed-off-by: Mark Brown <broonie@kernel.org>
2024-07-09 01:05:29 +00:00
ctlr->defer_optimize_message = 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");