]> pilppa.com Git - linux-2.6-omap-h63xx.git/commitdiff
ARM: OMAP: Update gpio-switch framework
authorJuha Yrjola <juha.yrjola@solidboot.com>
Mon, 7 Aug 2006 17:17:42 +0000 (20:17 +0300)
committerJuha Yrjola <juha.yrjola@solidboot.com>
Tue, 8 Aug 2006 21:29:59 +0000 (00:29 +0300)
- Support for in-kernel notifications

  Some boards require state-switch notifications from GPIOs
  that are also exposed to user-space through the gpio-switch
  framework.

- Support for board-specific debounce values
- Support for switches defined from board-specific file
- Support for GPIO banks that can trigger on both rising
  and falling edges

Signed-off-by: Juha Yrjola <juha.yrjola@solidboot.com>
arch/arm/plat-omap/gpio-switch.c
include/asm-arm/arch-omap/board.h
include/asm-arm/arch-omap/gpio-switch.h [new file with mode: 0644]

index fd1b5fa2c557131d66aaa945645e3b4164223faa..cd8bbd424b0280f21339fccb1fcbc2ca0c87a4a1 100644 (file)
 #include <asm/arch/irqs.h>
 #include <asm/arch/mux.h>
 #include <asm/arch/board.h>
+#include <asm/arch/gpio-switch.h>
 
 struct gpio_switch {
        char            name[14];
        u16             gpio;
-       int             flags;
-       int             type;
-       int             state;
+       unsigned        flags:4;
+       unsigned        type:4;
+       unsigned        state:1;
+       unsigned        both_edges:1;
+
+       u16             debounce_rising;
+       u16             debounce_falling;
+
+       void (* notify)(void *data, int state);
+       void *notify_data;
 
        struct work_struct      work;
        struct timer_list       timer;
@@ -43,14 +51,17 @@ static LIST_HEAD(gpio_switches);
 static struct platform_device *gpio_sw_platform_dev;
 static struct device_driver gpio_sw_driver;
 
+static const struct omap_gpio_switch *board_gpio_sw_table;
+static int board_gpio_sw_count;
+
 static const char *cover_str[2] = { "open", "closed" };
 static const char *connection_str[2] = { "disconnected", "connected" };
 static const char *activity_str[2] = { "inactive", "active" };
 
 /*
- * GPIO switch state debounce delay in ms
+ * GPIO switch state default debounce delay in ms
  */
-#define OMAP_GPIO_SW_DEBOUNCE_DELAY    10
+#define OMAP_GPIO_SW_DEFAULT_DEBOUNCE          10
 
 static const char **get_sw_str(struct gpio_switch *sw)
 {
@@ -174,8 +185,28 @@ static DEVICE_ATTR(direction, S_IRUGO, gpio_sw_direction_show, NULL);
 static irqreturn_t gpio_sw_irq_handler(int irq, void *arg, struct pt_regs *regs)
 {
        struct gpio_switch *sw = arg;
+       unsigned long timeout;
+       int state;
 
-       mod_timer(&sw->timer, jiffies + msecs_to_jiffies(OMAP_GPIO_SW_DEBOUNCE_DELAY));
+       if (!sw->both_edges) {
+               if (omap_get_gpio_datain(sw->gpio))
+                       set_irq_type(OMAP_GPIO_IRQ(sw->gpio), IRQT_FALLING);
+               else
+                       set_irq_type(OMAP_GPIO_IRQ(sw->gpio), IRQT_RISING);
+       }
+
+       state = gpio_sw_get_state(sw);
+       if (sw->state == state)
+               return IRQ_HANDLED;
+
+       if (state)
+               timeout = sw->debounce_rising;
+       else
+               timeout = sw->debounce_falling;
+       if (!timeout)
+               schedule_work(&sw->work);
+       else
+               mod_timer(&sw->timer, jiffies + msecs_to_jiffies(timeout));
 
        return IRQ_HANDLED;
 }
@@ -197,14 +228,26 @@ static void gpio_sw_handler(void *data)
                return;
 
        sw->state = state;
-       if (omap_get_gpio_datain(sw->gpio))
-               set_irq_type(OMAP_GPIO_IRQ(sw->gpio), IRQT_FALLING);
-       else
-               set_irq_type(OMAP_GPIO_IRQ(sw->gpio), IRQT_RISING);
+       if (sw->notify != NULL)
+               sw->notify(sw->notify_data, state);
        sysfs_notify(&sw->pdev.dev.kobj, NULL, "state");
        print_sw_state(sw, state);
 }
 
+static int __init can_do_both_edges(struct gpio_switch *sw)
+{
+       if (!cpu_class_is_omap1())
+               return 1;
+       if (OMAP_GPIO_IS_MPUIO(sw->gpio))
+               return 0;
+       else
+               return 1;
+}
+
+static void gpio_sw_release(struct device *dev)
+{
+}
+
 static int __init new_switch(struct gpio_switch *sw)
 {
        int r, direction, trigger;
@@ -224,11 +267,14 @@ static int __init new_switch(struct gpio_switch *sw)
 
        sw->pdev.dev.parent = &gpio_sw_platform_dev->dev;
        sw->pdev.dev.driver = &gpio_sw_driver;
+       sw->pdev.dev.release = gpio_sw_release;
 
        r = platform_device_register(&sw->pdev);
-       if (r)
+       if (r) {
+               printk(KERN_ERR "gpio-switch: platform device registration "
+                      "failed for %s", sw->name);
                return r;
-
+       }
        dev_set_drvdata(&sw->pdev.dev, sw);
 
        r = omap_request_gpio(sw->gpio);
@@ -241,24 +287,29 @@ static int __init new_switch(struct gpio_switch *sw)
        direction = !(sw->flags & OMAP_GPIO_SWITCH_FLAG_OUTPUT);
        omap_set_gpio_direction(sw->gpio, direction);
 
+       sw->state = gpio_sw_get_state(sw);
+
        device_create_file(&sw->pdev.dev, &dev_attr_state);
        device_create_file(&sw->pdev.dev, &dev_attr_type);
        device_create_file(&sw->pdev.dev, &dev_attr_direction);
 
-       list_add(&sw->node, &gpio_switches);
-
        if (!direction)
                return 0;
 
-       if (omap_get_gpio_datain(sw->gpio))
-               trigger = SA_TRIGGER_FALLING;
-       else
-               trigger = SA_TRIGGER_RISING;
+       if (can_do_both_edges(sw)) {
+               trigger = SA_TRIGGER_FALLING | SA_TRIGGER_RISING;
+               sw->both_edges = 1;
+       } else {
+               if (omap_get_gpio_datain(sw->gpio))
+                       trigger = SA_TRIGGER_FALLING;
+               else
+                       trigger = SA_TRIGGER_RISING;
+       }
        r = request_irq(OMAP_GPIO_IRQ(sw->gpio), gpio_sw_irq_handler,
                        SA_SHIRQ | trigger, sw->name, sw);
        if (r < 0) {
                printk(KERN_ERR "gpio-switch: request_irq() failed "
-                               "for GPIO %d\n", sw->gpio);
+                      "for GPIO %d\n", sw->gpio);
                platform_device_unregister(&sw->pdev);
                omap_free_gpio(sw->gpio);
                return r;
@@ -270,6 +321,8 @@ static int __init new_switch(struct gpio_switch *sw)
        sw->timer.function = gpio_sw_timer;
        sw->timer.data = (unsigned long)sw;
 
+       list_add(&sw->node, &gpio_switches);
+
        return 0;
 }
 
@@ -284,17 +337,91 @@ static int __init add_atag_switches(void)
                                         struct omap_gpio_switch_config, i);
                if (cfg == NULL)
                        break;
