spi: Add generic SPI multiplexer
authorChris Packham <chris.packham@alliedtelesis.co.nz>
Tue, 4 Feb 2020 03:28:38 +0000 (16:28 +1300)
committerMark Brown <broonie@kernel.org>
Wed, 12 Feb 2020 23:56:18 +0000 (23:56 +0000)
Add a SPI device driver that sits in-band and provides a SPI controller
which supports chip selects via a mux-control. This enables extra SPI
devices to be connected with limited native chip selects.

Signed-off-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
Reviewed-by: Andy Shevchenko <andy.shevchenko@gmail.com>
Link: https://lore.kernel.org/r/20200204032838.20739-3-chris.packham@alliedtelesis.co.nz
Signed-off-by: Mark Brown <broonie@kernel.org>
drivers/spi/Kconfig
drivers/spi/Makefile
drivers/spi/spi-mux.c [new file with mode: 0644]

index 0434614..887fefe 100644 (file)
@@ -897,6 +897,17 @@ config SPI_ZYNQMP_GQSPI
 # Add new SPI master controllers in alphabetical order above this line
 #
 
+comment "SPI Multiplexer support"
+
+config SPI_MUX
+       tristate "SPI multiplexer support"
+       select MULTIPLEXER
+       help
+         This adds support for SPI multiplexers. Each SPI mux will be
+         accessible as a SPI controller, the devices behind the mux will appear
+         to be chip selects on this controller. It is still necessary to
+         select one or more specific mux-controller drivers.
+
 #
 # There are lots of SPI device types, with sensors and memory
 # being probably the most widely used ones.
index 9892bdf..74db1f2 100644 (file)
@@ -9,6 +9,7 @@ ccflags-$(CONFIG_SPI_DEBUG) := -DDEBUG
 # config declarations into driver model code
 obj-$(CONFIG_SPI_MASTER)               += spi.o
 obj-$(CONFIG_SPI_MEM)                  += spi-mem.o
+obj-$(CONFIG_SPI_MUX)                  += spi-mux.o
 obj-$(CONFIG_SPI_SPIDEV)               += spidev.o
 obj-$(CONFIG_SPI_LOOPBACK_TEST)                += spi-loopback-test.o
 
diff --git a/drivers/spi/spi-mux.c b/drivers/spi/spi-mux.c
new file mode 100644 (file)
index 0000000..4f94c91
--- /dev/null
@@ -0,0 +1,187 @@
+// 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;
+
+       if (priv->current_cs == spi->chip_select)
+               return 0;
+
+       dev_dbg(&priv->spi->dev, "setting up the mux for cs %d\n",
+               spi->chip_select);
+
+       /* 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;
+
+       ret = mux_control_select(priv->mux, spi->chip_select);
+       if (ret)
+               return ret;
+
+       priv->current_cs = spi->chip_select;
+
+       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;
+
+       priv->mux = devm_mux_control_get(&spi->dev, NULL);
+       if (IS_ERR(priv->mux)) {
+               ret = PTR_ERR(priv->mux);
+               if (ret != -EPROBE_DEFER)
+                       dev_err(&spi->dev, "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;
+
+       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 of_device_id spi_mux_of_match[] = {
+       { .compatible = "spi-mux" },
+       { }
+};
+
+static struct spi_driver spi_mux_driver = {
+       .probe  = spi_mux_probe,
+       .driver = {
+               .name   = "spi-mux",
+               .of_match_table = spi_mux_of_match,
+       },
+};
+
+module_spi_driver(spi_mux_driver);
+
+MODULE_DESCRIPTION("SPI multiplexer");
+MODULE_LICENSE("GPL");