]> www.pilppa.org Git - linux-2.6-omap-h63xx.git/commitdiff
ARM: OMAP: Add support for GPIO switch
authorJuha Yrjola <juha.yrjola@nokia.com>
Mon, 9 May 2005 20:12:15 +0000 (13:12 -0700)
committerTony Lindgren <tony@atomide.com>
Mon, 9 May 2005 20:12:15 +0000 (13:12 -0700)
Adds support for GPIO switch

Signed-off-by: Juha Yrjola <juha.yrjola@nokia.com>
Signed-off-by: Tony Lindgren <tony@atomide.com>
arch/arm/mach-omap/Kconfig
arch/arm/mach-omap/Makefile
arch/arm/mach-omap/gpio-switch.c [new file with mode: 0644]

index 965bb989c7ca01fd0c603f1007fd47aec47cf69b..ae0549a1366725de13577be477d71b2437cd1d93 100644 (file)
@@ -50,6 +50,16 @@ config OMAP_BOOT_REASON
           Say Y, if you want to have a procfs entry for reading the boot
           reason in user-space.
 
+config OMAP_GPIO_SWITCH
+       bool "GPIO switch support"
+        depends on OMAP_BOOT_TAG
+        default n
+        help
+          Say Y, if you want to have support for input layer reporting
+          of GPIO switches (e.g. cover switches). Your bootloader has to
+          provide information about the switches to the kernel via the
+          ATAG_BOARD mechanism.
+
 config OMAP_MUX
        bool "OMAP multiplexing support"
         depends on ARCH_OMAP
index 769437d5bf88e8e4b9bc06131a9462eea82d3124..bcbbfb1fe47b61878af0e992184a9929805a91d7 100644 (file)
@@ -20,3 +20,4 @@ obj-$(CONFIG_PM) += pm.o sleep.o
 obj-$(CONFIG_CPU_FREQ) += cpu-omap.o
 obj-$(CONFIG_OMAP_DM_TIMER) += dmtimer.o
 obj-$(CONFIG_OMAP_BOOT_REASON) += bootreason.o