-               sw = kmalloc(sizeof(*sw), GFP_KERNEL);
+               sw = kzalloc(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->state = gpio_sw_get_state(sw);
+               sw->debounce_rising = OMAP_GPIO_SW_DEFAULT_DEBOUNCE;
+               sw->debounce_falling = OMAP_GPIO_SW_DEFAULT_DEBOUNCE;
+               if ((r = new_switch(sw)) < 0) {
+                       kfree(sw);
+                       return r;
+               }
+       }
+       return 0;
+}
+
+static struct gpio_switch * __init find_switch(int gpio, const char *name)
+{
+       struct gpio_switch *sw;
+
+       list_for_each_entry(sw, &gpio_switches, node) {
+               if ((gpio < 0 || sw->gpio != gpio) &&
+                   (name == NULL || strcmp(sw->name, name) != 0))
+                       continue;
+
+               if (gpio < 0 || name == NULL)
+                       goto no_check;
+
+               if (strcmp(sw->name, name) != 0)
+                       printk("gpio-switch: name mismatch for %d (%s, %s)\n",
+                              gpio, name, sw->name);
+               else if (sw->gpio != gpio)
+                       printk("gpio-switch: GPIO mismatch for %s (%d, %d)\n",
+                              name, gpio, sw->gpio);
+no_check:
+               return sw;
+       }
+       return NULL;
+}
+
+static int __init add_board_switches(void)
+{
+       int i;
+
+       for (i = 0; i < board_gpio_sw_count; i++) {
+               const struct omap_gpio_switch *cfg;
+               struct gpio_switch *sw;
+               int r;
+
+               cfg = board_gpio_sw_table + i;
+               if (strlen(cfg->name) > sizeof(sw->name) - 1)
+                       return -EINVAL;
+               /* Check whether we only update an existing switch
+                * or add a new switch. */
+               sw = find_switch(cfg->gpio, cfg->name);
+               if (sw != NULL) {
+                       sw->debounce_rising = cfg->debounce_rising;
+                       sw->debounce_falling = cfg->debounce_falling;
+                       sw->notify = cfg->notify;
+                       sw->notify_data = cfg->notify_data;
+                       continue;
+               } else {
+                       if (cfg->gpio < 0 || cfg->name == NULL) {
+                               printk("gpio-switch: required switch not "
+                                      "found (%d, %s)\n", cfg->gpio,
+                                      cfg->name);
+                               continue;
+                       }
+               }
+               sw = kzalloc(sizeof(*sw), GFP_KERNEL);
+               if (sw == NULL) {
+                       printk(KERN_ERR "gpio-switch: kmalloc failed\n");
+                       return -ENOMEM;
+               }
+               strlcpy(sw->name, cfg->name, sizeof(sw->name));
+               sw->gpio = cfg->gpio;
+               sw->flags = cfg->flags;
+               sw->type = cfg->type;
+               sw->debounce_rising = cfg->debounce_rising;
+               sw->debounce_falling = cfg->debounce_falling;
+               sw->notify = cfg->notify;
+               sw->notify_data = cfg->notify_data;
                if ((r = new_switch(sw)) < 0) {
                        kfree(sw);
                        return r;
@@ -308,8 +435,8 @@ static void gpio_sw_cleanup(void)
        struct gpio_switch *sw = NULL, *old = NULL;
 
        list_for_each_entry(sw, &gpio_switches, node) {
-               kfree(old);
-
+               if (old != NULL)
+                       kfree(old);
                flush_scheduled_work();
                del_timer_sync(&sw->timer);
 
@@ -323,8 +450,7 @@ static void gpio_sw_cleanup(void)
                omap_free_gpio(sw->gpio);
                old = sw;
        }
-
-       kfree(sw);
+       kfree(old);
 }
 
 static void __init report_initial_state(void)
@@ -337,6 +463,8 @@ static void __init report_initial_state(void)
                state = omap_get_gpio_datain(sw->gpio);
                if (sw->flags & OMAP_GPIO_SWITCH_FLAG_INVERTED)
                        state = !state;
+               if (sw->notify != NULL)
+                       sw->notify(sw->notify_data, state);
                print_sw_state(sw, state);
        }
 }
