From 3d2455f9da30f923c6bd69014fad4cc4ea738be6 Mon Sep 17 00:00:00 2001
From: Jussi Laako <jussi.laako@linux.intel.com>
Date: Thu, 10 Mar 2016 11:58:11 -0800
Subject: [PATCH] gpio-pca953x: add "drive" property

Galileo gen 2 has support for setting GPIO modes. Expose these
properties through the GPIO sysfs interface. This approach is bit hacky,
since it changes the interface semantics.

The original patch was by Josef Ahmad <josef.ahmad@linux.intel.com> and
made on top of kernel 3.8.

Signed-off-by: Ismo Puustinen <ismo.puustinen@intel.com>
Signed-off-by: Jussi Laako <jussi.laako@linux.intel.com>
Signed-off-by: Saul Wold <sgw@linux.intel.com>
Signed-off-by: Bruce Ashfield <bruce.ashfield@windriver.com>
---
 drivers/gpio/gpio-pca953x.c   | 57 +++++++++++++++++++++++++++----
 drivers/gpio/gpiolib-sysfs.c  | 78 +++++++++++++++++++++++++++++++++++++++++++
 drivers/gpio/gpiolib.c        | 18 ++++++++++
 drivers/gpio/gpiolib.h        |  4 +++
 include/asm-generic/gpio.h    |  5 +++
 include/linux/gpio.h          | 10 ++++++
 include/linux/gpio/consumer.h | 11 ++++++
 include/linux/gpio/driver.h   |  2 ++
 8 files changed, 178 insertions(+), 7 deletions(-)

diff --git a/drivers/gpio/gpio-pca953x.c b/drivers/gpio/gpio-pca953x.c
index f23a4b4c26a2..f99706fd470f 100644
--- a/drivers/gpio/gpio-pca953x.c
+++ b/drivers/gpio/gpio-pca953x.c
@@ -39,6 +39,9 @@
 #define PCA957X_MSK		6
 #define PCA957X_INTS		7
 
+#define PCA953X_PUPD_EN	35
+#define PCA953X_PUPD_SEL	36
+
 #define PCA_GPIO_MASK		0x00FF
 #define PCA_INT			0x0100
 #define PCA953X_TYPE		0x1000
@@ -375,6 +378,43 @@ exit:
 	mutex_unlock(&chip->i2c_lock);
 }
 
+static int pca953x_gpio_set_drive(struct gpio_chip *gc,
+				 unsigned off, unsigned mode)
+{
+	struct pca953x_chip *chip;
+	int ret = 0;
+	int val;
+
+	chip = container_of(gc, struct pca953x_chip, gpio_chip);
+
+	if (chip->chip_type != PCA953X_TYPE)
+		return -EINVAL;
+
+	mutex_lock(&chip->i2c_lock);
+
+	switch (mode) {
+	case GPIOF_DRIVE_PULLUP:
+		ret = pca953x_write_single(chip, PCA953X_PUPD_EN, 1, off) ||
+				pca953x_write_single(chip, PCA953X_PUPD_SEL, 1, off);
+		break;
+	case GPIOF_DRIVE_PULLDOWN:
+		ret = pca953x_write_single(chip, PCA953X_PUPD_EN, 1, off) ||
+				pca953x_write_single(chip, PCA953X_PUPD_SEL, 0, off);
+		break;
+	case GPIOF_DRIVE_STRONG:
+	case GPIOF_DRIVE_HIZ:
+		ret = pca953x_read_single(chip, PCA953X_PUPD_EN, &val, off) ||
+				pca953x_write_single(chip, PCA953X_PUPD_EN, 0, off) ||
+				pca953x_write_single(chip, PCA953X_PUPD_SEL, val, off);
+		break;
+	default:
+		ret = -EINVAL;
+	}
+
+	mutex_unlock(&chip->i2c_lock);
+	return ret;
+}
+
 static void pca953x_setup_gpio(struct pca953x_chip *chip, int gpios)
 {
 	struct gpio_chip *gc;
@@ -393,6 +433,9 @@ static void pca953x_setup_gpio(struct pca953x_chip *chip, int gpios)
 	gc->dev = &chip->client->dev;
 	gc->owner = THIS_MODULE;
 	gc->names = chip->names;
+
+	if (chip->chip_type == PCA953X_TYPE)
+		gc->set_drive = pca953x_gpio_set_drive;
 }
 
 #ifdef CONFIG_GPIO_PCA953X_IRQ
