[PATCH 2/3] Matrix keypad

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hi,
this is the gpio driven matrix keypad. Final version.
I also CCed it to linux-input since it might interest them.
Signed-off-by: Marek Vasut <marek.vasut@xxxxxxxxx>
diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig
index efd70a9..31b91ba 100644
--- a/drivers/input/keyboard/Kconfig
+++ b/drivers/input/keyboard/Kconfig
@@ -323,4 +323,14 @@ config KEYBOARD_SH_KEYSC
 
 	  To compile this driver as a module, choose M here: the
 	  module will be called sh_keysc.
+
+config KEYBOARD_MATRIX
+	tristate "GPIO driven matrix keypad support"
+	depends on GENERIC_GPIO
+	help
+	  Enable support for GPIO driven matrix keypad
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called matrix_keypad.
+
 endif
diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile
index 0edc8f2..b22bae1 100644
--- a/drivers/input/keyboard/Makefile
+++ b/drivers/input/keyboard/Makefile
@@ -27,3 +27,4 @@ obj-$(CONFIG_KEYBOARD_HP7XX)		+= jornada720_kbd.o
 obj-$(CONFIG_KEYBOARD_MAPLE)		+= maple_keyb.o
 obj-$(CONFIG_KEYBOARD_BFIN)		+= bf54x-keys.o
 obj-$(CONFIG_KEYBOARD_SH_KEYSC)		+= sh_keysc.o
