[PATCH 2/2] gpio: add a driver for GPIOs going through a level shifter

From: Alban Bedel
Date: Mon Nov 24 2014 - 09:02:12 EST


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

Signed-off-by: Alban Bedel <alban.bedel@xxxxxxxxxxxxxxxxx>
---
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.bedel@xxxxxxxxxxxxxxxxx>
+ *
+ * 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.bedel@xxxxxxxxxxxxxxxxx>");
+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 linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/