| /* 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> |
| |
| 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 input_dev *input_dev; |
| const struct gpio_event_input_info *info; |
| struct hrtimer timer; |
| int use_irq; |
| int debounce_count; |
| spinlock_t irq_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; |
| |
| #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; |
| 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)); |
| 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_dev, ds->info->type, |
| key_entry->code, pressed); |
| } |
| |
| #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); |
| |
| 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) { |
| 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(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_dev, ds->info->type, |
| key_entry->code, pressed); |
| } |
| 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; |
| } |
| enable_irq_wake(irq); |
| } |
| return 0; |
| |
| for (i = ds->info->keymap_size - 1; i >= 0; i--) { |
| free_irq(gpio_to_irq(ds->info->keymap[i].gpio), |
| &ds->key_state[i]); |
| err_request_irq_failed: |
| err_gpio_get_irq_num_failed: |
| ; |
| } |
| return err; |
| } |
| |
| int gpio_event_input_func(struct input_dev *input_dev, |
| 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) { |
| spin_lock_irqsave(&ds->irq_lock, irqflags); |
| if (ds->use_irq) |
| for (i = 0; i < di->keymap_size; i++) |
| disable_irq(gpio_to_irq(di->keymap[i].gpio)); |
| spin_unlock_irqrestore(&ds->irq_lock, irqflags); |
| 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_dev = input_dev; |
| ds->info = di; |
| spin_lock_init(&ds->irq_lock); |
| |
| for (i = 0; i < di->keymap_size; i++) { |
| input_set_capability(input_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 in %s " |
| "mode\n", |
| input_dev->name, 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--) { |
| free_irq(gpio_to_irq(di->keymap[i].gpio), |
| &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: |
| ; |
| } |
| kfree(ds); |
| err_ds_alloc_failed: |
| return ret; |
| } |