@@ -351,6 +479,15 @@ static struct device_driver gpio_sw_driver = {
        .shutdown       = gpio_sw_shutdown,
 };
 
+void __init omap_register_gpio_switches(const struct omap_gpio_switch *tbl,
+                                       int count)
+{
+       BUG_ON(board_gpio_sw_table != NULL);
+
+       board_gpio_sw_table = tbl;
+       board_gpio_sw_count = count;
+}
+
 static int __init gpio_sw_init(void)
 {
        int r;
@@ -364,21 +501,27 @@ static int __init gpio_sw_init(void)
        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 = PTR_ERR(gpio_sw_platform_dev);
+               goto err1;
        }
 
        r = add_atag_switches();
-       if (r < 0) {
-               platform_device_unregister(gpio_sw_platform_dev);
-               driver_unregister(&gpio_sw_driver);
-               gpio_sw_cleanup();
-               return r;
-       }
+       if (r < 0)
+               goto err2;
+
+       r = add_board_switches();
+       if (r < 0)
+               goto err2;
 
        report_initial_state();
 
        return 0;
+err2:
+       gpio_sw_cleanup();
+       platform_device_unregister(gpio_sw_platform_dev);
+err1:
+       driver_unregister(&gpio_sw_driver);
+       return r;
 }
 
 static void __exit gpio_sw_exit(void)
