summaryrefslogtreecommitdiff
path: root/drivers/siox
diff options
context:
space:
mode:
authorUwe Kleine-König <u.kleine-koenig@pengutronix.de>2017-12-19 10:00:13 +0100
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2017-12-19 10:56:53 +0100
commitfd639726bf15fca8ee1a00dce8e0096d0ad9bd18 (patch)
treefba7cf32dc096c9f1022991ba05777b45926a812 /drivers/siox
parent297a344d5238c049fdc234c34dd7bb0d83f01271 (diff)
siox: add gpio bus driver
This bus driver uses GPIOs to control the four SIOX bus lines. Acked-by: Gavin Schenk <g.schenk@eckelmann.de> Signed-off-by: Uwe Kleine-König <u.kleine-koenig@pengutronix.de> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers/siox')
-rw-r--r--drivers/siox/Kconfig9
-rw-r--r--drivers/siox/Makefile1
-rw-r--r--drivers/siox/siox-bus-gpio.c172
3 files changed, 182 insertions, 0 deletions
diff --git a/drivers/siox/Kconfig b/drivers/siox/Kconfig
index bd24d9b50dc6..083d2e62189a 100644
--- a/drivers/siox/Kconfig
+++ b/drivers/siox/Kconfig
@@ -7,3 +7,12 @@ menuconfig SIOX
to drive additional I/O units.
Unless you know better, it is probably safe to say "no" here.
+
+if SIOX
+
+config SIOX_BUS_GPIO
+ tristate "SIOX GPIO bus driver"
+ help
+ SIOX bus driver that controls the four bus lines using GPIOs.
+
+endif
diff --git a/drivers/siox/Makefile b/drivers/siox/Makefile
index d55cb5e08868..a956f65206d5 100644
--- a/drivers/siox/Makefile
+++ b/drivers/siox/Makefile
@@ -1 +1,2 @@
obj-$(CONFIG_SIOX) += siox-core.o
+obj-$(CONFIG_SIOX_BUS_GPIO) += siox-bus-gpio.o
diff --git a/drivers/siox/siox-bus-gpio.c b/drivers/siox/siox-bus-gpio.c
new file mode 100644
index 000000000000..ea7ef982968b
--- /dev/null
+++ b/drivers/siox/siox-bus-gpio.c
@@ -0,0 +1,172 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2015-2017 Pengutronix, Uwe Kleine-König <kernel@pengutronix.de>
+ */
+
+#include <linux/gpio/consumer.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#include <linux/delay.h>
+
+#include "siox.h"
+
+#define DRIVER_NAME "siox-gpio"
+
+struct siox_gpio_ddata {
+ struct gpio_desc *din;
+ struct gpio_desc *dout;
+ struct gpio_desc *dclk;
+ struct gpio_desc *dld;
+};
+
+static unsigned int siox_clkhigh_ns = 1000;
+static unsigned int siox_loadhigh_ns;
+static unsigned int siox_bytegap_ns;
+
+static int siox_gpio_pushpull(struct siox_master *smaster,
+ size_t setbuf_len, const u8 setbuf[],
+ size_t getbuf_len, u8 getbuf[])
+{
+ struct siox_gpio_ddata *ddata = siox_master_get_devdata(smaster);
+ size_t i;
+ size_t cycles = max(setbuf_len, getbuf_len);
+
+ /* reset data and clock */
+ gpiod_set_value_cansleep(ddata->dout, 0);
+ gpiod_set_value_cansleep(ddata->dclk, 0);
+
+ gpiod_set_value_cansleep(ddata->dld, 1);
+ ndelay(siox_loadhigh_ns);
+ gpiod_set_value_cansleep(ddata->dld, 0);
+
+ for (i = 0; i < cycles; ++i) {
+ u8 set = 0, get = 0;
+ size_t j;
+
+ if (i >= cycles - setbuf_len)
+ set = setbuf[i - (cycles - setbuf_len)];
+
+ for (j = 0; j < 8; ++j) {
+ get <<= 1;
+ if (gpiod_get_value_cansleep(ddata->din))
+ get |= 1;
+
+ /* DOUT is logically inverted */
+ gpiod_set_value_cansleep(ddata->dout, !(set & 0x80));
+ set <<= 1;
+
+ gpiod_set_value_cansleep(ddata->dclk, 1);
+ ndelay(siox_clkhigh_ns);
+ gpiod_set_value_cansleep(ddata->dclk, 0);
+ }
+
+ if (i < getbuf_len)
+ getbuf[i] = get;
+
+ ndelay(siox_bytegap_ns);
+ }
+
+ gpiod_set_value_cansleep(ddata->dld, 1);
+ ndelay(siox_loadhigh_ns);
+ gpiod_set_value_cansleep(ddata->dld, 0);
+
+ /*
+ * Resetting dout isn't necessary protocol wise, but it makes the
+ * signals more pretty because the dout level is deterministic between
+ * cycles. Note that this only affects dout between the master and the
+ * first siox device. dout for the later devices depend on the output of
+ * the previous siox device.
+ */
+ gpiod_set_value_cansleep(ddata->dout, 0);
+
+ return 0;
+}
+
+static int siox_gpio_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct siox_gpio_ddata *ddata;
+ int ret;
+ struct siox_master *smaster;
+
+ smaster = siox_master_alloc(&pdev->dev, sizeof(*ddata));
+ if (!smaster) {
+ dev_err(dev, "failed to allocate siox master\n");
+ return -ENOMEM;
+ }
+
+ platform_set_drvdata(pdev, smaster);
+ ddata = siox_master_get_devdata(smaster);
+
+ ddata->din = devm_gpiod_get(dev, "din", GPIOD_IN);
+ if (IS_ERR(ddata->din)) {
+ ret = PTR_ERR(ddata->din);
+ dev_err(dev, "Failed to get %s GPIO: %d\n", "din", ret);
+ goto err;
+ }
+
+ ddata->dout = devm_gpiod_get(dev, "dout", GPIOD_OUT_LOW);
+ if (IS_ERR(ddata->dout)) {
+ ret = PTR_ERR(ddata->dout);
+ dev_err(dev, "Failed to get %s GPIO: %d\n", "dout", ret);
+ goto err;
+ }
+
+ ddata->dclk = devm_gpiod_get(dev, "dclk", GPIOD_OUT_LOW);
+ if (IS_ERR(ddata->dclk)) {
+ ret = PTR_ERR(ddata->dclk);
+ dev_err(dev, "Failed to get %s GPIO: %d\n", "dclk", ret);
+ goto err;
+ }
+
+ ddata->dld = devm_gpiod_get(dev, "dld", GPIOD_OUT_LOW);
+ if (IS_ERR(ddata->dld)) {
+ ret = PTR_ERR(ddata->dld);
+ dev_err(dev, "Failed to get %s GPIO: %d\n", "dld", ret);
+ goto err;
+ }
+
+ smaster->pushpull = siox_gpio_pushpull;
+ /* XXX: determine automatically like spi does */
+ smaster->busno = 0;
+
+ ret = siox_master_register(smaster);
+ if (ret) {
+ dev_err(dev, "Failed to register siox master: %d\n", ret);
+err:
+ siox_master_put(smaster);
+ }
+
+ return ret;
+}
+
+static int siox_gpio_remove(struct platform_device *pdev)
+{
+ struct siox_master *master = platform_get_drvdata(pdev);
+
+ siox_master_unregister(master);
+
+ return 0;
+}
+
+static const struct of_device_id siox_gpio_dt_ids[] = {
+ { .compatible = "eckelmann,siox-gpio", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, siox_gpio_dt_ids);
+
+static struct platform_driver siox_gpio_driver = {
+ .probe = siox_gpio_probe,
+ .remove = siox_gpio_remove,
+
+ .driver = {
+ .name = DRIVER_NAME,
+ .of_match_table = siox_gpio_dt_ids,
+ },
+};
+module_platform_driver(siox_gpio_driver);
+
+MODULE_AUTHOR("Uwe Kleine-Koenig <u.kleine-koenig@pengutronix.de>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:" DRIVER_NAME);