+obj-$(CONFIG_KEYBOARD_MATRIX)		+= matrix_keypad.o
diff --git a/drivers/input/keyboard/matrix_keypad.c b/drivers/input/keyboard/matrix_keypad.c
new file mode 100644
index 0000000..1a1503d
--- /dev/null
+++ b/drivers/input/keyboard/matrix_keypad.c
@@ -0,0 +1,351 @@
+/*
+ * drivers/input/keyboard/matrix_keypad.c
+ *
+ *  GPIO driven matrix keyboard driver
+ *
+ *  Copyright (c) 2008 Marek Vasut <marek.vasut@xxxxxxxxx>
+ *
+ *  Based on corgikbd.c
+ *
+ *  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/delay.h>
+#include <linux/platform_device.h>
+#include <linux/init.h>
+#include <linux/input.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/jiffies.h>
+#include <linux/module.h>
+#include <linux/gpio.h>
+#include <linux/matrix_keypad.h>
+
+struct matrix_keypad {
+	struct matrix_keypad_platform_data *pdata;
+	struct input_dev *input_dev;
+
+	spinlock_t lock;
+	struct timer_list timer;
+
+	unsigned int suspended;
+	unsigned long suspend_jiffies;
+
+	/* on, off, alt flags */
+	unsigned int size;
+	unsigned int *flags;
+};
+
+/*
+ * Get alternate function button status (if there is any on our keypad)
+ */
+static unsigned char matrix_keypad_get_fn(struct matrix_keypad *keypad)
+{
+	unsigned char fn = 0;
+	struct matrix_keypad_platform_data *pdata = keypad->pdata;
+
+	if (!pdata->alt_row || !pdata->alt_col)
+		return 0;
+
+	/* get the altfn key status */
+	gpio_set_value(pdata->row_gpio[pdata->alt_row],	!pdata->row_polarity);
+	udelay(50);
+	fn = !!gpio_get_value(pdata->col_gpio[pdata->alt_col]);
+	if (pdata->col_polarity)
+		fn = !fn;
+	gpio_set_value(pdata->row_gpio[pdata->alt_row],	pdata->row_polarity);
+	return fn;
+}
+
+/*
+ * Lookup the key in our keymap
+ */
+static unsigned int matrix_keypad_lookup(int row, int col,
+				struct matrix_keypad *keypad, int altfn)
+{
+	int i;
+	struct matrix_keypad_platform_data *pdata = keypad->pdata;
+
+	for (i = 0; i < pdata->map_size; i++)
+		if ((row == ((pdata->map[i]>>28) & 0xf)) &&
+		     (col == ((pdata->map[i]>>24) & 0xf)))
+			return (pdata->map[i] >> (12 * altfn)) & 0xfff;
+	return 0xfff;
+}
+
+/*
+ * This gets the keys from keyboard and reports it to input subsystem
+ */
+static void matrix_keypad_process(struct matrix_keypad *keypad)
+{
+	unsigned char fn = 0;
+	int i, j, pressed = 0;
+	unsigned long flags;
+	int gpio, key = 0;
+	struct matrix_keypad_platform_data *pdata = keypad->pdata;
+
+	spin_lock_irqsave(&keypad->lock, flags);
+
+	/* disable interrupts */
+	for (i = 0; i < pdata->col_gpio_size; i++)
+		set_irq_type(gpio_to_irq(pdata->col_gpio[i]),
+				IRQF_TRIGGER_NONE);
+
+	/* set all unreadable */
+	for (i = 0; i < pdata->row_gpio_size; i++)
+		gpio_set_value(pdata->row_gpio[i], pdata->row_polarity);
+
+	fn = matrix_keypad_get_fn(keypad);
+
+	/* read the keypad matrix */
+	for (i = 0; i < pdata->row_gpio_size; i++) {
+		gpio_set_value(pdata->row_gpio[i], !pdata->row_polarity);
+		udelay(50);
+		for (j = 0; j < pdata->col_gpio_size; j++) {
+				gpio = gpio_get_value(pdata->col_gpio[j]);
+				if (pdata->col_polarity)
+					gpio = !gpio;
+				if (gpio)
+					pressed++;
+				key = matrix_keypad_lookup(i, j, keypad, fn);
+				if (key == 0xfff)
+					continue;
+				if (gpio) {
+					input_report_key(keypad->input_dev,
+							key, 1);
+					keypad->flags[(key/32) + (keypad->size
+						* fn)] |= 1<<(key%32);
+				} else if ((keypad->flags[(key/32) +
+						(keypad->size * fn)] &
+						(1<<(key%32))) && !gpio) {
+					input_report_key(keypad->input_dev,
+							key, 0);
+					keypad->flags[(key/32) + (keypad->size
+						* fn)] &= ~(1<<(key%32));
+				}
+		}
+		gpio_set_value(pdata->row_gpio[i], pdata->row_polarity);
+		udelay(50);
+	}
+
+	/* set all readable */
+	for (i = 0; i < pdata->row_gpio_size; i++)
+		gpio_set_value(pdata->row_gpio[i],
+				!pdata->row_polarity);
+
+	/* reenable interrupts */
+	for (i = 0; i < pdata->col_gpio_size; i++)
+		set_irq_type(gpio_to_irq(pdata->col_gpio[i]),
+			    IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING);
+
+	/* report to input subsystem */
+	input_sync(keypad->input_dev);
+
+	/* if there were any new keypresses, handle repeats by rereading */
+	if (pressed)
+		mod_timer(&keypad->timer, jiffies + msecs_to_jiffies(
+				    pdata->debounce_interval));
+
+	spin_unlock_irqrestore(&keypad->lock, flags);
+}
+
+/*
+ * Interrupt handler
+ */
+static irqreturn_t matrix_keypad_interrupt(int irq, void *id)
+{
+	struct matrix_keypad *keypad = id;
+
+	if (!timer_pending(&keypad->timer)) {
+		udelay(20);
+		matrix_keypad_process(keypad);
+	}
+
+	return IRQ_HANDLED;
+}
+
+/*
+ * Timer checking for released keys and still held keys
+ */
+static void matrix_keypad_timer_callback(unsigned long data)
+{
+	struct matrix_keypad *keypad = (struct matrix_keypad *) data;
+	matrix_keypad_process(keypad);
+}
+
+#ifdef CONFIG_PM
+static int matrix_keypad_suspend(struct platform_device *dev,
+				pm_message_t state)
+{
+	int i;
+	struct matrix_keypad *keypad = platform_get_drvdata(dev);
+
+	keypad->suspended = 1;
+
+	/* we dont really want to suspend power key here */
+	for (i = 1; i < keypad->pdata->row_gpio_size; i++)
+		gpio_direction_input(keypad->pdata->row_gpio[i]);
+
+	return 0;
+}
+
+static int matrix_keypad_resume(struct platform_device *dev)
+{
+	int i;
+	struct matrix_keypad *keypad = platform_get_drvdata(dev);
+
+	/* Set strobe lines as outputs, low */
+	for (i = 0; i < keypad->pdata->row_gpio_size; i++)
+		gpio_direction_output(keypad->pdata->row_gpio[i], 0);
+
+	/* Upon resume, ignore the suspend key for a short while */
+	keypad->suspend_jiffies = jiffies;
+	keypad->suspended = 0;
+
+	return 0;
+}
+#else
+#define matrix_keypad_suspend	NULL
+#define matrix_keypad_resume	NULL
+#endif
+
+/*
+ * Everything starts here
+ */
+static int __init matrix_keypad_probe(struct platform_device *pdev)
+{
+	struct matrix_keypad *keypad;
+	struct input_dev *input_dev;
+	int i, err = -ENOMEM;
+
+	keypad = kzalloc(sizeof(struct matrix_keypad), GFP_KERNEL);
+	input_dev = input_allocate_device();
+	if (!keypad || !input_dev)
+		goto fail;
+
+	platform_set_drvdata(pdev, keypad);
+
+	keypad->input_dev = input_dev;
+	keypad->pdata = pdev->dev.platform_data;
+	keypad->size = (keypad->pdata->map_size / sizeof(unsigned int))+1;
+	keypad->flags = kzalloc(2 * keypad->size, GFP_KERNEL);
+	if (!keypad->flags)
+		goto fail;
+
+	spin_lock_init(&keypad->lock);
+
+	/* Init Keyboard rescan timer */
+	init_timer(&keypad->timer);
+	keypad->timer.function = matrix_keypad_timer_callback;
+	keypad->timer.data = (unsigned long) keypad;
+
+	keypad->suspend_jiffies = jiffies;
+
+	input_dev->name = pdev->name;
+	input_dev->id.bustype = BUS_HOST;
+	input_dev->dev.parent = &pdev->dev;
+
+	input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP) |
+		BIT_MASK(EV_PWR) | BIT_MASK(EV_SW);
+	input_dev->keycodesize = sizeof(unsigned int);
+
+	for (i = 0; i < keypad->pdata->map_size; i++) {
+		if ((keypad->pdata->map[i] & 0xfff) != 0xfff)
+			set_bit((keypad->pdata->map[i] & 0xfff),
+				input_dev->keybit);
+		if (((keypad->pdata->map[i] >> 12) & 0xfff) != 0xfff)
+			set_bit((keypad->pdata->map[i] >> 12) & 0xfff,
+				input_dev->keybit);
+	}
+
+	err = input_register_device(keypad->input_dev);
+	if (err)
+		goto fail;
+
+	for (i = 0; i < keypad->pdata->col_gpio_size; i++) {
+		gpio_direction_input(keypad->pdata->col_gpio[i]);
+		if (request_irq(gpio_to_irq(keypad->pdata->col_gpio[i]),
+				matrix_keypad_interrupt, IRQF_DISABLED |
+				IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+				"matrix-keypad", keypad))
+			printk(KERN_ERR "Unable to acquire interrupt"
+				" for GPIO line %i\n",
+				keypad->pdata->col_gpio[i]);
+	}
+
+	/* Set strobe lines as outputs, low */
+	for (i = 0; i < keypad->pdata->row_gpio_size; i++) {
+		err = gpio_request(keypad->pdata->row_gpio[i],
+				    "KBD_LINE");
+		if (err)
+			goto gpio_err;
+		gpio_direction_output(keypad->pdata->row_gpio[i], 0);
+	}
+
+	return 0;
+
+gpio_err:
+	for (i = i-1; i >= 0; i--)
+		gpio_free(keypad->pdata->row_gpio[i]);
+
+fail:	input_free_device(input_dev);
+	kfree(keypad);
+	return err;
+}
+
+/*
+ * Everything ends here
+ */
+static int matrix_keypad_remove(struct platform_device *pdev)
+{
+	int i;
+	struct matrix_keypad *keypad = platform_get_drvdata(pdev);
+
+	for (i = 0; i < keypad->pdata->col_gpio_size; i++)
+		free_irq(gpio_to_irq(keypad->pdata->col_gpio[i]),
+				    keypad);
+
+	for (i = 0; i < keypad->pdata->row_gpio_size; i++)
+		gpio_free(keypad->pdata->row_gpio[i]);
+
+	del_timer_sync(&keypad->timer);
+
+	input_unregister_device(keypad->input_dev);
+
+	kfree(keypad->flags);
+	kfree(keypad);
+
+	return 0;
+}
+
+static struct platform_driver matrix_keypad_driver = {
+	.probe		= matrix_keypad_probe,
+	.remove		= matrix_keypad_remove,
+	.suspend	= matrix_keypad_suspend,
+	.resume		= matrix_keypad_resume,
+	.driver		= {
+		.name	= "matrix-keypad",
+		.owner	= THIS_MODULE,
+	},
+};
+
+static int __devinit matrix_keypad_init(void)
+{
+	return platform_driver_register(&matrix_keypad_driver);
+}
+
+static void __exit matrix_keypad_exit(void)
+{
+	platform_driver_unregister(&matrix_keypad_driver);
+}
+
+module_init(matrix_keypad_init);
+module_exit(matrix_keypad_exit);
+
+MODULE_AUTHOR("Marek Vasut <marek.vasut@xxxxxxxxx>");
+MODULE_DESCRIPTION("GPIO Driven Matrix Keypad Driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:matrix-keypad");
diff --git a/include/linux/matrix_keypad.h b/include/linux/matrix_keypad.h
new file mode 100644
index 0000000..fe72721
--- /dev/null
+++ b/include/linux/matrix_keypad.h
@@ -0,0 +1,35 @@
+#ifndef _MATRIX_KEYPAD_H
+#define _MATRIX_KEYPAD_H
+
+#include <linux/input.h>
+
+struct matrix_keypad_platform_data {
+
+	/* code map for the matrix keys */
+	unsigned int	*map;
+	int		map_size;
+
+	unsigned int	*col_gpio;
+	int		col_gpio_size;
+	unsigned int	*row_gpio;
+	int		row_gpio_size;
+
+	/* ALTFN key */
+	int		alt_row;
+	int		alt_col;
+
+	/* line polarities */
+	unsigned int	col_polarity;
+	unsigned int	row_polarity;
+
+	/* key debounce interval */
+	unsigned int	debounce_interval;
+};
+
+#define KEY(row, col, val, alt)	(((row) << 28) | ((col) << 24) | \
+				((alt & 0xfff) << 12) | (val & 0xfff))
+
+/* key not connected */
+#define KEY_NC	0xfff
+
+#endif /* _MATRIX_KEYPAD_H */

[Index of Archives]     [Linux Media Devel]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Linux Wireless Networking]     [Linux Omap]

  Powered by Linux