This driver support setting the level shifter direction and/or enable
as needed.

Signed-off-by: Alban Bedel <alban.be...@avionic-design.de>
---
 drivers/gpio/Kconfig              |   6 +
 drivers/gpio/Makefile             |   1 +
 drivers/gpio/gpio-level-shifter.c | 248 ++++++++++++++++++++++++++++++++++++++
 3 files changed, 255 insertions(+)
 create mode 100644 drivers/gpio/gpio-level-shifter.c

diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index 0959ca9..bb00cc5 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -181,6 +181,12 @@ config GPIO_F7188X
          To compile this driver as a module, choose M here: the module will
          be called f7188x-gpio.
 
+config GPIO_LEVEL_SHIFTER
+       tristate "Level shifter GPIO support"
+       help
+         This enables support for GPIOs that are going through a simple level
+         shifter.
+
 config GPIO_MOXART
        bool "MOXART GPIO support"
        depends on ARCH_MOXART
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index e5d346c..e9adc12 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -39,6 +39,7 @@ obj-$(CONFIG_GPIO_JANZ_TTL)   += gpio-janz-ttl.o
 obj-$(CONFIG_GPIO_KEMPLD)      += gpio-kempld.o
 obj-$(CONFIG_ARCH_KS8695)      += gpio-ks8695.o
 obj-$(CONFIG_GPIO_INTEL_MID)   += gpio-intel-mid.o
+obj-$(CONFIG_GPIO_LEVEL_SHIFTER)+= gpio-level-shifter.o
 obj-$(CONFIG_GPIO_LP3943)      += gpio-lp3943.o
 obj-$(CONFIG_ARCH_LPC32XX)     += gpio-lpc32xx.o
 obj-$(CONFIG_GPIO_LYNXPOINT)   += gpio-lynxpoint.o
