[PATCH] gpio: Add generic driver for simple memory mappedcontrollers

From: Anton Vorontsov
Date: Wed Aug 25 2010 - 15:43:00 EST


The simple memory mapped GPIO controllers may be found in various
onboard FPGAs that are used to control board's switches, LEDs,
chip-selects, Ethernet/USB PHY power, etc.

Usually these controllers do not privide any means of pin setup
(in/out/open drain).

The driver provides:
- Support for 8/16/32/64 bits registers;
- Support for GPIO controllers with clear/set registers;
- Support for GPIO controllers with a single "data" register;
- Support for big endian bits/GPIOs ordering (mostly used on PowerPC).

Signed-off-by: Anton Vorontsov <cbouatmailru@xxxxxxxxx>
---
drivers/gpio/Kconfig | 5 +
drivers/gpio/Makefile | 1 +
drivers/gpio/simple_mm_gpio.c | 243 +++++++++++++++++++++++++++++++++++++++++
3 files changed, 249 insertions(+), 0 deletions(-)
create mode 100644 drivers/gpio/simple_mm_gpio.c

diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index f623953..a145063 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -82,6 +82,11 @@ config GPIO_PL061
help
Say yes here to support the PrimeCell PL061 GPIO device

+config GPIO_SIMPLE_MM
+ tristate "Simple memory mapped GPIO controllers support"
+ help
+ Say yes here to support simple memory mapped GPIO controllers.
+
config GPIO_XILINX
bool "Xilinx GPIO support"
depends on PPC_OF || MICROBLAZE
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index a69e060..bfd43bb 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -20,6 +20,7 @@ obj-$(CONFIG_GPIO_MCP23S08) += mcp23s08.o
obj-$(CONFIG_GPIO_PCA953X) += pca953x.o
obj-$(CONFIG_GPIO_PCF857X) += pcf857x.o
obj-$(CONFIG_GPIO_PL061) += pl061.o
+obj-$(CONFIG_GPIO_SIMPLE_MM) += simple_mm_gpio.o
obj-$(CONFIG_GPIO_TC35892) += tc35892-gpio.o
obj-$(CONFIG_GPIO_TIMBERDALE) += timbgpio.o
obj-$(CONFIG_GPIO_TWL4030) += twl4030-gpio.o
diff --git a/drivers/gpio/simple_mm_gpio.c b/drivers/gpio/simple_mm_gpio.c
new file mode 100644
index 0000000..028dce8
--- /dev/null
+++ b/drivers/gpio/simple_mm_gpio.c
@@ -0,0 +1,243 @@
+/*
+ * Simple memory mapped GPIOs
+ *
+ * Copyright 2008 MontaVista Software, Inc.
+ * Copyright 2008,2010 Anton Vorontsov <cbouatmailru@xxxxxxxxx>
+ *
+ * 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/init.h>
+#include <linux/bug.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/spinlock.h>
+#include <linux/compiler.h>
+#include <linux/types.h>
+#include <linux/log2.h>
+#include <linux/ioport.h>
+#include <linux/io.h>
+#include <linux/gpio.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/mod_devicetable.h>
+
+struct sgpio_chip {
+ struct gpio_chip gc;
+ void __iomem *reg_dat;
+ void __iomem *reg_set;
+ void __iomem *reg_clr;
+ spinlock_t lock;
+
+ int bits;
+ int big_endian_bits;
+
+ /* shadowed data register to clear/set bits safely */
+ unsigned long data;
+};
+
+static struct sgpio_chip *to_sgpio_chip(struct gpio_chip *gc)
+{
+ return container_of(gc, struct sgpio_chip, gc);
+}
+
+static unsigned long sgpio_in(struct sgpio_chip *sgc)
+{
+ switch (sgc->bits) {
+ case 8:
+ return __raw_readb(sgc->reg_dat);
+ case 16:
+ return __raw_readw(sgc->reg_dat);
+ case 32:
+ return __raw_readl(sgc->reg_dat);
+#if BITS_PER_LONG >= 64
+ case 64:
+ return __raw_readq(sgc->reg_dat);
+#endif
+ }
+ BUG();
+}
+
+static void sgpio_out(struct sgpio_chip *sgc, void __iomem *reg,
+ unsigned long data)
+{
+ switch (sgc->bits) {
+ case 8:
+ __raw_writeb(data, reg);
+ return;
+ case 16:
+ __raw_writew(data, reg);
+ return;
+ case 32:
+ __raw_writel(data, reg);
+ return;
+#if BITS_PER_LONG >= 64
+ case 64:
+ __raw_writeq(data, reg);
+ return;
+#endif
+ }
+ BUG();
+}
+
+static unsigned long sgpio_pin2mask(struct sgpio_chip *sgc, unsigned int pin)
+{
+ if (sgc->big_endian_bits)
+ return 1 << (sgc->bits - 1 - pin);
+ else
+ return 1 << pin;
+}
+
+static int sgpio_get(struct gpio_chip *gc, unsigned int gpio)
+{
+ struct sgpio_chip *sgc = to_sgpio_chip(gc);
+
+ return sgpio_in(sgc) & sgpio_pin2mask(sgc, gpio);
+}
+
+static void sgpio_set(struct gpio_chip *gc, unsigned int gpio, int val)
+{
+ struct sgpio_chip *sgc = to_sgpio_chip(gc);
+ unsigned long mask = sgpio_pin2mask(sgc, gpio);
+ unsigned long flags;
+
+ if (sgc->reg_set) {
+ if (val)
+ sgpio_out(sgc, sgc->reg_set, mask);
+ else
+ sgpio_out(sgc, sgc->reg_clr, mask);
+ return;
+ }
+
+ spin_lock_irqsave(&sgc->lock, flags);
+
+ if (val)
+ sgc->data |= mask;
+ else
+ sgc->data &= ~mask;
+
+ sgpio_out(sgc, sgc->reg_dat, sgc->data);
+
+ spin_unlock_irqrestore(&sgc->lock, flags);
+}
+
+static int sgpio_dir_in(struct gpio_chip *gc, unsigned int gpio)
+{
+ return 0;
+}
+
+static int sgpio_dir_out(struct gpio_chip *gc, unsigned int gpio, int val)
+{
+ sgpio_set(gc, gpio, val);
+ return 0;
+}
+
+static int __devinit sgpio_probe(struct platform_device *pdev)
+{
+ const struct platform_device_id *platid = platform_get_device_id(pdev);
+ struct device *dev = &pdev->dev;
+ struct sgpio_chip *sgc;
+ struct resource *res_dat;
+ struct resource *res_set;
+ struct resource *res_clr;
+ resource_size_t dat_sz;
+ int bits;
+
+ res_dat = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res_dat)
+ return -EINVAL;
+
+ dat_sz = resource_size(res_dat);
+ if (!is_power_of_2(dat_sz))
+ return -EINVAL;
+
+ bits = dat_sz * 8;
+ if (bits > BITS_PER_LONG)
+ return -EINVAL;
+
+ sgc = devm_kzalloc(dev, sizeof(*sgc), GFP_KERNEL);
+ if (!sgc)
+ return -ENOMEM;
+
+ sgc->reg_dat = devm_ioremap(dev, res_dat->start, dat_sz);
+ if (!sgc->reg_dat)
+ return -ENOMEM;
+
+ res_set = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+ res_clr = platform_get_resource(pdev, IORESOURCE_MEM, 2);
+ if (res_set && res_clr) {
+ if (resource_size(res_set) != resource_size(res_clr) ||
+ resource_size(res_set) != dat_sz)
+ return -EINVAL;
+
+ sgc->reg_set = devm_ioremap(dev, res_set->start, dat_sz);
+ sgc->reg_clr = devm_ioremap(dev, res_clr->start, dat_sz);
+ if (!sgc->reg_set || !sgc->reg_clr)
+ return -ENOMEM;
+ } else if (res_set || res_clr) {
+ return -EINVAL;
+ }
+
+ spin_lock_init(&sgc->lock);
+
+ sgc->bits = bits;
+ sgc->big_endian_bits = !strcmp(platid->name, "simple-mm-gpio-be");
+ sgc->data = sgpio_in(sgc);
+
+ sgc->gc.ngpio = bits;
+ sgc->gc.direction_input = sgpio_dir_in;
+ sgc->gc.direction_output = sgpio_dir_out;
+ sgc->gc.get = sgpio_get;
+ sgc->gc.set = sgpio_set;
+
+ if (pdev->dev.platform_data)
+ sgc->gc.base = (unsigned long)pdev->dev.platform_data;
+ else
+ sgc->gc.base = -1;
+
+ dev_set_drvdata(dev, sgc);
+
+ return gpiochip_add(&sgc->gc);
+}
+
+static int __devexit sgpio_remove(struct platform_device *pdev)
+{
+ struct sgpio_chip *sgc = dev_get_drvdata(&pdev->dev);
+
+ return gpiochip_remove(&sgc->gc);
+}
+
+static const struct platform_device_id sgpio_id_table[] = {
+ { "simple-mm-gpio", },
+ { "simple-mm-gpio-be", },
+ {},
+};
+MODULE_DEVICE_TABLE(platform, sgpio_id_table);
+
+static struct platform_driver sgpio_driver = {
+ .driver = {
+ .name = "simple-mm-gpio",
+ },
+ .id_table = sgpio_id_table,
+ .probe = sgpio_probe,
+ .remove = __devexit_p(sgpio_remove),
+};
+
+static int __init sgpio_init(void)
+{
+ return platform_driver_register(&sgpio_driver);
+}
+module_init(sgpio_init);
+
+static void __exit sgpio_exit(void)
+{
+ platform_driver_unregister(&sgpio_driver);
+}
+module_exit(sgpio_exit);
+
+MODULE_DESCRIPTION("Driver for simple memory mapped GPIO controllers");
+MODULE_AUTHOR("Anton Vorontsov <cbouatmailru@xxxxxxxxx>");
+MODULE_LICENSE("GPL");
--
1.7.0.5
--
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/