diff options
| author | Arve Hjønnevåg <arve@android.com> | 2008-10-15 18:23:47 -0700 | 
|---|---|---|
| committer | Arve Hjønnevåg <arve@android.com> | 2013-07-01 13:40:21 -0700 | 
| commit | 18b6624a3b4dd7ca4d8480c6e71dd2a532e9665b (patch) | |
| tree | 0a64da81a152f9ccb42cea295439530d11384e64 /drivers/input/misc/gpio_input.c | |
| parent | aa3305f2ba5976a95637c69c63409fdf007e1414 (diff) | |
| download | olio-linux-3.10-18b6624a3b4dd7ca4d8480c6e71dd2a532e9665b.tar.xz olio-linux-3.10-18b6624a3b4dd7ca4d8480c6e71dd2a532e9665b.zip  | |
Input: Generic GPIO Input device.
Supports keyboard matrixces, direct inputs, direct outputs and axes connected to gpios.
Change-Id: I5e921e6e3a1cc169316ee3b665f4cc21b5735114
Signed-off-by: Arve Hjønnevåg <arve@android.com>
Signed-off-by: Nick Pelly <npelly@google.com>
Diffstat (limited to 'drivers/input/misc/gpio_input.c')
| -rw-r--r-- | drivers/input/misc/gpio_input.c | 376 | 
1 files changed, 376 insertions, 0 deletions
diff --git a/drivers/input/misc/gpio_input.c b/drivers/input/misc/gpio_input.c new file mode 100644 index 00000000000..6a0c3151096 --- /dev/null +++ b/drivers/input/misc/gpio_input.c @@ -0,0 +1,376 @@ +/* drivers/input/misc/gpio_input.c + * + * Copyright (C) 2007 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + */ + +#include <linux/kernel.h> +#include <linux/gpio.h> +#include <linux/gpio_event.h> +#include <linux/hrtimer.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/wakelock.h> + +enum { +	DEBOUNCE_UNSTABLE     = BIT(0),	/* Got irq, while debouncing */ +	DEBOUNCE_PRESSED      = BIT(1), +	DEBOUNCE_NOTPRESSED   = BIT(2), +	DEBOUNCE_WAIT_IRQ     = BIT(3),	/* Stable irq state */ +	DEBOUNCE_POLL         = BIT(4),	/* Stable polling state */ + +	DEBOUNCE_UNKNOWN = +		DEBOUNCE_PRESSED | DEBOUNCE_NOTPRESSED, +}; + +struct gpio_key_state { +	struct gpio_input_state *ds; +	uint8_t debounce; +}; + +struct gpio_input_state { +	struct gpio_event_input_devs *input_devs; +	const struct gpio_event_input_info *info; +	struct hrtimer timer; +	int use_irq; +	int debounce_count; +	spinlock_t irq_lock; +	struct wake_lock wake_lock; +	struct gpio_key_state key_state[0]; +}; + +static enum hrtimer_restart gpio_event_input_timer_func(struct hrtimer *timer) +{ +	int i; +	int pressed; +	struct gpio_input_state *ds = +		container_of(timer, struct gpio_input_state, timer); +	unsigned gpio_flags = ds->info->flags; +	unsigned npolarity; +	int nkeys = ds->info->keymap_size; +	const struct gpio_event_direct_entry *key_entry; +	struct gpio_key_state *key_state; +	unsigned long irqflags; +	uint8_t debounce; +	bool sync_needed; + +#if 0 +	key_entry = kp->keys_info->keymap; +	key_state = kp->key_state; +	for (i = 0; i < nkeys; i++, key_entry++, key_state++) +		pr_info("gpio_read_detect_status %d %d\n", key_entry->gpio, +			gpio_read_detect_status(key_entry->gpio)); +#endif +	key_entry = ds->info->keymap; +	key_state = ds->key_state; +	sync_needed = false; +	spin_lock_irqsave(&ds->irq_lock, irqflags); +	for (i = 0; i < nkeys; i++, key_entry++, key_state++) { +		debounce = key_state->debounce; +		if (debounce & DEBOUNCE_WAIT_IRQ) +			continue; +		if (key_state->debounce & DEBOUNCE_UNSTABLE) { +			debounce = key_state->debounce = DEBOUNCE_UNKNOWN; +			enable_irq(gpio_to_irq(key_entry->gpio)); +			if (gpio_flags & GPIOEDF_PRINT_KEY_UNSTABLE) +				pr_info("gpio_keys_scan_keys: key %x-%x, %d " +					"(%d) continue debounce\n", +					ds->info->type, key_entry->code, +					i, key_entry->gpio); +		} +		npolarity = !(gpio_flags & GPIOEDF_ACTIVE_HIGH); +		pressed = gpio_get_value(key_entry->gpio) ^ npolarity; +		if (debounce & DEBOUNCE_POLL) { +			if (pressed == !(debounce & DEBOUNCE_PRESSED)) { +				ds->debounce_count++; +				key_state->debounce = DEBOUNCE_UNKNOWN; +				if (gpio_flags & GPIOEDF_PRINT_KEY_DEBOUNCE) +					pr_info("gpio_keys_scan_keys: key %x-" +						"%x, %d (%d) start debounce\n", +						ds->info->type, key_entry->code, +						i, key_entry->gpio); +			} +			continue; +		} +		if (pressed && (debounce & DEBOUNCE_NOTPRESSED)) { +			if (gpio_flags & GPIOEDF_PRINT_KEY_DEBOUNCE) +				pr_info("gpio_keys_scan_keys: key %x-%x, %d " +					"(%d) debounce pressed 1\n", +					ds->info->type, key_entry->code, +					i, key_entry->gpio); +			key_state->debounce = DEBOUNCE_PRESSED; +			continue; +		} +		if (!pressed && (debounce & DEBOUNCE_PRESSED)) { +			if (gpio_flags & GPIOEDF_PRINT_KEY_DEBOUNCE) +				pr_info("gpio_keys_scan_keys: key %x-%x, %d " +					"(%d) debounce pressed 0\n", +					ds->info->type, key_entry->code, +					i, key_entry->gpio); +			key_state->debounce = DEBOUNCE_NOTPRESSED; +			continue; +		} +		/* key is stable */ +		ds->debounce_count--; +		if (ds->use_irq) +			key_state->debounce |= DEBOUNCE_WAIT_IRQ; +		else +			key_state->debounce |= DEBOUNCE_POLL; +		if (gpio_flags & GPIOEDF_PRINT_KEYS) +			pr_info("gpio_keys_scan_keys: key %x-%x, %d (%d) " +				"changed to %d\n", ds->info->type, +				key_entry->code, i, key_entry->gpio, pressed); +		input_event(ds->input_devs->dev[key_entry->dev], ds->info->type, +			    key_entry->code, pressed); +		sync_needed = true; +	} +	if (sync_needed) { +		for (i = 0; i < ds->input_devs->count; i++) +			input_sync(ds->input_devs->dev[i]); +	} + +#if 0 +	key_entry = kp->keys_info->keymap; +	key_state = kp->key_state; +	for (i = 0; i < nkeys; i++, key_entry++, key_state++) { +		pr_info("gpio_read_detect_status %d %d\n", key_entry->gpio, +			gpio_read_detect_status(key_entry->gpio)); +	} +#endif + +	if (ds->debounce_count) +		hrtimer_start(timer, ds->info->debounce_time, HRTIMER_MODE_REL); +	else if (!ds->use_irq) +		hrtimer_start(timer, ds->info->poll_time, HRTIMER_MODE_REL); +	else +		wake_unlock(&ds->wake_lock); + +	spin_unlock_irqrestore(&ds->irq_lock, irqflags); + +	return HRTIMER_NORESTART; +} + +static irqreturn_t gpio_event_input_irq_handler(int irq, void *dev_id) +{ +	struct gpio_key_state *ks = dev_id; +	struct gpio_input_state *ds = ks->ds; +	int keymap_index = ks - ds->key_state; +	const struct gpio_event_direct_entry *key_entry; +	unsigned long irqflags; +	int pressed; + +	if (!ds->use_irq) +		return IRQ_HANDLED; + +	key_entry = &ds->info->keymap[keymap_index]; + +	if (ds->info->debounce_time.tv64) { +		spin_lock_irqsave(&ds->irq_lock, irqflags); +		if (ks->debounce & DEBOUNCE_WAIT_IRQ) { +			ks->debounce = DEBOUNCE_UNKNOWN; +			if (ds->debounce_count++ == 0) { +				wake_lock(&ds->wake_lock); +				hrtimer_start( +					&ds->timer, ds->info->debounce_time, +					HRTIMER_MODE_REL); +			} +			if (ds->info->flags & GPIOEDF_PRINT_KEY_DEBOUNCE) +				pr_info("gpio_event_input_irq_handler: " +					"key %x-%x, %d (%d) start debounce\n", +					ds->info->type, key_entry->code, +					keymap_index, key_entry->gpio); +		} else { +			disable_irq_nosync(irq); +			ks->debounce = DEBOUNCE_UNSTABLE; +		} +		spin_unlock_irqrestore(&ds->irq_lock, irqflags); +	} else { +		pressed = gpio_get_value(key_entry->gpio) ^ +			!(ds->info->flags & GPIOEDF_ACTIVE_HIGH); +		if (ds->info->flags & GPIOEDF_PRINT_KEYS) +			pr_info("gpio_event_input_irq_handler: key %x-%x, %d " +				"(%d) changed to %d\n", +				ds->info->type, key_entry->code, keymap_index, +				key_entry->gpio, pressed); +		input_event(ds->input_devs->dev[key_entry->dev], ds->info->type, +			    key_entry->code, pressed); +		input_sync(ds->input_devs->dev[key_entry->dev]); +	} +	return IRQ_HANDLED; +} + +static int gpio_event_input_request_irqs(struct gpio_input_state *ds) +{ +	int i; +	int err; +	unsigned int irq; +	unsigned long req_flags = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING; + +	for (i = 0; i < ds->info->keymap_size; i++) { +		err = irq = gpio_to_irq(ds->info->keymap[i].gpio); +		if (err < 0) +			goto err_gpio_get_irq_num_failed; +		err = request_irq(irq, gpio_event_input_irq_handler, +				  req_flags, "gpio_keys", &ds->key_state[i]); +		if (err) { +			pr_err("gpio_event_input_request_irqs: request_irq " +				"failed for input %d, irq %d\n", +				ds->info->keymap[i].gpio, irq); +			goto err_request_irq_failed; +		} +		if (ds->info->info.no_suspend) { +			err = enable_irq_wake(irq); +			if (err) { +				pr_err("gpio_event_input_request_irqs: " +					"enable_irq_wake failed for input %d, " +					"irq %d\n", +					ds->info->keymap[i].gpio, irq); +				goto err_enable_irq_wake_failed; +			} +		} +	} +	return 0; + +	for (i = ds->info->keymap_size - 1; i >= 0; i--) { +		irq = gpio_to_irq(ds->info->keymap[i].gpio); +		if (ds->info->info.no_suspend) +			disable_irq_wake(irq); +err_enable_irq_wake_failed: +		free_irq(irq, &ds->key_state[i]); +err_request_irq_failed: +err_gpio_get_irq_num_failed: +		; +	} +	return err; +} + +int gpio_event_input_func(struct gpio_event_input_devs *input_devs, +			struct gpio_event_info *info, void **data, int func) +{ +	int ret; +	int i; +	unsigned long irqflags; +	struct gpio_event_input_info *di; +	struct gpio_input_state *ds = *data; + +	di = container_of(info, struct gpio_event_input_info, info); + +	if (func == GPIO_EVENT_FUNC_SUSPEND) { +		if (ds->use_irq) +			for (i = 0; i < di->keymap_size; i++) +				disable_irq(gpio_to_irq(di->keymap[i].gpio)); +		hrtimer_cancel(&ds->timer); +		return 0; +	} +	if (func == GPIO_EVENT_FUNC_RESUME) { +		spin_lock_irqsave(&ds->irq_lock, irqflags); +		if (ds->use_irq) +			for (i = 0; i < di->keymap_size; i++) +				enable_irq(gpio_to_irq(di->keymap[i].gpio)); +		hrtimer_start(&ds->timer, ktime_set(0, 0), HRTIMER_MODE_REL); +		spin_unlock_irqrestore(&ds->irq_lock, irqflags); +		return 0; +	} + +	if (func == GPIO_EVENT_FUNC_INIT) { +		if (ktime_to_ns(di->poll_time) <= 0) +			di->poll_time = ktime_set(0, 20 * NSEC_PER_MSEC); + +		*data = ds = kzalloc(sizeof(*ds) + sizeof(ds->key_state[0]) * +					di->keymap_size, GFP_KERNEL); +		if (ds == NULL) { +			ret = -ENOMEM; +			pr_err("gpio_event_input_func: " +				"Failed to allocate private data\n"); +			goto err_ds_alloc_failed; +		} +		ds->debounce_count = di->keymap_size; +		ds->input_devs = input_devs; +		ds->info = di; +		wake_lock_init(&ds->wake_lock, WAKE_LOCK_SUSPEND, "gpio_input"); +		spin_lock_init(&ds->irq_lock); + +		for (i = 0; i < di->keymap_size; i++) { +			int dev = di->keymap[i].dev; +			if (dev >= input_devs->count) { +				pr_err("gpio_event_input_func: bad device " +					"index %d >= %d for key code %d\n", +					dev, input_devs->count, +					di->keymap[i].code); +				ret = -EINVAL; +				goto err_bad_keymap; +			} +			input_set_capability(input_devs->dev[dev], di->type, +					     di->keymap[i].code); +			ds->key_state[i].ds = ds; +			ds->key_state[i].debounce = DEBOUNCE_UNKNOWN; +		} + +		for (i = 0; i < di->keymap_size; i++) { +			ret = gpio_request(di->keymap[i].gpio, "gpio_kp_in"); +			if (ret) { +				pr_err("gpio_event_input_func: gpio_request " +					"failed for %d\n", di->keymap[i].gpio); +				goto err_gpio_request_failed; +			} +			ret = gpio_direction_input(di->keymap[i].gpio); +			if (ret) { +				pr_err("gpio_event_input_func: " +					"gpio_direction_input failed for %d\n", +					di->keymap[i].gpio); +				goto err_gpio_configure_failed; +			} +		} + +		ret = gpio_event_input_request_irqs(ds); + +		spin_lock_irqsave(&ds->irq_lock, irqflags); +		ds->use_irq = ret == 0; + +		pr_info("GPIO Input Driver: Start gpio inputs for %s%s in %s " +			"mode\n", input_devs->dev[0]->name, +			(input_devs->count > 1) ? "..." : "", +			ret == 0 ? "interrupt" : "polling"); + +		hrtimer_init(&ds->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); +		ds->timer.function = gpio_event_input_timer_func; +		hrtimer_start(&ds->timer, ktime_set(0, 0), HRTIMER_MODE_REL); +		spin_unlock_irqrestore(&ds->irq_lock, irqflags); +		return 0; +	} + +	ret = 0; +	spin_lock_irqsave(&ds->irq_lock, irqflags); +	hrtimer_cancel(&ds->timer); +	if (ds->use_irq) { +		for (i = di->keymap_size - 1; i >= 0; i--) { +			int irq = gpio_to_irq(di->keymap[i].gpio); +			if (ds->info->info.no_suspend) +				disable_irq_wake(irq); +			free_irq(irq, &ds->key_state[i]); +		} +	} +	spin_unlock_irqrestore(&ds->irq_lock, irqflags); + +	for (i = di->keymap_size - 1; i >= 0; i--) { +err_gpio_configure_failed: +		gpio_free(di->keymap[i].gpio); +err_gpio_request_failed: +		; +	} +err_bad_keymap: +	wake_lock_destroy(&ds->wake_lock); +	kfree(ds); +err_ds_alloc_failed: +	return ret; +}  |