diff --git a/drivers/gpio/gpio-level-shifter.c 
b/drivers/gpio/gpio-level-shifter.c
new file mode 100644
index 0000000..1760048
--- /dev/null
+++ b/drivers/gpio/gpio-level-shifter.c
@@ -0,0 +1,248 @@
+/*
+ * Driver for GPIOs that are going through a level shifter.
+ *
+ * Copyright (C) 2014 - Alban Bedel
+ *
+ * Author: Alban Bedel <alban.be...@avionic-design.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/spinlock.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/gpio/driver.h>
+#include <linux/gpio/consumer.h>
+#include <linux/regulator/consumer.h>
+
+#define MAX_DATA_GPIO 32
+
+enum level_shifter_direction {
+       DIRECTION_NONE,
+       DIRECTION_INPUT,
+       DIRECTION_OUTPUT
+};
+
+struct level_shifter_gpio {
+       struct gpio_chip gc;
+
+       spinlock_t lock;
+       int num_requested;
+
+       struct gpio_desc *data_gpio[MAX_DATA_GPIO];
+       struct gpio_desc *enable_gpio;
+       struct gpio_desc *direction_gpio;
+       enum level_shifter_direction direction;
+
+       struct regulator *vcc_a;
+       struct regulator *vcc_b;
+};
+
+#define to_level_shifter_gpio(c) \
+       container_of(c, struct level_shifter_gpio, gc)
+
+int level_shifter_gpio_request(struct gpio_chip *chip, unsigned offset)
+{
+       struct level_shifter_gpio *ls = to_level_shifter_gpio(chip);
+
+       spin_lock(&ls->lock);
+
+       if (ls->num_requested == 0 && ls->enable_gpio)
+               gpiod_set_value(ls->enable_gpio, 1);
+
+       ls->num_requested++;
+
+       spin_unlock(&ls->lock);
+
+       return 0;
+}
+
+void level_shifter_gpio_free(struct gpio_chip *chip, unsigned offset)
+{
+       struct level_shifter_gpio *ls = to_level_shifter_gpio(chip);
+
+       spin_lock(&ls->lock);
+
+       ls->num_requested--;
+
+       if (ls->num_requested == 0) {
+               if (ls->enable_gpio)
+                       gpiod_set_value(ls->enable_gpio, 0);
+               if (ls->direction_gpio)
+                       ls->direction = DIRECTION_NONE;
+       }
+
+       spin_unlock(&ls->lock);
+}
+
+static void level_shifter_gpio_set(
+       struct gpio_chip *chip, unsigned offset, int value)
+{
+       struct level_shifter_gpio *ls = to_level_shifter_gpio(chip);
+
+       gpiod_set_value(ls->data_gpio[offset], value);
+}
+
+static int level_shifter_gpio_direction_output(
+       struct gpio_chip *chip, unsigned offset, int value)
+{
+       struct level_shifter_gpio *ls = to_level_shifter_gpio(chip);
+       int err;
+
+       spin_lock(&ls->lock);
+
+       /* Set the direction GPIO if needed */
+       if (ls->direction_gpio) {
+               /* We can't change the direction once set */
+               if (ls->direction == DIRECTION_INPUT) {
+                       spin_unlock(&ls->lock);
+                       return -EINVAL;
+               }
+               gpiod_set_value(ls->direction_gpio, 1);
+       }
+
+       err = gpiod_direction_output(ls->data_gpio[offset], value);
+
+       /* Save the direction if there was no error */
+       if (!err && ls->direction_gpio)
+               ls->direction = DIRECTION_OUTPUT;
+
+       spin_unlock(&ls->lock);
+
+       return err;
+}
+
+static int level_shifter_gpio_get(struct gpio_chip *chip, unsigned offset)
+{
+       struct level_shifter_gpio *ls = to_level_shifter_gpio(chip);
+
+       return gpiod_get_value(ls->data_gpio[offset]);
+}
+
+static int level_shifter_gpio_direction_input(
+       struct gpio_chip *chip, unsigned offset)
+{
+       struct level_shifter_gpio *ls = to_level_shifter_gpio(chip);
+       int err;
+
+       spin_lock(&ls->lock);
+
+       /* Set the direction GPIO if needed */
+       if (ls->direction_gpio) {
+               /* We can't change the direction once set */
+               if (ls->direction == DIRECTION_OUTPUT) {
+                       spin_unlock(&ls->lock);
+                       return -EINVAL;
+               }
+               gpiod_set_value(ls->direction_gpio, 1);
+       }
+
+       err = gpiod_direction_input(ls->data_gpio[offset]);
+
+       /* Save the direction if there was no error */
+       if (!err && ls->direction_gpio)
+               ls->direction = DIRECTION_INPUT;
+
+       spin_unlock(&ls->lock);
+
+       return err;
+}
+
+static int level_shifter_gpio_probe(struct platform_device *pdev)
+{
+       struct device_node *np = pdev->dev.of_node;
+       struct level_shifter_gpio *ls;
+       struct gpio_desc *gpiod;
+       int err, i;
+
+       ls = devm_kzalloc(&pdev->dev, sizeof(*ls), GFP_KERNEL);
+       if (!ls)
+               return -ENOMEM;
+
+       spin_lock_init(&ls->lock);
+
+       if (np)
+               ls->gc.label = np->name;
+       else
+               ls->gc.label = "level-shifter-gpio";
+
+       ls->gc.of_node = pdev->dev.of_node;
+       ls->gc.base = -1;
+       ls->gc.request = level_shifter_gpio_request;
+       ls->gc.free = level_shifter_gpio_free;
+       ls->gc.set = level_shifter_gpio_set;
+       ls->gc.direction_output = level_shifter_gpio_direction_output;
+       ls->gc.get = level_shifter_gpio_get;
+       ls->gc.direction_input = level_shifter_gpio_direction_input;
+
+       for (i = 0; i < MAX_DATA_GPIO; i++) {
+               gpiod = devm_gpiod_get_index_optional(
+                       &pdev->dev, "data", i, GPIOD_IN);
+               if (!gpiod)
+                       continue;
+               if (IS_ERR(gpiod))
+                       return PTR_ERR(gpiod);
+
+               ls->data_gpio[ls->gc.ngpio++] = gpiod;
+       }
+
+       ls->enable_gpio = devm_gpiod_get_optional(
+               &pdev->dev, "enable", GPIOD_OUT_LOW);
+       if (IS_ERR(ls->enable_gpio))
+               return PTR_ERR(ls->enable_gpio);
+
+       ls->direction_gpio = devm_gpiod_get_optional(
+               &pdev->dev, "direction", GPIOD_OUT_LOW);
+       if (IS_ERR(ls->direction_gpio))
+               return PTR_ERR(ls->direction_gpio);
+
+       ls->vcc_a = devm_regulator_get_optional(&pdev->dev, "vcca");
+       if (IS_ERR(ls->vcc_a) && PTR_ERR(ls->vcc_a) != -ENODEV)
+               return PTR_ERR(ls->vcc_a);
+       if (!IS_ERR(ls->vcc_a)) {
+               err = regulator_enable(ls->vcc_a);
+               if (err)
+                       return err;
+       }
+
+       ls->vcc_b = devm_regulator_get_optional(&pdev->dev, "vccb");
+       if (IS_ERR(ls->vcc_b) && PTR_ERR(ls->vcc_b) != -ENODEV)
+               return PTR_ERR(ls->vcc_b);
+       if (!IS_ERR(ls->vcc_b)) {
+               err = regulator_enable(ls->vcc_b);
+               if (err)
+                       return err;
+       }
+
+       err = gpiochip_add(&ls->gc);
+       if (err) {
+               if (!IS_ERR(ls->vcc_a))
+                       regulator_disable(ls->vcc_a);
+               if (!IS_ERR(ls->vcc_b))
+                       regulator_disable(ls->vcc_b);
+       }
+       return err;
+}
+
+static const struct of_device_id level_shifter_gpio_of_match[] = {
+       { .compatible = "gpio-level-shifter"},
+       {},
+};
+MODULE_DEVICE_TABLE(of, level_shifter_gpio_of_match);
+
+static struct platform_driver level_shifter_gpio_driver = {
+       .driver = {
+               .name = "level-shifter-gpio",
+               .owner = THIS_MODULE,
+               .of_match_table = level_shifter_gpio_of_match,
+       },
+       .probe = level_shifter_gpio_probe,
+};
+module_platform_driver(level_shifter_gpio_driver);
+
+MODULE_AUTHOR("Alban Bedel <alban.be...@avionic-design.de>");
+MODULE_DESCRIPTION("Driver for GPIO going thru a level shifter");
+MODULE_LICENSE("GPL v2");
-- 
2.1.3

--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to