Palm USER_PINS driver
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index be0901b..993ef99 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -750,6 +750,11 @@
 	---help---
 	This option exports the Nova Device ID to /proc/nduid.
 
+config USER_PINS
+	tristate "Sysfs GPIO pins control"
+	---help---
+		Select Y if you want to expose some gpio pins through sysfs.
+
 source "drivers/misc/c2port/Kconfig"
 source "drivers/misc/eeprom/Kconfig"
 source "drivers/misc/cb710/Kconfig"
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index 838eb35..e123479 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -85,4 +85,4 @@
 obj-$(CONFIG_A6)		+= a6/
 obj-$(CONFIG_HRES_COUNTER)	+= hres_counter.o
 obj-$(CONFIG_NDUID)		+= nduid.o
-
+obj-$(CONFIG_USER_PINS)		+= user-pins.o
diff --git a/drivers/misc/user-pins.c b/drivers/misc/user-pins.c
new file mode 100644
index 0000000..818b6b1
--- /dev/null
+++ b/drivers/misc/user-pins.c
@@ -0,0 +1,1015 @@
+/*
+ * 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");
diff --git a/include/linux/user-pins.h b/include/linux/user-pins.h
new file mode 100644
index 0000000..499b250
--- /dev/null
+++ b/include/linux/user-pins.h
@@ -0,0 +1,44 @@
+#ifndef  __USER_PINS_INCLUDED__
+#define  __USER_PINS_INCLUDED__
+
+typedef enum {
+	PIN_MODE_ACTIVE,
+	PIN_MODE_SUSPENDED
+} PIN_MODE;
+
+struct user_pin {
+	const char *name;  // pin name 
+	int gpio;          // gpio num/id
+	int options;       // options
+	int act_level;     // active level
+	int direction;     // 1 - an input, 0 - output
+	int def_level;     // default level: 0, 1 or -1 if undefined
+	int sysfs_mask;    // sysfs file mode
+	char *pin_mode;    // board specific pin mode
+	irqreturn_t (*irq_handler)(int irq, void *data);
+	int (*pinmux)(int gpio, int mode);
+	int irq_config;
+	int irq_handle_mode;
+};
+
+struct user_pin_set {
+	const char  *set_name;   // pin set name  
+	int          num_pins;   // number of pins in the group 
+	struct user_pin *pins;   // pins array.
+};
+
+struct user_pins_platform_data {
+	int              num_sets;   // number of pin sets 
+	struct user_pin_set *sets;   // pin sets.
+};
+
+/* Pin option constants */
+#define PIN_READ_ONLY		(1 << 0)    //  pin is read only
+#define PIN_WAKEUP_SOURCE	(1 << 1)    //  pin is a wakeup source
+#define PIN_IRQ			(1 << 2)    //  pin generates irq
+
+#define IRQ_HANDLE_NONE         (0)         //  IRQ handling is not defined
+#define IRQ_HANDLE_AUTO         (1 << 0)    //  IRQ handling is automatic
+#define IRQ_HANDLE_OFF          (1 << 1)    //  IRQ handling is off
+
+#endif // __USER_PINS_INCLUDED__