+obj-$(CONFIG_OMAP_GPIO_SWITCH) += gpio-switch.o
diff --git a/arch/arm/mach-omap/gpio-switch.c b/arch/arm/mach-omap/gpio-switch.c
new file mode 100644 (file)
index 0000000..ca47111
--- /dev/null
@@ -0,0 +1,332 @@
+/*
+ *  linux/arch/arm/mach-omap/gpio-switch.c
+ *
+ *  Copyright (C) 2004, 2005 Nokia Corporation
+ *  Written by Juha Yrjölä <juha.yrjola@nokia.com>
+ *         and Paul Mundt <paul.mundt@nokia.com>
+ *
+ * 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/sched.h>
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/timer.h>
+#include <linux/err.h>
+#include <asm/arch/hardware.h>
+#include <asm/arch/gpio.h>
+#include <asm/arch/irqs.h>
+#include <asm/arch/mux.h>
+#include <asm/arch/board.h>
+
+struct gpio_switch {
+       char            name[14];
+       u16             gpio;
+       int             flags;
+       int             type;
+       int             key_code;
+       int             state;
+
+       struct work_struct      work;
+       struct timer_list       timer;
+       struct platform_device  pdev;
+
+       struct list_head        node;
+};
+
+static LIST_HEAD(gpio_switches);
+static struct platform_device *gpio_sw_platform_dev;
+static struct device_driver gpio_sw_driver;
+
+static const char *cover_str[2] = { "open", "closed" };
+static const char *connection_str[2] = { "disconnected", "connected" };
+
+/*
+ * GPIO switch state poll delay in ms
+ */
+#define OMAP_GPIO_SW_POLL_DELAY        10
+
+static void print_sw_state(struct gpio_switch *sw, int state)
+{
+       const char **str;
+
+       switch (sw->type) {
+       case OMAP_GPIO_SWITCH_TYPE_COVER:
+               str = cover_str;
+               break;
+       case OMAP_GPIO_SWITCH_TYPE_CONNECTION:
+               str = connection_str;
+               break;
+       default:
+               str = NULL;
+       }
+       if (str != NULL)
+               printk(KERN_INFO "%s (GPIO %d) is now %s\n", sw->name, sw->gpio, str[state]);
+}
+
+static int gpio_sw_get_state(struct gpio_switch *sw)
+{
+       int state;
+
+       state = omap_get_gpio_datain(sw->gpio);
+       if (sw->flags & OMAP_GPIO_SWITCH_FLAG_INVERTED)
+               state = !state;
+
+       return state;
+}
+
+static ssize_t gpio_sw_store(struct device *dev, const char *buf, size_t count)
+{
+       struct gpio_switch *sw = dev_get_drvdata(dev);
+       int enable = (int)simple_strtoul(buf, NULL, 10);
+       omap_set_gpio_dataout(sw->gpio, enable);
+       return count;
+}
+
+#define gpio_sw_switch_attr(name)                                      \
+static ssize_t gpio_sw_show_##name(struct device *dev, char *buf)      \
+{                                                                      \
+       struct gpio_switch *sw = dev_get_drvdata(dev);                  \
+       return sprintf(buf, "%s\n", name##_str[gpio_sw_get_state(sw)]); \
+}                                                                      \
+static DEVICE_ATTR(name##_switch, S_IRUGO | S_IWUSR,                   \
+                  gpio_sw_show_##name, gpio_sw_store)
+
+gpio_sw_switch_attr(cover);
+gpio_sw_switch_attr(connection);
+
+static irqreturn_t gpio_sw_irq_handler(int irq, void *arg, struct pt_regs *regs)
+{
+       struct gpio_switch *sw = arg;
+
+       mod_timer(&sw->timer, jiffies + OMAP_GPIO_SW_POLL_DELAY / (1000 / HZ));
+
+       return IRQ_HANDLED;
+}
+
+static void gpio_sw_timer(unsigned long arg)
+{
+       struct gpio_switch *sw = (struct gpio_switch *)arg;
+
+       schedule_work(&sw->work);
+}
+
+static void gpio_sw_handler(void *data)
+{
+       struct gpio_switch *sw = data;
+       int state = gpio_sw_get_state(sw);
+
+       if (sw->state == state)
+               return;
+
+       if (sw->type == OMAP_GPIO_SWITCH_TYPE_CONNECTION)
+               kobject_uevent(&sw->pdev.dev.kobj, KOBJ_CHANGE,
+                              &dev_attr_connection_switch.attr);
+       else
+               kobject_uevent(&sw->pdev.dev.kobj, KOBJ_CHANGE,
+                              &dev_attr_cover_switch.attr);
+       sw->state = state;
+       if (omap_get_gpio_datain(sw->gpio))
+               omap_set_gpio_edge_ctrl(sw->gpio, OMAP_GPIO_FALLING_EDGE);
+       else
+               omap_set_gpio_edge_ctrl(sw->gpio, OMAP_GPIO_RISING_EDGE);
+       print_sw_state(sw, state);
+}
+
+static int __init new_switch(struct gpio_switch *sw)
+{
+       int r, direction;
+
+       sw->pdev.name   = sw->name;
+       sw->pdev.id     = -1;
+
+       sw->pdev.dev.parent = &gpio_sw_platform_dev->dev;
+       sw->pdev.dev.driver = &gpio_sw_driver;
+
+       r = platform_device_register(&sw->pdev);
+       if (r)
+               return r;
+
+       dev_set_drvdata(&sw->pdev.dev, sw);
+
+       r = omap_request_gpio(sw->gpio);
+       if (r < 0) {
+               platform_device_unregister(&sw->pdev);
+               return r;
+       }
+
+       /* input: 1, output: 0 */
+       direction = !(sw->flags & OMAP_GPIO_SWITCH_FLAG_OUTPUT);
+       omap_set_gpio_direction(sw->gpio, direction);
+
+       if (omap_get_gpio_datain(sw->gpio))
+               omap_set_gpio_edge_ctrl(sw->gpio, OMAP_GPIO_FALLING_EDGE);
+       else
+               omap_set_gpio_edge_ctrl(sw->gpio, OMAP_GPIO_RISING_EDGE);
+
+       switch (sw->type) {
+       case OMAP_GPIO_SWITCH_TYPE_COVER:
+               device_create_file(&sw->pdev.dev, &dev_attr_cover_switch);
+               break;
+       case OMAP_GPIO_SWITCH_TYPE_CONNECTION:
+               device_create_file(&sw->pdev.dev, &dev_attr_connection_switch);
+               break;
+       }
+
+       list_add(&sw->node, &gpio_switches);
+
+       if (!direction)
+               return 0;
+
+       r = request_irq(OMAP_GPIO_IRQ(sw->gpio), gpio_sw_irq_handler, SA_SHIRQ,                         sw->name, sw);
+       if (r < 0) {
+               printk(KERN_ERR "gpio-switch: request_irq() failed "
+                               "for GPIO %d\n", sw->gpio);
+               platform_device_unregister(&sw->pdev);
+               omap_free_gpio(sw->gpio);
+               return r;
+       }
+
+       INIT_WORK(&sw->work, gpio_sw_handler, sw);
+       init_timer(&sw->timer);
+
+       sw->timer.function = gpio_sw_timer;
+       sw->timer.data = (unsigned long)sw;
+
+       return 0;
+}
+
+static int __init add_atag_switches(void)
+{
+       const struct omap_gpio_switch_config *cfg;
+       struct gpio_switch *sw;
+       int i, r;
+
+       for (i = 0; ; i++) {
+               cfg = omap_get_nr_config(OMAP_TAG_GPIO_SWITCH,
+                                        struct omap_gpio_switch_config, i);
+               if (cfg == NULL)
+                       break;
+               sw = kmalloc(sizeof(*sw), GFP_KERNEL);
+               if (sw == NULL) {
+                       printk(KERN_ERR "gpio-switch: kmalloc failed\n");
+                       return -ENOMEM;
+               }
+               memset(sw, 0, sizeof(*sw));
+               strncpy(sw->name, cfg->name, sizeof(cfg->name));
+               sw->gpio = cfg->gpio;
+               sw->flags = cfg->flags;
+               sw->type = cfg->type;
+               sw->key_code = cfg->key_code;
+               sw->state = gpio_sw_get_state(sw);
+               if ((r = new_switch(sw)) < 0) {
+                       kfree(sw);
+                       return r;
+               }
+       }
+       return 0;
+}
+
+static void gpio_sw_cleanup(void)
+{
+       struct gpio_switch *sw = NULL, *old = NULL;
+
+       list_for_each_entry(sw, &gpio_switches, node) {
+               kfree(old);
+
+               flush_scheduled_work();
+               del_timer_sync(&sw->timer);
+
+               free_irq(OMAP_GPIO_IRQ(sw->gpio), sw);
+
+               if (sw->type == OMAP_GPIO_SWITCH_TYPE_CONNECTION)
+                       device_remove_file(&sw->pdev.dev,
+                                          &dev_attr_connection_switch);
+               else
+                       device_remove_file(&sw->pdev.dev,
+                                          &dev_attr_cover_switch);
+
+               platform_device_unregister(&sw->pdev);
+               omap_free_gpio(sw->gpio);
+               old = sw;
+       }
+
+       kfree(sw);
+}
+
+static void __init report_initial_state(void)
+{
+       struct gpio_switch *sw;
+
+       list_for_each_entry(sw, &gpio_switches, node) {
+               int state;
+
+               state = omap_get_gpio_datain(sw->gpio);
+               if (sw->flags & OMAP_GPIO_SWITCH_FLAG_INVERTED)
+                       state = !state;
+               print_sw_state(sw, state);
+       }
+}
+
+static void gpio_sw_shutdown(struct device *dev)
+{
+}
+
+static struct device_driver gpio_sw_driver = {
+       .name           = "gpio-switch",
+       .bus            = &platform_bus_type,
+       .shutdown       = gpio_sw_shutdown,
+};
+
+static int __init gpio_sw_init(void)
+{
+       int r;
+
+       printk(KERN_INFO "OMAP GPIO switch handler initializing\n");
+
+       r = driver_register(&gpio_sw_driver);
+       if (r)
+               return r;
+
+       gpio_sw_platform_dev = platform_device_register_simple("gpio-switch",
+                                                              -1, NULL, 0);
+       if (IS_ERR(gpio_sw_platform_dev)) {
+               driver_unregister(&gpio_sw_driver);
+               return PTR_ERR(gpio_sw_platform_dev);
+       }
+
+       r = add_atag_switches();
+       if (r < 0) {
+               platform_device_unregister(gpio_sw_platform_dev);
+               driver_unregister(&gpio_sw_driver);
+               gpio_sw_cleanup();
+               return r;
+       }
+
+       report_initial_state();
+
+       return 0;
+}
+
+static void __exit gpio_sw_exit(void)
+{
+       gpio_sw_cleanup();
+       platform_device_unregister(gpio_sw_platform_dev);
+       driver_unregister(&gpio_sw_driver);
+}
+
+#ifndef MODULE
+late_initcall(gpio_sw_init);
+#else
+module_init(gpio_sw_init);
+#endif
+module_exit(gpio_sw_exit);
+
+MODULE_AUTHOR("Juha Yrjölä <juha.yrjola@nokia.com>, Paul Mundt <paul.mundt@nokia.com");
+MODULE_DESCRIPTION("GPIO switch driver");
+MODULE_LICENSE("GPL");