blob: 818b6b15f9d973f0d5bcb9c7c0ef1f0b02f342d4 [file] [log] [blame]
/*
* drivers/user-pins.c
*
* Copyright (C) 2008 Palm, Inc.
*
* 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/kernel.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/err.h>
#include <linux/clk.h>
#include <linux/ctype.h>
#include <linux/debugfs.h>
#include <linux/gpio.h>
#include <linux/sysfs.h>
#include <linux/device.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/spinlock.h>
#include <linux/user-pins.h>
#include <linux/module.h>
#ifdef CONFIG_PINMUX
#include <linux/pinmux.h>
#endif
#include <asm/io.h>
#include <asm/mach-types.h>
#include <asm/mach/arch.h>
#include <asm/mach/map.h>
#undef MODDEBUG
//#define MODDEBUG 1
#ifdef MODDEBUG
#define PDBG(args...) printk(args)
#else
#define PDBG(args...)
#endif
#define DRIVER_NAME "user-pins"
enum event {
USER_PIN_EVENT_IRQ,
USER_PIN_EVENT_IRQ_READ,
};
struct user_pins_log_event {
ktime_t timestamp;
enum event event;
int gpio;
};
#if defined(CONFIG_DEBUG_FS)
#define NR_LOG_ENTRIES 512
static struct user_pins_log_event user_pins_log[NR_LOG_ENTRIES];
static int user_pins_log_idx;
static DEFINE_SPINLOCK(debug_lock);
static char debug_buffer[PAGE_SIZE];
static void user_pins_log_event(int gpio, enum event event)
{
unsigned long flags;
spin_lock_irqsave(&debug_lock, flags);
user_pins_log[user_pins_log_idx].timestamp = ktime_get();
user_pins_log[user_pins_log_idx].event = event;
user_pins_log[user_pins_log_idx].gpio = gpio;
user_pins_log_idx += 1;
if (user_pins_log_idx == NR_LOG_ENTRIES) {
user_pins_log_idx = 0;
}
spin_unlock_irqrestore(&debug_lock, flags);
}
static int debug_open(struct inode *inode, struct file *file)
{
file->private_data = inode->i_private;
return 0;
}
static ssize_t debug_show_log(struct file *file, char __user *ubuf,
size_t count, loff_t *ppos)
{
char *buf = debug_buffer;
int i, n = 0;
const char *event;
unsigned long flags;
spin_lock_irqsave(&debug_lock, flags);
for (i = 0; i < user_pins_log_idx; i++) {
switch (user_pins_log[i].event) {
case USER_PIN_EVENT_IRQ:
event = "IRQ";
break;
case USER_PIN_EVENT_IRQ_READ:
event = "IRQ_READ";
break;
default:
event = "<null>";
break;
}
n += scnprintf(buf + n, PAGE_SIZE - n,
"%010llu: %-8d %s\n",
ktime_to_ns(user_pins_log[i].timestamp),
user_pins_log[i].gpio, event);
}
user_pins_log_idx = 0;
spin_unlock_irqrestore(&debug_lock, flags);
return simple_read_from_buffer(ubuf, count, ppos, buf, n);
}
static const struct file_operations debug_log_fops = {
.open = debug_open,
.read = debug_show_log,
};
static void user_pins_debug_init(void)
{
struct dentry *dent;
dent = debugfs_create_dir(DRIVER_NAME, 0);
if (IS_ERR(dent)) {
return;
}
debugfs_create_file("log", 0444, dent, NULL, &debug_log_fops);
user_pins_log_idx = 0;
}
#else
static void user_pins_log_event(int gpio, enum event event) { }
static void user_pins_debug_init(void) { }
#endif
static struct kobject *user_hw_kobj;
static struct kobject *pins_kobj;
DEFINE_SPINLOCK(pins_lock);
static int __init user_hw_init(void)
{
user_hw_kobj = kobject_create_and_add("user_hw", NULL);
if (user_hw_kobj == NULL) {
return -ENOMEM;
}
return 0;
}
arch_initcall(user_hw_init);
struct pin_attribute {
struct attribute attr;
ssize_t (*show) (struct pin_attribute *attr, char *buf);
ssize_t (*store)(struct pin_attribute *attr, const char *buf, size_t count);
};
#define to_pin_attr(_attr) container_of(_attr, struct pin_attribute, attr)
static ssize_t
pin_attr_show(struct kobject *kobj, struct attribute *attr, char *buf)
{
struct pin_attribute * pin_attr = to_pin_attr(attr);
if (pin_attr->show) {
return pin_attr->show(pin_attr, buf);
} else {
return -EIO;
}
return 0;
}
static ssize_t
pin_attr_store(struct kobject *kobj, struct attribute *attr, const char *buf,
size_t count)
{
struct pin_attribute * pin_attr = to_pin_attr(attr);
if (pin_attr->store) {
return pin_attr->store (pin_attr, buf, count);
} else {
return -EIO;
}
}
static struct sysfs_ops pin_sysfs_ops = {
.show = pin_attr_show,
.store = pin_attr_store,
};
static struct kobj_type ktype_pin = {
.release = NULL,
.sysfs_ops = &pin_sysfs_ops,
};
struct gpio_pin {
int gpio;
int options;
int direction;
int act_level;
int def_level;
int active_power_collapse;
irqreturn_t (*irq_handler)(int irq, void *data);
int (*pinmux)(int gpio, int mode);
atomic_t irq_count;
int irq_config;
int irq_masked;
int irq_requested;
const char * name;
int irq_handle_mode;
atomic_t irqs_during_suspend;
struct sysfs_dirent *sd;
struct pin_attribute attr_gpio;
struct pin_attribute attr_level;
struct pin_attribute attr_active;
struct pin_attribute attr_direction;
struct pin_attribute attr_irq;
struct pin_attribute attr_irqconfig;
struct pin_attribute attr_irqrequest;
struct pin_attribute attr_irqmask;
struct pin_attribute attr_irq_handle_mode;
struct pin_attribute attr_active_power_collapse;
struct attribute *attr_ptr_arr[11];
};
struct gpio_pin_set_item {
struct attribute_group attr_grp;
struct gpio_pin pin;
};
struct gpio_pin_set {
const char *set_name;
struct kobject kobj;
int num_pins;
struct gpio_pin_set_item pins[];
};
struct gpio_pin_dev_ctxt {
int num_sets;
struct gpio_pin_set *sets[];
};
/*
* Show irq handle mode
*
* If AUTO, irq will be handled by irq_handler
*/
static int
pin_show_irq_mode ( struct pin_attribute *attr, char *buf)
{
struct gpio_pin *pin = container_of(attr, struct gpio_pin, attr_irq_handle_mode );
return sprintf(buf, "%d\n", pin->irq_handle_mode );
}
/*
* Set irq handle mode for specified pin
*
*/
static ssize_t
pin_store_irq_mode( struct pin_attribute *attr, const char * buf, size_t count)
{
int irq_handle_mode;
unsigned long flags;
struct gpio_pin *pin = container_of(attr, struct gpio_pin, attr_irq_handle_mode );
sscanf(buf, "%d", &irq_handle_mode);
spin_lock_irqsave(&pins_lock, flags);
// reset irq count to detect user suspend and kernel suspend
pin->irq_handle_mode = irq_handle_mode;
if(pin->irq_handle_mode == IRQ_HANDLE_OFF)
atomic_set(&pin->irqs_during_suspend, 0);
spin_unlock_irqrestore(&pins_lock, flags);
printk(KERN_INFO"USERPIN: setting irq handle mode of pin gpio %d to %d\n",
pin->gpio, irq_handle_mode);
return count;
}
static irqreturn_t user_pins_irq(int irq, void *data)
{
unsigned long flags;
struct gpio_pin *pin = (struct gpio_pin *)data;
user_pins_log_event(pin->gpio, USER_PIN_EVENT_IRQ);
atomic_inc(&pin->irq_count);
spin_lock_irqsave(&pins_lock, flags);
if (pin->irq_handle_mode & IRQ_HANDLE_OFF)
atomic_inc(&pin->irqs_during_suspend);
spin_unlock_irqrestore(&pins_lock, flags);
if (pin->sd != NULL) {
sysfs_notify_dirent(pin->sd);
}
if (pin->irq_handler != NULL) {
pin->irq_handler(irq, NULL);
}
return IRQ_HANDLED;
}
static int user_pins_irq_request(struct gpio_pin *pin)
{
int rc = 0;
printk("user-pins: configuring irq for gpio %d\n", pin->gpio);
rc = request_irq(gpio_to_irq(pin->gpio), user_pins_irq,
pin->irq_config, "userpins", pin);
if (rc) {
printk("user-pins: failed to request irq\n");
}
return rc;
}
/*
* Show gpio direction
*/
static int pin_show_direction(struct pin_attribute *attr, char *buf)
{
struct gpio_pin *pin = container_of(attr, struct gpio_pin, attr_direction);
return sprintf(buf, "%d\n", pin->direction);
}
/*
* Show active level for specified pin
*/
static int pin_show_active(struct pin_attribute *attr, char *buf)
{
struct gpio_pin *pin = container_of(attr, struct gpio_pin, attr_active);
return sprintf(buf, "%d\n", pin->act_level);
}
static int pin_show_active_power_collapse(struct pin_attribute *attr, char *buf)
{
struct gpio_pin *pin = container_of(attr, struct gpio_pin, attr_active_power_collapse);
return sprintf(buf, "%d\n", pin->active_power_collapse);
}
/*
* Show gpio number
*/
static int pin_show_gpio(struct pin_attribute *attr, char *buf)
{
struct gpio_pin *pin = container_of(attr, struct gpio_pin, attr_gpio);
return sprintf(buf, "%d\n", pin->gpio);
}
/*
* Show current for specified pin
*/
static int pin_show_level(struct pin_attribute *attr, char *buf)
{
int val;
struct gpio_pin *pin = container_of(attr, struct gpio_pin, attr_level);
val = gpio_get_value(pin->gpio);
PDBG("get: gpio[%d] = %d\n", pin->gpio, val);
if (val) {
return sprintf(buf, "1\n" );
} else {
return sprintf(buf, "0\n" );
}
}
/*
* Set level for specified pin
*/
static ssize_t
pin_store_level(struct pin_attribute *attr, const char *buf, size_t count)
{
int i = 0, len, val = -1;
struct gpio_pin *pin = container_of(attr, struct gpio_pin, attr_level);
if (pin->options & PIN_READ_ONLY) {
return count; // just ignore writes
}
/* skip leading white spaces */
while (i < count && isspace(buf[i])) {
i++;
}
len = count - i;
if (len >= 1 && strncmp(buf+i, "1", 1) == 0) {
val = 1;
goto set;
}
if (len >= 1 && strncmp(buf+i, "0", 1) == 0) {
val = 0;
goto set;
}
if (len >= 4 && strncmp(buf+i, "high", 4) == 0) {
val = 1;
goto set;
}
if (len >= 3 && strncmp(buf+i, "low", 3) == 0) {
val = 0;
goto set;
}
return count;
set:
PDBG("set: gpio[%d] = %d\n", pin->gpio, val);
gpio_set_value(pin->gpio, val);
return count;
}
static ssize_t
pin_store_active_power_collapse(struct pin_attribute *attr, const char *buf, size_t count)
{
int i = 0, len, val = -1;
struct gpio_pin *pin = container_of(attr, struct gpio_pin, attr_active_power_collapse);
if (pin->options & PIN_READ_ONLY) {
return count; // just ignore writes
}
/* skip leading white spaces */
while (i < count && isspace(buf[i])) {
i++;
}
len = count - i;
if (len >= 1 && strncmp(buf+i, "1", 1) == 0) {
val = 1;
} else if (len >= 1 && strncmp(buf+i, "0", 1) == 0) {
val = 0;
} else { /* invalid input */
goto end;
}
PDBG("set: active_power_collapse[%d] = %d\n", pin->gpio, val);
#ifdef CONFIG_PINMUX
// This is an old legacy mechanism for muxing pins, it's not that clean
// and creates dependency on pinmux driver that is not really
// used on some systems.
pinmux_set_power_collapse(pin->name, val);
#endif
pin->active_power_collapse = val;
end:
return count;
}
static int pin_show_irq(struct pin_attribute *attr, char *buf)
{
struct gpio_pin *pin = container_of(attr, struct gpio_pin, attr_irq);
int count = atomic_xchg(&pin->irq_count, 0);
user_pins_log_event(pin->gpio, USER_PIN_EVENT_IRQ_READ);
return sprintf(buf, "%d\n", count);
}
static int pin_show_irqconfig(struct pin_attribute *attr, char *buf)
{
struct gpio_pin *pin = container_of(attr, struct gpio_pin, attr_irqconfig);
return sprintf(buf, "%d\n", pin->irq_config);
}
static ssize_t
pin_store_irqconfig(struct pin_attribute *attr, const char *buf, size_t count)
{
struct gpio_pin *pin = container_of(attr, struct gpio_pin, attr_irqconfig);
unsigned long flags;
int config;
config = simple_strtoul(buf, NULL, 10) & IRQF_TRIGGER_MASK;
spin_lock_irqsave(&pins_lock, flags);
pin->irq_config = config;
spin_unlock_irqrestore(&pins_lock, flags);
return count;
}
static int pin_show_irqrequest(struct pin_attribute *attr, char *buf)
{
struct gpio_pin *pin = container_of(attr, struct gpio_pin, attr_irqrequest);
return sprintf(buf, "%d\n", !!pin->irq_requested);
}
static ssize_t
pin_store_irqrequest(struct pin_attribute *attr, const char *buf, size_t count)
{
struct gpio_pin *pin = container_of(attr, struct gpio_pin, attr_irqrequest);
unsigned long flags;
int request;
int rc = 0;
if ((count > 0) && (buf[0] == '1')) {
request = 1;
} else {
request = 0;
}
if (request != pin->irq_requested) {
if (request) {
rc = user_pins_irq_request(pin);
if (rc) {
goto fail;
}
} else {
free_irq(gpio_to_irq(pin->gpio), pin);
}
spin_lock_irqsave(&pins_lock, flags);
pin->irq_requested = request;
pin->irq_masked = 0;
spin_unlock_irqrestore(&pins_lock, flags);
}
return count;
fail:
return rc;
}
static int pin_show_irqmask(struct pin_attribute *attr, char *buf)
{
struct gpio_pin *pin = container_of(attr, struct gpio_pin, attr_irqmask);
return sprintf(buf, "%d\n", !!pin->irq_masked);
}
static ssize_t
pin_store_irqmask(struct pin_attribute *attr, const char *buf, size_t count)
{
struct gpio_pin *pin = container_of(attr, struct gpio_pin, attr_irqmask);
unsigned long flags;
int mask;
if ((count > 0) && (buf[0] == '1')) {
mask = 1;
} else {
mask = 0;
}
spin_lock_irqsave(&pins_lock, flags);
if (mask != pin->irq_masked) {
if (mask) {
disable_irq(gpio_to_irq(pin->gpio));
} else {
enable_irq(gpio_to_irq(pin->gpio));
}
pin->irq_masked = mask;
}
spin_unlock_irqrestore(&pins_lock, flags);
return count;
}
static void
pin_set_item_init(struct gpio_pin_set_item *psi, struct user_pin *up)
{
psi->pin.name = up->name;
psi->pin.gpio = up->gpio;
psi->pin.options = up->options;
psi->pin.direction = up->direction;
psi->pin.act_level = up->act_level;
psi->pin.def_level = up->def_level;
psi->pin.irq_handler = up->irq_handler;
psi->pin.pinmux = up->pinmux;
psi->pin.irq_config = up->irq_config;
psi->pin.irq_handle_mode = up->irq_handle_mode;
atomic_set(&psi->pin.irqs_during_suspend, 0);
atomic_set(&psi->pin.irq_count, 0);
psi->pin.irq_requested = 0;
psi->pin.irq_masked = 0;
// gpio attr
psi->pin.attr_gpio.attr.name = "gpio";
psi->pin.attr_gpio.attr.mode = 0444;
psi->pin.attr_gpio.show = pin_show_gpio;
// level attr
psi->pin.attr_level.attr.name = "level";
psi->pin.attr_level.attr.mode = 0644;
psi->pin.attr_level.show = pin_show_level;
psi->pin.attr_level.store = pin_store_level;
// active attr
psi->pin.attr_active.attr.name = "active";
psi->pin.attr_active.attr.mode = 0444;
psi->pin.attr_active.show = pin_show_active;
// direction
psi->pin.attr_direction.attr.name = "direction";
psi->pin.attr_direction.attr.mode = 0444;
psi->pin.attr_direction.show = pin_show_direction;
psi->pin.attr_active_power_collapse.attr.name = "active_power_collapse";
psi->pin.attr_active_power_collapse.attr.mode = 0644;
psi->pin.attr_active_power_collapse.show = pin_show_active_power_collapse;
psi->pin.attr_active_power_collapse.store = pin_store_active_power_collapse;
if (psi->pin.options & PIN_IRQ) {
// irq
psi->pin.attr_irq.attr.name = "irq";
psi->pin.attr_irq.attr.mode = 0444;
psi->pin.attr_irq.show = pin_show_irq;
// irqconfig
psi->pin.attr_irqconfig.attr.name = "irqconfig";
psi->pin.attr_irqconfig.attr.mode = 0666;
psi->pin.attr_irqconfig.show = pin_show_irqconfig;
psi->pin.attr_irqconfig.store = pin_store_irqconfig;
// irqrequest
psi->pin.attr_irqrequest.attr.name = "irqrequest";
psi->pin.attr_irqrequest.attr.mode = 0666;
psi->pin.attr_irqrequest.show = pin_show_irqrequest;
psi->pin.attr_irqrequest.store = pin_store_irqrequest;
// irqmask
psi->pin.attr_irqmask.attr.name = "irqmask";
psi->pin.attr_irqmask.attr.mode = 0666;
psi->pin.attr_irqmask.show = pin_show_irqmask;
psi->pin.attr_irqmask.store = pin_store_irqmask;
// irq handle mode
psi->pin.attr_irq_handle_mode.attr.name = "irq_handle_mode";
psi->pin.attr_irq_handle_mode.attr.mode = 0644;
psi->pin.attr_irq_handle_mode.show = pin_show_irq_mode;
psi->pin.attr_irq_handle_mode.store = pin_store_irq_mode;
}
// setup attr pointer array
{
int i = 0;
psi->pin.attr_ptr_arr[i++] = &psi->pin.attr_gpio.attr;
psi->pin.attr_ptr_arr[i++] = &psi->pin.attr_level.attr;
psi->pin.attr_ptr_arr[i++] = &psi->pin.attr_active.attr;
psi->pin.attr_ptr_arr[i++] = &psi->pin.attr_direction.attr;
psi->pin.attr_ptr_arr[i++] = &psi->pin.attr_active_power_collapse.attr;
if (psi->pin.options & PIN_IRQ) {
psi->pin.attr_ptr_arr[i++] = &psi->pin.attr_irq.attr;
psi->pin.attr_ptr_arr[i++] = &psi->pin.attr_irqconfig.attr;
psi->pin.attr_ptr_arr[i++] = &psi->pin.attr_irqrequest.attr;
psi->pin.attr_ptr_arr[i++] = &psi->pin.attr_irqmask.attr;
psi->pin.attr_ptr_arr[i++] = &psi->pin.attr_irq_handle_mode.attr;
}
psi->pin.attr_ptr_arr[i++] = NULL;
/* if this is triggered, then we need a larger attr_ptr_arr array */
BUG_ON(i > ARRAY_SIZE(psi->pin.attr_ptr_arr));
}
// setup attribute group
psi->attr_grp.name = psi->pin.name;
psi->attr_grp.attrs = psi->pin.attr_ptr_arr;
return;
}
/*
*
*/
static struct gpio_pin_set * pin_set_alloc(struct user_pin_set *ups)
{
int i;
struct gpio_pin_set *gps = NULL;
gps = kzalloc(sizeof(struct gpio_pin_set) +
ups->num_pins * sizeof(struct gpio_pin_set_item), GFP_KERNEL);
if (gps == NULL) {
return NULL;
}
gps->num_pins = ups->num_pins;
gps->set_name = ups->set_name;
for (i = 0; i < gps->num_pins; i++) {
pin_set_item_init(&gps->pins[i], &ups->pins[i]);
}
return gps;
}
/*
* Registers specified pin set
*/
static int pin_set_register(struct gpio_pin_set *s)
{
int rc, i;
struct sysfs_dirent *grp_sd;
if (s == NULL) {
return -EINVAL;
}
rc = kobject_init_and_add(&s->kobj, &ktype_pin, pins_kobj, s->set_name);
if (rc) {
printk (KERN_ERR "Failed to register kobject (%s)\n", s->set_name);
return -ENODEV;
}
/* for all pins */
for (i = 0; i < s->num_pins; i++) {
rc = gpio_request(s->pins[i].pin.gpio, "gpio");
if (rc) {
printk(KERN_ERR "Failed to request gpio (%d)\n",
s->pins[i].pin.gpio);
continue;
}
if (s->pins[i].pin.direction != -1) { // direction is set
if (s->pins[i].pin.direction == 0) { // an output
/* A setting of def_level == -1 means that we
* keep the current level of the GPIO.
* Otherwise we set def_level.
*/
int level = (-1 == s->pins[i].pin.def_level) ?
gpio_get_value(s->pins[i].pin.gpio) :
s->pins[i].pin.def_level;
gpio_direction_output(s->pins[i].pin.gpio, level);
} else { // an input
gpio_direction_input(s->pins[i].pin.gpio);
}
}
// create attribute group
rc = sysfs_create_group(&s->kobj, &s->pins[i].attr_grp);
if (rc) {
printk(KERN_ERR "Failed to create sysfs attr group (%s)\n",
s->pins[i].pin.name );
}
grp_sd = sysfs_get_dirent(s->kobj.sd, NULL, s->pins[i].attr_grp.name);
if (grp_sd == NULL) {
printk(KERN_ERR "user-pins: failed to get sd for %s\n",
s->pins[i].attr_grp.name);
} else {
s->pins[i].pin.sd = sysfs_get_dirent(grp_sd, NULL, "irq");
if ((s->pins[i].pin.sd == NULL) &&
(s->pins[i].pin.options & PIN_IRQ)) {
printk(KERN_ERR "user-pins: failed to get sd for %s/irq\n",
s->pins[i].attr_grp.name);
}
}
}
return 0;
}
static void pin_set_unregister(struct gpio_pin_set *s)
{
int i;
if (s == NULL) {
return;
}
/* for all pins */
for (i = 0; s->num_pins; i++) {
if ((s->pins[i].pin.options & PIN_IRQ) &&
(s->pins[i].pin.irq_requested != 0)) {
free_irq(gpio_to_irq(s->pins[i].pin.gpio), &s->pins[i].pin);
}
sysfs_remove_group(&s->kobj, &s->pins[i].attr_grp);
gpio_free(s->pins[i].pin.gpio);
}
kobject_del(&s->kobj);
kfree(s);
}
static int user_pins_probe(struct platform_device *pdev)
{
int i, rc;
struct user_pins_platform_data *pdata;
struct gpio_pin_dev_ctxt *dev_ctxt;
pdata = pdev->dev.platform_data;
if( pdata == NULL ) {
return -ENODEV;
}
dev_ctxt = kzalloc(sizeof (struct gpio_pin_dev_ctxt) +
pdata->num_sets * sizeof(struct gpio_pin_set *), GFP_KERNEL );
if (dev_ctxt == NULL) {
return -ENOMEM;
}
dev_ctxt->num_sets = pdata->num_sets;
for (i = 0; i < dev_ctxt->num_sets; i++) {
dev_ctxt->sets[i] = pin_set_alloc (pdata->sets + i);
if (dev_ctxt->sets[i] == NULL) {
printk(KERN_ERR "Failed to init pin set '%s'\n",
pdata->sets[i].set_name );
continue;
}
rc = pin_set_register(dev_ctxt->sets[i]);
if (rc) {
printk(KERN_ERR "Failed to register pin set '%s'\n",
pdata->sets[i].set_name );
}
}
dev_set_drvdata(&pdev->dev, dev_ctxt);
return 0;
}
/*
*
*/
static int user_pins_remove(struct platform_device *pdev)
{
int i;
struct gpio_pin_dev_ctxt *dev_ctxt;
dev_ctxt = dev_get_drvdata(&pdev->dev);
if (dev_ctxt == NULL) {
return 0;
}
for (i = 0; i < dev_ctxt->num_sets; i++) {
pin_set_unregister(dev_ctxt->sets[i]);
}
dev_set_drvdata(&pdev->dev, NULL);
kfree(dev_ctxt);
return 0;
}
#ifdef CONFIG_PM
static int user_pins_suspend(struct platform_device *pdev, pm_message_t state)
{
int i, j;
struct gpio_pin_dev_ctxt *dev_ctxt;
struct gpio_pin_set *pset;
struct gpio_pin *pin;
unsigned long flags;
dev_ctxt = platform_get_drvdata(pdev);
if (dev_ctxt == NULL) {
return 0;
}
spin_lock_irqsave ( &pins_lock, flags );
/*
* The first loop is to check for any pending interrupts from
* the time starting IRQ_HANDLE_OFF is set (from user space).
* If so, fail the suspend, and let the system to go back up
* to userspace.
*/
for( i = 0; i < dev_ctxt->num_sets; i++ ) {
pset = dev_ctxt->sets[i];
for( j = 0; j < pset->num_pins; j++ ) {
pin = &(pset->pins[j].pin);
if ((pin->options & PIN_IRQ) &&
(pin->irq_handle_mode & IRQ_HANDLE_OFF) &&
(atomic_read(&pin->irqs_during_suspend) != 0)) {
atomic_set(&pin->irqs_during_suspend, 0);
spin_unlock_irqrestore ( &pins_lock, flags );
printk(KERN_INFO"%s: not suspending due to pending irqs for gpio %d\n",
__func__, pin->gpio);
return -EBUSY;
}
}
}
for (i = 0; i < dev_ctxt->num_sets; i++) {
pset = dev_ctxt->sets[i];
for (j = 0; j < pset->num_pins; j++) {
pin = &(pset->pins[j].pin);
if (pin->options & PIN_WAKEUP_SOURCE) {
int irq = gpio_to_irq(pin->gpio);
if ((pin->options & PIN_IRQ) &&
pin->irq_requested &&
!pin->irq_masked) {
disable_irq(gpio_to_irq(pin->gpio));
}
enable_irq_wake(irq);
}
// If machine installed pinmux hook for the pin, call it
// to mux it into suspended mode. Exception is made for
// pins that are specifically configured to stay active
// even during suspend periods.
if (pin->pinmux && !pin->active_power_collapse)
pin->pinmux(pin->gpio, PIN_MODE_SUSPENDED);
}
}
spin_unlock_irqrestore ( &pins_lock, flags );
return 0;
}
static int user_pins_resume(struct platform_device *pdev)
{
int i, j;
unsigned long flags;
struct gpio_pin_dev_ctxt *dev_ctxt;
struct gpio_pin_set *pset;
struct gpio_pin *pin;
dev_ctxt = platform_get_drvdata(pdev);
if (dev_ctxt == NULL) {
return 0;
}
spin_lock_irqsave(&pins_lock, flags);
for (i = 0; i < dev_ctxt->num_sets; i++) {
pset = dev_ctxt->sets[i];
for (j = 0; j < pset->num_pins; j++) {
pin = &(pset->pins[j].pin);
// If machine installed pinmux hook for the pin, call it
// to mux it into active mode. If the pin is configured
// to be active during suspend periods we assume we don't
// need to remux it into active state again.
if (pin->pinmux && !pin->active_power_collapse)
pin->pinmux(pin->gpio, PIN_MODE_ACTIVE);
if (pin->options & PIN_WAKEUP_SOURCE) {
int irq = gpio_to_irq(pin->gpio);
disable_irq_wake(irq);
if ((pin->options & PIN_IRQ) &&
pin->irq_requested &&
!pin->irq_masked) {
enable_irq(gpio_to_irq(pin->gpio));
}
}
atomic_set(&pin->irqs_during_suspend, 0);
}
}
spin_unlock_irqrestore(&pins_lock, flags);
return 0;
}
#else
#define user_pins_suspend NULL
#define user_pins_resume NULL
#endif
static struct platform_driver user_pins_driver = {
.driver = {
.name = DRIVER_NAME,
},
.probe = user_pins_probe,
.remove = __devexit_p(user_pins_remove),
.suspend = user_pins_suspend,
.resume = user_pins_resume,
};
static int __init user_pins_init(void)
{
int rc;
if (user_hw_kobj == NULL) {
return -ENOMEM;
}
pins_kobj = kobject_create_and_add("pins", user_hw_kobj);
if (pins_kobj == NULL) {
return -ENOMEM;
}
/* register pins platform device */
rc = platform_driver_register(&user_pins_driver);
if (rc) {
kobject_del(pins_kobj);
}
user_pins_debug_init();
return rc;
}
static void __exit
user_pins_exit(void)
{
kobject_del(pins_kobj);
}
module_init(user_pins_init);
module_exit(user_pins_exit);
MODULE_DESCRIPTION(DRIVER_DESC);
MODULE_LICENSE("GPL");