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@xxxxxxxxxxxxxxxxxxx> --- 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 --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"); -- 2.21.0