Commit 8fba45f5 authored by Juha Yrjola's avatar Juha Yrjola Committed by Tony Lindgren

ARM: OMAP: Add support for GPIO switch

Adds support for GPIO switch
Signed-off-by: default avatarJuha Yrjola <juha.yrjola@nokia.com>
Signed-off-by: default avatarTony Lindgren <tony@atomide.com>
parent 4f753b62
...@@ -50,6 +50,16 @@ config OMAP_BOOT_REASON ...@@ -50,6 +50,16 @@ config OMAP_BOOT_REASON
Say Y, if you want to have a procfs entry for reading the boot Say Y, if you want to have a procfs entry for reading the boot
reason in user-space. 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 config OMAP_MUX
bool "OMAP multiplexing support" bool "OMAP multiplexing support"
depends on ARCH_OMAP depends on ARCH_OMAP
......
...@@ -20,3 +20,4 @@ obj-$(CONFIG_PM) += pm.o sleep.o ...@@ -20,3 +20,4 @@ obj-$(CONFIG_PM) += pm.o sleep.o
obj-$(CONFIG_CPU_FREQ) += cpu-omap.o obj-$(CONFIG_CPU_FREQ) += cpu-omap.o
obj-$(CONFIG_OMAP_DM_TIMER) += dmtimer.o obj-$(CONFIG_OMAP_DM_TIMER) += dmtimer.o
obj-$(CONFIG_OMAP_BOOT_REASON) += bootreason.o obj-$(CONFIG_OMAP_BOOT_REASON) += bootreason.o
obj-$(CONFIG_OMAP_GPIO_SWITCH) += gpio-switch.o
/*
* linux/arch/arm/mach-omap/gpio-switch.c
*
* Copyright (C) 2004, 2005 Nokia Corporation
* Written by Juha Yrjl <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 Yrjl <juha.yrjola@nokia.com>, Paul Mundt <paul.mundt@nokia.com");
MODULE_DESCRIPTION("GPIO switch driver");
MODULE_LICENSE("GPL");
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment