gpio: Add WM831X GPIO driver
Mark Brown [Mon, 27 Jul 2009 13:46:00 +0000 (14:46 +0100)]
Add support for the GPIO pins on the WM831x. No direct support is
currently supplied for configuring non-gpiolib functionality such
as pull configuration and alternate functions, soft configuration
of these will be provided in a future patch.

Currently use of these pins as interrupts is not supported due to
the ongoing issues with generic irq not support interrupt controllers
on interrupt driven buses. Users can directly request the interrupts
with the wm831x-specific APIs currently provided if required.

Signed-off-by: Mark Brown <broonie@opensource.wolfsonmicro.com>
Acked-by: David Brownell <dbrownell@users.sourceforge.net>
Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>

drivers/gpio/Kconfig
drivers/gpio/Makefile
drivers/gpio/wm831x-gpio.c [new file with mode: 0644]
include/linux/mfd/wm831x/gpio.h [new file with mode: 0644]

index 96dda81..6b4c484 100644 (file)
@@ -155,6 +155,13 @@ config GPIO_TWL4030
          Say yes here to access the GPIO signals of various multi-function
          power management chips from Texas Instruments.
 
+config GPIO_WM831X
+       tristate "WM831x GPIOs"
+       depends on MFD_WM831X
+       help
+         Say yes here to access the GPIO signals of WM831x power management
+         chips from Wolfson Microelectronics.
+
 comment "PCI GPIO expanders:"
 
 config GPIO_BT8XX
index 9244c6f..ea7c745 100644 (file)
@@ -14,3 +14,4 @@ obj-$(CONFIG_GPIO_TWL4030)    += twl4030-gpio.o
 obj-$(CONFIG_GPIO_XILINX)      += xilinx_gpio.o
 obj-$(CONFIG_GPIO_BT8XX)       += bt8xxgpio.o
 obj-$(CONFIG_GPIO_VR41XX)      += vr41xx_giu.o