@@ -552,7 +595,7 @@ static irqreturn_t pca953x_irq_handler(int irq, void *devid)
 }
 
 static int pca953x_irq_setup(struct pca953x_chip *chip,
-			     int irq_base)
+				 int irq_base)
 {
 	struct i2c_client *client = chip->client;
 	int ret, i, offset = 0;
@@ -595,10 +638,10 @@ static int pca953x_irq_setup(struct pca953x_chip *chip,
 		}
 
 		ret =  gpiochip_irqchip_add(&chip->gpio_chip,
-					    &pca953x_irq_chip,
-					    irq_base,
-					    handle_simple_irq,
-					    IRQ_TYPE_NONE);
+						&pca953x_irq_chip,
+						irq_base,
+						handle_simple_irq,
+						IRQ_TYPE_NONE);
 		if (ret) {
 			dev_err(&client->dev,
 				"could not connect irqchip to gpiochip\n");
@@ -615,7 +658,7 @@ static int pca953x_irq_setup(struct pca953x_chip *chip,
 
 #else /* CONFIG_GPIO_PCA953X_IRQ */
 static int pca953x_irq_setup(struct pca953x_chip *chip,
-			     int irq_base)
+				 int irq_base)
 {
 	struct i2c_client *client = chip->client;
 
@@ -636,7 +679,7 @@ static int device_pca953x_init(struct pca953x_chip *chip, u32 invert)
 		goto out;
 
 	ret = pca953x_read_regs(chip, PCA953X_DIRECTION,
-			       chip->reg_direction);
+				   chip->reg_direction);
 	if (ret)
 		goto out;
 
diff --git a/drivers/gpio/gpiolib-sysfs.c b/drivers/gpio/gpiolib-sysfs.c
index b57ed8e55ab5..b60e47738f84 100644
--- a/drivers/gpio/gpiolib-sysfs.c
+++ b/drivers/gpio/gpiolib-sysfs.c
@@ -7,6 +7,7 @@
 #include <linux/interrupt.h>
 #include <linux/kdev_t.h>
 #include <linux/slab.h>
+#include <linux/gpio.h>
 
 #include "gpiolib.h"
 
@@ -350,6 +351,82 @@ static ssize_t active_low_store(struct device *dev,
 }
 static DEVICE_ATTR_RW(active_low);
 
+
+static ssize_t gpio_drive_show(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	const struct gpio_desc	*desc = dev_get_drvdata(dev);
+	ssize_t			status;
+
+	mutex_lock(&sysfs_lock);
+
+	if (!test_bit(FLAG_EXPORT, &desc->flags)) {
+		status = -EIO;
+	} else {
+		if (test_bit(FLAG_PULLUP, &desc->flags))
+			status = sprintf(buf, "pullup\n");
+		else if (test_bit(FLAG_PULLDOWN, &desc->flags))
+			status = sprintf(buf, "pulldown\n");
+		else if (test_bit(FLAG_STRONG, &desc->flags))
+			status = sprintf(buf, "strong\n");
+		else if (test_bit(FLAG_HIZ, &desc->flags))
+			status = sprintf(buf, "hiz\n");
+		else
+			status = -EINVAL;
+	}
+
+	mutex_unlock(&sysfs_lock);
+	return status;
+}
+
+static ssize_t gpio_drive_store(struct device *dev,
+		struct device_attribute *attr, const char *buf, size_t size)
+{
+	struct gpio_desc	*desc = dev_get_drvdata(dev);
+	ssize_t			status;
+
+	mutex_lock(&sysfs_lock);
+
+	if (!test_bit(FLAG_EXPORT, &desc->flags))
+		status = -EIO;
+	else {
+		clear_bit(FLAG_PULLUP, &desc->flags);
+		clear_bit(FLAG_PULLDOWN, &desc->flags);
+		clear_bit(FLAG_STRONG, &desc->flags);
+		clear_bit(FLAG_HIZ, &desc->flags);
+		if (sysfs_streq(buf, "pullup")) {
+			status = gpiod_set_drive(desc, GPIOF_DRIVE_PULLUP);
+			if (!status) {
+				set_bit(FLAG_PULLUP, &desc->flags);
+			}
+		} else if (sysfs_streq(buf, "pulldown")) {
+			status = gpiod_set_drive(desc, GPIOF_DRIVE_PULLDOWN);
+			if (!status) {
+				set_bit(FLAG_PULLDOWN, &desc->flags);
+			}
+		} else if (sysfs_streq(buf, "strong")) {
+			status = gpiod_set_drive(desc, GPIOF_DRIVE_STRONG);
+			if (!status) {
+				set_bit(FLAG_STRONG, &desc->flags);
+			}
+		} else if (sysfs_streq(buf, "hiz")) {
+			status = gpiod_set_drive(desc, GPIOF_DRIVE_HIZ);
+			if (!status) {
+				set_bit(FLAG_HIZ, &desc->flags);
+			}
+		} else {
+			status = -EINVAL;
+		}
+	}
+
+	mutex_unlock(&sysfs_lock);
+	return status ? : size;
+}
+
+static const DEVICE_ATTR(drive, 0644,
+		gpio_drive_show, gpio_drive_store);
+
+
 static umode_t gpio_is_visible(struct kobject *kobj, struct attribute *attr,
 			       int n)
 {
@@ -377,6 +454,7 @@ static struct attribute *gpio_attrs[] = {
 	&dev_attr_edge.attr,
 	&dev_attr_value.attr,
 	&dev_attr_active_low.attr,
+	&dev_attr_drive.attr,
 	NULL,
 };
 
diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c
index 4e4c3083ae56..1f49d1522474 100644
--- a/drivers/gpio/gpiolib.c
+++ b/drivers/gpio/gpiolib.c
@@ -1248,6 +1248,24 @@ int gpiod_is_active_low(const struct gpio_desc *desc)
 }
 EXPORT_SYMBOL_GPL(gpiod_is_active_low);
 
+int gpiod_set_drive(struct gpio_desc *desc, unsigned mode)
+{
+	unsigned long		flags;
+	struct gpio_chip	*chip;
+
+	chip = desc->chip;
+	if (!chip || !chip->set || !chip->set_drive)
+		goto fail;
+
+	might_sleep_if(chip->can_sleep);
+
+	return chip->set_drive(chip, gpio_chip_hwgpio(desc), mode);
+
+fail:
+	return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(gpiod_set_drive);
+
 /* I/O calls are only valid after configuration completed; the relevant
  * "is this a valid GPIO" error checks should already have been done.
  *
diff --git a/drivers/gpio/gpiolib.h b/drivers/gpio/gpiolib.h
index 98ab08c0aa2d..138faa1f6eff 100644
--- a/drivers/gpio/gpiolib.h
+++ b/drivers/gpio/gpiolib.h
@@ -96,6 +96,10 @@ struct gpio_desc {
 #define FLAG_OPEN_SOURCE 8	/* Gpio is open source type */
 #define FLAG_USED_AS_IRQ 9	/* GPIO is connected to an IRQ */
 #define FLAG_IS_HOGGED	11	/* GPIO is hogged */
+#define FLAG_PULLUP	12	/* Gpio drive is resistive pullup */
+#define FLAG_PULLDOWN	13	/* Gpio drive is resistive pulldown */
+#define FLAG_STRONG	14	/* Gpio drive is strong (fast output) */
+#define FLAG_HIZ	15	/* Gpio drive is Hi-Z (input) */
 
 	/* Connection label */
 	const char		*label;
diff --git a/include/asm-generic/gpio.h b/include/asm-generic/gpio.h
index 40ec1433f05d..519e8d458878 100644
--- a/include/asm-generic/gpio.h
+++ b/include/asm-generic/gpio.h
@@ -76,6 +76,11 @@ static inline int gpio_set_debounce(unsigned gpio, unsigned debounce)
 	return gpiod_set_debounce(gpio_to_desc(gpio), debounce);
 }
 
+static inline int gpio_set_drive(unsigned gpio, unsigned mode)
+{
+	return gpiod_set_drive(gpio_to_desc(gpio), mode);
+}
+
 static inline int gpio_get_value_cansleep(unsigned gpio)
 {
 	return gpiod_get_raw_value_cansleep(gpio_to_desc(gpio));
diff --git a/include/linux/gpio.h b/include/linux/gpio.h
index d12b5d566e4b..344e62d27700 100644
--- a/include/linux/gpio.h
+++ b/include/linux/gpio.h
@@ -30,6 +30,11 @@
 #define GPIOF_EXPORT_DIR_FIXED	(GPIOF_EXPORT)
 #define GPIOF_EXPORT_DIR_CHANGEABLE (GPIOF_EXPORT | GPIOF_EXPORT_CHANGEABLE)
 
+#define GPIOF_DRIVE_PULLUP	(1 << 7)
+#define GPIOF_DRIVE_PULLDOWN	(1 << 8)
+#define GPIOF_DRIVE_STRONG	(1 << 9)
+#define GPIOF_DRIVE_HIZ		(1 << 10)
+
 /**
  * struct gpio - a structure describing a GPIO with configuration
  * @gpio:	the GPIO number
@@ -148,6 +153,11 @@ static inline int gpio_set_debounce(unsigned gpio, unsigned debounce)
 	return -ENOSYS;
 }
 
+static inline int gpio_set_drive(unsigned gpio, unsigned mode)
+{
+	return -ENOSYS;
+}
+
 static inline int gpio_get_value(unsigned gpio)
 {
 	/* GPIO can never have been requested or set as {in,out}put */
diff --git a/include/linux/gpio/consumer.h b/include/linux/gpio/consumer.h
index fb0fde686cb1..17f5f187699b 100644
--- a/include/linux/gpio/consumer.h
+++ b/include/linux/gpio/consumer.h
@@ -122,6 +122,8 @@ void gpiod_set_raw_array_value_cansleep(unsigned int array_size,
 
 int gpiod_set_debounce(struct gpio_desc *desc, unsigned debounce);
 
+int gpiod_set_drive(struct gpio_desc *desc, unsigned mode);
+
 int gpiod_is_active_low(const struct gpio_desc *desc);
 int gpiod_cansleep(const struct gpio_desc *desc);
 
@@ -376,6 +378,15 @@ static inline int gpiod_set_debounce(struct gpio_desc *desc, unsigned debounce)
 	return -ENOSYS;
 }
 
+
+static inline int gpiod_set_drive(unsigned gpio, unsigned mode)
+{
+	/* GPIO can never have been requested */
+	WARN_ON(1);
+	return -ENOSYS;
+}
+
+
 static inline int gpiod_is_active_low(const struct gpio_desc *desc)
 {
 	/* GPIO can never have been requested */
diff --git a/include/linux/gpio/driver.h b/include/linux/gpio/driver.h
index d1baebf350d8..8e86f56a730a 100644
--- a/include/linux/gpio/driver.h
+++ b/include/linux/gpio/driver.h
@@ -114,6 +114,8 @@ struct gpio_chip {
 	int			(*set_debounce)(struct gpio_chip *chip,
 						unsigned offset,
 						unsigned debounce);
+	int			(*set_drive)(struct gpio_chip *chip,
+						unsigned offset, unsigned mode);
 
 	int			(*to_irq)(struct gpio_chip *chip,
 						unsigned offset);
-- 
2.5.0

