From 5c105d9f3fd086aff195d3849dcf847d6b0bd927 Mon Sep 17 00:00:00 2001 From: blogic Date: Fri, 5 Oct 2012 10:12:53 +0000 Subject: branch Attitude Adjustment git-svn-id: svn://svn.openwrt.org/openwrt/branches/attitude_adjustment@33625 3c298f89-4303-0410-b956-a3cf2f4a3e73 --- .../omap24xx/patches-3.3/200-omap-platform.patch | 897 +++++++++++++++++++++ 1 file changed, 897 insertions(+) create mode 100644 target/linux/omap24xx/patches-3.3/200-omap-platform.patch (limited to 'target/linux/omap24xx/patches-3.3/200-omap-platform.patch') diff --git a/target/linux/omap24xx/patches-3.3/200-omap-platform.patch b/target/linux/omap24xx/patches-3.3/200-omap-platform.patch new file mode 100644 index 000000000..6c215bdbe --- /dev/null +++ b/target/linux/omap24xx/patches-3.3/200-omap-platform.patch @@ -0,0 +1,897 @@ +--- /dev/null ++++ b/arch/arm/plat-omap/bootreason.c +@@ -0,0 +1,79 @@ ++/* ++ * linux/arch/arm/plat-omap/bootreason.c ++ * ++ * OMAP Bootreason passing ++ * ++ * Copyright (c) 2004 Nokia ++ * ++ * Written by David Weinehall ++ * ++ * 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. ++ * ++ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED ++ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF ++ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN ++ * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, ++ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT ++ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF ++ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ++ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ++ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF ++ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ++ * ++ * You should have received a copy of the GNU General Public License along ++ * with this program; if not, write to the Free Software Foundation, Inc., ++ * 675 Mass Ave, Cambridge, MA 02139, USA. ++ */ ++#include ++#include ++#include ++ ++static char boot_reason[16]; ++ ++static int omap_bootreason_read_proc(char *page, char **start, off_t off, ++ int count, int *eof, void *data) ++{ ++ int len = 0; ++ ++ len += sprintf(page + len, "%s\n", boot_reason); ++ ++ *start = page + off; ++ ++ if (len > off) ++ len -= off; ++ else ++ len = 0; ++ ++ return len < count ? len : count; ++} ++ ++static int __init bootreason_init(void) ++{ ++ const struct omap_boot_reason_config *cfg; ++ int reason_valid = 0; ++ ++ cfg = omap_get_config(OMAP_TAG_BOOT_REASON, struct omap_boot_reason_config); ++ if (cfg != NULL) { ++ strncpy(boot_reason, cfg->reason_str, sizeof(cfg->reason_str)); ++ boot_reason[sizeof(cfg->reason_str)] = 0; ++ reason_valid = 1; ++ } else { ++ /* Read the boot reason from the OMAP registers */ ++ } ++ ++ if (!reason_valid) ++ return -ENOENT; ++ ++ printk(KERN_INFO "Bootup reason: %s\n", boot_reason); ++ ++ if (!create_proc_read_entry("bootreason", S_IRUGO, NULL, ++ omap_bootreason_read_proc, NULL)) ++ return -ENOMEM; ++ ++ return 0; ++} ++ ++late_initcall(bootreason_init); +--- a/arch/arm/plat-omap/common.c ++++ b/arch/arm/plat-omap/common.c +@@ -24,18 +24,90 @@ + + #include + ++#include ++ + + #define NO_LENGTH_CHECK 0xffffffff + + struct omap_board_config_kernel *omap_board_config __initdata; + int omap_board_config_size; + ++unsigned char omap_bootloader_tag[1024]; ++int omap_bootloader_tag_len; ++ ++/* used by omap-smp.c and board-4430sdp.c */ ++void __iomem *gic_cpu_base_addr; ++ ++#ifdef CONFIG_OMAP_BOOT_TAG ++ ++static int __init parse_tag_omap(const struct tag *tag) ++{ ++ u32 size = tag->hdr.size - (sizeof(tag->hdr) >> 2); ++ ++ size <<= 2; ++ if (size > sizeof(omap_bootloader_tag)) ++ return -1; ++ ++ memcpy(omap_bootloader_tag, tag->u.omap.data, size); ++ omap_bootloader_tag_len = size; ++ ++ return 0; ++} ++ ++__tagtable(ATAG_BOARD, parse_tag_omap); ++ ++#endif ++ + static const void *__init get_config(u16 tag, size_t len, + int skip, size_t *len_out) + { + struct omap_board_config_kernel *kinfo = NULL; + int i; + ++#ifdef CONFIG_OMAP_BOOT_TAG ++ struct omap_board_config_entry *info = NULL; ++ ++ if (omap_bootloader_tag_len > 4) ++ info = (struct omap_board_config_entry *) omap_bootloader_tag; ++ while (info != NULL) { ++ u8 *next; ++ ++ if (info->tag == tag) { ++ if (skip == 0) ++ break; ++ skip--; ++ } ++ ++ if ((info->len & 0x03) != 0) { ++ /* We bail out to avoid an alignment fault */ ++ printk(KERN_ERR "OMAP peripheral config: Length (%d) not word-aligned (tag %04x)\n", ++ info->len, info->tag); ++ return NULL; ++ } ++ next = (u8 *) info + sizeof(*info) + info->len; ++ if (next >= omap_bootloader_tag + omap_bootloader_tag_len) ++ info = NULL; ++ else ++ info = (struct omap_board_config_entry *) next; ++ } ++ if (info != NULL) { ++ /* Check the length as a lame attempt to check for ++ * binary inconsistency. */ ++ if (len != NO_LENGTH_CHECK) { ++ /* Word-align len */ ++ if (len & 0x03) ++ len = (len + 3) & ~0x03; ++ if (info->len != len) { ++ printk(KERN_ERR "OMAP peripheral config: Length mismatch with tag %x (want %d, got %d)\n", ++ tag, len, info->len); ++ return NULL; ++ } ++ } ++ if (len_out != NULL) ++ *len_out = info->len; ++ return info->data; ++ } ++#endif + /* Try to find the config from the board-specific structures + * in the kernel. */ + for (i = 0; i < omap_board_config_size; i++) { +--- /dev/null ++++ b/arch/arm/plat-omap/component-version.c +@@ -0,0 +1,64 @@ ++/* ++ * linux/arch/arm/plat-omap/component-version.c ++ * ++ * Copyright (C) 2005 Nokia Corporation ++ * Written by Juha Yrjölä ++ * ++ * 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 ++ ++static int component_version_read_proc(char *page, char **start, off_t off, ++ int count, int *eof, void *data) ++{ ++ int len, i; ++ const struct omap_version_config *ver; ++ char *p; ++ ++ i = 0; ++ p = page; ++ while ((ver = omap_get_nr_config(OMAP_TAG_VERSION_STR, ++ struct omap_version_config, i)) != NULL) { ++ p += sprintf(p, "%-12s%s\n", ver->component, ver->version); ++ i++; ++ } ++ ++ len = (p - page) - off; ++ if (len < 0) ++ len = 0; ++ ++ *eof = (len <= count) ? 1 : 0; ++ *start = page + off; ++ ++ return len; ++} ++ ++static int __init component_version_init(void) ++{ ++ if (omap_get_config(OMAP_TAG_VERSION_STR, struct omap_version_config) == NULL) ++ return -ENODEV; ++ if (!create_proc_read_entry("component_version", S_IRUGO, NULL, ++ component_version_read_proc, NULL)) ++ return -ENOMEM; ++ ++ return 0; ++} ++ ++static void __exit component_version_exit(void) ++{ ++ remove_proc_entry("component_version", NULL); ++} ++ ++late_initcall(component_version_init); ++module_exit(component_version_exit); ++ ++MODULE_AUTHOR("Juha Yrjölä "); ++MODULE_DESCRIPTION("Component version driver"); ++MODULE_LICENSE("GPL"); +--- a/arch/arm/plat-omap/Kconfig ++++ b/arch/arm/plat-omap/Kconfig +@@ -84,6 +84,38 @@ config OMAP_RESET_CLOCKS + probably do not want this option enabled until your + device drivers work properly. + ++config OMAP_BOOT_TAG ++ bool "OMAP bootloader information passing" ++ depends on ARCH_OMAP ++ default n ++ help ++ Say Y, if you have a bootloader which passes information ++ about your board and its peripheral configuration. ++ ++config OMAP_BOOT_REASON ++ bool "Support for boot reason" ++ depends on OMAP_BOOT_TAG ++ default n ++ help ++ Say Y, if you want to have a procfs entry for reading the boot ++ reason in user-space. ++ ++config OMAP_COMPONENT_VERSION ++ bool "Support for component version display" ++ depends on OMAP_BOOT_TAG && PROC_FS ++ default n ++ help ++ Say Y, if you want to have a procfs entry for reading component ++ versions (supplied by the bootloader) in user-space. ++ ++config OMAP_GPIO_SWITCH ++ bool "GPIO switch support" ++ help ++ Say Y, if you want to have support for reporting of GPIO ++ switches (e.g. cover switches) via sysfs. Your bootloader has ++ to provide information about the switches to the kernel via the ++ ATAG_BOARD mechanism if they're not defined by the board config. ++ + config OMAP_MUX + bool "OMAP multiplexing support" + depends on ARCH_OMAP +--- a/arch/arm/plat-omap/Makefile ++++ b/arch/arm/plat-omap/Makefile +@@ -20,6 +20,9 @@ obj-$(CONFIG_ARCH_OMAP4) += omap_device. + obj-$(CONFIG_OMAP_MCBSP) += mcbsp.o + + obj-$(CONFIG_OMAP_DM_TIMER) += dmtimer.o ++obj-$(CONFIG_OMAP_BOOT_REASON) += bootreason.o ++obj-$(CONFIG_OMAP_COMPONENT_VERSION) += component-version.o ++obj-$(CONFIG_OMAP_GPIO_SWITCH) += gpio-switch.o + obj-$(CONFIG_OMAP_DEBUG_DEVICES) += debug-devices.o + obj-$(CONFIG_OMAP_DEBUG_LEDS) += debug-leds.o + i2c-omap-$(CONFIG_I2C_OMAP) := i2c.o +--- a/arch/arm/include/asm/setup.h ++++ b/arch/arm/include/asm/setup.h +@@ -136,6 +136,13 @@ struct tag_acorn { + __u8 adfsdrives; + }; + ++/* TI OMAP specific information */ ++#define ATAG_BOARD 0x414f4d50 ++ ++struct tag_omap { ++ u8 data[0]; ++}; ++ + /* footbridge memory clock, see arch/arm/mach-footbridge/arch.c */ + #define ATAG_MEMCLK 0x41000402 + +@@ -162,6 +169,11 @@ struct tag { + struct tag_acorn acorn; + + /* ++ * OMAP specific ++ */ ++ struct tag_omap omap; ++ ++ /* + * DC21285 specific + */ + struct tag_memclk memclk; +--- /dev/null ++++ b/arch/arm/plat-omap/gpio-switch.c +@@ -0,0 +1,554 @@ ++/* ++ * linux/arch/arm/plat-omap/gpio-switch.c ++ * ++ * Copyright (C) 2004-2006 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 ++#include ++#include ++#include ++ ++struct gpio_switch { ++ char name[14]; ++ u16 gpio; ++ 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; ++ struct platform_device pdev; ++ ++ struct list_head node; ++}; ++ ++static LIST_HEAD(gpio_switches); ++static struct platform_device *gpio_sw_platform_dev; ++static struct platform_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 default debounce delay in ms ++ */ ++#define OMAP_GPIO_SW_DEFAULT_DEBOUNCE 10 ++ ++static const char **get_sw_str(struct gpio_switch *sw) ++{ ++ switch (sw->type) { ++ case OMAP_GPIO_SWITCH_TYPE_COVER: ++ return cover_str; ++ case OMAP_GPIO_SWITCH_TYPE_CONNECTION: ++ return connection_str; ++ case OMAP_GPIO_SWITCH_TYPE_ACTIVITY: ++ return activity_str; ++ default: ++ BUG(); ++ return NULL; ++ } ++} ++ ++static const char *get_sw_type(struct gpio_switch *sw) ++{ ++ switch (sw->type) { ++ case OMAP_GPIO_SWITCH_TYPE_COVER: ++ return "cover"; ++ case OMAP_GPIO_SWITCH_TYPE_CONNECTION: ++ return "connection"; ++ case OMAP_GPIO_SWITCH_TYPE_ACTIVITY: ++ return "activity"; ++ default: ++ BUG(); ++ return NULL; ++ } ++} ++ ++static void print_sw_state(struct gpio_switch *sw, int state) ++{ ++ const char **str; ++ ++ str = get_sw_str(sw); ++ 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 = gpio_get_value(sw->gpio); ++ if (sw->flags & OMAP_GPIO_SWITCH_FLAG_INVERTED) ++ state = !state; ++ ++ return state; ++} ++ ++static ssize_t gpio_sw_state_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, ++ size_t count) ++{ ++ struct gpio_switch *sw = dev_get_drvdata(dev); ++ const char **str; ++ char state[16]; ++ int enable; ++ ++ if (!(sw->flags & OMAP_GPIO_SWITCH_FLAG_OUTPUT)) ++ return -EPERM; ++ ++ if (sscanf(buf, "%15s", state) != 1) ++ return -EINVAL; ++ ++ str = get_sw_str(sw); ++ if (strcmp(state, str[0]) == 0) ++ sw->state = enable = 0; ++ else if (strcmp(state, str[1]) == 0) ++ sw->state = enable = 1; ++ else ++ return -EINVAL; ++ ++ if (sw->flags & OMAP_GPIO_SWITCH_FLAG_INVERTED) ++ enable = !enable; ++ gpio_set_value(sw->gpio, enable); ++ ++ return count; ++} ++ ++static ssize_t gpio_sw_state_show(struct device *dev, ++ struct device_attribute *attr, ++ char *buf) ++{ ++ struct gpio_switch *sw = dev_get_drvdata(dev); ++ const char **str; ++ ++ str = get_sw_str(sw); ++ return sprintf(buf, "%s\n", str[sw->state]); ++} ++ ++static DEVICE_ATTR(state, S_IRUGO | S_IWUSR, gpio_sw_state_show, ++ gpio_sw_state_store); ++ ++static ssize_t gpio_sw_type_show(struct device *dev, ++ struct device_attribute *attr, ++ char *buf) ++{ ++ struct gpio_switch *sw = dev_get_drvdata(dev); ++ ++ return sprintf(buf, "%s\n", get_sw_type(sw)); ++} ++ ++static DEVICE_ATTR(type, S_IRUGO, gpio_sw_type_show, NULL); ++ ++static ssize_t gpio_sw_direction_show(struct device *dev, ++ struct device_attribute *attr, ++ char *buf) ++{ ++ struct gpio_switch *sw = dev_get_drvdata(dev); ++ int is_output; ++ ++ is_output = sw->flags & OMAP_GPIO_SWITCH_FLAG_OUTPUT; ++ return sprintf(buf, "%s\n", is_output ? "output" : "input"); ++} ++ ++static DEVICE_ATTR(direction, S_IRUGO, gpio_sw_direction_show, NULL); ++ ++ ++static irqreturn_t gpio_sw_irq_handler(int irq, void *arg) ++{ ++ struct gpio_switch *sw = arg; ++ unsigned long timeout; ++ int state; ++ ++ if (!sw->both_edges) { ++ if (gpio_get_value(sw->gpio)) ++ irq_set_irq_type(OMAP_GPIO_IRQ(sw->gpio), IRQ_TYPE_EDGE_FALLING); ++ else ++ irq_set_irq_type(OMAP_GPIO_IRQ(sw->gpio), IRQ_TYPE_EDGE_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; ++} ++ ++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(struct work_struct *work) ++{ ++ struct gpio_switch *sw = container_of(work, struct gpio_switch, work); ++ int state; ++ ++ state = gpio_sw_get_state(sw); ++ if (sw->state == state) ++ return; ++ ++ sw->state = state; ++ 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; ++ ++ switch (sw->type) { ++ case OMAP_GPIO_SWITCH_TYPE_COVER: ++ case OMAP_GPIO_SWITCH_TYPE_CONNECTION: ++ case OMAP_GPIO_SWITCH_TYPE_ACTIVITY: ++ break; ++ default: ++ printk(KERN_ERR "invalid GPIO switch type: %d\n", sw->type); ++ return -EINVAL; ++ } ++ ++ 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.driver; ++ sw->pdev.dev.release = gpio_sw_release; ++ ++ r = platform_device_register(&sw->pdev); ++ 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 = gpio_request(sw->gpio, "gpio-switch"); ++ if (r < 0) { ++ platform_device_unregister(&sw->pdev); ++ return r; ++ } ++ ++ /* input: 1, output: 0 */ ++ direction = !(sw->flags & OMAP_GPIO_SWITCH_FLAG_OUTPUT); ++ if (direction) ++ gpio_direction_input(sw->gpio); ++ else ++ gpio_direction_output(sw->gpio, 0); ++ ++ sw->state = gpio_sw_get_state(sw); ++ ++ r = 0; ++ r |= device_create_file(&sw->pdev.dev, &dev_attr_state); ++ r |= device_create_file(&sw->pdev.dev, &dev_attr_type); ++ r |= device_create_file(&sw->pdev.dev, &dev_attr_direction); ++ if (r) ++ printk(KERN_ERR "gpio-switch: attribute file creation " ++ "failed for %s\n", sw->name); ++ ++ if (!direction) ++ return 0; ++ ++ if (can_do_both_edges(sw)) { ++ trigger = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING; ++ sw->both_edges = 1; ++ } else { ++ if (gpio_get_value(sw->gpio)) ++ trigger = IRQF_TRIGGER_FALLING; ++ else ++ trigger = IRQF_TRIGGER_RISING; ++ } ++ r = request_irq(OMAP_GPIO_IRQ(sw->gpio), gpio_sw_irq_handler, ++ IRQF_SHARED | trigger, 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); ++ gpio_free(sw->gpio); ++ return r; ++ } ++ ++ INIT_WORK(&sw->work, gpio_sw_handler); ++ init_timer(&sw->timer); ++ ++ sw->timer.function = gpio_sw_timer; ++ sw->timer.data = (unsigned long)sw; ++ ++ list_add(&sw->node, &gpio_switches); ++ ++ 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 = kzalloc(sizeof(*sw), GFP_KERNEL); ++ if (sw == NULL) { ++ printk(KERN_ERR "gpio-switch: kmalloc failed\n"); ++ return -ENOMEM; ++ } ++ strncpy(sw->name, cfg->name, sizeof(cfg->name)); ++ sw->gpio = cfg->gpio; ++ sw->flags = cfg->flags; ++ sw->type = cfg->type; ++ 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; ++ } ++ } ++ return 0; ++} ++ ++static void gpio_sw_cleanup(void) ++{ ++ struct gpio_switch *sw = NULL, *old = NULL; ++ ++ list_for_each_entry(sw, &gpio_switches, node) { ++ if (old != NULL) ++ kfree(old); ++ flush_scheduled_work(); ++ del_timer_sync(&sw->timer); ++ ++ free_irq(OMAP_GPIO_IRQ(sw->gpio), sw); ++ ++ device_remove_file(&sw->pdev.dev, &dev_attr_state); ++ device_remove_file(&sw->pdev.dev, &dev_attr_type); ++ device_remove_file(&sw->pdev.dev, &dev_attr_direction); ++ ++ platform_device_unregister(&sw->pdev); ++ gpio_free(sw->gpio); ++ old = sw; ++ } ++ kfree(old); ++} ++ ++static void __init report_initial_state(void) ++{ ++ struct gpio_switch *sw; ++ ++ list_for_each_entry(sw, &gpio_switches, node) { ++ int state; ++ ++ state = gpio_get_value(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); ++ } ++} ++ ++static int gpio_sw_remove(struct platform_device *dev) ++{ ++ return 0; ++} ++ ++static struct platform_driver gpio_sw_driver = { ++ .remove = gpio_sw_remove, ++ .driver = { ++ .name = "gpio-switch", ++ }, ++}; ++ ++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; ++ ++ printk(KERN_INFO "OMAP GPIO switch handler initializing\n"); ++ ++ r = platform_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)) { ++ r = PTR_ERR(gpio_sw_platform_dev); ++ goto err1; ++ } ++ ++ r = add_atag_switches(); ++ 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: ++ platform_driver_unregister(&gpio_sw_driver); ++ return r; ++} ++ ++static void __exit gpio_sw_exit(void) ++{ ++ gpio_sw_cleanup(); ++ platform_device_unregister(gpio_sw_platform_dev); ++ platform_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