[PATCH] generic GPIO driven matrix keypad

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

 



Hi,
the following patch adds the generic GPIO driven matrix keypad driver. I 
tested this on two devices I had available - palmtc and palmt3. It should be 
able to replace corgikbd.c later as well, but I dont have that device. And 
since this is platform independent driver, any chip that makes it's GPIOs 
available through gpiolib should be OK with this driver as well. Please 
consider applying.
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..7fc20f4
--- /dev/null
+++ b/drivers/input/keyboard/matrix_keypad.c
@@ -0,0 +1,346 @@
+/*
+ * 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/module.h>
+#include <linux/gpio.h>
+#include <linux/matrix_keypad.h>
+#include <linux/kthread.h>
+#include <linux/freezer.h>
+
+struct matrix_keypad {
+	struct matrix_keypad_platform_data *pdata;
+	struct input_dev *input_dev;
+
+	struct task_struct *kthread;
+	unsigned char irqs;
+
+	/* on, off, alt flags */
+	unsigned char *flags;
+};
+
+/*
+ * Lookup the key in our keymap
+ */
+static unsigned int matrix_keypad_lookup(int row, int col,
+				struct matrix_keypad *keypad)
+{
+	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] & 0xfff;
+	return 0xfff;
+}
+
+/*
+ * This analyzes the key and reports it to the input subsystem
+ */
+static unsigned int matrix_keypad_process(struct matrix_keypad *keypad,
+					int i, int j)
+{
+	struct matrix_keypad_platform_data *pdata = keypad->pdata;
+	int pressed = 0, key = 0, gpio;
+
+	gpio = gpio_get_value(pdata->col_gpio[j]);
+	if (pdata->col_polarity)
+		gpio = !gpio;
+	if (gpio)
+		pressed++;
+	key = matrix_keypad_lookup(i, j, keypad);
+	if (key == 0xfff)
+		return pressed;
+	if (key & KEY_SFT)
+		input_report_key(keypad->input_dev, KEY_LEFTSHIFT, 1);
+	if (gpio) {
+		input_report_key(keypad->input_dev, key & 0x7ff, 1);
+		keypad->flags[key / 8] |= 1 << (key % 8);
+	} else if ((keypad->flags[key / 8] & (1 << (key % 8))) && !gpio) {
+		input_report_key(keypad->input_dev, key & 0x7ff, 0);
+		keypad->flags[key / 8] &= ~(1 << (key % 8));
+	}
+	if (key & KEY_SFT)
+		input_report_key(keypad->input_dev, KEY_LEFTSHIFT, 0);
+
+	return pressed;
+}
+
+/*
+ * This gets the keys from keyboard
+ */
+static int matrix_keypad_thread(void *arg)
+{
+	int i, j, pressed;
+	struct matrix_keypad *keypad = arg;
+	struct matrix_keypad_platform_data *pdata = keypad->pdata;
+
+	set_freezable();
+	while (!kthread_should_stop()) {
+		if (try_to_freeze())
+			continue;
+
+		pressed = 0;
+
+		/* set all unreadable */
+		for (i = 0; i < pdata->row_gpio_size; i++)
+			gpio_set_value(pdata->row_gpio[i], pdata->row_polarity);
+
+		/* read the keypad matrix */
+		for (i = 0; i < pdata->row_gpio_size; i++) {
+			gpio_set_value(pdata->row_gpio[i],
+					!pdata->row_polarity);
+			udelay(pdata->gpio_delay);
+
+			for (j = 0; j < pdata->col_gpio_size; j++)
+				pressed += matrix_keypad_process(keypad, i, j);
+
+			gpio_set_value(pdata->row_gpio[i], pdata->row_polarity);
+			udelay(pdata->gpio_delay);
+		}
+
+		/* set all readable */
+		for (i = 0; i < pdata->row_gpio_size; i++)
+			gpio_set_value(pdata->row_gpio[i],
+					!pdata->row_polarity);
+
+
+		/* report to input subsystem */
+		input_sync(keypad->input_dev);
+
+		if (pressed)
+			msleep(pdata->scan_delay);
+		else {
+			/* reenable interrupts */
+			if (!keypad->irqs) {
+				for (i = 0; i < pdata->col_gpio_size; i++)
+					enable_irq(gpio_to_irq(
+							pdata->col_gpio[i]));
+				keypad->irqs = 1;
+			}
+			schedule();
+		}
+	}
+	return 0;
+}
+
+/*
+ * Interrupt handler
+ */
+static irqreturn_t matrix_keypad_interrupt(int irq, void *id)
+{
+	struct matrix_keypad *keypad = id;
+	unsigned long flags;
+	int i;
+
+	/* disable interrupts as soon as possible */
+	local_irq_save(flags);
+	if (keypad->irqs) {
+		for (i = 0; i < keypad->pdata->col_gpio_size; i++)
+			disable_irq(gpio_to_irq(keypad->pdata->col_gpio[i]));
+		keypad->irqs = 0;
+		wake_up_process(keypad->kthread);
+	}
+	local_irq_restore(flags);
+
+	return IRQ_HANDLED;
+}
+
+#ifdef CONFIG_PM
+static int matrix_keypad_suspend(struct platform_device *dev,
+				pm_message_t state)
+{
+	struct matrix_keypad *keypad = platform_get_drvdata(dev);
+
+	return keypad->pdata->suspend ?
+		keypad->pdata->suspend(dev, state) : 0;
+}
+
+static int matrix_keypad_resume(struct platform_device *dev)
+{
+	struct matrix_keypad *keypad = platform_get_drvdata(dev);
+
+	return keypad->pdata->resume ? keypad->pdata->resume(dev) : 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->flags = kzalloc(4 * KEY_CNT, GFP_KERNEL);
+	if (!keypad->flags)
+		goto fail;
+
+	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++) {
+		err = gpio_request(keypad->pdata->col_gpio[i], "KBD_IRQ");
+		if (err)
+			goto gpio_err;
+		err = gpio_direction_input(keypad->pdata->col_gpio[i]);
+		if (err) {
+			gpio_free(keypad->pdata->col_gpio[i]);
+			goto gpio_err;
+		}
+		if (request_irq(gpio_to_irq(keypad->pdata->col_gpio[i]),
+				matrix_keypad_interrupt, IRQF_DISABLED |
+				IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING |
+				IRQF_SAMPLE_RANDOM,
+				"matrix-keypad", keypad)) {
+			printk(KERN_ERR "Unable to acquire interrupt"
+				" for GPIO line %i\n",
+				keypad->pdata->col_gpio[i]);
+			gpio_free(keypad->pdata->col_gpio[i]);
+			goto gpio_err;
+		}
+	}
+
+	/* 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_err2;
+		err = gpio_direction_output(keypad->pdata->row_gpio[i],
+				    !keypad->pdata->row_polarity);
+		if (err) {
+			gpio_free(keypad->pdata->row_gpio[i]);
+			goto gpio_err2;
+		}
+	}
+	keypad->irqs = 1;
+	keypad->kthread = kthread_run(matrix_keypad_thread, keypad, "matrix");
+	if (IS_ERR(keypad->kthread))
+		goto thread_err;
+
+	return 0;
+
+thread_err:
+	i = keypad->pdata->row_gpio_size;
+gpio_err2:
+	for (i = i-1; i >= 0; i--)
+		gpio_free(keypad->pdata->row_gpio[i]);
+	for (i = 0; i < keypad->pdata->col_gpio_size; i++) {
+		free_irq(gpio_to_irq(keypad->pdata->col_gpio[i]), keypad);
+		gpio_free(keypad->pdata->col_gpio[i]);
+	}
+	goto fail;
+gpio_err:
+	for (i = i-1; i >= 0; i--) {
+		free_irq(gpio_to_irq(keypad->pdata->col_gpio[i]), keypad);
+		gpio_free(keypad->pdata->col_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);
+
+	kthread_stop(keypad->kthread);
+
+	for (i = 0; i < keypad->pdata->col_gpio_size; i++) {
+		free_irq(gpio_to_irq(keypad->pdata->col_gpio[i]),
+				    keypad);
+		gpio_free(keypad->pdata->col_gpio[i]);
+	}
+
+	for (i = 0; i < keypad->pdata->row_gpio_size; i++)
+		gpio_free(keypad->pdata->row_gpio[i]);
+
+	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..653b33c
--- /dev/null
+++ b/include/linux/matrix_keypad.h
@@ -0,0 +1,39 @@
+#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;
+
+	/* line polarities */
+	unsigned int	col_polarity;
+	unsigned int	row_polarity;
+
+	/* key debounce interval */
+	unsigned int	scan_delay;
+
+	/* GPIO level change delay */
+	unsigned int	gpio_delay;
+
+	/* Callbacks */
+	int (*suspend)(struct platform_device *dev, pm_message_t state);
+	int (*resume)(struct platform_device *dev);
+};
+
+#define KEY(row, col, val)	(((row) << 28) | ((col) << 24) | (val & 0xfff))
+
+/* key not connected */
+#define KEY_NC	0xfff
+/* key to be pressed with shift */
+#define KEY_SFT	0x800
+
+#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