From ea4bd079b6f7460058925750e6caf37a20ab5d5c Mon Sep 17 00:00:00 2001 From: Juha Yrjola Date: Mon, 7 Aug 2006 20:17:42 +0300 Subject: [PATCH] ARM: OMAP: Update gpio-switch framework - 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 --- arch/arm/plat-omap/gpio-switch.c | 211 ++++++++++++++++++++---- include/asm-arm/arch-omap/board.h | 19 +-- include/asm-arm/arch-omap/gpio-switch.h | 54 ++++++ 3 files changed, 234 insertions(+), 50 deletions(-) create mode 100644 include/asm-arm/arch-omap/gpio-switch.h diff --git a/arch/arm/plat-omap/gpio-switch.c b/arch/arm/plat-omap/gpio-switch.c index fd1b5fa2c55..cd8bbd424b0 100644 --- a/arch/arm/plat-omap/gpio-switch.c +++ b/arch/arm/plat-omap/gpio-switch.c @@ -24,13 +24,21 @@ #include #include #include +#include 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) diff --git a/include/asm-arm/arch-omap/board.h b/include/asm-arm/arch-omap/board.h index 5a93ed1715a..9e1cbe348b8 100644 --- a/include/asm-arm/arch-omap/board.h +++ b/include/asm-arm/arch-omap/board.h @@ -12,6 +12,8 @@ #include +#include + /* 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 index 00000000000..10da0e07c0c --- /dev/null +++ b/include/asm-arm/arch-omap/gpio-switch.h @@ -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 + +/* 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 -- 2.41.1