index 5a93ed1715af22c383045b5293588cac25a6da0b..9e1cbe348b8b96284b902933720013dd37bd00da 100644 (file)
@@ -12,6 +12,8 @@
 
 #include <linux/types.h>
 
+#include <asm/arch/gpio-switch.h>
+
 /* Different peripheral ids */
 #define OMAP_TAG_CLOCK         0x4f01
 #define OMAP_TAG_MMC           0x4f02
@@ -115,22 +117,7 @@ struct omap_pwm_led_platform_data {
        void (*set_power)(struct omap_pwm_led_platform_data *self, int on_off);
 };
 
-/* Cover:
- *     high -> closed
- *     low  -> open
- * Connection:
- *     high -> connected
- *     low  -> disconnected
- * Activity:
- *     high -> active
- *     low  -> inactive
- *
- */
-#define OMAP_GPIO_SWITCH_TYPE_COVER            0x0000
-#define OMAP_GPIO_SWITCH_TYPE_CONNECTION       0x0001
-#define OMAP_GPIO_SWITCH_TYPE_ACTIVITY         0x0002
-#define OMAP_GPIO_SWITCH_FLAG_INVERTED         0x0001
-#define OMAP_GPIO_SWITCH_FLAG_OUTPUT           0x0002
+/* See include/asm-arm/arch-omap/gpio-switch.h for definitions */
 struct omap_gpio_switch_config {
        char name[12];
        u16 gpio;
diff --git a/include/asm-arm/arch-omap/gpio-switch.h b/include/asm-arm/arch-omap/gpio-switch.h
new file mode 100644 (file)
index 0000000..10da0e0
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * GPIO switch definitions
+ *
+ * Copyright (C) 2006 Nokia Corporation
+ *
+ * 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.
+ */
+
+#ifndef __ASM_ARCH_OMAP_GPIO_SWITCH_H
+#define __ASM_ARCH_OMAP_GPIO_SWITCH_H
+
+#include <linux/types.h>
+
+/* Cover:
+ *     high -> closed
+ *     low  -> open
+ * Connection:
+ *     high -> connected
+ *     low  -> disconnected
+ * Activity:
+ *     high -> active
+ *     low  -> inactive
+ *
+ */
+#define OMAP_GPIO_SWITCH_TYPE_COVER            0x0000
+#define OMAP_GPIO_SWITCH_TYPE_CONNECTION       0x0001
+#define OMAP_GPIO_SWITCH_TYPE_ACTIVITY         0x0002
+#define OMAP_GPIO_SWITCH_FLAG_INVERTED         0x0001
+#define OMAP_GPIO_SWITCH_FLAG_OUTPUT           0x0002
+
+struct omap_gpio_switch {
+       const char *name;
+       s16 gpio;
+       unsigned flags:4;
+       unsigned type:4;
+
+       /* Time in ms to debounce when transitioning from
+        * inactive state to active state. */
+       u16 debounce_rising;
+       /* Same for transition from active to inactive state. */
+       u16 debounce_falling;
+
+       /* notify board-specific code about state changes */
+       void (* notify)(void *data, int state);
+       void *notify_data;
+};
+
+/* Call at init time only */
+extern void omap_register_gpio_switches(const struct omap_gpio_switch *tbl,
+                                       int count);
+
+#endif