From 8fba45f5a599685179b489a1dd3aa5d0d2c49f85 Mon Sep 17 00:00:00 2001 From: Juha Yrjola Date: Mon, 9 May 2005 13:12:15 -0700 Subject: [PATCH] ARM: OMAP: Add support for GPIO switch Adds support for GPIO switch Signed-off-by: Juha Yrjola Signed-off-by: Tony Lindgren --- arch/arm/mach-omap/Kconfig | 10 + arch/arm/mach-omap/Makefile | 1 + arch/arm/mach-omap/gpio-switch.c | 332 +++++++++++++++++++++++++++++++ 3 files changed, 343 insertions(+) create mode 100644 arch/arm/mach-omap/gpio-switch.c diff --git a/arch/arm/mach-omap/Kconfig b/arch/arm/mach-omap/Kconfig index 965bb989c7c..ae0549a1366 100644 --- a/arch/arm/mach-omap/Kconfig +++ b/arch/arm/mach-omap/Kconfig @@ -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 diff --git a/arch/arm/mach-omap/Makefile b/arch/arm/mach-omap/Makefile index 769437d5bf8..bcbbfb1fe47 100644 --- a/arch/arm/mach-omap/Makefile +++ b/arch/arm/mach-omap/Makefile @@ -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 index 00000000000..ca47111d55e --- /dev/null +++ b/arch/arm/mach-omap/gpio-switch.c @@ -0,0 +1,332 @@ +/* + * linux/arch/arm/mach-omap/gpio-switch.c + * + * Copyright (C) 2004, 2005 Nokia Corporation + * Written by Juha Yrjölä + * and Paul Mundt + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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ä , Paul Mundt