From 0a304203ac7daf36070f8fecd8974bfaf84d9df6 Mon Sep 17 00:00:00 2001
From: Lars-Peter Clausen <lars@metafoo.de>
Date: Fri, 22 Jan 2010 12:47:01 +0100
Subject: [PATCH 36/69] pcf50633-gpio: Add gpiolib support.

---
 drivers/mfd/pcf50633-core.c       |    6 +-
 drivers/mfd/pcf50633-gpio.c       |  157 +++++++++++++++++++++++++++++++------
 include/linux/mfd/pcf50633/core.h |    4 +
 include/linux/mfd/pcf50633/gpio.h |   59 +++++++-------
 4 files changed, 171 insertions(+), 55 deletions(-)

diff --git a/drivers/mfd/pcf50633-core.c b/drivers/mfd/pcf50633-core.c
index 57868416..3d2eccd 100644
--- a/drivers/mfd/pcf50633-core.c
+++ b/drivers/mfd/pcf50633-core.c
@@ -211,7 +211,7 @@ static struct attribute_group pcf_attr_group = {
 
 static void
 pcf50633_client_dev_register(struct pcf50633 *pcf, const char *name,
-						struct platform_device **pdev)
+							struct platform_device **pdev)
 {
 	int ret;
 
@@ -300,7 +300,8 @@ static int __devinit pcf50633_probe(struct i2c_client *client,
 						&pcf->adc_pdev);
 	pcf50633_client_dev_register(pcf, "pcf50633-backlight",
 						&pcf->bl_pdev);
-
+	pcf50633_client_dev_register(pcf, "pcf50633-gpio",
+						&pcf->gpio_pdev);
 
 	for (i = 0; i < PCF50633_NUM_REGULATORS; i++) {
 		struct platform_device *pdev;
@@ -342,6 +343,7 @@ static int __devexit pcf50633_remove(struct i2c_client *client)
 	sysfs_remove_group(&client->dev.kobj, &pcf_attr_group);
 	pcf50633_irq_free(pcf);
 
+	platform_device_unregister(pcf->gpio_pdev);
 	platform_device_unregister(pcf->input_pdev);
 	platform_device_unregister(pcf->rtc_pdev);
 	platform_device_unregister(pcf->mbc_pdev);
diff --git a/drivers/mfd/pcf50633-gpio.c b/drivers/mfd/pcf50633-gpio.c
index 9ab19a8..eb044e8 100644
--- a/drivers/mfd/pcf50633-gpio.c
+++ b/drivers/mfd/pcf50633-gpio.c
@@ -2,6 +2,7 @@
  *
  * (C) 2006-2008 by Openmoko, Inc.
  * Author: Balaji Rao <balajirrao@openmoko.org>
+ * Copyright 2010, Lars-Peter Clausen <lars@metafoo.de>
  * All rights reserved.
  *
  * Broken down from monstrous PCF50633 driver mainly by
@@ -16,9 +17,15 @@
 
 #include <linux/kernel.h>
 #include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
 
 #include <linux/mfd/pcf50633/core.h>
 #include <linux/mfd/pcf50633/gpio.h>
+#include <linux/gpio.h>
+
+#define PCF50633_REG_GPIOCTL	0x13
+#define PCF50633_REG_GPIOCFG(x) (0x14 + (x))
 
 enum pcf50633_regulator_id {
 	PCF50633_REGULATOR_AUTO,
@@ -60,54 +67,89 @@ static const u8 pcf50633_regulator_registers[PCF50633_NUM_REGULATORS] = {
 	[PCF50633_REGULATOR_HCLDO]	= PCF50633_REG_HCLDOOUT,
 };
 
-int pcf50633_gpio_set(struct pcf50633 *pcf, int gpio, u8 val)
+struct pcf50633_gpio {
+	struct pcf50633 *pcf;
+	struct gpio_chip chip;
+};
+
+static inline struct pcf50633 *gpio_chip_to_pcf50633(struct gpio_chip *chip)
+{
+	struct pcf50633 *pcf = dev_to_pcf50633(chip->dev->parent);
+	return pcf;
+}
+
+static void pcf50633_gpio_set_value(struct gpio_chip *chip, unsigned gpio, int value)
 {
+	struct pcf50633 *pcf = gpio_chip_to_pcf50633(chip);
 	u8 reg;
 
-	reg = gpio - PCF50633_GPIO1 + PCF50633_REG_GPIO1CFG;
+	reg = PCF50633_REG_GPIOCFG(gpio);
 
-	return pcf50633_reg_set_bit_mask(pcf, reg, 0x07, val);
+	pcf50633_reg_set_bit_mask(pcf, reg, 0x07, value ? 0x7 : 0x0);
 }
-EXPORT_SYMBOL_GPL(pcf50633_gpio_set);
 
-u8 pcf50633_gpio_get(struct pcf50633 *pcf, int gpio)
+static int pcf50633_gpio_get_value(struct gpio_chip *chip, unsigned gpio)
 {
-	u8 reg, val;
-
-	reg = gpio - PCF50633_GPIO1 + PCF50633_REG_GPIO1CFG;
-	val = pcf50633_reg_read(pcf, reg) & 0x07;
-
-	return val;
+	struct pcf50633 *pcf = gpio_chip_to_pcf50633(chip);
+	return pcf50633_reg_read(pcf, PCF50633_REG_GPIOCFG(gpio)) >> 3;
 }
-EXPORT_SYMBOL_GPL(pcf50633_gpio_get);
 
-int pcf50633_gpio_invert_set(struct pcf50633 *pcf, int gpio, int invert)
+
+static int pcf50633_gpio_direction_output(struct gpio_chip *chip, unsigned gpio,
+                                          int value)
 {
-	u8 val, reg;
+	struct pcf50633 *pcf = gpio_chip_to_pcf50633(chip);
+	int ret;
 
-	reg = gpio - PCF50633_GPIO1 + PCF50633_REG_GPIO1CFG;
-	val = !!invert << 3;
+	ret = pcf50633_gpio_set_config(pcf, pcf->pdata->gpio_base + gpio,
+	                               PCF50633_GPIO_CONFIG_OUTPUT);
+	if (!ret)
+	    pcf50633_gpio_set_value(chip, gpio, value);
 
-	return pcf50633_reg_set_bit_mask(pcf, reg, 1 << 3, val);
+	return ret;
 }
-EXPORT_SYMBOL_GPL(pcf50633_gpio_invert_set);
 
-int pcf50633_gpio_invert_get(struct pcf50633 *pcf, int gpio)
+static int pcf50633_gpio_direction_input(struct gpio_chip *chip, unsigned gpio)
 {
-	u8 reg, val;
-
-	reg = gpio - PCF50633_GPIO1 + PCF50633_REG_GPIO1CFG;
-	val = pcf50633_reg_read(pcf, reg);
+	return -ENOSYS;
+}
 
-	return val & (1 << 3);
+int pcf50633_gpio_set_config(struct pcf50633 *pcf, unsigned gpio,
+                              enum pcf50633_gpio_config config)
+{
+	u8 reg;
+	u8 direction;
+	int ret;
+
+	gpio -= pcf->pdata->gpio_base;
+
+	if (gpio < 3) {
+	    direction = (config == PCF50633_GPIO_CONFIG_INPUT) ? (1 << gpio) : 0;
+	    ret = pcf50633_reg_set_bit_mask(pcf, PCF50633_REG_GPIOCTL, (1 << gpio),
+					    direction);
+	    if (ret) {
+			return ret;
+		}
+	} else if (gpio > 3 || config == PCF50633_GPIO_CONFIG_INPUT) {
+	    return -EINVAL;
+	}
+
+	if (config != PCF50633_GPIO_CONFIG_INPUT) {
+	    reg = PCF50633_REG_GPIOCFG(gpio);
+	    ret = pcf50633_reg_set_bit_mask(pcf, reg, 0x0f, config);
+	}
+
+	return ret;
 }
-EXPORT_SYMBOL_GPL(pcf50633_gpio_invert_get);
+EXPORT_SYMBOL_GPL(pcf50633_gpio_set_config);
 
 int pcf50633_gpio_power_supply_set(struct pcf50633 *pcf,
 					int gpio, int regulator, int on)
 {
 	u8 reg, val, mask;
 
+	gpio -= pcf->pdata->gpio_base;
+
 	/* the *ENA register is always one after the *OUT register */
 	reg = pcf50633_regulator_registers[regulator] + 1;
 
@@ -118,4 +160,69 @@ int pcf50633_gpio_power_supply_set(struct pcf50633 *pcf,
 }
 EXPORT_SYMBOL_GPL(pcf50633_gpio_power_supply_set);
 
+
+static int __devinit pcf50633_gpio_probe(struct platform_device *pdev)
+{
+	struct pcf50633 *pcf = dev_to_pcf50633(pdev->dev.parent);
+	struct pcf50633_gpio *pcf_gpio;
+
+	pcf_gpio = kzalloc(sizeof(*pcf_gpio), GFP_KERNEL);
+
+	if (!pcf_gpio)
+		return -ENOMEM;
+
+	pcf_gpio->pcf = pcf;
+
+	pcf_gpio->chip.direction_input = pcf50633_gpio_direction_input;
+	pcf_gpio->chip.direction_output = pcf50633_gpio_direction_output;
+	pcf_gpio->chip.get = pcf50633_gpio_get_value;
+	pcf_gpio->chip.set = pcf50633_gpio_set_value;
+
+	pcf_gpio->chip.base = pcf->pdata->gpio_base;
+	pcf_gpio->chip.ngpio = 4;
+	pcf_gpio->chip.label = dev_name(pcf->dev);
+	pcf_gpio->chip.can_sleep = 1;
+	pcf_gpio->chip.owner = THIS_MODULE;
+	pcf_gpio->chip.dev = &pdev->dev;
+
+	platform_set_drvdata(pdev, pcf_gpio);
+
+	return gpiochip_add(&pcf_gpio->chip);
+}
+
+static int __devexit pcf50633_gpio_remove(struct platform_device *pdev)
+{
+	struct pcf50633_gpio *pcf_gpio = platform_get_drvdata(pdev);
+
+	gpiochip_remove(&pcf_gpio->chip);
+
+	platform_set_drvdata(pdev, NULL);
+	kfree(pcf_gpio);
+
+	return 0;
+}
+
+static struct platform_driver pcf50633_gpio_driver = {
+	.probe = pcf50633_gpio_probe,
+	.remove = __devexit_p(pcf50633_gpio_remove),
+	.driver = {
+		.name = "pcf50633-gpio",
+		.owner = THIS_MODULE,
+	},
+};
+
+int __init pcf50633_gpio_init(void)
+{
+	return platform_driver_register(&pcf50633_gpio_driver);
+}
+module_init(pcf50633_gpio_init);
+
+void __exit pcf50633_gpio_exit(void)
+{
+	platform_driver_unregister(&pcf50633_gpio_driver);
+}
+module_exit(pcf50633_gpio_exit);
+
+MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
+MODULE_DESCRIPTION("GPIO driver for the PCF50633");
 MODULE_LICENSE("GPL");
diff --git a/include/linux/mfd/pcf50633/core.h b/include/linux/mfd/pcf50633/core.h
index 50d4a04..e0bd9a2 100644
--- a/include/linux/mfd/pcf50633/core.h
+++ b/include/linux/mfd/pcf50633/core.h
@@ -19,6 +19,7 @@
 #include <linux/regulator/machine.h>
 #include <linux/power_supply.h>
 #include <linux/mfd/pcf50633/backlight.h>
+#include <linux/gpio.h>
 
 struct pcf50633;
 
@@ -46,6 +47,8 @@ struct pcf50633_platform_data {
 	u8 resumers[5];
 
 	struct pcf50633_bl_platform_data *backlight_data;
+
+	int gpio_base;
 };
 
 struct pcf50633_irq {
@@ -151,6 +154,7 @@ struct pcf50633 {
 
 	int onkey1s_held;
 
+	struct platform_device *gpio_pdev;
 	struct platform_device *rtc_pdev;
 	struct platform_device *mbc_pdev;
 	struct platform_device *adc_pdev;
diff --git a/include/linux/mfd/pcf50633/gpio.h b/include/linux/mfd/pcf50633/gpio.h
index a42b845..af2c341 100644
--- a/include/linux/mfd/pcf50633/gpio.h
+++ b/include/linux/mfd/pcf50633/gpio.h
@@ -15,37 +15,40 @@
 
 #include <linux/mfd/pcf50633/core.h>
 
-#define PCF50633_GPIO1		1
-#define PCF50633_GPIO2		2
-#define PCF50633_GPIO3		3
-#define PCF50633_GPO		4
-
-#define PCF50633_REG_GPIO1CFG	0x14
-#define PCF50633_REG_GPIO2CFG	0x15
-#define PCF50633_REG_GPIO3CFG	0x16
-#define PCF50633_REG_GPOCFG 	0x17
-
-#define PCF50633_GPOCFG_GPOSEL_MASK	0x07
-
-enum pcf50633_reg_gpocfg {
-	PCF50633_GPOCFG_GPOSEL_0	= 0x00,
-	PCF50633_GPOCFG_GPOSEL_LED_NFET	= 0x01,
-	PCF50633_GPOCFG_GPOSEL_SYSxOK	= 0x02,
-	PCF50633_GPOCFG_GPOSEL_CLK32K	= 0x03,
-	PCF50633_GPOCFG_GPOSEL_ADAPUSB	= 0x04,
-	PCF50633_GPOCFG_GPOSEL_USBxOK	= 0x05,
-	PCF50633_GPOCFG_GPOSEL_ACTPH4	= 0x06,
-	PCF50633_GPOCFG_GPOSEL_1	= 0x07,
-	PCF50633_GPOCFG_GPOSEL_INVERSE	= 0x08,
+#define PCF50633_GPIO1		0
+#define PCF50633_GPIO2		1
+#define PCF50633_GPIO3		2
+#define PCF50633_GPO		3
+
+#define PCF50633_REG_GPIOCFG(x) (0x14 + (x))
+
+enum pcf50633_gpio_config {
+	PCF50633_GPIO_CONFIG_OUTPUT	= 0x0,
+	PCF50633_GPIO_CONFIG_SYSxOK	= 0x2,
+	PCF50633_GPIO_CONFIG_CHARGING	= 0x3,
+	PCF50633_GPIO_CONFIG_MOBILE_MODE = 0x4,
+	PCF50633_GPIO_CONFIG_USBxOK	= 0x5,
+	PCF50633_GPIO_CONFIG_ACTPH	= 0x6,
+	PCF50633_GPIO_CONFIG_INPUT	= 0x7,
+
+	PCF50633_GPIO_CONFIG_INVERT	= 0x8,
+
+	PCF50633_GPO_CONFIG_OUTPUT	= 0x0,
+	PCF50633_GPO_CONFIG_LED_NFET	= 0x1,
+	PCF50633_GPO_CONFIG_SYSxOK	= 0x2,
+	PCF50633_GPO_CONFIG_CLK32K	= 0x3,
+	PCF50633_GPO_CONFIG_MOBILE_MODE = 0x4,
+	PCF50633_GPO_CONFIG_USBxOK	= 0x5,
+	PCF50633_GPO_CONFIG_ACTPH	= 0x6,
+	PCF50633_GPO_CONFIG_INPUT	= 0x7,
+
+	PCF50633_GPO_CONFIG_INVERT	= 0x8,
 };
 
-int pcf50633_gpio_set(struct pcf50633 *pcf, int gpio, u8 val);
-u8 pcf50633_gpio_get(struct pcf50633 *pcf, int gpio);
+int pcf50633_gpio_set_config(struct pcf50633 *pcf, unsigned gpio,
+                              enum pcf50633_gpio_config config);
 
-int pcf50633_gpio_invert_set(struct pcf50633 *, int gpio, int invert);
-int pcf50633_gpio_invert_get(struct pcf50633 *pcf, int gpio);
-
-int pcf50633_gpio_power_supply_set(struct pcf50633 *,
+int pcf50633_gpio_power_supply_set(struct pcf50633 *pcf,
 					int gpio, int regulator, int on);
 #endif /* __LINUX_MFD_PCF50633_GPIO_H */
 
-- 
1.7.2.5