+obj-$(CONFIG_GPIO_WM831X)      += wm831x-gpio.o
diff --git a/drivers/gpio/wm831x-gpio.c b/drivers/gpio/wm831x-gpio.c
new file mode 100644 (file)
index 0000000..f9c09a5
--- /dev/null
@@ -0,0 +1,252 @@
+/*
+ * wm831x-gpio.c  --  gpiolib support for Wolfson WM831x PMICs
+ *
+ * Copyright 2009 Wolfson Microelectronics PLC.
+ *
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ *  This program is free software; you can redistribute  it and/or modify it
+ *  under  the terms of  the GNU General  Public License as published by the
+ *  Free Software Foundation;  either version 2 of the  License, or (at your
+ *  option) any later version.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/gpio.h>
+#include <linux/mfd/core.h>
+#include <linux/platform_device.h>
+#include <linux/seq_file.h>
+
+#include <linux/mfd/wm831x/core.h>
+#include <linux/mfd/wm831x/pdata.h>
+#include <linux/mfd/wm831x/gpio.h>
+
+#define WM831X_GPIO_MAX 16
+
+struct wm831x_gpio {
+       struct wm831x *wm831x;
+       struct gpio_chip gpio_chip;
+};
+
+static inline struct wm831x_gpio *to_wm831x_gpio(struct gpio_chip *chip)
+{
+       return container_of(chip, struct wm831x_gpio, gpio_chip);
+}
+
+static int wm831x_gpio_direction_in(struct gpio_chip *chip, unsigned offset)
+{
+       struct wm831x_gpio *wm831x_gpio = to_wm831x_gpio(chip);
+       struct wm831x *wm831x = wm831x_gpio->wm831x;
+
+       return wm831x_set_bits(wm831x, WM831X_GPIO1_CONTROL + offset,
+                              WM831X_GPN_DIR | WM831X_GPN_TRI,
+                              WM831X_GPN_DIR);
+}
+
+static int wm831x_gpio_get(struct gpio_chip *chip, unsigned offset)
+{
+       struct wm831x_gpio *wm831x_gpio = to_wm831x_gpio(chip);
+       struct wm831x *wm831x = wm831x_gpio->wm831x;
+       int ret;
+
+       ret = wm831x_reg_read(wm831x, WM831X_GPIO_LEVEL);
+       if (ret < 0)
+               return ret;
+
+       if (ret & 1 << offset)
+               return 1;
+       else
+               return 0;
+}
+
+static int wm831x_gpio_direction_out(struct gpio_chip *chip,
+                                    unsigned offset, int value)
+{
+       struct wm831x_gpio *wm831x_gpio = to_wm831x_gpio(chip);
+       struct wm831x *wm831x = wm831x_gpio->wm831x;
+
+       return wm831x_set_bits(wm831x, WM831X_GPIO1_CONTROL + offset,
+                              WM831X_GPN_DIR | WM831X_GPN_TRI, 0);
+}
+
+static void wm831x_gpio_set(struct gpio_chip *chip, unsigned offset, int value)
+{
+       struct wm831x_gpio *wm831x_gpio = to_wm831x_gpio(chip);
+       struct wm831x *wm831x = wm831x_gpio->wm831x;
+
+       wm831x_set_bits(wm831x, WM831X_GPIO_LEVEL, 1 << offset,
+                       value << offset);
+}
+
+#ifdef CONFIG_DEBUG_FS
+static void wm831x_gpio_dbg_show(struct seq_file *s, struct gpio_chip *chip)
+{
+       struct wm831x_gpio *wm831x_gpio = to_wm831x_gpio(chip);
+       struct wm831x *wm831x = wm831x_gpio->wm831x;
+       int i;
+
+       for (i = 0; i < chip->ngpio; i++) {
+               int gpio = i + chip->base;
+               int reg;
+               const char *label, *pull, *powerdomain;
+
+               /* We report the GPIO even if it's not requested since
+                * we're also reporting things like alternate
+                * functions which apply even when the GPIO is not in
+                * use as a GPIO.
+                */
+               label = gpiochip_is_requested(chip, i);
+               if (!label)
+                       label = "Unrequested";
+
+               seq_printf(s, " gpio-%-3d (%-20.20s) ", gpio, label);
+
+               reg = wm831x_reg_read(wm831x, WM831X_GPIO1_CONTROL + i);
+               if (reg < 0) {
+                       dev_err(wm831x->dev,
+                               "GPIO control %d read failed: %d\n",
+                               gpio, reg);
+                       seq_printf(s, "\n");
+                       continue;
+               }
+
+               switch (reg & WM831X_GPN_PULL_MASK) {
+               case WM831X_GPIO_PULL_NONE:
+                       pull = "nopull";
+                       break;
+               case WM831X_GPIO_PULL_DOWN:
+                       pull = "pulldown";
+                       break;
+               case WM831X_GPIO_PULL_UP:
+                       pull = "pullup";
+               default:
+                       pull = "INVALID PULL";
+                       break;
+               }
+
+               switch (i + 1) {
+               case 1 ... 3:
+               case 7 ... 9:
+                       if (reg & WM831X_GPN_PWR_DOM)
+                               powerdomain = "VPMIC";
+                       else
+                               powerdomain = "DBVDD";
+                       break;
+
+               case 4 ... 6:
+               case 10 ... 12:
+                       if (reg & WM831X_GPN_PWR_DOM)
+                               powerdomain = "SYSVDD";
+                       else
+                               powerdomain = "DBVDD";
+                       break;
+
+               case 13 ... 16:
+                       powerdomain = "TPVDD";
+                       break;
+
+               default:
+                       BUG();
+                       break;
+               }
+
+               seq_printf(s, " %s %s %s %s%s\n"
+                          "                                  %s%s (0x%4x)\n",
+                          reg & WM831X_GPN_DIR ? "in" : "out",
+                          wm831x_gpio_get(chip, i) ? "high" : "low",
+                          pull,
+                          powerdomain,
+                          reg & WM831X_GPN_POL ? " inverted" : "",
+                          reg & WM831X_GPN_OD ? "open-drain" : "CMOS",
+                          reg & WM831X_GPN_TRI ? " tristated" : "",
+                          reg);
+       }
+}
+#else
+#define wm831x_gpio_dbg_show NULL
+#endif
+
+static struct gpio_chip template_chip = {
+       .label                  = "wm831x",
+       .owner                  = THIS_MODULE,
+       .direction_input        = wm831x_gpio_direction_in,
+       .get                    = wm831x_gpio_get,
+       .direction_output       = wm831x_gpio_direction_out,
+       .set                    = wm831x_gpio_set,
+       .dbg_show               = wm831x_gpio_dbg_show,
+       .can_sleep              = 1,
+};
+
+static int __devinit wm831x_gpio_probe(struct platform_device *pdev)
+{
+       struct wm831x *wm831x = dev_get_drvdata(pdev->dev.parent);
+       struct wm831x_pdata *pdata = wm831x->dev->platform_data;
+       struct wm831x_gpio *wm831x_gpio;
+       int ret;
+
+       wm831x_gpio = kzalloc(sizeof(*wm831x_gpio), GFP_KERNEL);
+       if (wm831x_gpio == NULL)
+               return -ENOMEM;
+
+       wm831x_gpio->wm831x = wm831x;
+       wm831x_gpio->gpio_chip = template_chip;
+       wm831x_gpio->gpio_chip.ngpio = WM831X_GPIO_MAX;
+       wm831x_gpio->gpio_chip.dev = &pdev->dev;
+       if (pdata && pdata->gpio_base)
+               wm831x_gpio->gpio_chip.base = pdata->gpio_base;
+       else
+               wm831x_gpio->gpio_chip.base = -1;
+
+       ret = gpiochip_add(&wm831x_gpio->gpio_chip);
+       if (ret < 0) {
+               dev_err(&pdev->dev, "Could not register gpiochip, %d\n",
+                       ret);
+               goto err;
+       }
+
+       platform_set_drvdata(pdev, wm831x_gpio);
+
+       return ret;
+
+err:
+       kfree(wm831x_gpio);
+       return ret;
+}
+
+static int __devexit wm831x_gpio_remove(struct platform_device *pdev)
+{
+       struct wm831x_gpio *wm831x_gpio = platform_get_drvdata(pdev);
+       int ret;
+
+       ret = gpiochip_remove(&wm831x_gpio->gpio_chip);
+       if (ret == 0)
+               kfree(wm831x_gpio);
+
+       return ret;
+}
+
+static struct platform_driver wm831x_gpio_driver = {
+       .driver.name    = "wm831x-gpio",
+       .driver.owner   = THIS_MODULE,
+       .probe          = wm831x_gpio_probe,
+       .remove         = __devexit_p(wm831x_gpio_remove),
+};
+
+static int __init wm831x_gpio_init(void)
+{
+       return platform_driver_register(&wm831x_gpio_driver);
+}
+subsys_initcall(wm831x_gpio_init);
+
+static void __exit wm831x_gpio_exit(void)
+{
+       platform_driver_unregister(&wm831x_gpio_driver);
+}
+module_exit(wm831x_gpio_exit);
+
+MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
+MODULE_DESCRIPTION("GPIO interface for WM831x PMICs");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:wm831x-gpio");
diff --git a/include/linux/mfd/wm831x/gpio.h b/include/linux/mfd/wm831x/gpio.h
new file mode 100644 (file)
index 0000000..2835614
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * include/linux/mfd/wm831x/gpio.h -- GPIO for WM831x
+ *
+ * Copyright 2009 Wolfson Microelectronics PLC.
+ *
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ *  This program is free software; you can redistribute  it and/or modify it
+ *  under  the terms of  the GNU General  Public License as published by the
+ *  Free Software Foundation;  either version 2 of the  License, or (at your
+ *  option) any later version.
+ *
+ */
+
+#ifndef __MFD_WM831X_GPIO_H__
+#define __MFD_WM831X_GPIO_H__
+
+/*
+ * R16440-16455 (0x4038-0x4047) - GPIOx Control
+ */
+#define WM831X_GPN_DIR                          0x8000  /* GPN_DIR */
+#define WM831X_GPN_DIR_MASK                     0x8000  /* GPN_DIR */
+#define WM831X_GPN_DIR_SHIFT                        15  /* GPN_DIR */
+#define WM831X_GPN_DIR_WIDTH                         1  /* GPN_DIR */
+#define WM831X_GPN_PULL_MASK                    0x6000  /* GPN_PULL - [14:13] */
+#define WM831X_GPN_PULL_SHIFT                       13  /* GPN_PULL - [14:13] */
+#define WM831X_GPN_PULL_WIDTH                        2  /* GPN_PULL - [14:13] */
+#define WM831X_GPN_INT_MODE                     0x1000  /* GPN_INT_MODE */
+#define WM831X_GPN_INT_MODE_MASK                0x1000  /* GPN_INT_MODE */
+#define WM831X_GPN_INT_MODE_SHIFT                   12  /* GPN_INT_MODE */
+#define WM831X_GPN_INT_MODE_WIDTH                    1  /* GPN_INT_MODE */
+#define WM831X_GPN_PWR_DOM                      0x0800  /* GPN_PWR_DOM */
+#define WM831X_GPN_PWR_DOM_MASK                 0x0800  /* GPN_PWR_DOM */
+#define WM831X_GPN_PWR_DOM_SHIFT                    11  /* GPN_PWR_DOM */
+#define WM831X_GPN_PWR_DOM_WIDTH                     1  /* GPN_PWR_DOM */
+#define WM831X_GPN_POL                          0x0400  /* GPN_POL */
+#define WM831X_GPN_POL_MASK                     0x0400  /* GPN_POL */
+#define WM831X_GPN_POL_SHIFT                        10  /* GPN_POL */
+#define WM831X_GPN_POL_WIDTH                         1  /* GPN_POL */
+#define WM831X_GPN_OD                           0x0200  /* GPN_OD */
+#define WM831X_GPN_OD_MASK                      0x0200  /* GPN_OD */
+#define WM831X_GPN_OD_SHIFT                          9  /* GPN_OD */
+#define WM831X_GPN_OD_WIDTH                          1  /* GPN_OD */
+#define WM831X_GPN_TRI                          0x0080  /* GPN_TRI */
+#define WM831X_GPN_TRI_MASK                     0x0080  /* GPN_TRI */
+#define WM831X_GPN_TRI_SHIFT                         7  /* GPN_TRI */
+#define WM831X_GPN_TRI_WIDTH                         1  /* GPN_TRI */
+#define WM831X_GPN_FN_MASK                      0x000F  /* GPN_FN - [3:0] */
+#define WM831X_GPN_FN_SHIFT                          0  /* GPN_FN - [3:0] */
+#define WM831X_GPN_FN_WIDTH                          4  /* GPN_FN - [3:0] */
+
+#define WM831X_GPIO_PULL_NONE (0 << WM831X_GPN_PULL_SHIFT)
+#define WM831X_GPIO_PULL_DOWN (1 << WM831X_GPN_PULL_SHIFT)
+#define WM831X_GPIO_PULL_UP   (2 << WM831X_GPN_PULL_SHIFT)
+#endif