@@ -426,6 +426,13 @@ config SPI_MT65XX
say Y or M here.If you are not sure, say N.
SPI drivers for Mediatek MT65XX and MT81XX series ARM SoCs.
+config SPI_MUX_GPIO
+ tristate "SPI bus gpio multiplexer"
+ help
+ This selects the SPI bus gpio multiplexer.
+ If you have a hardware design that requires multiplexing
+ on the SPI bus say Y or M here. If you are not sure, say N.
+
config SPI_NPCM_PSPI
tristate "Nuvoton NPCM PSPI Controller"
depends on ARCH_NPCM || COMPILE_TEST
@@ -62,6 +62,7 @@ obj-$(CONFIG_SPI_MPC52xx) += spi-mpc52xx.o
obj-$(CONFIG_SPI_MT65XX) += spi-mt65xx.o
obj-$(CONFIG_SPI_MXIC) += spi-mxic.o
obj-$(CONFIG_SPI_MXS) += spi-mxs.o
+obj-$(CONFIG_SPI_MUX_GPIO) += spi-mux-gpio.o
obj-$(CONFIG_SPI_NPCM_PSPI) += spi-npcm-pspi.o
obj-$(CONFIG_SPI_NUC900) += spi-nuc900.o
obj-$(CONFIG_SPI_NXP_FLEXSPI) += spi-nxp-fspi.o
new file mode 100644
@@ -0,0 +1,169 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2019 Allied Telesis
+ */
+
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_device.h>
+#include <linux/spi/spi.h>
+#include <linux/gpio/consumer.h>
+
+struct spi_mux_gpio {
+ struct gpio_descs *gpios;
+ struct spi_controller *ctlr;
+ struct spi_controller *parent_ctlr;
+ int chip_select;
+};
+
+static void spi_mux_set_cs(struct spi_device *spi, bool enable)
+{
+ DECLARE_BITMAP(values, BITS_PER_TYPE(spi->chip_select));
+ struct spi_mux_gpio *mux = spi_master_get_devdata(spi->controller);
+ struct spi_device spidev = *spi;
+
+ values[0] = spi->chip_select;
+
+ gpiod_set_array_value_cansleep(mux->gpios->ndescs,
+ mux->gpios->desc,
+ mux->gpios->info, values);
+
+ spidev.controller = mux->parent_ctlr;
+ spidev.master = mux->parent_ctlr;
+ spidev.chip_select = mux->chip_select;
+
+ mux->parent_ctlr->set_cs(&spidev, enable);
+}
+
+static int spi_mux_transfer_one(struct spi_controller *ctlr,
+ struct spi_device *spi,
+ struct spi_transfer *transfer)
+{
+ struct spi_mux_gpio *mux = spi_master_get_devdata(ctlr);
+ struct spi_device spidev = *spi;
+
+ spidev.controller = mux->parent_ctlr;
+ spidev.master = mux->parent_ctlr;
+ spidev.chip_select = mux->chip_select;
+
+ return mux->parent_ctlr->transfer_one(mux->parent_ctlr, &spidev, transfer);
+
+}
+
+static int spi_mux_setup(struct spi_device *spi)
+{
+ struct spi_mux_gpio *mux = spi_master_get_devdata(spi->controller);
+ struct spi_device spidev = *spi;
+
+ spidev.controller = mux->parent_ctlr;
+ spidev.master = mux->parent_ctlr;
+ spidev.chip_select = mux->chip_select;
+
+ return mux->parent_ctlr->setup(&spidev);
+}
+
+static int spi_mux_gpio_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct device_node *parent;
+ struct spi_controller *parent_ctlr;
+ struct spi_controller *ctlr;
+ struct spi_mux_gpio *mux;
+ struct gpio_descs *gpios;
+ int ret;
+
+ gpios = devm_gpiod_get_array(&pdev->dev, NULL, GPIOD_OUT_LOW);
+ if (IS_ERR(gpios))
+ return PTR_ERR(gpios);
+
+ parent = of_parse_phandle(np, "spi-parent-bus", 0);
+ if (!parent)
+ return -ENODEV;
+
+ parent_ctlr = of_find_spi_controller_by_node(parent);
+ if (!parent_ctlr) {
+ ret = -EPROBE_DEFER;
+ goto err_put_node;
+ }
+
+ ctlr = spi_alloc_master(&pdev->dev, sizeof(*mux));
+ if (!ctlr) {
+ ret = -ENOMEM;
+ goto err_put_device;
+ }
+ mux = spi_master_get_devdata(ctlr);
+ platform_set_drvdata(pdev, mux);
+
+ ctlr->mode_bits = parent_ctlr->mode_bits;
+ ctlr->bits_per_word_mask = parent_ctlr->bits_per_word_mask;
+ ctlr->auto_runtime_pm = parent_ctlr->auto_runtime_pm;
+ ctlr->flags = parent_ctlr->flags;
+ ctlr->set_cs = spi_mux_set_cs;
+ ctlr->transfer_one = spi_mux_transfer_one;
+ ctlr->setup = spi_mux_setup;
+ ctlr->num_chipselect = of_get_available_child_count(np);
+ ctlr->bus_num = -1;
+
+ mux->gpios = gpios;
+ mux->ctlr = ctlr;
+ mux->parent_ctlr = parent_ctlr;
+ ret = of_property_read_u32(np, "spi-parent-cs",
+ &mux->chip_select);
+ if (ret)
+ mux->chip_select = 0;
+
+ ctlr->dev.of_node = np;
+ ret = devm_spi_register_controller(&pdev->dev, ctlr);
+ if (ret) {
+ dev_err(&pdev->dev, "Error: failed to register SPI bus %pOF %d\n",
+ np, ret);
+ goto err_put_ctlr;
+ }
+
+ return 0;
+
+err_put_ctlr:
+ spi_controller_put(ctlr);
+err_put_device:
+ put_device(&parent_ctlr->dev);
+err_put_node:
+ of_node_put(parent);
+
+ return ret;
+}
+
+static int spi_mux_gpio_remove(struct platform_device *pdev)
+{
+ struct spi_mux_gpio *mux = platform_get_drvdata(pdev);
+
+ spi_controller_put(mux->ctlr);
+ put_device(&mux->parent_ctlr->dev);
+
+ return 0;
+}
+
+static const struct of_device_id spi_mux_gpio_of_match[] = {
+ { .compatible = "spi-mux-gpio", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, spi_mux_gpio_of_match);
+
+static struct platform_driver spi_mux_gpio_driver = {
+ .probe = spi_mux_gpio_probe,
+ .remove = spi_mux_gpio_remove,
+ .driver = {
+ .name = "spi-mux-gpio",
+ .of_match_table = spi_mux_gpio_of_match,
+ },
+};
+
+module_platform_driver(spi_mux_gpio_driver);
+
+MODULE_DESCRIPTION("SPI bus mutliplexer driver");
+MODULE_AUTHOR("Allied Telesis");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:spi-mux-gpio");
This add support for a gpio based multiplexer for SPI buses. This can be used in situations where the cs-gpios property does not work with the hardware design. In particular this support situations where a single gpio is used to select between two possible devices. Signed-off-by: Chris Packham <chris.packham@alliedtelesis.co.nz> --- drivers/spi/Kconfig | 7 ++ drivers/spi/Makefile | 1 + drivers/spi/spi-mux-gpio.c | 169 +++++++++++++++++++++++++++++++++++++ 3 files changed, 177 insertions(+) create mode 100644 drivers/spi/spi-mux-gpio.c