diff mbox series

[3/3] spi: Add SPI bus gpio multiplexer

Message ID 20190412050213.17698-4-chris.packham@alliedtelesis.co.nz (mailing list archive)
State New, archived
Headers show
Series spi: SPI bus multiplexer | expand

Commit Message

Chris Packham April 12, 2019, 5:02 a.m. UTC
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
diff mbox series

Patch

diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index f761655e2a36..bfb4bd5bc2f3 100644
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -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
diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
index d8fc03c9faa2..32d831374e1f 100644
--- a/drivers/spi/Makefile
+++ b/drivers/spi/Makefile
@@ -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
diff --git a/drivers/spi/spi-mux-gpio.c b/drivers/spi/spi-mux-gpio.c
new file mode 100644
index 000000000000..3666863a4e3f
--- /dev/null
+++ b/drivers/spi/spi-mux-gpio.c
@@ -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");