All of lore.kernel.org
 help / color / mirror / Atom feed
* MXC: input: add mxc-keypad driver to support the keypad interface present in the mxc application processors family.
@ 2010-01-08 18:58 ` Alberto Panizzo
  0 siblings, 0 replies; 16+ messages in thread
From: Alberto Panizzo @ 2010-01-08 18:58 UTC (permalink / raw)
  To: linux-input
  Cc: linux-kernel, Dmitry Torokhov, linux-arm-kernel-infradead,
	Sascha linux-arm

The MXC family of Application Processors is shipped with a
Keypad Port supported now by this driver.

The peripheral can control up to an 8x8 matrix key pad where
all the scanning procedure is done via software. 

The hardware provide two interrupts: one for a key pressed (KDI)
and one for all key releases (KRI). There is also a simple circuit for glitch 
reduction (said for synchronization) made by two series of 3 D-latches
that stabilize the interrupts sources.
KDI and KRI are fired only if the respective conditions are maintained for 
at last 4 keypad-clock cycle.

Those simple synchronization circuits are used also for multiple
key pressures: between a KDI and a KRI the driver reset the 
sync circuit and re-enable the KDI interrupt so after 3
keypad-clock cycle another KDI is fired making possible to repeat
the matrix scan operation.

This algorithm is done via a threaded management of the keypad
interrupt source and delayed by a proper (and longer) debounce
interval controlled by the platform initialization.
If a key is pressed for a lot of time, the driver relaxes a bit
the timings to not over load the cpu in a long time keypad interaction.

Key configuration is done via platform initialization in 
a standard matrix_keypad manner.

This driver is tested for build in kernel or as a module
and follow the specification of freescale i.MX 25 27 31 35 51
especially it is tested in the mx31pdk board.


Signed-off-by: Alberto Panizzo <maramaopercheseimorto@gmail.com>
---
 arch/arm/plat-mxc/include/mach/mxc_keypad.h |   20 ++
 drivers/input/keyboard/Kconfig              |    9 +
 drivers/input/keyboard/Makefile             |    1 +
 drivers/input/keyboard/mxc_keypad.c         |  485 +++++++++++++++++++++++++++
 4 files changed, 515 insertions(+), 0 deletions(-)
 create mode 100644 arch/arm/plat-mxc/include/mach/mxc_keypad.h
 create mode 100644 drivers/input/keyboard/mxc_keypad.c

diff --git a/arch/arm/plat-mxc/include/mach/mxc_keypad.h b/arch/arm/plat-mxc/include/mach/mxc_keypad.h
new file mode 100644
index 0000000..1b05093
--- /dev/null
+++ b/arch/arm/plat-mxc/include/mach/mxc_keypad.h
@@ -0,0 +1,20 @@
+#ifndef __MACH_MXC_KEYPAD_H
+#define __MACH_MXC_KEYPAD_H
+
+#define MAX_MATRIX_KEY_ROWS	(8)
+#define MAX_MATRIX_KEY_COLS	(8)
+#define MATRIX_ROW_SHIFT	(3)
+
+struct mxc_keypad_platform_data {
+
+	/* code map for the matrix keys */
+	unsigned int	matrix_key_rows;
+	unsigned int	matrix_key_cols;
+	unsigned int	*matrix_key_map;
+	int		matrix_key_map_size;
+
+	/* key debounce interval */
+	unsigned int	debounce_ms;
+};
+
+#endif /* __MACH_MXC_KEYPAD_H */
diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig
index ee98b1b..eff32fe 100644
--- a/drivers/input/keyboard/Kconfig
+++ b/drivers/input/keyboard/Kconfig
@@ -282,6 +282,15 @@ config KEYBOARD_MAX7359
 	  To compile this driver as a module, choose M here: the
 	  module will be called max7359_keypad.
 
+config KEYBOARD_MXC
+	tristate "MXC keypad support"
+	depends on ARCH_MXC
+	help
+	  Enable support for MXC keypad port.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called mxc_keypad.
+
 config KEYBOARD_NEWTON
 	tristate "Newton keyboard"
 	select SERIO
diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile
index babad5e..9c7133a 100644
--- a/drivers/input/keyboard/Makefile
+++ b/drivers/input/keyboard/Makefile
@@ -23,6 +23,7 @@ obj-$(CONFIG_KEYBOARD_LOCOMO)		+= locomokbd.o
 obj-$(CONFIG_KEYBOARD_MAPLE)		+= maple_keyb.o
 obj-$(CONFIG_KEYBOARD_MATRIX)		+= matrix_keypad.o
 obj-$(CONFIG_KEYBOARD_MAX7359)		+= max7359_keypad.o
+obj-$(CONFIG_KEYBOARD_MXC)		+= mxc_keypad.o
 obj-$(CONFIG_KEYBOARD_NEWTON)		+= newtonkbd.o
 obj-$(CONFIG_KEYBOARD_OMAP)		+= omap-keypad.o
 obj-$(CONFIG_KEYBOARD_OPENCORES)	+= opencores-kbd.o
diff --git a/drivers/input/keyboard/mxc_keypad.c b/drivers/input/keyboard/mxc_keypad.c
new file mode 100644
index 0000000..4acd311
--- /dev/null
+++ b/drivers/input/keyboard/mxc_keypad.c
@@ -0,0 +1,485 @@
+/*
+ * linux/drivers/input/keyboard/mxc_keypad.c
+ *
+ * Driver for the MXC keypad port.
+ * Copyright (C) 2009 Alberto Panizzo <maramaopercheseimorto@gmail.com>
+ *  based on Rodolfo Giometti & others work in pxa27x_keypad.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.
+ *
+ * >>Power management need to be implemented<<.
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/input.h>
+#include <linux/input/matrix_keypad.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#include <mach/mxc_keypad.h>
+
+/*
+ * Keypad Controller registers (halfword)
+ */
+#define KPCR		0x00 /* Keypad Control Register */
+
+#define KPSR		0x02 /* Keypad Status Register */
+#define KBD_STAT_KPKD	(0x1 << 0) /* Key Press Interrupt Status bit */
+#define KBD_STAT_KPKR	(0x1 << 1) /* Key Release Interrupt Status bit */
+#define KBD_STAT_KDSC	(0x1 << 2) /* Key Depress Synch Chain Status bit */
+#define KBD_STAT_KRSS	(0x1 << 3) /* Key Release Synch Status bit */
+#define KBD_STAT_KDIE	(0x1 << 8) /* Key Depress Interrupt Enable Status bit */
+#define KBD_STAT_KRIE	(0x1 << 9) /* Key Release Interrupt Enable */
+#define KBD_STAT_KPPEN	(0x1 << 10) /* Keypad Clock Enable */
+
+#define KDDR		0x04 /* Keypad Data Direction Register */
+#define KPDR		0x06 /* Keypad Data Register */
+
+#define keypad_readw(off)	__raw_readw(keypad->mmio_base + (off))
+#define keypad_writew(off, v)	__raw_writew((v), keypad->mmio_base + (off))
+
+#define MAX_MATRIX_KEY_NUM	(MAX_MATRIX_KEY_ROWS * MAX_MATRIX_KEY_COLS)
+
+struct mxc_keypad {
+	struct mxc_keypad_platform_data *pdata;
+
+	struct clk *clk;
+	struct input_dev *input_dev;
+	void __iomem *mmio_base;
+
+	int			irq;
+
+#define MXC_IRQ_DEPRESS		1
+#define MXC_IRQ_RELEASE		2
+	unsigned int		irq_type;
+
+	int 			irq_since_last_change;
+
+	unsigned short 		keycodes[MAX_MATRIX_KEY_NUM];
+
+	/* state row bits of each column scan */
+	unsigned short 		matrix_key_state[MAX_MATRIX_KEY_COLS];
+};
+
+static void mxc_keypad_build_keycode(struct mxc_keypad *keypad)
+{
+	struct mxc_keypad_platform_data *pdata = keypad->pdata;
+	struct input_dev *input_dev = keypad->input_dev;
+	unsigned short keycode;
+	int i;
+
+	for (i = 0; i < pdata->matrix_key_map_size; i++) {
+		unsigned int key = pdata->matrix_key_map[i];
+		unsigned int row = KEY_ROW(key);
+		unsigned int col = KEY_COL(key);
+		unsigned int scancode = MATRIX_SCAN_CODE(row, col,
+							 MATRIX_ROW_SHIFT);
+
+		keycode = KEY_VAL(key);
+		keypad->keycodes[scancode] = keycode;
+		__set_bit(keycode, input_dev->keybit);
+	}
+
+	__clear_bit(KEY_RESERVED, input_dev->keybit);
+}
+
+/* Return 0 if no changes are detected in matrix */
+static int mxc_keypad_scan_matrix(struct mxc_keypad *keypad)
+{
+	struct mxc_keypad_platform_data *pdata = keypad->pdata;
+	struct input_dev *input_dev = keypad->input_dev;
+	int row, col, changed = 0;
+	unsigned short new_state[MAX_MATRIX_KEY_COLS];
+	unsigned short reg_val;
+
+	memset(new_state, 0, sizeof(new_state));
+
+	for (col = 0; col < pdata->matrix_key_cols; col++) {
+		/* Discharge keypad capacitance:
+		 * 2. write 1s on column data.
+		 * 3. configure columns as totem-pole to discharge capacitance.
+		 * 4. configure columns as open-drain.*/
+		reg_val = keypad_readw(KPDR);
+		reg_val |= 0xff00;
+		keypad_writew(KPDR, reg_val);
+
+		reg_val = keypad_readw(KPCR);
+		reg_val &= ~(((1 << pdata->matrix_key_cols) - 1) << 8);
+		keypad_writew(KPCR, reg_val);
+
+		udelay(2);
+
+		reg_val = keypad_readw(KPCR);
+		reg_val |= ((1 << pdata->matrix_key_cols) - 1) << 8;
+		keypad_writew(KPCR, reg_val);
+
+		/*
+		 * 5. Write a single column to 0, others to 1.
+		 * 6. Sample row inputs and save data.
+		 * 7. Repeat steps 2 - 6 for remaining columns.
+		 */
+		reg_val = keypad_readw(KPDR);
+		reg_val &= ~(1 << (8 + col));
+		keypad_writew(KPDR, reg_val);
+
+		/* Delay added to avoid propagating the 0 from column to row
+		 * when scanning. */
+		udelay(5);
+
+		/* 1s in state detect a key pressure */
+		new_state[col] = (~keypad_readw(KPDR)) & 0x00ff;
+	}
+
+	/* Test the state changes */
+	for (col = 0; col < pdata->matrix_key_cols; col++) {
+		unsigned short bits_changed;
+		int code;
+
+		bits_changed = keypad->matrix_key_state[col] ^ new_state[col];
+		if (bits_changed == 0)
+			continue;
+
+		changed = 1;
+		for (row = 0; row < pdata->matrix_key_rows; row++) {
+			if ((bits_changed & (1 << row)) == 0)
+				continue;
+
+			code = MATRIX_SCAN_CODE(row, col, MATRIX_ROW_SHIFT);
+			input_event(input_dev, EV_MSC, MSC_SCAN, code);
+			input_report_key(input_dev, keypad->keycodes[code],
+					 new_state[col] & (1 << row));
+			dev_dbg(&input_dev->dev, "Event code: %d, val: %d",
+					keypad->keycodes[code],
+					new_state[col] & (1 << row));
+		}
+	}
+	input_sync(input_dev);
+	memcpy(keypad->matrix_key_state, new_state, sizeof(new_state));
+
+	/* Return in standby mode:
+	 * 9. write 0s to columns */
+	reg_val = keypad_readw(KPDR);
+	reg_val &= 0x00ff;
+	keypad_writew(KPDR, reg_val);
+
+	return changed;
+}
+
+static irqreturn_t mxc_keypad_irq_handler_thread(int irq, void *dev_id)
+{
+	struct mxc_keypad *keypad = dev_id;
+	struct mxc_keypad_platform_data *pdata = keypad->pdata;
+	struct input_dev *idev = keypad->input_dev;
+	unsigned short reg_val;
+
+	dev_dbg(&idev->dev, "Handling Interrupt of type %x\n",
+						keypad->irq_type);
+
+	/* Wait till debounce */
+	msleep(pdata->debounce_ms);
+
+	/* Do the scan routine and Keep track for how many time we got
+	 * interrupt that make no change */
+	if (mxc_keypad_scan_matrix(keypad))
+		keypad->irq_since_last_change = 0;
+	else
+		keypad->irq_since_last_change++;
+
+	/* If the key is pressed since too many time, relax the update period */
+	if (keypad->irq_since_last_change > 2)
+		msleep(100);
+
+	/* 10. Clear KPKD and KPKR status bits
+	 *     Set the KPKR sync chain and clear the KPKD sync chain */
+	reg_val = keypad_readw(KPSR);
+	reg_val |= KBD_STAT_KPKD | KBD_STAT_KPKR |
+		   KBD_STAT_KDSC | KBD_STAT_KRSS;
+	keypad_writew(KPSR, reg_val);
+
+	/* Re enable interrupts and clear sync reset bits.
+	 * Next KDI is used for detect multiple pressures. */
+	reg_val = keypad_readw(KPSR);
+	reg_val &= ~(KBD_STAT_KDSC | KBD_STAT_KRSS);
+	keypad_writew(KPSR, reg_val);
+
+	reg_val |= KBD_STAT_KDIE | KBD_STAT_KRIE;
+	if (keypad->irq_type == MXC_IRQ_RELEASE)
+		reg_val &= ~KBD_STAT_KRIE;
+	keypad_writew(KPSR, reg_val);
+
+	return IRQ_HANDLED;
+}
+static irqreturn_t mxc_keypad_irq_handler(int irq, void *dev_id)
+{
+	struct mxc_keypad *keypad = dev_id;
+	unsigned short reg_val;
+
+	/* Disable every interrupt */
+	reg_val = keypad_readw(KPSR);
+	reg_val &= ~(KBD_STAT_KRIE | KBD_STAT_KDIE);
+	keypad_writew(KPSR, reg_val);
+
+	keypad->irq_type = reg_val & KBD_STAT_KPKD ?
+			   MXC_IRQ_DEPRESS : MXC_IRQ_RELEASE;
+
+	return IRQ_WAKE_THREAD;
+}
+
+static void mxc_keypad_config(struct mxc_keypad *keypad)
+{
+	struct mxc_keypad_platform_data *pdata = keypad->pdata;
+	unsigned short reg_val;
+
+	/* Enable number of rows in keypad (KPCR[7:0])
+	 * Configure keypad columns as open-drain (KPCR[15:8])
+	 */
+	reg_val = keypad_readw(KPCR);
+	reg_val |= (1 << pdata->matrix_key_rows) - 1;		/* LSB */
+	reg_val |= ((1 << pdata->matrix_key_cols) - 1) << 8;	/* MSB */
+	keypad_writew(KPCR, reg_val);
+
+	/* Write 0's to KPDR[15:8] (Colums)*/
+	reg_val = keypad_readw(KPDR);
+	reg_val &= 0x00ff;
+	keypad_writew(KPDR, reg_val);
+
+	/* Configure columns as output, rows as input (KDDR[15:0]) */
+	reg_val = keypad_readw(KDDR);
+	reg_val |= 0xff00;
+	reg_val &= 0xff00;
+	keypad_writew(KDDR, reg_val);
+
+	/* Clear Key Depress and Key Release status bit.
+	 * Clear synchronizer chain.
+	 * */
+	reg_val = keypad_readw(KPSR);
+	reg_val |= KBD_STAT_KPKR | KBD_STAT_KPKD |
+		   KBD_STAT_KDSC | KBD_STAT_KRSS;
+	keypad_writew(KPSR, reg_val);
+
+	/* Set the KDIE control bit, and clear the KRIE control bit
+	 * (avoid false release events). */
+	reg_val |= KBD_STAT_KDIE;
+	reg_val &= ~KBD_STAT_KRIE;
+	keypad_writew(KPSR, reg_val);
+}
+
+static void mxc_keypad_inhibit(struct mxc_keypad *keypad)
+{
+	unsigned short reg_val;
+
+	/* Colums as open drain and disable rows */
+	keypad_writew(KPCR, 0xff00);
+
+	/* Clear the KPKD status flag and synchronizer chain.
+	 * Clear KDIE control bit and KRIE control bit.
+	 */
+	reg_val = keypad_readw(KPSR);
+	reg_val |= KBD_STAT_KPKD | KBD_STAT_KDSC;
+	reg_val &= ~(KBD_STAT_KRIE | KBD_STAT_KDIE);
+	keypad_writew(KPSR, reg_val);
+}
+
+static int mxc_keypad_open(struct input_dev *dev)
+{
+	struct mxc_keypad *keypad = input_get_drvdata(dev);
+	struct mxc_keypad_platform_data *pdata = keypad->pdata;
+
+	dev_dbg(&dev->dev, "%s\n", __func__);
+
+	/* Enable unit clock */
+	clk_enable(keypad->clk);
+	mxc_keypad_config(keypad);
+
+	/* Sanity control, not all the rows must be to 0s now. */
+	if ((keypad_readw(KPDR) & ((1 << pdata->matrix_key_rows) - 1)) == 0) {
+		dev_err(&dev->dev, "Too much keys pressed for now. "
+				"Control pins initialisation.\n");
+		goto open_err;
+	}
+
+	return 0;
+
+open_err:
+	mxc_keypad_inhibit(keypad);
+	return -EIO;
+}
+
+static void mxc_keypad_close(struct input_dev *dev)
+{
+	struct mxc_keypad *keypad = input_get_drvdata(dev);
+
+	mxc_keypad_inhibit(keypad);
+
+	/* Disable clock unit */
+	clk_disable(keypad->clk);
+}
+
+static int __devinit mxc_keypad_probe(struct platform_device *pdev)
+{
+	struct mxc_keypad_platform_data *pdata = pdev->dev.platform_data;
+	struct mxc_keypad *keypad;
+	struct input_dev *input_dev;
+	struct resource *res;
+	int irq, error;
+
+	if (pdata == NULL) {
+		dev_err(&pdev->dev, "no platform data defined\n");
+		return -EINVAL;
+	}
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0) {
+		dev_err(&pdev->dev, "failed to get keypad irq\n");
+		return -ENXIO;
+	}
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (res == NULL) {
+		dev_err(&pdev->dev, "failed to get I/O memory\n");
+		return -ENXIO;
+	}
+
+	keypad = kzalloc(sizeof(struct mxc_keypad), GFP_KERNEL);
+	input_dev = input_allocate_device();
+	if (!keypad || !input_dev) {
+		dev_err(&pdev->dev, "failed to allocate memory\n");
+		error = -ENOMEM;
+		goto failed_free;
+	}
+
+	keypad->pdata = pdata;
+	keypad->input_dev = input_dev;
+	keypad->irq = irq;
+
+	res = request_mem_region(res->start, resource_size(res), pdev->name);
+	if (res == NULL) {
+		dev_err(&pdev->dev, "failed to request I/O memory\n");
+		error = -EBUSY;
+		goto failed_free;
+	}
+
+	keypad->mmio_base = ioremap(res->start, resource_size(res));
+	if (keypad->mmio_base == NULL) {
+		dev_err(&pdev->dev, "failed to remap I/O memory\n");
+		error = -ENXIO;
+		goto failed_free_mem;
+	}
+
+	keypad->clk = clk_get(NULL, "kpp");
+	if (IS_ERR(keypad->clk)) {
+		dev_err(&pdev->dev, "failed to get keypad clock\n");
+		error = PTR_ERR(keypad->clk);
+		goto failed_free_io;
+	}
+
+	input_dev->name = pdev->name;
+	input_dev->id.bustype = BUS_HOST;
+	input_dev->open = mxc_keypad_open;
+	input_dev->close = mxc_keypad_close;
+	input_dev->dev.parent = &pdev->dev;
+
+	input_dev->keycode = keypad->keycodes;
+	input_dev->keycodesize = sizeof(keypad->keycodes[0]);
+	input_dev->keycodemax = ARRAY_SIZE(keypad->keycodes);
+
+	input_set_drvdata(input_dev, keypad);
+
+	input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);
+	input_set_capability(input_dev, EV_MSC, MSC_SCAN);
+
+	mxc_keypad_build_keycode(keypad);
+
+	error = request_threaded_irq(irq, mxc_keypad_irq_handler,
+			mxc_keypad_irq_handler_thread, IRQF_DISABLED,
+			pdev->name, keypad);
+	if (error) {
+		dev_err(&pdev->dev, "failed to request IRQ\n");
+		goto failed_put_clk;
+	}
+
+	/* Register the input device */
+	error = input_register_device(input_dev);
+	if (error) {
+		dev_err(&pdev->dev, "failed to register input device\n");
+		goto failed_free_irq;
+	}
+
+	platform_set_drvdata(pdev, keypad);
+	device_init_wakeup(&pdev->dev, 1);
+
+	dev_info(&pdev->dev, "device probed.\n");
+
+	return 0;
+
+failed_free_irq:
+	free_irq(irq, pdev);
+failed_put_clk:
+	clk_put(keypad->clk);
+failed_free_io:
+	iounmap(keypad->mmio_base);
+failed_free_mem:
+	release_mem_region(res->start, resource_size(res));
+failed_free:
+	input_free_device(input_dev);
+	kfree(keypad);
+	return error;
+}
+
+static int __devexit mxc_keypad_remove(struct platform_device *pdev)
+{
+	struct mxc_keypad *keypad = platform_get_drvdata(pdev);
+	struct resource *res;
+
+	free_irq(keypad->irq, keypad);
+	clk_put(keypad->clk);
+
+	input_unregister_device(keypad->input_dev);
+	input_free_device(keypad->input_dev);
+
+	iounmap(keypad->mmio_base);
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	release_mem_region(res->start, resource_size(res));
+
+	platform_set_drvdata(pdev, NULL);
+	kfree(keypad);
+
+	return 0;
+}
+
+static struct platform_driver mxc_keypad_driver = {
+	.driver		= {
+		.name	= "mxc-keypad",
+		.owner	= THIS_MODULE,
+	},
+	.probe		= mxc_keypad_probe,
+	.remove		= __devexit_p(mxc_keypad_remove),
+};
+
+static int __init mxc_keypad_init(void)
+{
+	return platform_driver_register(&mxc_keypad_driver);
+}
+
+static void __exit mxc_keypad_exit(void)
+{
+	platform_driver_unregister(&mxc_keypad_driver);
+}
+
+module_init(mxc_keypad_init);
+module_exit(mxc_keypad_exit);
+
+MODULE_AUTHOR("Alberto Panizzo <maramaopercheseimorto@gmail.com>");
+MODULE_DESCRIPTION("MXC Keypad Port Driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:mxc-keypad");
-- 
1.6.3.3




^ permalink raw reply related	[flat|nested] 16+ messages in thread

* MXC: input: add mxc-keypad driver to support the keypad interface present in the mxc application processors family.
@ 2010-01-08 18:58 ` Alberto Panizzo
  0 siblings, 0 replies; 16+ messages in thread
From: Alberto Panizzo @ 2010-01-08 18:58 UTC (permalink / raw)
  To: linux-arm-kernel

The MXC family of Application Processors is shipped with a
Keypad Port supported now by this driver.

The peripheral can control up to an 8x8 matrix key pad where
all the scanning procedure is done via software. 

The hardware provide two interrupts: one for a key pressed (KDI)
and one for all key releases (KRI). There is also a simple circuit for glitch 
reduction (said for synchronization) made by two series of 3 D-latches
that stabilize the interrupts sources.
KDI and KRI are fired only if the respective conditions are maintained for 
at last 4 keypad-clock cycle.

Those simple synchronization circuits are used also for multiple
key pressures: between a KDI and a KRI the driver reset the 
sync circuit and re-enable the KDI interrupt so after 3
keypad-clock cycle another KDI is fired making possible to repeat
the matrix scan operation.

This algorithm is done via a threaded management of the keypad
interrupt source and delayed by a proper (and longer) debounce
interval controlled by the platform initialization.
If a key is pressed for a lot of time, the driver relaxes a bit
the timings to not over load the cpu in a long time keypad interaction.

Key configuration is done via platform initialization in 
a standard matrix_keypad manner.

This driver is tested for build in kernel or as a module
and follow the specification of freescale i.MX 25 27 31 35 51
especially it is tested in the mx31pdk board.


Signed-off-by: Alberto Panizzo <maramaopercheseimorto@gmail.com>
---
 arch/arm/plat-mxc/include/mach/mxc_keypad.h |   20 ++
 drivers/input/keyboard/Kconfig              |    9 +
 drivers/input/keyboard/Makefile             |    1 +
 drivers/input/keyboard/mxc_keypad.c         |  485 +++++++++++++++++++++++++++
 4 files changed, 515 insertions(+), 0 deletions(-)
 create mode 100644 arch/arm/plat-mxc/include/mach/mxc_keypad.h
 create mode 100644 drivers/input/keyboard/mxc_keypad.c

diff --git a/arch/arm/plat-mxc/include/mach/mxc_keypad.h b/arch/arm/plat-mxc/include/mach/mxc_keypad.h
new file mode 100644
index 0000000..1b05093
--- /dev/null
+++ b/arch/arm/plat-mxc/include/mach/mxc_keypad.h
@@ -0,0 +1,20 @@
+#ifndef __MACH_MXC_KEYPAD_H
+#define __MACH_MXC_KEYPAD_H
+
+#define MAX_MATRIX_KEY_ROWS	(8)
+#define MAX_MATRIX_KEY_COLS	(8)
+#define MATRIX_ROW_SHIFT	(3)
+
+struct mxc_keypad_platform_data {
+
+	/* code map for the matrix keys */
+	unsigned int	matrix_key_rows;
+	unsigned int	matrix_key_cols;
+	unsigned int	*matrix_key_map;
+	int		matrix_key_map_size;
+
+	/* key debounce interval */
+	unsigned int	debounce_ms;
+};
+
+#endif /* __MACH_MXC_KEYPAD_H */
diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig
index ee98b1b..eff32fe 100644
--- a/drivers/input/keyboard/Kconfig
+++ b/drivers/input/keyboard/Kconfig
@@ -282,6 +282,15 @@ config KEYBOARD_MAX7359
 	  To compile this driver as a module, choose M here: the
 	  module will be called max7359_keypad.
 
+config KEYBOARD_MXC
+	tristate "MXC keypad support"
+	depends on ARCH_MXC
+	help
+	  Enable support for MXC keypad port.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called mxc_keypad.
+
 config KEYBOARD_NEWTON
 	tristate "Newton keyboard"
 	select SERIO
diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile
index babad5e..9c7133a 100644
--- a/drivers/input/keyboard/Makefile
+++ b/drivers/input/keyboard/Makefile
@@ -23,6 +23,7 @@ obj-$(CONFIG_KEYBOARD_LOCOMO)		+= locomokbd.o
 obj-$(CONFIG_KEYBOARD_MAPLE)		+= maple_keyb.o
 obj-$(CONFIG_KEYBOARD_MATRIX)		+= matrix_keypad.o
 obj-$(CONFIG_KEYBOARD_MAX7359)		+= max7359_keypad.o
+obj-$(CONFIG_KEYBOARD_MXC)		+= mxc_keypad.o
 obj-$(CONFIG_KEYBOARD_NEWTON)		+= newtonkbd.o
 obj-$(CONFIG_KEYBOARD_OMAP)		+= omap-keypad.o
 obj-$(CONFIG_KEYBOARD_OPENCORES)	+= opencores-kbd.o
diff --git a/drivers/input/keyboard/mxc_keypad.c b/drivers/input/keyboard/mxc_keypad.c
new file mode 100644
index 0000000..4acd311
--- /dev/null
+++ b/drivers/input/keyboard/mxc_keypad.c
@@ -0,0 +1,485 @@
+/*
+ * linux/drivers/input/keyboard/mxc_keypad.c
+ *
+ * Driver for the MXC keypad port.
+ * Copyright (C) 2009 Alberto Panizzo <maramaopercheseimorto@gmail.com>
+ *  based on Rodolfo Giometti & others work in pxa27x_keypad.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.
+ *
+ * >>Power management need to be implemented<<.
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/input.h>
+#include <linux/input/matrix_keypad.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#include <mach/mxc_keypad.h>
+
+/*
+ * Keypad Controller registers (halfword)
+ */
+#define KPCR		0x00 /* Keypad Control Register */
+
+#define KPSR		0x02 /* Keypad Status Register */
+#define KBD_STAT_KPKD	(0x1 << 0) /* Key Press Interrupt Status bit */
+#define KBD_STAT_KPKR	(0x1 << 1) /* Key Release Interrupt Status bit */
+#define KBD_STAT_KDSC	(0x1 << 2) /* Key Depress Synch Chain Status bit */
+#define KBD_STAT_KRSS	(0x1 << 3) /* Key Release Synch Status bit */
+#define KBD_STAT_KDIE	(0x1 << 8) /* Key Depress Interrupt Enable Status bit */
+#define KBD_STAT_KRIE	(0x1 << 9) /* Key Release Interrupt Enable */
+#define KBD_STAT_KPPEN	(0x1 << 10) /* Keypad Clock Enable */
+
+#define KDDR		0x04 /* Keypad Data Direction Register */
+#define KPDR		0x06 /* Keypad Data Register */
+
+#define keypad_readw(off)	__raw_readw(keypad->mmio_base + (off))
+#define keypad_writew(off, v)	__raw_writew((v), keypad->mmio_base + (off))
+
+#define MAX_MATRIX_KEY_NUM	(MAX_MATRIX_KEY_ROWS * MAX_MATRIX_KEY_COLS)
+
+struct mxc_keypad {
+	struct mxc_keypad_platform_data *pdata;
+
+	struct clk *clk;
+	struct input_dev *input_dev;
+	void __iomem *mmio_base;
+
+	int			irq;
+
+#define MXC_IRQ_DEPRESS		1
+#define MXC_IRQ_RELEASE		2
+	unsigned int		irq_type;
+
+	int 			irq_since_last_change;
+
+	unsigned short 		keycodes[MAX_MATRIX_KEY_NUM];
+
+	/* state row bits of each column scan */
+	unsigned short 		matrix_key_state[MAX_MATRIX_KEY_COLS];
+};
+
+static void mxc_keypad_build_keycode(struct mxc_keypad *keypad)
+{
+	struct mxc_keypad_platform_data *pdata = keypad->pdata;
+	struct input_dev *input_dev = keypad->input_dev;
+	unsigned short keycode;
+	int i;
+
+	for (i = 0; i < pdata->matrix_key_map_size; i++) {
+		unsigned int key = pdata->matrix_key_map[i];
+		unsigned int row = KEY_ROW(key);
+		unsigned int col = KEY_COL(key);
+		unsigned int scancode = MATRIX_SCAN_CODE(row, col,
+							 MATRIX_ROW_SHIFT);
+
+		keycode = KEY_VAL(key);
+		keypad->keycodes[scancode] = keycode;
+		__set_bit(keycode, input_dev->keybit);
+	}
+
+	__clear_bit(KEY_RESERVED, input_dev->keybit);
+}
+
+/* Return 0 if no changes are detected in matrix */
+static int mxc_keypad_scan_matrix(struct mxc_keypad *keypad)
+{
+	struct mxc_keypad_platform_data *pdata = keypad->pdata;
+	struct input_dev *input_dev = keypad->input_dev;
+	int row, col, changed = 0;
+	unsigned short new_state[MAX_MATRIX_KEY_COLS];
+	unsigned short reg_val;
+
+	memset(new_state, 0, sizeof(new_state));
+
+	for (col = 0; col < pdata->matrix_key_cols; col++) {
+		/* Discharge keypad capacitance:
+		 * 2. write 1s on column data.
+		 * 3. configure columns as totem-pole to discharge capacitance.
+		 * 4. configure columns as open-drain.*/
+		reg_val = keypad_readw(KPDR);
+		reg_val |= 0xff00;
+		keypad_writew(KPDR, reg_val);
+
+		reg_val = keypad_readw(KPCR);
+		reg_val &= ~(((1 << pdata->matrix_key_cols) - 1) << 8);
+		keypad_writew(KPCR, reg_val);
+
+		udelay(2);
+
+		reg_val = keypad_readw(KPCR);
+		reg_val |= ((1 << pdata->matrix_key_cols) - 1) << 8;
+		keypad_writew(KPCR, reg_val);
+
+		/*
+		 * 5. Write a single column to 0, others to 1.
+		 * 6. Sample row inputs and save data.
+		 * 7. Repeat steps 2 - 6 for remaining columns.
+		 */
+		reg_val = keypad_readw(KPDR);
+		reg_val &= ~(1 << (8 + col));
+		keypad_writew(KPDR, reg_val);
+
+		/* Delay added to avoid propagating the 0 from column to row
+		 * when scanning. */
+		udelay(5);
+
+		/* 1s in state detect a key pressure */
+		new_state[col] = (~keypad_readw(KPDR)) & 0x00ff;
+	}
+
+	/* Test the state changes */
+	for (col = 0; col < pdata->matrix_key_cols; col++) {
+		unsigned short bits_changed;
+		int code;
+
+		bits_changed = keypad->matrix_key_state[col] ^ new_state[col];
+		if (bits_changed == 0)
+			continue;
+
+		changed = 1;
+		for (row = 0; row < pdata->matrix_key_rows; row++) {
+			if ((bits_changed & (1 << row)) == 0)
+				continue;
+
+			code = MATRIX_SCAN_CODE(row, col, MATRIX_ROW_SHIFT);
+			input_event(input_dev, EV_MSC, MSC_SCAN, code);
+			input_report_key(input_dev, keypad->keycodes[code],
+					 new_state[col] & (1 << row));
+			dev_dbg(&input_dev->dev, "Event code: %d, val: %d",
+					keypad->keycodes[code],
+					new_state[col] & (1 << row));
+		}
+	}
+	input_sync(input_dev);
+	memcpy(keypad->matrix_key_state, new_state, sizeof(new_state));
+
+	/* Return in standby mode:
+	 * 9. write 0s to columns */
+	reg_val = keypad_readw(KPDR);
+	reg_val &= 0x00ff;
+	keypad_writew(KPDR, reg_val);
+
+	return changed;
+}
+
+static irqreturn_t mxc_keypad_irq_handler_thread(int irq, void *dev_id)
+{
+	struct mxc_keypad *keypad = dev_id;
+	struct mxc_keypad_platform_data *pdata = keypad->pdata;
+	struct input_dev *idev = keypad->input_dev;
+	unsigned short reg_val;
+
+	dev_dbg(&idev->dev, "Handling Interrupt of type %x\n",
+						keypad->irq_type);
+
+	/* Wait till debounce */
+	msleep(pdata->debounce_ms);
+
+	/* Do the scan routine and Keep track for how many time we got
+	 * interrupt that make no change */
+	if (mxc_keypad_scan_matrix(keypad))
+		keypad->irq_since_last_change = 0;
+	else
+		keypad->irq_since_last_change++;
+
+	/* If the key is pressed since too many time, relax the update period */
+	if (keypad->irq_since_last_change > 2)
+		msleep(100);
+
+	/* 10. Clear KPKD and KPKR status bits
+	 *     Set the KPKR sync chain and clear the KPKD sync chain */
+	reg_val = keypad_readw(KPSR);
+	reg_val |= KBD_STAT_KPKD | KBD_STAT_KPKR |
+		   KBD_STAT_KDSC | KBD_STAT_KRSS;
+	keypad_writew(KPSR, reg_val);
+
+	/* Re enable interrupts and clear sync reset bits.
+	 * Next KDI is used for detect multiple pressures. */
+	reg_val = keypad_readw(KPSR);
+	reg_val &= ~(KBD_STAT_KDSC | KBD_STAT_KRSS);
+	keypad_writew(KPSR, reg_val);
+
+	reg_val |= KBD_STAT_KDIE | KBD_STAT_KRIE;
+	if (keypad->irq_type == MXC_IRQ_RELEASE)
+		reg_val &= ~KBD_STAT_KRIE;
+	keypad_writew(KPSR, reg_val);
+
+	return IRQ_HANDLED;
+}
+static irqreturn_t mxc_keypad_irq_handler(int irq, void *dev_id)
+{
+	struct mxc_keypad *keypad = dev_id;
+	unsigned short reg_val;
+
+	/* Disable every interrupt */
+	reg_val = keypad_readw(KPSR);
+	reg_val &= ~(KBD_STAT_KRIE | KBD_STAT_KDIE);
+	keypad_writew(KPSR, reg_val);
+
+	keypad->irq_type = reg_val & KBD_STAT_KPKD ?
+			   MXC_IRQ_DEPRESS : MXC_IRQ_RELEASE;
+
+	return IRQ_WAKE_THREAD;
+}
+
+static void mxc_keypad_config(struct mxc_keypad *keypad)
+{
+	struct mxc_keypad_platform_data *pdata = keypad->pdata;
+	unsigned short reg_val;
+
+	/* Enable number of rows in keypad (KPCR[7:0])
+	 * Configure keypad columns as open-drain (KPCR[15:8])
+	 */
+	reg_val = keypad_readw(KPCR);
+	reg_val |= (1 << pdata->matrix_key_rows) - 1;		/* LSB */
+	reg_val |= ((1 << pdata->matrix_key_cols) - 1) << 8;	/* MSB */
+	keypad_writew(KPCR, reg_val);
+
+	/* Write 0's to KPDR[15:8] (Colums)*/
+	reg_val = keypad_readw(KPDR);
+	reg_val &= 0x00ff;
+	keypad_writew(KPDR, reg_val);
+
+	/* Configure columns as output, rows as input (KDDR[15:0]) */
+	reg_val = keypad_readw(KDDR);
+	reg_val |= 0xff00;
+	reg_val &= 0xff00;
+	keypad_writew(KDDR, reg_val);
+
+	/* Clear Key Depress and Key Release status bit.
+	 * Clear synchronizer chain.
+	 * */
+	reg_val = keypad_readw(KPSR);
+	reg_val |= KBD_STAT_KPKR | KBD_STAT_KPKD |
+		   KBD_STAT_KDSC | KBD_STAT_KRSS;
+	keypad_writew(KPSR, reg_val);
+
+	/* Set the KDIE control bit, and clear the KRIE control bit
+	 * (avoid false release events). */
+	reg_val |= KBD_STAT_KDIE;
+	reg_val &= ~KBD_STAT_KRIE;
+	keypad_writew(KPSR, reg_val);
+}
+
+static void mxc_keypad_inhibit(struct mxc_keypad *keypad)
+{
+	unsigned short reg_val;
+
+	/* Colums as open drain and disable rows */
+	keypad_writew(KPCR, 0xff00);
+
+	/* Clear the KPKD status flag and synchronizer chain.
+	 * Clear KDIE control bit and KRIE control bit.
+	 */
+	reg_val = keypad_readw(KPSR);
+	reg_val |= KBD_STAT_KPKD | KBD_STAT_KDSC;
+	reg_val &= ~(KBD_STAT_KRIE | KBD_STAT_KDIE);
+	keypad_writew(KPSR, reg_val);
+}
+
+static int mxc_keypad_open(struct input_dev *dev)
+{
+	struct mxc_keypad *keypad = input_get_drvdata(dev);
+	struct mxc_keypad_platform_data *pdata = keypad->pdata;
+
+	dev_dbg(&dev->dev, "%s\n", __func__);
+
+	/* Enable unit clock */
+	clk_enable(keypad->clk);
+	mxc_keypad_config(keypad);
+
+	/* Sanity control, not all the rows must be to 0s now. */
+	if ((keypad_readw(KPDR) & ((1 << pdata->matrix_key_rows) - 1)) == 0) {
+		dev_err(&dev->dev, "Too much keys pressed for now. "
+				"Control pins initialisation.\n");
+		goto open_err;
+	}
+
+	return 0;
+
+open_err:
+	mxc_keypad_inhibit(keypad);
+	return -EIO;
+}
+
+static void mxc_keypad_close(struct input_dev *dev)
+{
+	struct mxc_keypad *keypad = input_get_drvdata(dev);
+
+	mxc_keypad_inhibit(keypad);
+
+	/* Disable clock unit */
+	clk_disable(keypad->clk);
+}
+
+static int __devinit mxc_keypad_probe(struct platform_device *pdev)
+{
+	struct mxc_keypad_platform_data *pdata = pdev->dev.platform_data;
+	struct mxc_keypad *keypad;
+	struct input_dev *input_dev;
+	struct resource *res;
+	int irq, error;
+
+	if (pdata == NULL) {
+		dev_err(&pdev->dev, "no platform data defined\n");
+		return -EINVAL;
+	}
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0) {
+		dev_err(&pdev->dev, "failed to get keypad irq\n");
+		return -ENXIO;
+	}
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (res == NULL) {
+		dev_err(&pdev->dev, "failed to get I/O memory\n");
+		return -ENXIO;
+	}
+
+	keypad = kzalloc(sizeof(struct mxc_keypad), GFP_KERNEL);
+	input_dev = input_allocate_device();
+	if (!keypad || !input_dev) {
+		dev_err(&pdev->dev, "failed to allocate memory\n");
+		error = -ENOMEM;
+		goto failed_free;
+	}
+
+	keypad->pdata = pdata;
+	keypad->input_dev = input_dev;
+	keypad->irq = irq;
+
+	res = request_mem_region(res->start, resource_size(res), pdev->name);
+	if (res == NULL) {
+		dev_err(&pdev->dev, "failed to request I/O memory\n");
+		error = -EBUSY;
+		goto failed_free;
+	}
+
+	keypad->mmio_base = ioremap(res->start, resource_size(res));
+	if (keypad->mmio_base == NULL) {
+		dev_err(&pdev->dev, "failed to remap I/O memory\n");
+		error = -ENXIO;
+		goto failed_free_mem;
+	}
+
+	keypad->clk = clk_get(NULL, "kpp");
+	if (IS_ERR(keypad->clk)) {
+		dev_err(&pdev->dev, "failed to get keypad clock\n");
+		error = PTR_ERR(keypad->clk);
+		goto failed_free_io;
+	}
+
+	input_dev->name = pdev->name;
+	input_dev->id.bustype = BUS_HOST;
+	input_dev->open = mxc_keypad_open;
+	input_dev->close = mxc_keypad_close;
+	input_dev->dev.parent = &pdev->dev;
+
+	input_dev->keycode = keypad->keycodes;
+	input_dev->keycodesize = sizeof(keypad->keycodes[0]);
+	input_dev->keycodemax = ARRAY_SIZE(keypad->keycodes);
+
+	input_set_drvdata(input_dev, keypad);
+
+	input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);
+	input_set_capability(input_dev, EV_MSC, MSC_SCAN);
+
+	mxc_keypad_build_keycode(keypad);
+
+	error = request_threaded_irq(irq, mxc_keypad_irq_handler,
+			mxc_keypad_irq_handler_thread, IRQF_DISABLED,
+			pdev->name, keypad);
+	if (error) {
+		dev_err(&pdev->dev, "failed to request IRQ\n");
+		goto failed_put_clk;
+	}
+
+	/* Register the input device */
+	error = input_register_device(input_dev);
+	if (error) {
+		dev_err(&pdev->dev, "failed to register input device\n");
+		goto failed_free_irq;
+	}
+
+	platform_set_drvdata(pdev, keypad);
+	device_init_wakeup(&pdev->dev, 1);
+
+	dev_info(&pdev->dev, "device probed.\n");
+
+	return 0;
+
+failed_free_irq:
+	free_irq(irq, pdev);
+failed_put_clk:
+	clk_put(keypad->clk);
+failed_free_io:
+	iounmap(keypad->mmio_base);
+failed_free_mem:
+	release_mem_region(res->start, resource_size(res));
+failed_free:
+	input_free_device(input_dev);
+	kfree(keypad);
+	return error;
+}
+
+static int __devexit mxc_keypad_remove(struct platform_device *pdev)
+{
+	struct mxc_keypad *keypad = platform_get_drvdata(pdev);
+	struct resource *res;
+
+	free_irq(keypad->irq, keypad);
+	clk_put(keypad->clk);
+
+	input_unregister_device(keypad->input_dev);
+	input_free_device(keypad->input_dev);
+
+	iounmap(keypad->mmio_base);
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	release_mem_region(res->start, resource_size(res));
+
+	platform_set_drvdata(pdev, NULL);
+	kfree(keypad);
+
+	return 0;
+}
+
+static struct platform_driver mxc_keypad_driver = {
+	.driver		= {
+		.name	= "mxc-keypad",
+		.owner	= THIS_MODULE,
+	},
+	.probe		= mxc_keypad_probe,
+	.remove		= __devexit_p(mxc_keypad_remove),
+};
+
+static int __init mxc_keypad_init(void)
+{
+	return platform_driver_register(&mxc_keypad_driver);
+}
+
+static void __exit mxc_keypad_exit(void)
+{
+	platform_driver_unregister(&mxc_keypad_driver);
+}
+
+module_init(mxc_keypad_init);
+module_exit(mxc_keypad_exit);
+
+MODULE_AUTHOR("Alberto Panizzo <maramaopercheseimorto@gmail.com>");
+MODULE_DESCRIPTION("MXC Keypad Port Driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:mxc-keypad");
-- 
1.6.3.3

^ permalink raw reply related	[flat|nested] 16+ messages in thread

* RE: input: add mxc-keypad driver to support the keypad interface present in the mxc application processors family.
  2010-01-08 18:58 ` Alberto Panizzo
  (?)
@ 2010-01-08 20:33   ` H Hartley Sweeten
  -1 siblings, 0 replies; 16+ messages in thread
From: H Hartley Sweeten @ 2010-01-08 20:33 UTC (permalink / raw)
  To: Alberto Panizzo, linux-input
  Cc: linux-kernel, Dmitry Torokhov, linux-arm-kernel-infradead,
	Sascha linux-arm

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #1: Type: text/plain; charset="utf-8", Size: 19725 bytes --]

On Friday, January 08, 2010 11:58 AM, Alberto Panizzo wrote:
> The MXC family of Application Processors is shipped with a
> Keypad Port supported now by this driver.
> 
> The peripheral can control up to an 8x8 matrix key pad where
> all the scanning procedure is done via software. 
> 
> The hardware provide two interrupts: one for a key pressed (KDI)
> and one for all key releases (KRI). There is also a simple circuit for glitch 
> reduction (said for synchronization) made by two series of 3 D-latches
> that stabilize the interrupts sources.
> KDI and KRI are fired only if the respective conditions are maintained for 
> at last 4 keypad-clock cycle.
> 
> Those simple synchronization circuits are used also for multiple
> key pressures: between a KDI and a KRI the driver reset the 
> sync circuit and re-enable the KDI interrupt so after 3
> keypad-clock cycle another KDI is fired making possible to repeat
> the matrix scan operation.
> 
> This algorithm is done via a threaded management of the keypad
> interrupt source and delayed by a proper (and longer) debounce
> interval controlled by the platform initialization.
> If a key is pressed for a lot of time, the driver relaxes a bit
> the timings to not over load the cpu in a long time keypad interaction.
> 
> Key configuration is done via platform initialization in 
> a standard matrix_keypad manner.
> 
> This driver is tested for build in kernel or as a module
> and follow the specification of freescale i.MX 25 27 31 35 51
> especially it is tested in the mx31pdk board.
> 

You might consider using some of the matrix_keypad support already in
the kernel.  Specific's below.

> 
> Signed-off-by: Alberto Panizzo <maramaopercheseimorto@gmail.com>
> ---
>  arch/arm/plat-mxc/include/mach/mxc_keypad.h |   20 ++
>  drivers/input/keyboard/Kconfig              |    9 +
>  drivers/input/keyboard/Makefile             |    1 +
>  drivers/input/keyboard/mxc_keypad.c         |  485 +++++++++++++++++++++++++++
>  4 files changed, 515 insertions(+), 0 deletions(-)
>  create mode 100644 arch/arm/plat-mxc/include/mach/mxc_keypad.h
>  create mode 100644 drivers/input/keyboard/mxc_keypad.c
> 
> diff --git a/arch/arm/plat-mxc/include/mach/mxc_keypad.h b/arch/arm/plat-mxc/include/mach/mxc_keypad.h
> new file mode 100644
> index 0000000..1b05093
> --- /dev/null
> +++ b/arch/arm/plat-mxc/include/mach/mxc_keypad.h
> @@ -0,0 +1,20 @@
> +#ifndef __MACH_MXC_KEYPAD_H
> +#define __MACH_MXC_KEYPAD_H
> +
> +#define MAX_MATRIX_KEY_ROWS	(8)
> +#define MAX_MATRIX_KEY_COLS	(8)
> +#define MATRIX_ROW_SHIFT	(3)
> +
> +struct mxc_keypad_platform_data {
> +
> +	/* code map for the matrix keys */
> +	unsigned int	matrix_key_rows;
> +	unsigned int	matrix_key_cols;
> +	unsigned int	*matrix_key_map;
> +	int		matrix_key_map_size;
> +
> +	/* key debounce interval */
> +	unsigned int	debounce_ms;
> +};

You could use struct matrix_keymap_data to hold your matrix_key_map and
matrix_key_map_size.  Take a look at include/linux/input/matrix_keypad.h.

> +
> +#endif /* __MACH_MXC_KEYPAD_H */
> diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig
> index ee98b1b..eff32fe 100644
> --- a/drivers/input/keyboard/Kconfig
> +++ b/drivers/input/keyboard/Kconfig
> @@ -282,6 +282,15 @@ config KEYBOARD_MAX7359
>  	  To compile this driver as a module, choose M here: the
>  	  module will be called max7359_keypad.
>  
> +config KEYBOARD_MXC
> +	tristate "MXC keypad support"
> +	depends on ARCH_MXC
> +	help
> +	  Enable support for MXC keypad port.
> +
> +	  To compile this driver as a module, choose M here: the
> +	  module will be called mxc_keypad.
> +
>  config KEYBOARD_NEWTON
>  	tristate "Newton keyboard"
>  	select SERIO
> diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile
> index babad5e..9c7133a 100644
> --- a/drivers/input/keyboard/Makefile
> +++ b/drivers/input/keyboard/Makefile
> @@ -23,6 +23,7 @@ obj-$(CONFIG_KEYBOARD_LOCOMO)		+= locomokbd.o
>  obj-$(CONFIG_KEYBOARD_MAPLE)		+= maple_keyb.o
>  obj-$(CONFIG_KEYBOARD_MATRIX)		+= matrix_keypad.o
>  obj-$(CONFIG_KEYBOARD_MAX7359)		+= max7359_keypad.o
> +obj-$(CONFIG_KEYBOARD_MXC)		+= mxc_keypad.o
>  obj-$(CONFIG_KEYBOARD_NEWTON)		+= newtonkbd.o
>  obj-$(CONFIG_KEYBOARD_OMAP)		+= omap-keypad.o
>  obj-$(CONFIG_KEYBOARD_OPENCORES)	+= opencores-kbd.o
> diff --git a/drivers/input/keyboard/mxc_keypad.c b/drivers/input/keyboard/mxc_keypad.c
> new file mode 100644
> index 0000000..4acd311
> --- /dev/null
> +++ b/drivers/input/keyboard/mxc_keypad.c
> @@ -0,0 +1,485 @@
> +/*
> + * linux/drivers/input/keyboard/mxc_keypad.c
> + *
> + * Driver for the MXC keypad port.
> + * Copyright (C) 2009 Alberto Panizzo <maramaopercheseimorto@gmail.com>
> + *  based on Rodolfo Giometti & others work in pxa27x_keypad.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.
> + *
> + * >>Power management need to be implemented<<.
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/delay.h>
> +#include <linux/device.h>
> +#include <linux/err.h>
> +#include <linux/init.h>
> +#include <linux/input.h>
> +#include <linux/input/matrix_keypad.h>

input.h is not needed if you are including matrix_keypad.h

> +#include <linux/interrupt.h>
> +#include <linux/io.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +
> +#include <mach/mxc_keypad.h>
> +
> +/*
> + * Keypad Controller registers (halfword)
> + */
> +#define KPCR		0x00 /* Keypad Control Register */
> +
> +#define KPSR		0x02 /* Keypad Status Register */
> +#define KBD_STAT_KPKD	(0x1 << 0) /* Key Press Interrupt Status bit */
> +#define KBD_STAT_KPKR	(0x1 << 1) /* Key Release Interrupt Status bit */
> +#define KBD_STAT_KDSC	(0x1 << 2) /* Key Depress Synch Chain Status bit */
> +#define KBD_STAT_KRSS	(0x1 << 3) /* Key Release Synch Status bit */
> +#define KBD_STAT_KDIE	(0x1 << 8) /* Key Depress Interrupt Enable Status bit */
> +#define KBD_STAT_KRIE	(0x1 << 9) /* Key Release Interrupt Enable */
> +#define KBD_STAT_KPPEN	(0x1 << 10) /* Keypad Clock Enable */
> +
> +#define KDDR		0x04 /* Keypad Data Direction Register */
> +#define KPDR		0x06 /* Keypad Data Register */
> +
> +#define keypad_readw(off)	__raw_readw(keypad->mmio_base + (off))
> +#define keypad_writew(off, v)	__raw_writew((v), keypad->mmio_base + (off))

Please make the above macros inline functions.
See Documentation/CodingStyle, Chapter 12.

> +
> +#define MAX_MATRIX_KEY_NUM	(MAX_MATRIX_KEY_ROWS * MAX_MATRIX_KEY_COLS)
> +
> +struct mxc_keypad {
> +	struct mxc_keypad_platform_data *pdata;
> +
> +	struct clk *clk;
> +	struct input_dev *input_dev;
> +	void __iomem *mmio_base;
> +
> +	int			irq;
> +
> +#define MXC_IRQ_DEPRESS		1
> +#define MXC_IRQ_RELEASE		2
> +	unsigned int		irq_type;
> +
> +	int 			irq_since_last_change;
> +
> +	unsigned short 		keycodes[MAX_MATRIX_KEY_NUM];
> +
> +	/* state row bits of each column scan */
> +	unsigned short 		matrix_key_state[MAX_MATRIX_KEY_COLS];
> +};
> +
> +static void mxc_keypad_build_keycode(struct mxc_keypad *keypad)
> +{
> +	struct mxc_keypad_platform_data *pdata = keypad->pdata;
> +	struct input_dev *input_dev = keypad->input_dev;
> +	unsigned short keycode;
> +	int i;
> +
> +	for (i = 0; i < pdata->matrix_key_map_size; i++) {
> +		unsigned int key = pdata->matrix_key_map[i];
> +		unsigned int row = KEY_ROW(key);
> +		unsigned int col = KEY_COL(key);
> +		unsigned int scancode = MATRIX_SCAN_CODE(row, col,
> +							 MATRIX_ROW_SHIFT);
> +
> +		keycode = KEY_VAL(key);
> +		keypad->keycodes[scancode] = keycode;
> +		__set_bit(keycode, input_dev->keybit);
> +	}
> +
> +	__clear_bit(KEY_RESERVED, input_dev->keybit);
> +}

You should be able to use matrix_keypad_build_keymap() if you use
struct matrix_keymap_data as mentioned above.

> +
> +/* Return 0 if no changes are detected in matrix */
> +static int mxc_keypad_scan_matrix(struct mxc_keypad *keypad)
> +{
> +	struct mxc_keypad_platform_data *pdata = keypad->pdata;
> +	struct input_dev *input_dev = keypad->input_dev;
> +	int row, col, changed = 0;
> +	unsigned short new_state[MAX_MATRIX_KEY_COLS];
> +	unsigned short reg_val;
> +
> +	memset(new_state, 0, sizeof(new_state));
> +
> +	for (col = 0; col < pdata->matrix_key_cols; col++) {
> +		/* Discharge keypad capacitance:
> +		 * 2. write 1s on column data.
> +		 * 3. configure columns as totem-pole to discharge capacitance.
> +		 * 4. configure columns as open-drain.*/
> +		reg_val = keypad_readw(KPDR);
> +		reg_val |= 0xff00;
> +		keypad_writew(KPDR, reg_val);
> +
> +		reg_val = keypad_readw(KPCR);
> +		reg_val &= ~(((1 << pdata->matrix_key_cols) - 1) << 8);
> +		keypad_writew(KPCR, reg_val);
> +
> +		udelay(2);
> +
> +		reg_val = keypad_readw(KPCR);
> +		reg_val |= ((1 << pdata->matrix_key_cols) - 1) << 8;
> +		keypad_writew(KPCR, reg_val);
> +
> +		/*
> +		 * 5. Write a single column to 0, others to 1.
> +		 * 6. Sample row inputs and save data.
> +		 * 7. Repeat steps 2 - 6 for remaining columns.
> +		 */
> +		reg_val = keypad_readw(KPDR);
> +		reg_val &= ~(1 << (8 + col));
> +		keypad_writew(KPDR, reg_val);
> +
> +		/* Delay added to avoid propagating the 0 from column to row
> +		 * when scanning. */
> +		udelay(5);
> +
> +		/* 1s in state detect a key pressure */
> +		new_state[col] = (~keypad_readw(KPDR)) & 0x00ff;
> +	}
> +
> +	/* Test the state changes */
> +	for (col = 0; col < pdata->matrix_key_cols; col++) {
> +		unsigned short bits_changed;
> +		int code;
> +
> +		bits_changed = keypad->matrix_key_state[col] ^ new_state[col];
> +		if (bits_changed == 0)
> +			continue;
> +
> +		changed = 1;
> +		for (row = 0; row < pdata->matrix_key_rows; row++) {
> +			if ((bits_changed & (1 << row)) == 0)
> +				continue;
> +
> +			code = MATRIX_SCAN_CODE(row, col, MATRIX_ROW_SHIFT);
> +			input_event(input_dev, EV_MSC, MSC_SCAN, code);
> +			input_report_key(input_dev, keypad->keycodes[code],
> +					 new_state[col] & (1 << row));
> +			dev_dbg(&input_dev->dev, "Event code: %d, val: %d",
> +					keypad->keycodes[code],
> +					new_state[col] & (1 << row));
> +		}
> +	}
> +	input_sync(input_dev);
> +	memcpy(keypad->matrix_key_state, new_state, sizeof(new_state));
> +
> +	/* Return in standby mode:
> +	 * 9. write 0s to columns */
> +	reg_val = keypad_readw(KPDR);
> +	reg_val &= 0x00ff;
> +	keypad_writew(KPDR, reg_val);
> +
> +	return changed;
> +}
> +
> +static irqreturn_t mxc_keypad_irq_handler_thread(int irq, void *dev_id)
> +{
> +	struct mxc_keypad *keypad = dev_id;
> +	struct mxc_keypad_platform_data *pdata = keypad->pdata;
> +	struct input_dev *idev = keypad->input_dev;
> +	unsigned short reg_val;
> +
> +	dev_dbg(&idev->dev, "Handling Interrupt of type %x\n",
> +						keypad->irq_type);
> +
> +	/* Wait till debounce */
> +	msleep(pdata->debounce_ms);
> +
> +	/* Do the scan routine and Keep track for how many time we got
> +	 * interrupt that make no change */
> +	if (mxc_keypad_scan_matrix(keypad))
> +		keypad->irq_since_last_change = 0;
> +	else
> +		keypad->irq_since_last_change++;
> +
> +	/* If the key is pressed since too many time, relax the update period */
> +	if (keypad->irq_since_last_change > 2)
> +		msleep(100);
> +
> +	/* 10. Clear KPKD and KPKR status bits
> +	 *     Set the KPKR sync chain and clear the KPKD sync chain */
> +	reg_val = keypad_readw(KPSR);
> +	reg_val |= KBD_STAT_KPKD | KBD_STAT_KPKR |
> +		   KBD_STAT_KDSC | KBD_STAT_KRSS;
> +	keypad_writew(KPSR, reg_val);
> +
> +	/* Re enable interrupts and clear sync reset bits.
> +	 * Next KDI is used for detect multiple pressures. */
> +	reg_val = keypad_readw(KPSR);
> +	reg_val &= ~(KBD_STAT_KDSC | KBD_STAT_KRSS);
> +	keypad_writew(KPSR, reg_val);
> +
> +	reg_val |= KBD_STAT_KDIE | KBD_STAT_KRIE;
> +	if (keypad->irq_type == MXC_IRQ_RELEASE)
> +		reg_val &= ~KBD_STAT_KRIE;
> +	keypad_writew(KPSR, reg_val);
> +
> +	return IRQ_HANDLED;
> +}
> +static irqreturn_t mxc_keypad_irq_handler(int irq, void *dev_id)
> +{
> +	struct mxc_keypad *keypad = dev_id;
> +	unsigned short reg_val;
> +
> +	/* Disable every interrupt */
> +	reg_val = keypad_readw(KPSR);
> +	reg_val &= ~(KBD_STAT_KRIE | KBD_STAT_KDIE);
> +	keypad_writew(KPSR, reg_val);
> +
> +	keypad->irq_type = reg_val & KBD_STAT_KPKD ?
> +			   MXC_IRQ_DEPRESS : MXC_IRQ_RELEASE;
> +
> +	return IRQ_WAKE_THREAD;
> +}
> +
> +static void mxc_keypad_config(struct mxc_keypad *keypad)
> +{
> +	struct mxc_keypad_platform_data *pdata = keypad->pdata;
> +	unsigned short reg_val;
> +
> +	/* Enable number of rows in keypad (KPCR[7:0])
> +	 * Configure keypad columns as open-drain (KPCR[15:8])
> +	 */
> +	reg_val = keypad_readw(KPCR);
> +	reg_val |= (1 << pdata->matrix_key_rows) - 1;		/* LSB */
> +	reg_val |= ((1 << pdata->matrix_key_cols) - 1) << 8;	/* MSB */
> +	keypad_writew(KPCR, reg_val);
> +
> +	/* Write 0's to KPDR[15:8] (Colums)*/
> +	reg_val = keypad_readw(KPDR);
> +	reg_val &= 0x00ff;
> +	keypad_writew(KPDR, reg_val);
> +
> +	/* Configure columns as output, rows as input (KDDR[15:0]) */
> +	reg_val = keypad_readw(KDDR);
> +	reg_val |= 0xff00;
> +	reg_val &= 0xff00;
> +	keypad_writew(KDDR, reg_val);
> +
> +	/* Clear Key Depress and Key Release status bit.
> +	 * Clear synchronizer chain.
> +	 * */
> +	reg_val = keypad_readw(KPSR);
> +	reg_val |= KBD_STAT_KPKR | KBD_STAT_KPKD |
> +		   KBD_STAT_KDSC | KBD_STAT_KRSS;
> +	keypad_writew(KPSR, reg_val);
> +
> +	/* Set the KDIE control bit, and clear the KRIE control bit
> +	 * (avoid false release events). */
> +	reg_val |= KBD_STAT_KDIE;
> +	reg_val &= ~KBD_STAT_KRIE;
> +	keypad_writew(KPSR, reg_val);
> +}
> +
> +static void mxc_keypad_inhibit(struct mxc_keypad *keypad)
> +{
> +	unsigned short reg_val;
> +
> +	/* Colums as open drain and disable rows */
> +	keypad_writew(KPCR, 0xff00);
> +
> +	/* Clear the KPKD status flag and synchronizer chain.
> +	 * Clear KDIE control bit and KRIE control bit.
> +	 */
> +	reg_val = keypad_readw(KPSR);
> +	reg_val |= KBD_STAT_KPKD | KBD_STAT_KDSC;
> +	reg_val &= ~(KBD_STAT_KRIE | KBD_STAT_KDIE);
> +	keypad_writew(KPSR, reg_val);
> +}
> +
> +static int mxc_keypad_open(struct input_dev *dev)
> +{
> +	struct mxc_keypad *keypad = input_get_drvdata(dev);
> +	struct mxc_keypad_platform_data *pdata = keypad->pdata;
> +
> +	dev_dbg(&dev->dev, "%s\n", __func__);
> +
> +	/* Enable unit clock */
> +	clk_enable(keypad->clk);
> +	mxc_keypad_config(keypad);
> +
> +	/* Sanity control, not all the rows must be to 0s now. */
> +	if ((keypad_readw(KPDR) & ((1 << pdata->matrix_key_rows) - 1)) == 0) {
> +		dev_err(&dev->dev, "Too much keys pressed for now. "
> +				"Control pins initialisation.\n");
> +		goto open_err;
> +	}
> +
> +	return 0;
> +
> +open_err:
> +	mxc_keypad_inhibit(keypad);
> +	return -EIO;
> +}
> +
> +static void mxc_keypad_close(struct input_dev *dev)
> +{
> +	struct mxc_keypad *keypad = input_get_drvdata(dev);
> +
> +	mxc_keypad_inhibit(keypad);
> +
> +	/* Disable clock unit */
> +	clk_disable(keypad->clk);
> +}
> +
> +static int __devinit mxc_keypad_probe(struct platform_device *pdev)
> +{
> +	struct mxc_keypad_platform_data *pdata = pdev->dev.platform_data;
> +	struct mxc_keypad *keypad;
> +	struct input_dev *input_dev;
> +	struct resource *res;
> +	int irq, error;
> +
> +	if (pdata == NULL) {
> +		dev_err(&pdev->dev, "no platform data defined\n");
> +		return -EINVAL;
> +	}
> +
> +	irq = platform_get_irq(pdev, 0);
> +	if (irq < 0) {
> +		dev_err(&pdev->dev, "failed to get keypad irq\n");
> +		return -ENXIO;
> +	}
> +
> +	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +	if (res == NULL) {
> +		dev_err(&pdev->dev, "failed to get I/O memory\n");
> +		return -ENXIO;
> +	}
> +
> +	keypad = kzalloc(sizeof(struct mxc_keypad), GFP_KERNEL);
> +	input_dev = input_allocate_device();
> +	if (!keypad || !input_dev) {
> +		dev_err(&pdev->dev, "failed to allocate memory\n");
> +		error = -ENOMEM;
> +		goto failed_free;
> +	}
> +
> +	keypad->pdata = pdata;
> +	keypad->input_dev = input_dev;
> +	keypad->irq = irq;
> +
> +	res = request_mem_region(res->start, resource_size(res), pdev->name);
> +	if (res == NULL) {
> +		dev_err(&pdev->dev, "failed to request I/O memory\n");
> +		error = -EBUSY;
> +		goto failed_free;
> +	}
> +
> +	keypad->mmio_base = ioremap(res->start, resource_size(res));
> +	if (keypad->mmio_base == NULL) {
> +		dev_err(&pdev->dev, "failed to remap I/O memory\n");
> +		error = -ENXIO;
> +		goto failed_free_mem;
> +	}
> +
> +	keypad->clk = clk_get(NULL, "kpp");
> +	if (IS_ERR(keypad->clk)) {
> +		dev_err(&pdev->dev, "failed to get keypad clock\n");
> +		error = PTR_ERR(keypad->clk);
> +		goto failed_free_io;
> +	}
> +
> +	input_dev->name = pdev->name;
> +	input_dev->id.bustype = BUS_HOST;
> +	input_dev->open = mxc_keypad_open;
> +	input_dev->close = mxc_keypad_close;
> +	input_dev->dev.parent = &pdev->dev;
> +
> +	input_dev->keycode = keypad->keycodes;
> +	input_dev->keycodesize = sizeof(keypad->keycodes[0]);
> +	input_dev->keycodemax = ARRAY_SIZE(keypad->keycodes);
> +
> +	input_set_drvdata(input_dev, keypad);
> +
> +	input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);
> +	input_set_capability(input_dev, EV_MSC, MSC_SCAN);
> +
> +	mxc_keypad_build_keycode(keypad);
> +
> +	error = request_threaded_irq(irq, mxc_keypad_irq_handler,
> +			mxc_keypad_irq_handler_thread, IRQF_DISABLED,
> +			pdev->name, keypad);
> +	if (error) {
> +		dev_err(&pdev->dev, "failed to request IRQ\n");
> +		goto failed_put_clk;
> +	}
> +
> +	/* Register the input device */
> +	error = input_register_device(input_dev);
> +	if (error) {
> +		dev_err(&pdev->dev, "failed to register input device\n");
> +		goto failed_free_irq;
> +	}
> +
> +	platform_set_drvdata(pdev, keypad);
> +	device_init_wakeup(&pdev->dev, 1);
> +
> +	dev_info(&pdev->dev, "device probed.\n");
> +
> +	return 0;
> +
> +failed_free_irq:
> +	free_irq(irq, pdev);
> +failed_put_clk:
> +	clk_put(keypad->clk);
> +failed_free_io:
> +	iounmap(keypad->mmio_base);
> +failed_free_mem:
> +	release_mem_region(res->start, resource_size(res));
> +failed_free:
> +	input_free_device(input_dev);
> +	kfree(keypad);
> +	return error;
> +}
> +
> +static int __devexit mxc_keypad_remove(struct platform_device *pdev)
> +{
> +	struct mxc_keypad *keypad = platform_get_drvdata(pdev);
> +	struct resource *res;
> +
> +	free_irq(keypad->irq, keypad);

You might want to move the platform_set_drvdata(pdev, NULL) to prevent
any race conditions.

> +	clk_put(keypad->clk);
> +
> +	input_unregister_device(keypad->input_dev);
> +	input_free_device(keypad->input_dev);

input_free_device is unnecessary for already registered devices.

> +
> +	iounmap(keypad->mmio_base);
> +
> +	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +	release_mem_region(res->start, resource_size(res));
> +
> +	platform_set_drvdata(pdev, NULL);
> +	kfree(keypad);
> +
> +	return 0;
> +}
> +
> +static struct platform_driver mxc_keypad_driver = {
> +	.driver		= {
> +		.name	= "mxc-keypad",
> +		.owner	= THIS_MODULE,
> +	},
> +	.probe		= mxc_keypad_probe,
> +	.remove		= __devexit_p(mxc_keypad_remove),
> +};
> +
> +static int __init mxc_keypad_init(void)
> +{
> +	return platform_driver_register(&mxc_keypad_driver);
> +}
> +
> +static void __exit mxc_keypad_exit(void)
> +{
> +	platform_driver_unregister(&mxc_keypad_driver);
> +}
> +
> +module_init(mxc_keypad_init);
> +module_exit(mxc_keypad_exit);
> +
> +MODULE_AUTHOR("Alberto Panizzo <maramaopercheseimorto@gmail.com>");
> +MODULE_DESCRIPTION("MXC Keypad Port Driver");
> +MODULE_LICENSE("GPL v2");
> +MODULE_ALIAS("platform:mxc-keypad");
ÿôèº{.nÇ+‰·Ÿ®‰­†+%ŠËÿ±éݶ\x17¥Šwÿº{.nÇ+‰·¥Š{±þG«éÿŠ{ayº\x1dʇڙë,j\a­¢f£¢·hšïêÿ‘êçz_è®\x03(­éšŽŠÝ¢j"ú\x1a¶^[m§ÿÿ¾\a«þG«éÿ¢¸?™¨è­Ú&£ø§~á¶iO•æ¬z·švØ^\x14\x04\x1a¶^[m§ÿÿÃ\fÿ¶ìÿ¢¸?–I¥

^ permalink raw reply	[flat|nested] 16+ messages in thread

* RE: input: add mxc-keypad driver to support the keypad interface present in the mxc application processors family.
@ 2010-01-08 20:33   ` H Hartley Sweeten
  0 siblings, 0 replies; 16+ messages in thread
From: H Hartley Sweeten @ 2010-01-08 20:33 UTC (permalink / raw)
  To: Alberto Panizzo, linux-input
  Cc: linux-kernel, Dmitry Torokhov, linux-arm-kernel-infradead,
	Sascha linux-arm

On Friday, January 08, 2010 11:58 AM, Alberto Panizzo wrote:
> The MXC family of Application Processors is shipped with a
> Keypad Port supported now by this driver.
> 
> The peripheral can control up to an 8x8 matrix key pad where
> all the scanning procedure is done via software. 
> 
> The hardware provide two interrupts: one for a key pressed (KDI)
> and one for all key releases (KRI). There is also a simple circuit for glitch 
> reduction (said for synchronization) made by two series of 3 D-latches
> that stabilize the interrupts sources.
> KDI and KRI are fired only if the respective conditions are maintained for 
> at last 4 keypad-clock cycle.
> 
> Those simple synchronization circuits are used also for multiple
> key pressures: between a KDI and a KRI the driver reset the 
> sync circuit and re-enable the KDI interrupt so after 3
> keypad-clock cycle another KDI is fired making possible to repeat
> the matrix scan operation.
> 
> This algorithm is done via a threaded management of the keypad
> interrupt source and delayed by a proper (and longer) debounce
> interval controlled by the platform initialization.
> If a key is pressed for a lot of time, the driver relaxes a bit
> the timings to not over load the cpu in a long time keypad interaction.
> 
> Key configuration is done via platform initialization in 
> a standard matrix_keypad manner.
> 
> This driver is tested for build in kernel or as a module
> and follow the specification of freescale i.MX 25 27 31 35 51
> especially it is tested in the mx31pdk board.
> 

You might consider using some of the matrix_keypad support already in
the kernel.  Specific's below.

> 
> Signed-off-by: Alberto Panizzo <maramaopercheseimorto@gmail.com>
> ---
>  arch/arm/plat-mxc/include/mach/mxc_keypad.h |   20 ++
>  drivers/input/keyboard/Kconfig              |    9 +
>  drivers/input/keyboard/Makefile             |    1 +
>  drivers/input/keyboard/mxc_keypad.c         |  485 +++++++++++++++++++++++++++
>  4 files changed, 515 insertions(+), 0 deletions(-)
>  create mode 100644 arch/arm/plat-mxc/include/mach/mxc_keypad.h
>  create mode 100644 drivers/input/keyboard/mxc_keypad.c
> 
> diff --git a/arch/arm/plat-mxc/include/mach/mxc_keypad.h b/arch/arm/plat-mxc/include/mach/mxc_keypad.h
> new file mode 100644
> index 0000000..1b05093
> --- /dev/null
> +++ b/arch/arm/plat-mxc/include/mach/mxc_keypad.h
> @@ -0,0 +1,20 @@
> +#ifndef __MACH_MXC_KEYPAD_H
> +#define __MACH_MXC_KEYPAD_H
> +
> +#define MAX_MATRIX_KEY_ROWS	(8)
> +#define MAX_MATRIX_KEY_COLS	(8)
> +#define MATRIX_ROW_SHIFT	(3)
> +
> +struct mxc_keypad_platform_data {
> +
> +	/* code map for the matrix keys */
> +	unsigned int	matrix_key_rows;
> +	unsigned int	matrix_key_cols;
> +	unsigned int	*matrix_key_map;
> +	int		matrix_key_map_size;
> +
> +	/* key debounce interval */
> +	unsigned int	debounce_ms;
> +};

You could use struct matrix_keymap_data to hold your matrix_key_map and
matrix_key_map_size.  Take a look at include/linux/input/matrix_keypad.h.

> +
> +#endif /* __MACH_MXC_KEYPAD_H */
> diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig
> index ee98b1b..eff32fe 100644
> --- a/drivers/input/keyboard/Kconfig
> +++ b/drivers/input/keyboard/Kconfig
> @@ -282,6 +282,15 @@ config KEYBOARD_MAX7359
>  	  To compile this driver as a module, choose M here: the
>  	  module will be called max7359_keypad.
>  
> +config KEYBOARD_MXC
> +	tristate "MXC keypad support"
> +	depends on ARCH_MXC
> +	help
> +	  Enable support for MXC keypad port.
> +
> +	  To compile this driver as a module, choose M here: the
> +	  module will be called mxc_keypad.
> +
>  config KEYBOARD_NEWTON
>  	tristate "Newton keyboard"
>  	select SERIO
> diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile
> index babad5e..9c7133a 100644
> --- a/drivers/input/keyboard/Makefile
> +++ b/drivers/input/keyboard/Makefile
> @@ -23,6 +23,7 @@ obj-$(CONFIG_KEYBOARD_LOCOMO)		+= locomokbd.o
>  obj-$(CONFIG_KEYBOARD_MAPLE)		+= maple_keyb.o
>  obj-$(CONFIG_KEYBOARD_MATRIX)		+= matrix_keypad.o
>  obj-$(CONFIG_KEYBOARD_MAX7359)		+= max7359_keypad.o
> +obj-$(CONFIG_KEYBOARD_MXC)		+= mxc_keypad.o
>  obj-$(CONFIG_KEYBOARD_NEWTON)		+= newtonkbd.o
>  obj-$(CONFIG_KEYBOARD_OMAP)		+= omap-keypad.o
>  obj-$(CONFIG_KEYBOARD_OPENCORES)	+= opencores-kbd.o
> diff --git a/drivers/input/keyboard/mxc_keypad.c b/drivers/input/keyboard/mxc_keypad.c
> new file mode 100644
> index 0000000..4acd311
> --- /dev/null
> +++ b/drivers/input/keyboard/mxc_keypad.c
> @@ -0,0 +1,485 @@
> +/*
> + * linux/drivers/input/keyboard/mxc_keypad.c
> + *
> + * Driver for the MXC keypad port.
> + * Copyright (C) 2009 Alberto Panizzo <maramaopercheseimorto@gmail.com>
> + *  based on Rodolfo Giometti & others work in pxa27x_keypad.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.
> + *
> + * >>Power management need to be implemented<<.
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/delay.h>
> +#include <linux/device.h>
> +#include <linux/err.h>
> +#include <linux/init.h>
> +#include <linux/input.h>
> +#include <linux/input/matrix_keypad.h>

input.h is not needed if you are including matrix_keypad.h

> +#include <linux/interrupt.h>
> +#include <linux/io.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +
> +#include <mach/mxc_keypad.h>
> +
> +/*
> + * Keypad Controller registers (halfword)
> + */
> +#define KPCR		0x00 /* Keypad Control Register */
> +
> +#define KPSR		0x02 /* Keypad Status Register */
> +#define KBD_STAT_KPKD	(0x1 << 0) /* Key Press Interrupt Status bit */
> +#define KBD_STAT_KPKR	(0x1 << 1) /* Key Release Interrupt Status bit */
> +#define KBD_STAT_KDSC	(0x1 << 2) /* Key Depress Synch Chain Status bit */
> +#define KBD_STAT_KRSS	(0x1 << 3) /* Key Release Synch Status bit */
> +#define KBD_STAT_KDIE	(0x1 << 8) /* Key Depress Interrupt Enable Status bit */
> +#define KBD_STAT_KRIE	(0x1 << 9) /* Key Release Interrupt Enable */
> +#define KBD_STAT_KPPEN	(0x1 << 10) /* Keypad Clock Enable */
> +
> +#define KDDR		0x04 /* Keypad Data Direction Register */
> +#define KPDR		0x06 /* Keypad Data Register */
> +
> +#define keypad_readw(off)	__raw_readw(keypad->mmio_base + (off))
> +#define keypad_writew(off, v)	__raw_writew((v), keypad->mmio_base + (off))

Please make the above macros inline functions.
See Documentation/CodingStyle, Chapter 12.

> +
> +#define MAX_MATRIX_KEY_NUM	(MAX_MATRIX_KEY_ROWS * MAX_MATRIX_KEY_COLS)
> +
> +struct mxc_keypad {
> +	struct mxc_keypad_platform_data *pdata;
> +
> +	struct clk *clk;
> +	struct input_dev *input_dev;
> +	void __iomem *mmio_base;
> +
> +	int			irq;
> +
> +#define MXC_IRQ_DEPRESS		1
> +#define MXC_IRQ_RELEASE		2
> +	unsigned int		irq_type;
> +
> +	int 			irq_since_last_change;
> +
> +	unsigned short 		keycodes[MAX_MATRIX_KEY_NUM];
> +
> +	/* state row bits of each column scan */
> +	unsigned short 		matrix_key_state[MAX_MATRIX_KEY_COLS];
> +};
> +
> +static void mxc_keypad_build_keycode(struct mxc_keypad *keypad)
> +{
> +	struct mxc_keypad_platform_data *pdata = keypad->pdata;
> +	struct input_dev *input_dev = keypad->input_dev;
> +	unsigned short keycode;
> +	int i;
> +
> +	for (i = 0; i < pdata->matrix_key_map_size; i++) {
> +		unsigned int key = pdata->matrix_key_map[i];
> +		unsigned int row = KEY_ROW(key);
> +		unsigned int col = KEY_COL(key);
> +		unsigned int scancode = MATRIX_SCAN_CODE(row, col,
> +							 MATRIX_ROW_SHIFT);
> +
> +		keycode = KEY_VAL(key);
> +		keypad->keycodes[scancode] = keycode;
> +		__set_bit(keycode, input_dev->keybit);
> +	}
> +
> +	__clear_bit(KEY_RESERVED, input_dev->keybit);
> +}

You should be able to use matrix_keypad_build_keymap() if you use
struct matrix_keymap_data as mentioned above.

> +
> +/* Return 0 if no changes are detected in matrix */
> +static int mxc_keypad_scan_matrix(struct mxc_keypad *keypad)
> +{
> +	struct mxc_keypad_platform_data *pdata = keypad->pdata;
> +	struct input_dev *input_dev = keypad->input_dev;
> +	int row, col, changed = 0;
> +	unsigned short new_state[MAX_MATRIX_KEY_COLS];
> +	unsigned short reg_val;
> +
> +	memset(new_state, 0, sizeof(new_state));
> +
> +	for (col = 0; col < pdata->matrix_key_cols; col++) {
> +		/* Discharge keypad capacitance:
> +		 * 2. write 1s on column data.
> +		 * 3. configure columns as totem-pole to discharge capacitance.
> +		 * 4. configure columns as open-drain.*/
> +		reg_val = keypad_readw(KPDR);
> +		reg_val |= 0xff00;
> +		keypad_writew(KPDR, reg_val);
> +
> +		reg_val = keypad_readw(KPCR);
> +		reg_val &= ~(((1 << pdata->matrix_key_cols) - 1) << 8);
> +		keypad_writew(KPCR, reg_val);
> +
> +		udelay(2);
> +
> +		reg_val = keypad_readw(KPCR);
> +		reg_val |= ((1 << pdata->matrix_key_cols) - 1) << 8;
> +		keypad_writew(KPCR, reg_val);
> +
> +		/*
> +		 * 5. Write a single column to 0, others to 1.
> +		 * 6. Sample row inputs and save data.
> +		 * 7. Repeat steps 2 - 6 for remaining columns.
> +		 */
> +		reg_val = keypad_readw(KPDR);
> +		reg_val &= ~(1 << (8 + col));
> +		keypad_writew(KPDR, reg_val);
> +
> +		/* Delay added to avoid propagating the 0 from column to row
> +		 * when scanning. */
> +		udelay(5);
> +
> +		/* 1s in state detect a key pressure */
> +		new_state[col] = (~keypad_readw(KPDR)) & 0x00ff;
> +	}
> +
> +	/* Test the state changes */
> +	for (col = 0; col < pdata->matrix_key_cols; col++) {
> +		unsigned short bits_changed;
> +		int code;
> +
> +		bits_changed = keypad->matrix_key_state[col] ^ new_state[col];
> +		if (bits_changed == 0)
> +			continue;
> +
> +		changed = 1;
> +		for (row = 0; row < pdata->matrix_key_rows; row++) {
> +			if ((bits_changed & (1 << row)) == 0)
> +				continue;
> +
> +			code = MATRIX_SCAN_CODE(row, col, MATRIX_ROW_SHIFT);
> +			input_event(input_dev, EV_MSC, MSC_SCAN, code);
> +			input_report_key(input_dev, keypad->keycodes[code],
> +					 new_state[col] & (1 << row));
> +			dev_dbg(&input_dev->dev, "Event code: %d, val: %d",
> +					keypad->keycodes[code],
> +					new_state[col] & (1 << row));
> +		}
> +	}
> +	input_sync(input_dev);
> +	memcpy(keypad->matrix_key_state, new_state, sizeof(new_state));
> +
> +	/* Return in standby mode:
> +	 * 9. write 0s to columns */
> +	reg_val = keypad_readw(KPDR);
> +	reg_val &= 0x00ff;
> +	keypad_writew(KPDR, reg_val);
> +
> +	return changed;
> +}
> +
> +static irqreturn_t mxc_keypad_irq_handler_thread(int irq, void *dev_id)
> +{
> +	struct mxc_keypad *keypad = dev_id;
> +	struct mxc_keypad_platform_data *pdata = keypad->pdata;
> +	struct input_dev *idev = keypad->input_dev;
> +	unsigned short reg_val;
> +
> +	dev_dbg(&idev->dev, "Handling Interrupt of type %x\n",
> +						keypad->irq_type);
> +
> +	/* Wait till debounce */
> +	msleep(pdata->debounce_ms);
> +
> +	/* Do the scan routine and Keep track for how many time we got
> +	 * interrupt that make no change */
> +	if (mxc_keypad_scan_matrix(keypad))
> +		keypad->irq_since_last_change = 0;
> +	else
> +		keypad->irq_since_last_change++;
> +
> +	/* If the key is pressed since too many time, relax the update period */
> +	if (keypad->irq_since_last_change > 2)
> +		msleep(100);
> +
> +	/* 10. Clear KPKD and KPKR status bits
> +	 *     Set the KPKR sync chain and clear the KPKD sync chain */
> +	reg_val = keypad_readw(KPSR);
> +	reg_val |= KBD_STAT_KPKD | KBD_STAT_KPKR |
> +		   KBD_STAT_KDSC | KBD_STAT_KRSS;
> +	keypad_writew(KPSR, reg_val);
> +
> +	/* Re enable interrupts and clear sync reset bits.
> +	 * Next KDI is used for detect multiple pressures. */
> +	reg_val = keypad_readw(KPSR);
> +	reg_val &= ~(KBD_STAT_KDSC | KBD_STAT_KRSS);
> +	keypad_writew(KPSR, reg_val);
> +
> +	reg_val |= KBD_STAT_KDIE | KBD_STAT_KRIE;
> +	if (keypad->irq_type == MXC_IRQ_RELEASE)
> +		reg_val &= ~KBD_STAT_KRIE;
> +	keypad_writew(KPSR, reg_val);
> +
> +	return IRQ_HANDLED;
> +}
> +static irqreturn_t mxc_keypad_irq_handler(int irq, void *dev_id)
> +{
> +	struct mxc_keypad *keypad = dev_id;
> +	unsigned short reg_val;
> +
> +	/* Disable every interrupt */
> +	reg_val = keypad_readw(KPSR);
> +	reg_val &= ~(KBD_STAT_KRIE | KBD_STAT_KDIE);
> +	keypad_writew(KPSR, reg_val);
> +
> +	keypad->irq_type = reg_val & KBD_STAT_KPKD ?
> +			   MXC_IRQ_DEPRESS : MXC_IRQ_RELEASE;
> +
> +	return IRQ_WAKE_THREAD;
> +}
> +
> +static void mxc_keypad_config(struct mxc_keypad *keypad)
> +{
> +	struct mxc_keypad_platform_data *pdata = keypad->pdata;
> +	unsigned short reg_val;
> +
> +	/* Enable number of rows in keypad (KPCR[7:0])
> +	 * Configure keypad columns as open-drain (KPCR[15:8])
> +	 */
> +	reg_val = keypad_readw(KPCR);
> +	reg_val |= (1 << pdata->matrix_key_rows) - 1;		/* LSB */
> +	reg_val |= ((1 << pdata->matrix_key_cols) - 1) << 8;	/* MSB */
> +	keypad_writew(KPCR, reg_val);
> +
> +	/* Write 0's to KPDR[15:8] (Colums)*/
> +	reg_val = keypad_readw(KPDR);
> +	reg_val &= 0x00ff;
> +	keypad_writew(KPDR, reg_val);
> +
> +	/* Configure columns as output, rows as input (KDDR[15:0]) */
> +	reg_val = keypad_readw(KDDR);
> +	reg_val |= 0xff00;
> +	reg_val &= 0xff00;
> +	keypad_writew(KDDR, reg_val);
> +
> +	/* Clear Key Depress and Key Release status bit.
> +	 * Clear synchronizer chain.
> +	 * */
> +	reg_val = keypad_readw(KPSR);
> +	reg_val |= KBD_STAT_KPKR | KBD_STAT_KPKD |
> +		   KBD_STAT_KDSC | KBD_STAT_KRSS;
> +	keypad_writew(KPSR, reg_val);
> +
> +	/* Set the KDIE control bit, and clear the KRIE control bit
> +	 * (avoid false release events). */
> +	reg_val |= KBD_STAT_KDIE;
> +	reg_val &= ~KBD_STAT_KRIE;
> +	keypad_writew(KPSR, reg_val);
> +}
> +
> +static void mxc_keypad_inhibit(struct mxc_keypad *keypad)
> +{
> +	unsigned short reg_val;
> +
> +	/* Colums as open drain and disable rows */
> +	keypad_writew(KPCR, 0xff00);
> +
> +	/* Clear the KPKD status flag and synchronizer chain.
> +	 * Clear KDIE control bit and KRIE control bit.
> +	 */
> +	reg_val = keypad_readw(KPSR);
> +	reg_val |= KBD_STAT_KPKD | KBD_STAT_KDSC;
> +	reg_val &= ~(KBD_STAT_KRIE | KBD_STAT_KDIE);
> +	keypad_writew(KPSR, reg_val);
> +}
> +
> +static int mxc_keypad_open(struct input_dev *dev)
> +{
> +	struct mxc_keypad *keypad = input_get_drvdata(dev);
> +	struct mxc_keypad_platform_data *pdata = keypad->pdata;
> +
> +	dev_dbg(&dev->dev, "%s\n", __func__);
> +
> +	/* Enable unit clock */
> +	clk_enable(keypad->clk);
> +	mxc_keypad_config(keypad);
> +
> +	/* Sanity control, not all the rows must be to 0s now. */
> +	if ((keypad_readw(KPDR) & ((1 << pdata->matrix_key_rows) - 1)) == 0) {
> +		dev_err(&dev->dev, "Too much keys pressed for now. "
> +				"Control pins initialisation.\n");
> +		goto open_err;
> +	}
> +
> +	return 0;
> +
> +open_err:
> +	mxc_keypad_inhibit(keypad);
> +	return -EIO;
> +}
> +
> +static void mxc_keypad_close(struct input_dev *dev)
> +{
> +	struct mxc_keypad *keypad = input_get_drvdata(dev);
> +
> +	mxc_keypad_inhibit(keypad);
> +
> +	/* Disable clock unit */
> +	clk_disable(keypad->clk);
> +}
> +
> +static int __devinit mxc_keypad_probe(struct platform_device *pdev)
> +{
> +	struct mxc_keypad_platform_data *pdata = pdev->dev.platform_data;
> +	struct mxc_keypad *keypad;
> +	struct input_dev *input_dev;
> +	struct resource *res;
> +	int irq, error;
> +
> +	if (pdata == NULL) {
> +		dev_err(&pdev->dev, "no platform data defined\n");
> +		return -EINVAL;
> +	}
> +
> +	irq = platform_get_irq(pdev, 0);
> +	if (irq < 0) {
> +		dev_err(&pdev->dev, "failed to get keypad irq\n");
> +		return -ENXIO;
> +	}
> +
> +	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +	if (res == NULL) {
> +		dev_err(&pdev->dev, "failed to get I/O memory\n");
> +		return -ENXIO;
> +	}
> +
> +	keypad = kzalloc(sizeof(struct mxc_keypad), GFP_KERNEL);
> +	input_dev = input_allocate_device();
> +	if (!keypad || !input_dev) {
> +		dev_err(&pdev->dev, "failed to allocate memory\n");
> +		error = -ENOMEM;
> +		goto failed_free;
> +	}
> +
> +	keypad->pdata = pdata;
> +	keypad->input_dev = input_dev;
> +	keypad->irq = irq;
> +
> +	res = request_mem_region(res->start, resource_size(res), pdev->name);
> +	if (res == NULL) {
> +		dev_err(&pdev->dev, "failed to request I/O memory\n");
> +		error = -EBUSY;
> +		goto failed_free;
> +	}
> +
> +	keypad->mmio_base = ioremap(res->start, resource_size(res));
> +	if (keypad->mmio_base == NULL) {
> +		dev_err(&pdev->dev, "failed to remap I/O memory\n");
> +		error = -ENXIO;
> +		goto failed_free_mem;
> +	}
> +
> +	keypad->clk = clk_get(NULL, "kpp");
> +	if (IS_ERR(keypad->clk)) {
> +		dev_err(&pdev->dev, "failed to get keypad clock\n");
> +		error = PTR_ERR(keypad->clk);
> +		goto failed_free_io;
> +	}
> +
> +	input_dev->name = pdev->name;
> +	input_dev->id.bustype = BUS_HOST;
> +	input_dev->open = mxc_keypad_open;
> +	input_dev->close = mxc_keypad_close;
> +	input_dev->dev.parent = &pdev->dev;
> +
> +	input_dev->keycode = keypad->keycodes;
> +	input_dev->keycodesize = sizeof(keypad->keycodes[0]);
> +	input_dev->keycodemax = ARRAY_SIZE(keypad->keycodes);
> +
> +	input_set_drvdata(input_dev, keypad);
> +
> +	input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);
> +	input_set_capability(input_dev, EV_MSC, MSC_SCAN);
> +
> +	mxc_keypad_build_keycode(keypad);
> +
> +	error = request_threaded_irq(irq, mxc_keypad_irq_handler,
> +			mxc_keypad_irq_handler_thread, IRQF_DISABLED,
> +			pdev->name, keypad);
> +	if (error) {
> +		dev_err(&pdev->dev, "failed to request IRQ\n");
> +		goto failed_put_clk;
> +	}
> +
> +	/* Register the input device */
> +	error = input_register_device(input_dev);
> +	if (error) {
> +		dev_err(&pdev->dev, "failed to register input device\n");
> +		goto failed_free_irq;
> +	}
> +
> +	platform_set_drvdata(pdev, keypad);
> +	device_init_wakeup(&pdev->dev, 1);
> +
> +	dev_info(&pdev->dev, "device probed.\n");
> +
> +	return 0;
> +
> +failed_free_irq:
> +	free_irq(irq, pdev);
> +failed_put_clk:
> +	clk_put(keypad->clk);
> +failed_free_io:
> +	iounmap(keypad->mmio_base);
> +failed_free_mem:
> +	release_mem_region(res->start, resource_size(res));
> +failed_free:
> +	input_free_device(input_dev);
> +	kfree(keypad);
> +	return error;
> +}
> +
> +static int __devexit mxc_keypad_remove(struct platform_device *pdev)
> +{
> +	struct mxc_keypad *keypad = platform_get_drvdata(pdev);
> +	struct resource *res;
> +
> +	free_irq(keypad->irq, keypad);

You might want to move the platform_set_drvdata(pdev, NULL) to prevent
any race conditions.

> +	clk_put(keypad->clk);
> +
> +	input_unregister_device(keypad->input_dev);
> +	input_free_device(keypad->input_dev);

input_free_device is unnecessary for already registered devices.

> +
> +	iounmap(keypad->mmio_base);
> +
> +	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +	release_mem_region(res->start, resource_size(res));
> +
> +	platform_set_drvdata(pdev, NULL);
> +	kfree(keypad);
> +
> +	return 0;
> +}
> +
> +static struct platform_driver mxc_keypad_driver = {
> +	.driver		= {
> +		.name	= "mxc-keypad",
> +		.owner	= THIS_MODULE,
> +	},
> +	.probe		= mxc_keypad_probe,
> +	.remove		= __devexit_p(mxc_keypad_remove),
> +};
> +
> +static int __init mxc_keypad_init(void)
> +{
> +	return platform_driver_register(&mxc_keypad_driver);
> +}
> +
> +static void __exit mxc_keypad_exit(void)
> +{
> +	platform_driver_unregister(&mxc_keypad_driver);
> +}
> +
> +module_init(mxc_keypad_init);
> +module_exit(mxc_keypad_exit);
> +
> +MODULE_AUTHOR("Alberto Panizzo <maramaopercheseimorto@gmail.com>");
> +MODULE_DESCRIPTION("MXC Keypad Port Driver");
> +MODULE_LICENSE("GPL v2");
> +MODULE_ALIAS("platform:mxc-keypad");

^ permalink raw reply	[flat|nested] 16+ messages in thread

* input: add mxc-keypad driver to support the keypad interface present in the mxc application processors family.
@ 2010-01-08 20:33   ` H Hartley Sweeten
  0 siblings, 0 replies; 16+ messages in thread
From: H Hartley Sweeten @ 2010-01-08 20:33 UTC (permalink / raw)
  To: linux-arm-kernel

On Friday, January 08, 2010 11:58 AM, Alberto Panizzo wrote:
> The MXC family of Application Processors is shipped with a
> Keypad Port supported now by this driver.
> 
> The peripheral can control up to an 8x8 matrix key pad where
> all the scanning procedure is done via software. 
> 
> The hardware provide two interrupts: one for a key pressed (KDI)
> and one for all key releases (KRI). There is also a simple circuit for glitch 
> reduction (said for synchronization) made by two series of 3 D-latches
> that stabilize the interrupts sources.
> KDI and KRI are fired only if the respective conditions are maintained for 
> at last 4 keypad-clock cycle.
> 
> Those simple synchronization circuits are used also for multiple
> key pressures: between a KDI and a KRI the driver reset the 
> sync circuit and re-enable the KDI interrupt so after 3
> keypad-clock cycle another KDI is fired making possible to repeat
> the matrix scan operation.
> 
> This algorithm is done via a threaded management of the keypad
> interrupt source and delayed by a proper (and longer) debounce
> interval controlled by the platform initialization.
> If a key is pressed for a lot of time, the driver relaxes a bit
> the timings to not over load the cpu in a long time keypad interaction.
> 
> Key configuration is done via platform initialization in 
> a standard matrix_keypad manner.
> 
> This driver is tested for build in kernel or as a module
> and follow the specification of freescale i.MX 25 27 31 35 51
> especially it is tested in the mx31pdk board.
> 

You might consider using some of the matrix_keypad support already in
the kernel.  Specific's below.

> 
> Signed-off-by: Alberto Panizzo <maramaopercheseimorto@gmail.com>
> ---
>  arch/arm/plat-mxc/include/mach/mxc_keypad.h |   20 ++
>  drivers/input/keyboard/Kconfig              |    9 +
>  drivers/input/keyboard/Makefile             |    1 +
>  drivers/input/keyboard/mxc_keypad.c         |  485 +++++++++++++++++++++++++++
>  4 files changed, 515 insertions(+), 0 deletions(-)
>  create mode 100644 arch/arm/plat-mxc/include/mach/mxc_keypad.h
>  create mode 100644 drivers/input/keyboard/mxc_keypad.c
> 
> diff --git a/arch/arm/plat-mxc/include/mach/mxc_keypad.h b/arch/arm/plat-mxc/include/mach/mxc_keypad.h
> new file mode 100644
> index 0000000..1b05093
> --- /dev/null
> +++ b/arch/arm/plat-mxc/include/mach/mxc_keypad.h
> @@ -0,0 +1,20 @@
> +#ifndef __MACH_MXC_KEYPAD_H
> +#define __MACH_MXC_KEYPAD_H
> +
> +#define MAX_MATRIX_KEY_ROWS	(8)
> +#define MAX_MATRIX_KEY_COLS	(8)
> +#define MATRIX_ROW_SHIFT	(3)
> +
> +struct mxc_keypad_platform_data {
> +
> +	/* code map for the matrix keys */
> +	unsigned int	matrix_key_rows;
> +	unsigned int	matrix_key_cols;
> +	unsigned int	*matrix_key_map;
> +	int		matrix_key_map_size;
> +
> +	/* key debounce interval */
> +	unsigned int	debounce_ms;
> +};

You could use struct matrix_keymap_data to hold your matrix_key_map and
matrix_key_map_size.  Take a look at include/linux/input/matrix_keypad.h.

> +
> +#endif /* __MACH_MXC_KEYPAD_H */
> diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig
> index ee98b1b..eff32fe 100644
> --- a/drivers/input/keyboard/Kconfig
> +++ b/drivers/input/keyboard/Kconfig
> @@ -282,6 +282,15 @@ config KEYBOARD_MAX7359
>  	  To compile this driver as a module, choose M here: the
>  	  module will be called max7359_keypad.
>  
> +config KEYBOARD_MXC
> +	tristate "MXC keypad support"
> +	depends on ARCH_MXC
> +	help
> +	  Enable support for MXC keypad port.
> +
> +	  To compile this driver as a module, choose M here: the
> +	  module will be called mxc_keypad.
> +
>  config KEYBOARD_NEWTON
>  	tristate "Newton keyboard"
>  	select SERIO
> diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile
> index babad5e..9c7133a 100644
> --- a/drivers/input/keyboard/Makefile
> +++ b/drivers/input/keyboard/Makefile
> @@ -23,6 +23,7 @@ obj-$(CONFIG_KEYBOARD_LOCOMO)		+= locomokbd.o
>  obj-$(CONFIG_KEYBOARD_MAPLE)		+= maple_keyb.o
>  obj-$(CONFIG_KEYBOARD_MATRIX)		+= matrix_keypad.o
>  obj-$(CONFIG_KEYBOARD_MAX7359)		+= max7359_keypad.o
> +obj-$(CONFIG_KEYBOARD_MXC)		+= mxc_keypad.o
>  obj-$(CONFIG_KEYBOARD_NEWTON)		+= newtonkbd.o
>  obj-$(CONFIG_KEYBOARD_OMAP)		+= omap-keypad.o
>  obj-$(CONFIG_KEYBOARD_OPENCORES)	+= opencores-kbd.o
> diff --git a/drivers/input/keyboard/mxc_keypad.c b/drivers/input/keyboard/mxc_keypad.c
> new file mode 100644
> index 0000000..4acd311
> --- /dev/null
> +++ b/drivers/input/keyboard/mxc_keypad.c
> @@ -0,0 +1,485 @@
> +/*
> + * linux/drivers/input/keyboard/mxc_keypad.c
> + *
> + * Driver for the MXC keypad port.
> + * Copyright (C) 2009 Alberto Panizzo <maramaopercheseimorto@gmail.com>
> + *  based on Rodolfo Giometti & others work in pxa27x_keypad.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.
> + *
> + * >>Power management need to be implemented<<.
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/delay.h>
> +#include <linux/device.h>
> +#include <linux/err.h>
> +#include <linux/init.h>
> +#include <linux/input.h>
> +#include <linux/input/matrix_keypad.h>

input.h is not needed if you are including matrix_keypad.h

> +#include <linux/interrupt.h>
> +#include <linux/io.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +
> +#include <mach/mxc_keypad.h>
> +
> +/*
> + * Keypad Controller registers (halfword)
> + */
> +#define KPCR		0x00 /* Keypad Control Register */
> +
> +#define KPSR		0x02 /* Keypad Status Register */
> +#define KBD_STAT_KPKD	(0x1 << 0) /* Key Press Interrupt Status bit */
> +#define KBD_STAT_KPKR	(0x1 << 1) /* Key Release Interrupt Status bit */
> +#define KBD_STAT_KDSC	(0x1 << 2) /* Key Depress Synch Chain Status bit */
> +#define KBD_STAT_KRSS	(0x1 << 3) /* Key Release Synch Status bit */
> +#define KBD_STAT_KDIE	(0x1 << 8) /* Key Depress Interrupt Enable Status bit */
> +#define KBD_STAT_KRIE	(0x1 << 9) /* Key Release Interrupt Enable */
> +#define KBD_STAT_KPPEN	(0x1 << 10) /* Keypad Clock Enable */
> +
> +#define KDDR		0x04 /* Keypad Data Direction Register */
> +#define KPDR		0x06 /* Keypad Data Register */
> +
> +#define keypad_readw(off)	__raw_readw(keypad->mmio_base + (off))
> +#define keypad_writew(off, v)	__raw_writew((v), keypad->mmio_base + (off))

Please make the above macros inline functions.
See Documentation/CodingStyle, Chapter 12.

> +
> +#define MAX_MATRIX_KEY_NUM	(MAX_MATRIX_KEY_ROWS * MAX_MATRIX_KEY_COLS)
> +
> +struct mxc_keypad {
> +	struct mxc_keypad_platform_data *pdata;
> +
> +	struct clk *clk;
> +	struct input_dev *input_dev;
> +	void __iomem *mmio_base;
> +
> +	int			irq;
> +
> +#define MXC_IRQ_DEPRESS		1
> +#define MXC_IRQ_RELEASE		2
> +	unsigned int		irq_type;
> +
> +	int 			irq_since_last_change;
> +
> +	unsigned short 		keycodes[MAX_MATRIX_KEY_NUM];
> +
> +	/* state row bits of each column scan */
> +	unsigned short 		matrix_key_state[MAX_MATRIX_KEY_COLS];
> +};
> +
> +static void mxc_keypad_build_keycode(struct mxc_keypad *keypad)
> +{
> +	struct mxc_keypad_platform_data *pdata = keypad->pdata;
> +	struct input_dev *input_dev = keypad->input_dev;
> +	unsigned short keycode;
> +	int i;
> +
> +	for (i = 0; i < pdata->matrix_key_map_size; i++) {
> +		unsigned int key = pdata->matrix_key_map[i];
> +		unsigned int row = KEY_ROW(key);
> +		unsigned int col = KEY_COL(key);
> +		unsigned int scancode = MATRIX_SCAN_CODE(row, col,
> +							 MATRIX_ROW_SHIFT);
> +
> +		keycode = KEY_VAL(key);
> +		keypad->keycodes[scancode] = keycode;
> +		__set_bit(keycode, input_dev->keybit);
> +	}
> +
> +	__clear_bit(KEY_RESERVED, input_dev->keybit);
> +}

You should be able to use matrix_keypad_build_keymap() if you use
struct matrix_keymap_data as mentioned above.

> +
> +/* Return 0 if no changes are detected in matrix */
> +static int mxc_keypad_scan_matrix(struct mxc_keypad *keypad)
> +{
> +	struct mxc_keypad_platform_data *pdata = keypad->pdata;
> +	struct input_dev *input_dev = keypad->input_dev;
> +	int row, col, changed = 0;
> +	unsigned short new_state[MAX_MATRIX_KEY_COLS];
> +	unsigned short reg_val;
> +
> +	memset(new_state, 0, sizeof(new_state));
> +
> +	for (col = 0; col < pdata->matrix_key_cols; col++) {
> +		/* Discharge keypad capacitance:
> +		 * 2. write 1s on column data.
> +		 * 3. configure columns as totem-pole to discharge capacitance.
> +		 * 4. configure columns as open-drain.*/
> +		reg_val = keypad_readw(KPDR);
> +		reg_val |= 0xff00;
> +		keypad_writew(KPDR, reg_val);
> +
> +		reg_val = keypad_readw(KPCR);
> +		reg_val &= ~(((1 << pdata->matrix_key_cols) - 1) << 8);
> +		keypad_writew(KPCR, reg_val);
> +
> +		udelay(2);
> +
> +		reg_val = keypad_readw(KPCR);
> +		reg_val |= ((1 << pdata->matrix_key_cols) - 1) << 8;
> +		keypad_writew(KPCR, reg_val);
> +
> +		/*
> +		 * 5. Write a single column to 0, others to 1.
> +		 * 6. Sample row inputs and save data.
> +		 * 7. Repeat steps 2 - 6 for remaining columns.
> +		 */
> +		reg_val = keypad_readw(KPDR);
> +		reg_val &= ~(1 << (8 + col));
> +		keypad_writew(KPDR, reg_val);
> +
> +		/* Delay added to avoid propagating the 0 from column to row
> +		 * when scanning. */
> +		udelay(5);
> +
> +		/* 1s in state detect a key pressure */
> +		new_state[col] = (~keypad_readw(KPDR)) & 0x00ff;
> +	}
> +
> +	/* Test the state changes */
> +	for (col = 0; col < pdata->matrix_key_cols; col++) {
> +		unsigned short bits_changed;
> +		int code;
> +
> +		bits_changed = keypad->matrix_key_state[col] ^ new_state[col];
> +		if (bits_changed == 0)
> +			continue;
> +
> +		changed = 1;
> +		for (row = 0; row < pdata->matrix_key_rows; row++) {
> +			if ((bits_changed & (1 << row)) == 0)
> +				continue;
> +
> +			code = MATRIX_SCAN_CODE(row, col, MATRIX_ROW_SHIFT);
> +			input_event(input_dev, EV_MSC, MSC_SCAN, code);
> +			input_report_key(input_dev, keypad->keycodes[code],
> +					 new_state[col] & (1 << row));
> +			dev_dbg(&input_dev->dev, "Event code: %d, val: %d",
> +					keypad->keycodes[code],
> +					new_state[col] & (1 << row));
> +		}
> +	}
> +	input_sync(input_dev);
> +	memcpy(keypad->matrix_key_state, new_state, sizeof(new_state));
> +
> +	/* Return in standby mode:
> +	 * 9. write 0s to columns */
> +	reg_val = keypad_readw(KPDR);
> +	reg_val &= 0x00ff;
> +	keypad_writew(KPDR, reg_val);
> +
> +	return changed;
> +}
> +
> +static irqreturn_t mxc_keypad_irq_handler_thread(int irq, void *dev_id)
> +{
> +	struct mxc_keypad *keypad = dev_id;
> +	struct mxc_keypad_platform_data *pdata = keypad->pdata;
> +	struct input_dev *idev = keypad->input_dev;
> +	unsigned short reg_val;
> +
> +	dev_dbg(&idev->dev, "Handling Interrupt of type %x\n",
> +						keypad->irq_type);
> +
> +	/* Wait till debounce */
> +	msleep(pdata->debounce_ms);
> +
> +	/* Do the scan routine and Keep track for how many time we got
> +	 * interrupt that make no change */
> +	if (mxc_keypad_scan_matrix(keypad))
> +		keypad->irq_since_last_change = 0;
> +	else
> +		keypad->irq_since_last_change++;
> +
> +	/* If the key is pressed since too many time, relax the update period */
> +	if (keypad->irq_since_last_change > 2)
> +		msleep(100);
> +
> +	/* 10. Clear KPKD and KPKR status bits
> +	 *     Set the KPKR sync chain and clear the KPKD sync chain */
> +	reg_val = keypad_readw(KPSR);
> +	reg_val |= KBD_STAT_KPKD | KBD_STAT_KPKR |
> +		   KBD_STAT_KDSC | KBD_STAT_KRSS;
> +	keypad_writew(KPSR, reg_val);
> +
> +	/* Re enable interrupts and clear sync reset bits.
> +	 * Next KDI is used for detect multiple pressures. */
> +	reg_val = keypad_readw(KPSR);
> +	reg_val &= ~(KBD_STAT_KDSC | KBD_STAT_KRSS);
> +	keypad_writew(KPSR, reg_val);
> +
> +	reg_val |= KBD_STAT_KDIE | KBD_STAT_KRIE;
> +	if (keypad->irq_type == MXC_IRQ_RELEASE)
> +		reg_val &= ~KBD_STAT_KRIE;
> +	keypad_writew(KPSR, reg_val);
> +
> +	return IRQ_HANDLED;
> +}
> +static irqreturn_t mxc_keypad_irq_handler(int irq, void *dev_id)
> +{
> +	struct mxc_keypad *keypad = dev_id;
> +	unsigned short reg_val;
> +
> +	/* Disable every interrupt */
> +	reg_val = keypad_readw(KPSR);
> +	reg_val &= ~(KBD_STAT_KRIE | KBD_STAT_KDIE);
> +	keypad_writew(KPSR, reg_val);
> +
> +	keypad->irq_type = reg_val & KBD_STAT_KPKD ?
> +			   MXC_IRQ_DEPRESS : MXC_IRQ_RELEASE;
> +
> +	return IRQ_WAKE_THREAD;
> +}
> +
> +static void mxc_keypad_config(struct mxc_keypad *keypad)
> +{
> +	struct mxc_keypad_platform_data *pdata = keypad->pdata;
> +	unsigned short reg_val;
> +
> +	/* Enable number of rows in keypad (KPCR[7:0])
> +	 * Configure keypad columns as open-drain (KPCR[15:8])
> +	 */
> +	reg_val = keypad_readw(KPCR);
> +	reg_val |= (1 << pdata->matrix_key_rows) - 1;		/* LSB */
> +	reg_val |= ((1 << pdata->matrix_key_cols) - 1) << 8;	/* MSB */
> +	keypad_writew(KPCR, reg_val);
> +
> +	/* Write 0's to KPDR[15:8] (Colums)*/
> +	reg_val = keypad_readw(KPDR);
> +	reg_val &= 0x00ff;
> +	keypad_writew(KPDR, reg_val);
> +
> +	/* Configure columns as output, rows as input (KDDR[15:0]) */
> +	reg_val = keypad_readw(KDDR);
> +	reg_val |= 0xff00;
> +	reg_val &= 0xff00;
> +	keypad_writew(KDDR, reg_val);
> +
> +	/* Clear Key Depress and Key Release status bit.
> +	 * Clear synchronizer chain.
> +	 * */
> +	reg_val = keypad_readw(KPSR);
> +	reg_val |= KBD_STAT_KPKR | KBD_STAT_KPKD |
> +		   KBD_STAT_KDSC | KBD_STAT_KRSS;
> +	keypad_writew(KPSR, reg_val);
> +
> +	/* Set the KDIE control bit, and clear the KRIE control bit
> +	 * (avoid false release events). */
> +	reg_val |= KBD_STAT_KDIE;
> +	reg_val &= ~KBD_STAT_KRIE;
> +	keypad_writew(KPSR, reg_val);
> +}
> +
> +static void mxc_keypad_inhibit(struct mxc_keypad *keypad)
> +{
> +	unsigned short reg_val;
> +
> +	/* Colums as open drain and disable rows */
> +	keypad_writew(KPCR, 0xff00);
> +
> +	/* Clear the KPKD status flag and synchronizer chain.
> +	 * Clear KDIE control bit and KRIE control bit.
> +	 */
> +	reg_val = keypad_readw(KPSR);
> +	reg_val |= KBD_STAT_KPKD | KBD_STAT_KDSC;
> +	reg_val &= ~(KBD_STAT_KRIE | KBD_STAT_KDIE);
> +	keypad_writew(KPSR, reg_val);
> +}
> +
> +static int mxc_keypad_open(struct input_dev *dev)
> +{
> +	struct mxc_keypad *keypad = input_get_drvdata(dev);
> +	struct mxc_keypad_platform_data *pdata = keypad->pdata;
> +
> +	dev_dbg(&dev->dev, "%s\n", __func__);
> +
> +	/* Enable unit clock */
> +	clk_enable(keypad->clk);
> +	mxc_keypad_config(keypad);
> +
> +	/* Sanity control, not all the rows must be to 0s now. */
> +	if ((keypad_readw(KPDR) & ((1 << pdata->matrix_key_rows) - 1)) == 0) {
> +		dev_err(&dev->dev, "Too much keys pressed for now. "
> +				"Control pins initialisation.\n");
> +		goto open_err;
> +	}
> +
> +	return 0;
> +
> +open_err:
> +	mxc_keypad_inhibit(keypad);
> +	return -EIO;
> +}
> +
> +static void mxc_keypad_close(struct input_dev *dev)
> +{
> +	struct mxc_keypad *keypad = input_get_drvdata(dev);
> +
> +	mxc_keypad_inhibit(keypad);
> +
> +	/* Disable clock unit */
> +	clk_disable(keypad->clk);
> +}
> +
> +static int __devinit mxc_keypad_probe(struct platform_device *pdev)
> +{
> +	struct mxc_keypad_platform_data *pdata = pdev->dev.platform_data;
> +	struct mxc_keypad *keypad;
> +	struct input_dev *input_dev;
> +	struct resource *res;
> +	int irq, error;
> +
> +	if (pdata == NULL) {
> +		dev_err(&pdev->dev, "no platform data defined\n");
> +		return -EINVAL;
> +	}
> +
> +	irq = platform_get_irq(pdev, 0);
> +	if (irq < 0) {
> +		dev_err(&pdev->dev, "failed to get keypad irq\n");
> +		return -ENXIO;
> +	}
> +
> +	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +	if (res == NULL) {
> +		dev_err(&pdev->dev, "failed to get I/O memory\n");
> +		return -ENXIO;
> +	}
> +
> +	keypad = kzalloc(sizeof(struct mxc_keypad), GFP_KERNEL);
> +	input_dev = input_allocate_device();
> +	if (!keypad || !input_dev) {
> +		dev_err(&pdev->dev, "failed to allocate memory\n");
> +		error = -ENOMEM;
> +		goto failed_free;
> +	}
> +
> +	keypad->pdata = pdata;
> +	keypad->input_dev = input_dev;
> +	keypad->irq = irq;
> +
> +	res = request_mem_region(res->start, resource_size(res), pdev->name);
> +	if (res == NULL) {
> +		dev_err(&pdev->dev, "failed to request I/O memory\n");
> +		error = -EBUSY;
> +		goto failed_free;
> +	}
> +
> +	keypad->mmio_base = ioremap(res->start, resource_size(res));
> +	if (keypad->mmio_base == NULL) {
> +		dev_err(&pdev->dev, "failed to remap I/O memory\n");
> +		error = -ENXIO;
> +		goto failed_free_mem;
> +	}
> +
> +	keypad->clk = clk_get(NULL, "kpp");
> +	if (IS_ERR(keypad->clk)) {
> +		dev_err(&pdev->dev, "failed to get keypad clock\n");
> +		error = PTR_ERR(keypad->clk);
> +		goto failed_free_io;
> +	}
> +
> +	input_dev->name = pdev->name;
> +	input_dev->id.bustype = BUS_HOST;
> +	input_dev->open = mxc_keypad_open;
> +	input_dev->close = mxc_keypad_close;
> +	input_dev->dev.parent = &pdev->dev;
> +
> +	input_dev->keycode = keypad->keycodes;
> +	input_dev->keycodesize = sizeof(keypad->keycodes[0]);
> +	input_dev->keycodemax = ARRAY_SIZE(keypad->keycodes);
> +
> +	input_set_drvdata(input_dev, keypad);
> +
> +	input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);
> +	input_set_capability(input_dev, EV_MSC, MSC_SCAN);
> +
> +	mxc_keypad_build_keycode(keypad);
> +
> +	error = request_threaded_irq(irq, mxc_keypad_irq_handler,
> +			mxc_keypad_irq_handler_thread, IRQF_DISABLED,
> +			pdev->name, keypad);
> +	if (error) {
> +		dev_err(&pdev->dev, "failed to request IRQ\n");
> +		goto failed_put_clk;
> +	}
> +
> +	/* Register the input device */
> +	error = input_register_device(input_dev);
> +	if (error) {
> +		dev_err(&pdev->dev, "failed to register input device\n");
> +		goto failed_free_irq;
> +	}
> +
> +	platform_set_drvdata(pdev, keypad);
> +	device_init_wakeup(&pdev->dev, 1);
> +
> +	dev_info(&pdev->dev, "device probed.\n");
> +
> +	return 0;
> +
> +failed_free_irq:
> +	free_irq(irq, pdev);
> +failed_put_clk:
> +	clk_put(keypad->clk);
> +failed_free_io:
> +	iounmap(keypad->mmio_base);
> +failed_free_mem:
> +	release_mem_region(res->start, resource_size(res));
> +failed_free:
> +	input_free_device(input_dev);
> +	kfree(keypad);
> +	return error;
> +}
> +
> +static int __devexit mxc_keypad_remove(struct platform_device *pdev)
> +{
> +	struct mxc_keypad *keypad = platform_get_drvdata(pdev);
> +	struct resource *res;
> +
> +	free_irq(keypad->irq, keypad);

You might want to move the platform_set_drvdata(pdev, NULL) to prevent
any race conditions.

> +	clk_put(keypad->clk);
> +
> +	input_unregister_device(keypad->input_dev);
> +	input_free_device(keypad->input_dev);

input_free_device is unnecessary for already registered devices.

> +
> +	iounmap(keypad->mmio_base);
> +
> +	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +	release_mem_region(res->start, resource_size(res));
> +
> +	platform_set_drvdata(pdev, NULL);
> +	kfree(keypad);
> +
> +	return 0;
> +}
> +
> +static struct platform_driver mxc_keypad_driver = {
> +	.driver		= {
> +		.name	= "mxc-keypad",
> +		.owner	= THIS_MODULE,
> +	},
> +	.probe		= mxc_keypad_probe,
> +	.remove		= __devexit_p(mxc_keypad_remove),
> +};
> +
> +static int __init mxc_keypad_init(void)
> +{
> +	return platform_driver_register(&mxc_keypad_driver);
> +}
> +
> +static void __exit mxc_keypad_exit(void)
> +{
> +	platform_driver_unregister(&mxc_keypad_driver);
> +}
> +
> +module_init(mxc_keypad_init);
> +module_exit(mxc_keypad_exit);
> +
> +MODULE_AUTHOR("Alberto Panizzo <maramaopercheseimorto@gmail.com>");
> +MODULE_DESCRIPTION("MXC Keypad Port Driver");
> +MODULE_LICENSE("GPL v2");
> +MODULE_ALIAS("platform:mxc-keypad");

^ permalink raw reply	[flat|nested] 16+ messages in thread

* [PATCH v2] input: MXC: add mxc-keypad driver to support the Keypad Port present in the mxc application processors family.
  2010-01-08 20:33   ` H Hartley Sweeten
@ 2010-01-16 17:48     ` Alberto Panizzo
  -1 siblings, 0 replies; 16+ messages in thread
From: Alberto Panizzo @ 2010-01-16 17:48 UTC (permalink / raw)
  To: H Hartley Sweeten
  Cc: linux-input, linux-kernel, Dmitry Torokhov,
	linux-arm-kernel-infradead, Sascha linux-arm

Version 2 for this driver proposal.
--------------------------------------------------------------------------------
The MXC family of Application Processors is shipped with a Keypad Port 
supported now by this driver.

The peripheral can control up to an 8x8 matrix key pad where all the scanning 
procedure is done via software.

The hardware provide two interrupts: one for a key pressed (KDI) and one for 
all key releases (KRI). There is also a simple circuit for glitch reduction 
(said for synchronization) made by two series of 3 D-latches clocked by the 
keypad-clock that stabilize the interrupts sources.
KDI and KRI are fired only if the respective conditions are maintained for at 
last 4 keypad-clock cycle.

Those simple synchronization circuits are used also for multiple key pressures: 
between a KDI and a KRI the driver reset the sync circuit and re-enable the KDI
interrupt so after 3 keypad-clock cycle another KDI is fired making possible to
repeat the matrix scan operation.

This algorithm is done via a threaded management of the keypad interrupt source
and delayed by a proper (and longer) debounce interval controlled by the 
platform initialization.
If a key is pressed for a lot of time, the driver relaxes the interrupt repeat 
interval to not over load the cpu in a long time keypad interaction.

Changes in v2:
-This driver completely apply to matrix_keypad interface.
-Rearranged remove procedure.
-Automated hardware configuration, based on matrix_keypad_data.
-Removed useless macro.

This driver is tested to build in kernel or as a module and follow the 
specification of Freescale Application processors:
i.MX25 i.MX27 i.MX31 i.MX35 i.MX51 especially tested on i.MX31.

Signed-off-by: Alberto Panizzo <maramaopercheseimorto@gmail.com>
---
 arch/arm/plat-mxc/include/mach/mxc_keypad.h |   18 +
 drivers/input/keyboard/Kconfig              |    9 +
 drivers/input/keyboard/Makefile             |    1 +
 drivers/input/keyboard/mxc_keypad.c         |  496 +++++++++++++++++++++++++++
 4 files changed, 524 insertions(+), 0 deletions(-)
 create mode 100644 arch/arm/plat-mxc/include/mach/mxc_keypad.h
 create mode 100644 drivers/input/keyboard/mxc_keypad.c

diff --git a/arch/arm/plat-mxc/include/mach/mxc_keypad.h b/arch/arm/plat-mxc/include/mach/mxc_keypad.h
new file mode 100644
index 0000000..4509103
--- /dev/null
+++ b/arch/arm/plat-mxc/include/mach/mxc_keypad.h
@@ -0,0 +1,18 @@
+#ifndef __MACH_MXC_KEYPAD_H
+#define __MACH_MXC_KEYPAD_H
+
+#include <linux/input/matrix_keypad.h>
+
+#define MAX_MATRIX_KEY_ROWS	(8)
+#define MAX_MATRIX_KEY_COLS	(8)
+#define MATRIX_ROW_SHIFT	(3)
+
+struct mxc_keypad_platform_data {
+
+	const struct matrix_keymap_data *keymap_data;
+
+	/* key debounce interval */
+	unsigned int	debounce_ms;
+};
+
+#endif /* __MACH_MXC_KEYPAD_H */
diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig
index ee98b1b..eff32fe 100644
--- a/drivers/input/keyboard/Kconfig
+++ b/drivers/input/keyboard/Kconfig
@@ -282,6 +282,15 @@ config KEYBOARD_MAX7359
 	  To compile this driver as a module, choose M here: the
 	  module will be called max7359_keypad.
 
+config KEYBOARD_MXC
+	tristate "MXC keypad support"
+	depends on ARCH_MXC
+	help
+	  Enable support for MXC keypad port.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called mxc_keypad.
+
 config KEYBOARD_NEWTON
 	tristate "Newton keyboard"
 	select SERIO
diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile
index babad5e..9c7133a 100644
--- a/drivers/input/keyboard/Makefile
+++ b/drivers/input/keyboard/Makefile
@@ -23,6 +23,7 @@ obj-$(CONFIG_KEYBOARD_LOCOMO)		+= locomokbd.o
 obj-$(CONFIG_KEYBOARD_MAPLE)		+= maple_keyb.o
 obj-$(CONFIG_KEYBOARD_MATRIX)		+= matrix_keypad.o
 obj-$(CONFIG_KEYBOARD_MAX7359)		+= max7359_keypad.o
+obj-$(CONFIG_KEYBOARD_MXC)		+= mxc_keypad.o
 obj-$(CONFIG_KEYBOARD_NEWTON)		+= newtonkbd.o
 obj-$(CONFIG_KEYBOARD_OMAP)		+= omap-keypad.o
 obj-$(CONFIG_KEYBOARD_OPENCORES)	+= opencores-kbd.o
diff --git a/drivers/input/keyboard/mxc_keypad.c b/drivers/input/keyboard/mxc_keypad.c
new file mode 100644
index 0000000..13e8823
--- /dev/null
+++ b/drivers/input/keyboard/mxc_keypad.c
@@ -0,0 +1,494 @@
+/*
+ * linux/drivers/input/keyboard/mxc_keypad.c
+ *
+ * Driver for the MXC keypad port.
+ * Copyright (C) 2009 Alberto Panizzo <maramaopercheseimorto@gmail.com>
+ *  based on Rodolfo Giometti work in pxa27x_keypad.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.
+ *
+ * >>Power management need to be implemented<<.
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/input/matrix_keypad.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#include <mach/mxc_keypad.h>
+
+/*
+ * Keypad Controller registers (halfword)
+ */
+#define KPCR		0x00 /* Keypad Control Register */
+
+#define KPSR		0x02 /* Keypad Status Register */
+#define KBD_STAT_KPKD	(0x1 << 0) /* Key Press Interrupt Status bit */
+#define KBD_STAT_KPKR	(0x1 << 1) /* Key Release Interrupt Status bit */
+#define KBD_STAT_KDSC	(0x1 << 2) /* Key Depress Synch Chain Status bit */
+#define KBD_STAT_KRSS	(0x1 << 3) /* Key Release Synch Status bit */
+#define KBD_STAT_KDIE	(0x1 << 8) /* Key Depress Interrupt Enable Status bit */
+#define KBD_STAT_KRIE	(0x1 << 9) /* Key Release Interrupt Enable */
+#define KBD_STAT_KPPEN	(0x1 << 10) /* Keypad Clock Enable */
+
+#define KDDR		0x04 /* Keypad Data Direction Register */
+#define KPDR		0x06 /* Keypad Data Register */
+
+#define MAX_MATRIX_KEY_NUM	(MAX_MATRIX_KEY_ROWS * MAX_MATRIX_KEY_COLS)
+
+struct mxc_keypad {
+	struct mxc_keypad_platform_data *pdata;
+
+	struct clk *clk;
+	struct input_dev *input_dev;
+	void __iomem *mmio_base;
+
+	int			irq;
+
+#define MXC_IRQ_KDI		1
+#define MXC_IRQ_KRI		2
+	unsigned int		irq_type;
+	int 			irq_since_last_change;
+
+	/* Masks for enabled rows/cols */
+	unsigned short		rows_en_mask;
+	unsigned short		cols_en_mask;
+
+	unsigned short 		keycodes[MAX_MATRIX_KEY_NUM];
+
+	/* state row bits of each column scan */
+	unsigned short 		matrix_key_state[MAX_MATRIX_KEY_COLS];
+};
+
+/* Return 0 if no changes are detected in matrix */
+static int mxc_keypad_scan_matrix(struct mxc_keypad *keypad)
+{
+	struct input_dev *input_dev = keypad->input_dev;
+	int row, col, changed = 0;
+	unsigned short new_state[MAX_MATRIX_KEY_COLS];
+	unsigned short reg_val;
+
+	memset(new_state, 0, sizeof(new_state));
+
+	for (col = 0; col < MAX_MATRIX_KEY_COLS; col++) {
+		if ((keypad->cols_en_mask & (1 << col)) == 0)
+			continue;
+		/* Discharge keypad capacitance:
+		 * 2. write 1s on column data.
+		 * 3. configure columns as totem-pole to discharge capacitance.
+		 * 4. configure columns as open-drain.*/
+		reg_val = readw(keypad->mmio_base + KPDR);
+		reg_val |= 0xff00;
+		writew(reg_val, keypad->mmio_base + KPDR);
+
+		reg_val = readw(keypad->mmio_base + KPCR);
+		reg_val &= ~((keypad->cols_en_mask & 0xff) << 8);
+		writew(reg_val, keypad->mmio_base + KPCR);
+
+		udelay(2);
+
+		reg_val = readw(keypad->mmio_base + KPCR);
+		reg_val |= (keypad->cols_en_mask & 0xff) << 8;
+		writew(reg_val, keypad->mmio_base + KPCR);
+
+		/*
+		 * 5. Write a single column to 0, others to 1.
+		 * 6. Sample row inputs and save data.
+		 * 7. Repeat steps 2 - 6 for remaining columns.
+		 */
+		reg_val = readw(keypad->mmio_base + KPDR);
+		reg_val &= ~(1 << (8 + col));
+		writew(reg_val, keypad->mmio_base + KPDR);
+
+		/* Delay added to avoid propagating the 0 from column to row
+		 * when scanning. */
+		udelay(5);
+
+		/* 1s in state detect a key pressure */
+		new_state[col] = (~readw(keypad->mmio_base + KPDR)) & 0x00ff;
+	}
+
+	/* Test the state changes */
+	for (col = 0; col < MAX_MATRIX_KEY_COLS; col++) {
+		unsigned short bits_changed;
+		int code;
+
+		if ((keypad->cols_en_mask & (1 << col)) == 0)
+			continue;
+
+		bits_changed = keypad->matrix_key_state[col] ^ new_state[col];
+		if (bits_changed == 0)
+			continue;
+
+		changed = 1;
+		for (row = 0; row < MAX_MATRIX_KEY_ROWS; row++) {
+			if ((keypad->rows_en_mask & (1 << row)) == 0)
+				continue;
+			if ((bits_changed & (1 << row)) == 0)
+				continue;
+
+			code = MATRIX_SCAN_CODE(row, col, MATRIX_ROW_SHIFT);
+			input_event(input_dev, EV_MSC, MSC_SCAN, code);
+			input_report_key(input_dev, keypad->keycodes[code],
+					 new_state[col] & (1 << row));
+			dev_dbg(&input_dev->dev, "Event code: %d, val: %d",
+					keypad->keycodes[code],
+					new_state[col] & (1 << row));
+		}
+	}
+	input_sync(input_dev);
+	memcpy(keypad->matrix_key_state, new_state, sizeof(new_state));
+
+	/* Return in standby mode:
+	 * 9. write 0s to columns */
+	reg_val = readw(keypad->mmio_base + KPDR);
+	reg_val &= 0x00ff;
+	writew(reg_val, keypad->mmio_base + KPDR);
+
+	return changed;
+}
+
+static irqreturn_t mxc_keypad_irq_handler_thread(int irq, void *dev_id)
+{
+	struct mxc_keypad *keypad = dev_id;
+	struct mxc_keypad_platform_data *pdata = keypad->pdata;
+	struct input_dev *idev = keypad->input_dev;
+	unsigned short reg_val;
+
+	dev_dbg(&idev->dev, "Handling Interrupt of type %x\n",
+						keypad->irq_type);
+
+	/* Wait till debounce */
+	msleep(pdata->debounce_ms);
+
+	/* Do the scan routine and Keep track for how many time we got
+	 * interrupt that make no change */
+	if (mxc_keypad_scan_matrix(keypad))
+		keypad->irq_since_last_change = 0;
+	else
+		keypad->irq_since_last_change++;
+
+	/* If the key is pressed since too many time, relax the update period */
+	if (keypad->irq_since_last_change > 2)
+		msleep(100);
+
+	/* 10. Clear KPKD and KPKR status bits
+	 *     Set the KPKR sync chain and clear the KPKD sync chain */
+	reg_val = readw(keypad->mmio_base + KPSR);
+	reg_val |= KBD_STAT_KPKD | KBD_STAT_KPKR |
+		   KBD_STAT_KDSC | KBD_STAT_KRSS;
+	writew(reg_val, keypad->mmio_base + KPSR);
+
+	/* Re enable interrupts and clear sync reset bits.
+	 * Next KDI is used for detect multiple pressures. */
+	reg_val = readw(keypad->mmio_base + KPSR);
+	reg_val &= ~(KBD_STAT_KDSC | KBD_STAT_KRSS);
+	writew(reg_val, keypad->mmio_base + KPSR);
+
+	reg_val |= KBD_STAT_KDIE | KBD_STAT_KRIE;
+	if (keypad->irq_type == MXC_IRQ_KRI)
+		reg_val &= ~KBD_STAT_KRIE;
+	writew(reg_val, keypad->mmio_base + KPSR);
+
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t mxc_keypad_irq_handler(int irq, void *dev_id)
+{
+	struct mxc_keypad *keypad = dev_id;
+	unsigned short reg_val;
+
+	/* Disable every interrupt */
+	reg_val = readw(keypad->mmio_base + KPSR);
+	reg_val &= ~(KBD_STAT_KRIE | KBD_STAT_KDIE);
+	writew(reg_val, keypad->mmio_base + KPSR);
+
+	keypad->irq_type = reg_val & KBD_STAT_KPKD ?
+			   MXC_IRQ_KDI : MXC_IRQ_KRI;
+
+	return IRQ_WAKE_THREAD;
+}
+
+static void mxc_keypad_config(struct mxc_keypad *keypad)
+{
+	unsigned short reg_val;
+
+	/* Enable number of rows in keypad (KPCR[7:0])
+	 * Configure keypad columns as open-drain (KPCR[15:8])
+	 */
+	reg_val = readw(keypad->mmio_base + KPCR);
+	reg_val |= keypad->rows_en_mask & 0xff;		/* rows */
+	reg_val |= (keypad->cols_en_mask & 0xff) << 8;	/* cols */
+	writew(reg_val, keypad->mmio_base + KPCR);
+
+	/* Write 0's to KPDR[15:8] (Colums)*/
+	reg_val = readw(keypad->mmio_base + KPDR);
+	reg_val &= 0x00ff;
+	writew(reg_val, keypad->mmio_base + KPDR);
+
+	/* Configure columns as output, rows as input (KDDR[15:0]) */
+	reg_val = readw(keypad->mmio_base + KDDR);
+	reg_val |= 0xff00;
+	reg_val &= 0xff00;
+	writew(reg_val, keypad->mmio_base + KDDR);
+
+	/* Clear Key Depress and Key Release status bit.
+	 * Clear synchronizer chain.
+	 * */
+	reg_val = readw(keypad->mmio_base + KPSR);
+	reg_val |= KBD_STAT_KPKR | KBD_STAT_KPKD |
+		   KBD_STAT_KDSC | KBD_STAT_KRSS;
+	writew(reg_val, keypad->mmio_base + KPSR);
+
+	/* Set the KDIE control bit, and clear the KRIE control bit
+	 * (avoid false release events). */
+	reg_val |= KBD_STAT_KDIE;
+	reg_val &= ~KBD_STAT_KRIE;
+	writew(reg_val, keypad->mmio_base + KPSR);
+}
+
+static void mxc_keypad_inhibit(struct mxc_keypad *keypad)
+{
+	unsigned short reg_val;
+
+	/* Colums as open drain and disable rows */
+	writew(0xff00, keypad->mmio_base + KPCR);
+
+	/* Clear the KPKD status flag and synchronizer chain.
+	 * Clear KDIE control bit and KRIE control bit.
+	 */
+	reg_val = readw(keypad->mmio_base + KPSR);
+	reg_val |= KBD_STAT_KPKD | KBD_STAT_KDSC;
+	reg_val &= ~(KBD_STAT_KRIE | KBD_STAT_KDIE);
+	writew(reg_val, keypad->mmio_base + KPSR);
+}
+
+static int mxc_keypad_open(struct input_dev *dev)
+{
+	struct mxc_keypad *keypad = input_get_drvdata(dev);
+
+	dev_dbg(&dev->dev, "%s\n", __func__);
+
+	/* Enable unit clock */
+	clk_enable(keypad->clk);
+	mxc_keypad_config(keypad);
+
+	/* Sanity control, not all the rows must be to 0s now. */
+	if ((readw(keypad->mmio_base + KPDR) & keypad->rows_en_mask) == 0) {
+		dev_err(&dev->dev, "Too much keys pressed for now, "
+				"control pins initialisation\n");
+		goto open_err;
+	}
+
+	return 0;
+
+open_err:
+	mxc_keypad_inhibit(keypad);
+	return -EIO;
+}
+
+static void mxc_keypad_close(struct input_dev *dev)
+{
+	struct mxc_keypad *keypad = input_get_drvdata(dev);
+
+	mxc_keypad_inhibit(keypad);
+
+	/* Disable clock unit */
+	clk_disable(keypad->clk);
+}
+
+static int __devinit mxc_keypad_probe(struct platform_device *pdev)
+{
+	struct mxc_keypad_platform_data *pdata = pdev->dev.platform_data;
+	struct mxc_keypad *keypad;
+	struct input_dev *input_dev;
+	struct resource *res;
+	int irq, error, i;
+	struct matrix_keymap_data *keymap_data;
+
+	if (pdata == NULL) {
+		dev_err(&pdev->dev, "no platform data defined\n");
+		return -EINVAL;
+	}
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0) {
+		dev_err(&pdev->dev, "failed to get keypad irq\n");
+		return -ENXIO;
+	}
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (res == NULL) {
+		dev_err(&pdev->dev, "failed to get I/O memory\n");
+		return -ENXIO;
+	}
+
+	res = request_mem_region(res->start, resource_size(res), pdev->name);
+	if (res == NULL) {
+		dev_err(&pdev->dev, "failed to request I/O memory\n");
+		return -EBUSY;
+	}
+
+	input_dev = input_allocate_device();
+	if (!input_dev) {
+		dev_err(&pdev->dev, "failed to allocate the input device\n");
+		error = -ENOMEM;
+		goto failed_rel_mem;
+	}
+
+	keypad = kzalloc(sizeof(struct mxc_keypad), GFP_KERNEL);
+	if (!keypad) {
+		dev_err(&pdev->dev, "not enough memory for driver data\n");
+		error = -ENOMEM;
+		goto failed_free_input;
+	}
+
+	keypad->pdata = pdata;
+	keypad->input_dev = input_dev;
+	keypad->irq = irq;
+
+	keypad->mmio_base = ioremap(res->start, resource_size(res));
+	if (keypad->mmio_base == NULL) {
+		dev_err(&pdev->dev, "failed to remap I/O memory\n");
+		error = -ENXIO;
+		goto failed_free_priv;
+	}
+
+	keypad->clk = clk_get(NULL, "kpp");
+	if (IS_ERR(keypad->clk)) {
+		dev_err(&pdev->dev, "failed to get keypad clock\n");
+		error = PTR_ERR(keypad->clk);
+		goto failed_unmap;
+	}
+
+	input_dev->name = pdev->name;
+	input_dev->id.bustype = BUS_HOST;
+	input_dev->dev.parent = &pdev->dev;
+	input_dev->open = mxc_keypad_open;
+	input_dev->close = mxc_keypad_close;
+	input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);
+
+	/*
+	 * Search for rows and cols enabled
+	 */
+	keymap_data = (struct matrix_keymap_data *) pdata->keymap_data;
+	for (i = 0; i < keymap_data->keymap_size; i++) {
+		keypad->rows_en_mask |= 1 << KEY_ROW(keymap_data->keymap[i]);
+		keypad->cols_en_mask |= 1 << KEY_COL(keymap_data->keymap[i]);
+	}
+
+	if (keypad->rows_en_mask > ((1 << MAX_MATRIX_KEY_ROWS) - 1) ||
+	   keypad->cols_en_mask > ((1 << MAX_MATRIX_KEY_COLS) - 1)) {
+		dev_err(&pdev->dev, "Invalid key data (too rows or colums)\n");
+		error = -EINVAL;
+		goto failed_clock_put;
+	}
+	dev_dbg(&pdev->dev, "enabled rows mask: %x\n", keypad->rows_en_mask);
+	dev_dbg(&pdev->dev, "enabled cols mask: %x\n", keypad->cols_en_mask);
+
+	input_dev->keycode = keypad->keycodes;
+	input_dev->keycodesize = sizeof(keypad->keycodes[0]);
+	input_dev->keycodemax = ARRAY_SIZE(keypad->keycodes);
+
+	matrix_keypad_build_keymap(pdata->keymap_data, MATRIX_ROW_SHIFT,
+				keypad->keycodes, input_dev->keybit);
+
+	input_set_capability(input_dev, EV_MSC, MSC_SCAN);
+	input_set_drvdata(input_dev, keypad);
+
+	error = request_threaded_irq(irq, mxc_keypad_irq_handler,
+			mxc_keypad_irq_handler_thread, IRQF_DISABLED,
+			pdev->name, keypad);
+	if (error) {
+		dev_err(&pdev->dev, "failed to request IRQ\n");
+		goto failed_clock_put;
+	}
+
+	/* Register the input device */
+	error = input_register_device(input_dev);
+	if (error) {
+		dev_err(&pdev->dev, "failed to register input device\n");
+		goto failed_free_irq;
+	}
+
+	platform_set_drvdata(pdev, keypad);
+	device_init_wakeup(&pdev->dev, 1);
+
+	dev_info(&pdev->dev, "device probed\n");
+
+	return 0;
+
+failed_free_irq:
+	free_irq(irq, pdev);
+failed_clock_put:
+	clk_put(keypad->clk);
+failed_unmap:
+	iounmap(keypad->mmio_base);
+failed_free_priv:
+	kfree(keypad);
+failed_free_input:
+	input_free_device(input_dev);
+failed_rel_mem:
+	release_mem_region(res->start, resource_size(res));
+	return error;
+}
+
+static int __devexit mxc_keypad_remove(struct platform_device *pdev)
+{
+	struct mxc_keypad *keypad = platform_get_drvdata(pdev);
+	struct resource *res;
+
+	platform_set_drvdata(pdev, NULL);
+
+	free_irq(keypad->irq, keypad);
+	clk_put(keypad->clk);
+
+	iounmap(keypad->mmio_base);
+
+	input_unregister_device(keypad->input_dev);
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	release_mem_region(res->start, resource_size(res));
+
+	kfree(keypad);
+
+	dev_info(&pdev->dev, "device removed\n");
+
+	return 0;
+}
+
+static struct platform_driver mxc_keypad_driver = {
+	.driver		= {
+		.name	= "mxc-keypad",
+		.owner	= THIS_MODULE,
+	},
+	.probe		= mxc_keypad_probe,
+	.remove		= __devexit_p(mxc_keypad_remove),
+};
+
+static int __init mxc_keypad_init(void)
+{
+	return platform_driver_register(&mxc_keypad_driver);
+}
+
+static void __exit mxc_keypad_exit(void)
+{
+	platform_driver_unregister(&mxc_keypad_driver);
+}
+
+module_init(mxc_keypad_init);
+module_exit(mxc_keypad_exit);
+
+MODULE_AUTHOR("Alberto Panizzo <maramaopercheseimorto@gmail.com>");
+MODULE_DESCRIPTION("MXC Keypad Port Driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:mxc-keypad");
-- 
1.6.3.3




^ permalink raw reply related	[flat|nested] 16+ messages in thread

* [PATCH v2] input: MXC: add mxc-keypad driver to support the Keypad Port present in the mxc application processors family.
@ 2010-01-16 17:48     ` Alberto Panizzo
  0 siblings, 0 replies; 16+ messages in thread
From: Alberto Panizzo @ 2010-01-16 17:48 UTC (permalink / raw)
  To: linux-arm-kernel

Version 2 for this driver proposal.
--------------------------------------------------------------------------------
The MXC family of Application Processors is shipped with a Keypad Port 
supported now by this driver.

The peripheral can control up to an 8x8 matrix key pad where all the scanning 
procedure is done via software.

The hardware provide two interrupts: one for a key pressed (KDI) and one for 
all key releases (KRI). There is also a simple circuit for glitch reduction 
(said for synchronization) made by two series of 3 D-latches clocked by the 
keypad-clock that stabilize the interrupts sources.
KDI and KRI are fired only if the respective conditions are maintained for at 
last 4 keypad-clock cycle.

Those simple synchronization circuits are used also for multiple key pressures: 
between a KDI and a KRI the driver reset the sync circuit and re-enable the KDI
interrupt so after 3 keypad-clock cycle another KDI is fired making possible to
repeat the matrix scan operation.

This algorithm is done via a threaded management of the keypad interrupt source
and delayed by a proper (and longer) debounce interval controlled by the 
platform initialization.
If a key is pressed for a lot of time, the driver relaxes the interrupt repeat 
interval to not over load the cpu in a long time keypad interaction.

Changes in v2:
-This driver completely apply to matrix_keypad interface.
-Rearranged remove procedure.
-Automated hardware configuration, based on matrix_keypad_data.
-Removed useless macro.

This driver is tested to build in kernel or as a module and follow the 
specification of Freescale Application processors:
i.MX25 i.MX27 i.MX31 i.MX35 i.MX51 especially tested on i.MX31.

Signed-off-by: Alberto Panizzo <maramaopercheseimorto@gmail.com>
---
 arch/arm/plat-mxc/include/mach/mxc_keypad.h |   18 +
 drivers/input/keyboard/Kconfig              |    9 +
 drivers/input/keyboard/Makefile             |    1 +
 drivers/input/keyboard/mxc_keypad.c         |  496 +++++++++++++++++++++++++++
 4 files changed, 524 insertions(+), 0 deletions(-)
 create mode 100644 arch/arm/plat-mxc/include/mach/mxc_keypad.h
 create mode 100644 drivers/input/keyboard/mxc_keypad.c

diff --git a/arch/arm/plat-mxc/include/mach/mxc_keypad.h b/arch/arm/plat-mxc/include/mach/mxc_keypad.h
new file mode 100644
index 0000000..4509103
--- /dev/null
+++ b/arch/arm/plat-mxc/include/mach/mxc_keypad.h
@@ -0,0 +1,18 @@
+#ifndef __MACH_MXC_KEYPAD_H
+#define __MACH_MXC_KEYPAD_H
+
+#include <linux/input/matrix_keypad.h>
+
+#define MAX_MATRIX_KEY_ROWS	(8)
+#define MAX_MATRIX_KEY_COLS	(8)
+#define MATRIX_ROW_SHIFT	(3)
+
+struct mxc_keypad_platform_data {
+
+	const struct matrix_keymap_data *keymap_data;
+
+	/* key debounce interval */
+	unsigned int	debounce_ms;
+};
+
+#endif /* __MACH_MXC_KEYPAD_H */
diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig
index ee98b1b..eff32fe 100644
--- a/drivers/input/keyboard/Kconfig
+++ b/drivers/input/keyboard/Kconfig
@@ -282,6 +282,15 @@ config KEYBOARD_MAX7359
 	  To compile this driver as a module, choose M here: the
 	  module will be called max7359_keypad.
 
+config KEYBOARD_MXC
+	tristate "MXC keypad support"
+	depends on ARCH_MXC
+	help
+	  Enable support for MXC keypad port.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called mxc_keypad.
+
 config KEYBOARD_NEWTON
 	tristate "Newton keyboard"
 	select SERIO
diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile
index babad5e..9c7133a 100644
--- a/drivers/input/keyboard/Makefile
+++ b/drivers/input/keyboard/Makefile
@@ -23,6 +23,7 @@ obj-$(CONFIG_KEYBOARD_LOCOMO)		+= locomokbd.o
 obj-$(CONFIG_KEYBOARD_MAPLE)		+= maple_keyb.o
 obj-$(CONFIG_KEYBOARD_MATRIX)		+= matrix_keypad.o
 obj-$(CONFIG_KEYBOARD_MAX7359)		+= max7359_keypad.o
+obj-$(CONFIG_KEYBOARD_MXC)		+= mxc_keypad.o
 obj-$(CONFIG_KEYBOARD_NEWTON)		+= newtonkbd.o
 obj-$(CONFIG_KEYBOARD_OMAP)		+= omap-keypad.o
 obj-$(CONFIG_KEYBOARD_OPENCORES)	+= opencores-kbd.o
diff --git a/drivers/input/keyboard/mxc_keypad.c b/drivers/input/keyboard/mxc_keypad.c
new file mode 100644
index 0000000..13e8823
--- /dev/null
+++ b/drivers/input/keyboard/mxc_keypad.c
@@ -0,0 +1,494 @@
+/*
+ * linux/drivers/input/keyboard/mxc_keypad.c
+ *
+ * Driver for the MXC keypad port.
+ * Copyright (C) 2009 Alberto Panizzo <maramaopercheseimorto@gmail.com>
+ *  based on Rodolfo Giometti work in pxa27x_keypad.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.
+ *
+ * >>Power management need to be implemented<<.
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/input/matrix_keypad.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#include <mach/mxc_keypad.h>
+
+/*
+ * Keypad Controller registers (halfword)
+ */
+#define KPCR		0x00 /* Keypad Control Register */
+
+#define KPSR		0x02 /* Keypad Status Register */
+#define KBD_STAT_KPKD	(0x1 << 0) /* Key Press Interrupt Status bit */
+#define KBD_STAT_KPKR	(0x1 << 1) /* Key Release Interrupt Status bit */
+#define KBD_STAT_KDSC	(0x1 << 2) /* Key Depress Synch Chain Status bit */
+#define KBD_STAT_KRSS	(0x1 << 3) /* Key Release Synch Status bit */
+#define KBD_STAT_KDIE	(0x1 << 8) /* Key Depress Interrupt Enable Status bit */
+#define KBD_STAT_KRIE	(0x1 << 9) /* Key Release Interrupt Enable */
+#define KBD_STAT_KPPEN	(0x1 << 10) /* Keypad Clock Enable */
+
+#define KDDR		0x04 /* Keypad Data Direction Register */
+#define KPDR		0x06 /* Keypad Data Register */
+
+#define MAX_MATRIX_KEY_NUM	(MAX_MATRIX_KEY_ROWS * MAX_MATRIX_KEY_COLS)
+
+struct mxc_keypad {
+	struct mxc_keypad_platform_data *pdata;
+
+	struct clk *clk;
+	struct input_dev *input_dev;
+	void __iomem *mmio_base;
+
+	int			irq;
+
+#define MXC_IRQ_KDI		1
+#define MXC_IRQ_KRI		2
+	unsigned int		irq_type;
+	int 			irq_since_last_change;
+
+	/* Masks for enabled rows/cols */
+	unsigned short		rows_en_mask;
+	unsigned short		cols_en_mask;
+
+	unsigned short 		keycodes[MAX_MATRIX_KEY_NUM];
+
+	/* state row bits of each column scan */
+	unsigned short 		matrix_key_state[MAX_MATRIX_KEY_COLS];
+};
+
+/* Return 0 if no changes are detected in matrix */
+static int mxc_keypad_scan_matrix(struct mxc_keypad *keypad)
+{
+	struct input_dev *input_dev = keypad->input_dev;
+	int row, col, changed = 0;
+	unsigned short new_state[MAX_MATRIX_KEY_COLS];
+	unsigned short reg_val;
+
+	memset(new_state, 0, sizeof(new_state));
+
+	for (col = 0; col < MAX_MATRIX_KEY_COLS; col++) {
+		if ((keypad->cols_en_mask & (1 << col)) == 0)
+			continue;
+		/* Discharge keypad capacitance:
+		 * 2. write 1s on column data.
+		 * 3. configure columns as totem-pole to discharge capacitance.
+		 * 4. configure columns as open-drain.*/
+		reg_val = readw(keypad->mmio_base + KPDR);
+		reg_val |= 0xff00;
+		writew(reg_val, keypad->mmio_base + KPDR);
+
+		reg_val = readw(keypad->mmio_base + KPCR);
+		reg_val &= ~((keypad->cols_en_mask & 0xff) << 8);
+		writew(reg_val, keypad->mmio_base + KPCR);
+
+		udelay(2);
+
+		reg_val = readw(keypad->mmio_base + KPCR);
+		reg_val |= (keypad->cols_en_mask & 0xff) << 8;
+		writew(reg_val, keypad->mmio_base + KPCR);
+
+		/*
+		 * 5. Write a single column to 0, others to 1.
+		 * 6. Sample row inputs and save data.
+		 * 7. Repeat steps 2 - 6 for remaining columns.
+		 */
+		reg_val = readw(keypad->mmio_base + KPDR);
+		reg_val &= ~(1 << (8 + col));
+		writew(reg_val, keypad->mmio_base + KPDR);
+
+		/* Delay added to avoid propagating the 0 from column to row
+		 * when scanning. */
+		udelay(5);
+
+		/* 1s in state detect a key pressure */
+		new_state[col] = (~readw(keypad->mmio_base + KPDR)) & 0x00ff;
+	}
+
+	/* Test the state changes */
+	for (col = 0; col < MAX_MATRIX_KEY_COLS; col++) {
+		unsigned short bits_changed;
+		int code;
+
+		if ((keypad->cols_en_mask & (1 << col)) == 0)
+			continue;
+
+		bits_changed = keypad->matrix_key_state[col] ^ new_state[col];
+		if (bits_changed == 0)
+			continue;
+
+		changed = 1;
+		for (row = 0; row < MAX_MATRIX_KEY_ROWS; row++) {
+			if ((keypad->rows_en_mask & (1 << row)) == 0)
+				continue;
+			if ((bits_changed & (1 << row)) == 0)
+				continue;
+
+			code = MATRIX_SCAN_CODE(row, col, MATRIX_ROW_SHIFT);
+			input_event(input_dev, EV_MSC, MSC_SCAN, code);
+			input_report_key(input_dev, keypad->keycodes[code],
+					 new_state[col] & (1 << row));
+			dev_dbg(&input_dev->dev, "Event code: %d, val: %d",
+					keypad->keycodes[code],
+					new_state[col] & (1 << row));
+		}
+	}
+	input_sync(input_dev);
+	memcpy(keypad->matrix_key_state, new_state, sizeof(new_state));
+
+	/* Return in standby mode:
+	 * 9. write 0s to columns */
+	reg_val = readw(keypad->mmio_base + KPDR);
+	reg_val &= 0x00ff;
+	writew(reg_val, keypad->mmio_base + KPDR);
+
+	return changed;
+}
+
+static irqreturn_t mxc_keypad_irq_handler_thread(int irq, void *dev_id)
+{
+	struct mxc_keypad *keypad = dev_id;
+	struct mxc_keypad_platform_data *pdata = keypad->pdata;
+	struct input_dev *idev = keypad->input_dev;
+	unsigned short reg_val;
+
+	dev_dbg(&idev->dev, "Handling Interrupt of type %x\n",
+						keypad->irq_type);
+
+	/* Wait till debounce */
+	msleep(pdata->debounce_ms);
+
+	/* Do the scan routine and Keep track for how many time we got
+	 * interrupt that make no change */
+	if (mxc_keypad_scan_matrix(keypad))
+		keypad->irq_since_last_change = 0;
+	else
+		keypad->irq_since_last_change++;
+
+	/* If the key is pressed since too many time, relax the update period */
+	if (keypad->irq_since_last_change > 2)
+		msleep(100);
+
+	/* 10. Clear KPKD and KPKR status bits
+	 *     Set the KPKR sync chain and clear the KPKD sync chain */
+	reg_val = readw(keypad->mmio_base + KPSR);
+	reg_val |= KBD_STAT_KPKD | KBD_STAT_KPKR |
+		   KBD_STAT_KDSC | KBD_STAT_KRSS;
+	writew(reg_val, keypad->mmio_base + KPSR);
+
+	/* Re enable interrupts and clear sync reset bits.
+	 * Next KDI is used for detect multiple pressures. */
+	reg_val = readw(keypad->mmio_base + KPSR);
+	reg_val &= ~(KBD_STAT_KDSC | KBD_STAT_KRSS);
+	writew(reg_val, keypad->mmio_base + KPSR);
+
+	reg_val |= KBD_STAT_KDIE | KBD_STAT_KRIE;
+	if (keypad->irq_type == MXC_IRQ_KRI)
+		reg_val &= ~KBD_STAT_KRIE;
+	writew(reg_val, keypad->mmio_base + KPSR);
+
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t mxc_keypad_irq_handler(int irq, void *dev_id)
+{
+	struct mxc_keypad *keypad = dev_id;
+	unsigned short reg_val;
+
+	/* Disable every interrupt */
+	reg_val = readw(keypad->mmio_base + KPSR);
+	reg_val &= ~(KBD_STAT_KRIE | KBD_STAT_KDIE);
+	writew(reg_val, keypad->mmio_base + KPSR);
+
+	keypad->irq_type = reg_val & KBD_STAT_KPKD ?
+			   MXC_IRQ_KDI : MXC_IRQ_KRI;
+
+	return IRQ_WAKE_THREAD;
+}
+
+static void mxc_keypad_config(struct mxc_keypad *keypad)
+{
+	unsigned short reg_val;
+
+	/* Enable number of rows in keypad (KPCR[7:0])
+	 * Configure keypad columns as open-drain (KPCR[15:8])
+	 */
+	reg_val = readw(keypad->mmio_base + KPCR);
+	reg_val |= keypad->rows_en_mask & 0xff;		/* rows */
+	reg_val |= (keypad->cols_en_mask & 0xff) << 8;	/* cols */
+	writew(reg_val, keypad->mmio_base + KPCR);
+
+	/* Write 0's to KPDR[15:8] (Colums)*/
+	reg_val = readw(keypad->mmio_base + KPDR);
+	reg_val &= 0x00ff;
+	writew(reg_val, keypad->mmio_base + KPDR);
+
+	/* Configure columns as output, rows as input (KDDR[15:0]) */
+	reg_val = readw(keypad->mmio_base + KDDR);
+	reg_val |= 0xff00;
+	reg_val &= 0xff00;
+	writew(reg_val, keypad->mmio_base + KDDR);
+
+	/* Clear Key Depress and Key Release status bit.
+	 * Clear synchronizer chain.
+	 * */
+	reg_val = readw(keypad->mmio_base + KPSR);
+	reg_val |= KBD_STAT_KPKR | KBD_STAT_KPKD |
+		   KBD_STAT_KDSC | KBD_STAT_KRSS;
+	writew(reg_val, keypad->mmio_base + KPSR);
+
+	/* Set the KDIE control bit, and clear the KRIE control bit
+	 * (avoid false release events). */
+	reg_val |= KBD_STAT_KDIE;
+	reg_val &= ~KBD_STAT_KRIE;
+	writew(reg_val, keypad->mmio_base + KPSR);
+}
+
+static void mxc_keypad_inhibit(struct mxc_keypad *keypad)
+{
+	unsigned short reg_val;
+
+	/* Colums as open drain and disable rows */
+	writew(0xff00, keypad->mmio_base + KPCR);
+
+	/* Clear the KPKD status flag and synchronizer chain.
+	 * Clear KDIE control bit and KRIE control bit.
+	 */
+	reg_val = readw(keypad->mmio_base + KPSR);
+	reg_val |= KBD_STAT_KPKD | KBD_STAT_KDSC;
+	reg_val &= ~(KBD_STAT_KRIE | KBD_STAT_KDIE);
+	writew(reg_val, keypad->mmio_base + KPSR);
+}
+
+static int mxc_keypad_open(struct input_dev *dev)
+{
+	struct mxc_keypad *keypad = input_get_drvdata(dev);
+
+	dev_dbg(&dev->dev, "%s\n", __func__);
+
+	/* Enable unit clock */
+	clk_enable(keypad->clk);
+	mxc_keypad_config(keypad);
+
+	/* Sanity control, not all the rows must be to 0s now. */
+	if ((readw(keypad->mmio_base + KPDR) & keypad->rows_en_mask) == 0) {
+		dev_err(&dev->dev, "Too much keys pressed for now, "
+				"control pins initialisation\n");
+		goto open_err;
+	}
+
+	return 0;
+
+open_err:
+	mxc_keypad_inhibit(keypad);
+	return -EIO;
+}
+
+static void mxc_keypad_close(struct input_dev *dev)
+{
+	struct mxc_keypad *keypad = input_get_drvdata(dev);
+
+	mxc_keypad_inhibit(keypad);
+
+	/* Disable clock unit */
+	clk_disable(keypad->clk);
+}
+
+static int __devinit mxc_keypad_probe(struct platform_device *pdev)
+{
+	struct mxc_keypad_platform_data *pdata = pdev->dev.platform_data;
+	struct mxc_keypad *keypad;
+	struct input_dev *input_dev;
+	struct resource *res;
+	int irq, error, i;
+	struct matrix_keymap_data *keymap_data;
+
+	if (pdata == NULL) {
+		dev_err(&pdev->dev, "no platform data defined\n");
+		return -EINVAL;
+	}
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0) {
+		dev_err(&pdev->dev, "failed to get keypad irq\n");
+		return -ENXIO;
+	}
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (res == NULL) {
+		dev_err(&pdev->dev, "failed to get I/O memory\n");
+		return -ENXIO;
+	}
+
+	res = request_mem_region(res->start, resource_size(res), pdev->name);
+	if (res == NULL) {
+		dev_err(&pdev->dev, "failed to request I/O memory\n");
+		return -EBUSY;
+	}
+
+	input_dev = input_allocate_device();
+	if (!input_dev) {
+		dev_err(&pdev->dev, "failed to allocate the input device\n");
+		error = -ENOMEM;
+		goto failed_rel_mem;
+	}
+
+	keypad = kzalloc(sizeof(struct mxc_keypad), GFP_KERNEL);
+	if (!keypad) {
+		dev_err(&pdev->dev, "not enough memory for driver data\n");
+		error = -ENOMEM;
+		goto failed_free_input;
+	}
+
+	keypad->pdata = pdata;
+	keypad->input_dev = input_dev;
+	keypad->irq = irq;
+
+	keypad->mmio_base = ioremap(res->start, resource_size(res));
+	if (keypad->mmio_base == NULL) {
+		dev_err(&pdev->dev, "failed to remap I/O memory\n");
+		error = -ENXIO;
+		goto failed_free_priv;
+	}
+
+	keypad->clk = clk_get(NULL, "kpp");
+	if (IS_ERR(keypad->clk)) {
+		dev_err(&pdev->dev, "failed to get keypad clock\n");
+		error = PTR_ERR(keypad->clk);
+		goto failed_unmap;
+	}
+
+	input_dev->name = pdev->name;
+	input_dev->id.bustype = BUS_HOST;
+	input_dev->dev.parent = &pdev->dev;
+	input_dev->open = mxc_keypad_open;
+	input_dev->close = mxc_keypad_close;
+	input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);
+
+	/*
+	 * Search for rows and cols enabled
+	 */
+	keymap_data = (struct matrix_keymap_data *) pdata->keymap_data;
+	for (i = 0; i < keymap_data->keymap_size; i++) {
+		keypad->rows_en_mask |= 1 << KEY_ROW(keymap_data->keymap[i]);
+		keypad->cols_en_mask |= 1 << KEY_COL(keymap_data->keymap[i]);
+	}
+
+	if (keypad->rows_en_mask > ((1 << MAX_MATRIX_KEY_ROWS) - 1) ||
+	   keypad->cols_en_mask > ((1 << MAX_MATRIX_KEY_COLS) - 1)) {
+		dev_err(&pdev->dev, "Invalid key data (too rows or colums)\n");
+		error = -EINVAL;
+		goto failed_clock_put;
+	}
+	dev_dbg(&pdev->dev, "enabled rows mask: %x\n", keypad->rows_en_mask);
+	dev_dbg(&pdev->dev, "enabled cols mask: %x\n", keypad->cols_en_mask);
+
+	input_dev->keycode = keypad->keycodes;
+	input_dev->keycodesize = sizeof(keypad->keycodes[0]);
+	input_dev->keycodemax = ARRAY_SIZE(keypad->keycodes);
+
+	matrix_keypad_build_keymap(pdata->keymap_data, MATRIX_ROW_SHIFT,
+				keypad->keycodes, input_dev->keybit);
+
+	input_set_capability(input_dev, EV_MSC, MSC_SCAN);
+	input_set_drvdata(input_dev, keypad);
+
+	error = request_threaded_irq(irq, mxc_keypad_irq_handler,
+			mxc_keypad_irq_handler_thread, IRQF_DISABLED,
+			pdev->name, keypad);
+	if (error) {
+		dev_err(&pdev->dev, "failed to request IRQ\n");
+		goto failed_clock_put;
+	}
+
+	/* Register the input device */
+	error = input_register_device(input_dev);
+	if (error) {
+		dev_err(&pdev->dev, "failed to register input device\n");
+		goto failed_free_irq;
+	}
+
+	platform_set_drvdata(pdev, keypad);
+	device_init_wakeup(&pdev->dev, 1);
+
+	dev_info(&pdev->dev, "device probed\n");
+
+	return 0;
+
+failed_free_irq:
+	free_irq(irq, pdev);
+failed_clock_put:
+	clk_put(keypad->clk);
+failed_unmap:
+	iounmap(keypad->mmio_base);
+failed_free_priv:
+	kfree(keypad);
+failed_free_input:
+	input_free_device(input_dev);
+failed_rel_mem:
+	release_mem_region(res->start, resource_size(res));
+	return error;
+}
+
+static int __devexit mxc_keypad_remove(struct platform_device *pdev)
+{
+	struct mxc_keypad *keypad = platform_get_drvdata(pdev);
+	struct resource *res;
+
+	platform_set_drvdata(pdev, NULL);
+
+	free_irq(keypad->irq, keypad);
+	clk_put(keypad->clk);
+
+	iounmap(keypad->mmio_base);
+
+	input_unregister_device(keypad->input_dev);
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	release_mem_region(res->start, resource_size(res));
+
+	kfree(keypad);
+
+	dev_info(&pdev->dev, "device removed\n");
+
+	return 0;
+}
+
+static struct platform_driver mxc_keypad_driver = {
+	.driver		= {
+		.name	= "mxc-keypad",
+		.owner	= THIS_MODULE,
+	},
+	.probe		= mxc_keypad_probe,
+	.remove		= __devexit_p(mxc_keypad_remove),
+};
+
+static int __init mxc_keypad_init(void)
+{
+	return platform_driver_register(&mxc_keypad_driver);
+}
+
+static void __exit mxc_keypad_exit(void)
+{
+	platform_driver_unregister(&mxc_keypad_driver);
+}
+
+module_init(mxc_keypad_init);
+module_exit(mxc_keypad_exit);
+
+MODULE_AUTHOR("Alberto Panizzo <maramaopercheseimorto@gmail.com>");
+MODULE_DESCRIPTION("MXC Keypad Port Driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:mxc-keypad");
-- 
1.6.3.3

^ permalink raw reply related	[flat|nested] 16+ messages in thread

* Re: [PATCH v2] input: MXC: add mxc-keypad driver to support the Keypad Port present in the mxc application processors family.
  2010-01-16 17:48     ` Alberto Panizzo
@ 2010-01-21 16:17       ` Alberto Panizzo
  -1 siblings, 0 replies; 16+ messages in thread
From: Alberto Panizzo @ 2010-01-21 16:17 UTC (permalink / raw)
  To: Dmitry Torokhov
  Cc: linux-input, linux-kernel, linux-arm-kernel-infradead,
	Sascha linux-arm, H Hartley Sweeten


Ping on this driver proposal..

Thanks for reviewing!


^ permalink raw reply	[flat|nested] 16+ messages in thread

* [PATCH v2] input: MXC: add mxc-keypad driver to support the Keypad Port present in the mxc application processors family.
@ 2010-01-21 16:17       ` Alberto Panizzo
  0 siblings, 0 replies; 16+ messages in thread
From: Alberto Panizzo @ 2010-01-21 16:17 UTC (permalink / raw)
  To: linux-arm-kernel


Ping on this driver proposal..

Thanks for reviewing!

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH v2] input: MXC: add mxc-keypad driver to support the Keypad Port present in the mxc application processors family.
  2010-01-16 17:48     ` Alberto Panizzo
  (?)
@ 2010-01-23 10:46       ` Alberto Panizzo
  -1 siblings, 0 replies; 16+ messages in thread
From: Alberto Panizzo @ 2010-01-23 10:46 UTC (permalink / raw)
  To: Dmitry Torokhov
  Cc: linux-input, linux-kernel, linux-arm-kernel-infradead,
	Sascha linux-arm, H Hartley Sweeten

Ping.. Any comments on this Dmitry?
This should be applied to input next branch.

Regards.
Alberto!

Patch below..
--------------------------------------------------------------------------------
The MXC family of Application Processors is shipped with a Keypad Port 
supported now by this driver.

The peripheral can control up to an 8x8 matrix key pad where all the scanning 
procedure is done via software.

The hardware provide two interrupts: one for a key pressed (KDI) and one for 
all key releases (KRI). There is also a simple circuit for glitch reduction 
(said for synchronization) made by two series of 3 D-latches clocked by the 
keypad-clock that stabilize the interrupts sources.
KDI and KRI are fired only if the respective conditions are maintained for at 
last 4 keypad-clock cycle.

Those simple synchronization circuits are used also for multiple key pressures: 
between a KDI and a KRI the driver reset the sync circuit and re-enable the KDI
interrupt so after 3 keypad-clock cycle another KDI is fired making possible to
repeat the matrix scan operation.

This algorithm is done via a threaded management of the keypad interrupt source
and delayed by a proper (and longer) debounce interval controlled by the 
platform initialization.
If a key is pressed for a lot of time, the driver relaxes the interrupt repeat 
interval to not over load the cpu in a long time keypad interaction.

Changes in v2:
-This driver completely apply to matrix_keypad interface.
-Rearranged remove procedure.
-Automated hardware configuration, based on matrix_keypad_data.
-Removed useless macro.

This driver is tested to build in kernel or as a module and follow the 
specification of Freescale Application processors:
i.MX25 i.MX27 i.MX31 i.MX35 i.MX51 especially tested on i.MX31.

Signed-off-by: Alberto Panizzo <maramaopercheseimorto@gmail.com>
---
 arch/arm/plat-mxc/include/mach/mxc_keypad.h |   18 +
 drivers/input/keyboard/Kconfig              |    9 +
 drivers/input/keyboard/Makefile             |    1 +
 drivers/input/keyboard/mxc_keypad.c         |  496 +++++++++++++++++++++++++++
 4 files changed, 524 insertions(+), 0 deletions(-)
 create mode 100644 arch/arm/plat-mxc/include/mach/mxc_keypad.h
 create mode 100644 drivers/input/keyboard/mxc_keypad.c

diff --git a/arch/arm/plat-mxc/include/mach/mxc_keypad.h b/arch/arm/plat-mxc/include/mach/mxc_keypad.h
new file mode 100644
index 0000000..4509103
--- /dev/null
+++ b/arch/arm/plat-mxc/include/mach/mxc_keypad.h
@@ -0,0 +1,18 @@
+#ifndef __MACH_MXC_KEYPAD_H
+#define __MACH_MXC_KEYPAD_H
+
+#include <linux/input/matrix_keypad.h>
+
+#define MAX_MATRIX_KEY_ROWS    (8)
+#define MAX_MATRIX_KEY_COLS    (8)
+#define MATRIX_ROW_SHIFT       (3)
+
+struct mxc_keypad_platform_data {
+
+       const struct matrix_keymap_data *keymap_data;
+
+       /* key debounce interval */
+       unsigned int    debounce_ms;
+};
+
+#endif /* __MACH_MXC_KEYPAD_H */
diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig
index ee98b1b..eff32fe 100644
--- a/drivers/input/keyboard/Kconfig
+++ b/drivers/input/keyboard/Kconfig
@@ -282,6 +282,15 @@ config KEYBOARD_MAX7359
          To compile this driver as a module, choose M here: the
          module will be called max7359_keypad.
 
+config KEYBOARD_MXC
+       tristate "MXC keypad support"
+       depends on ARCH_MXC
+       help
+         Enable support for MXC keypad port.
+
+         To compile this driver as a module, choose M here: the
+         module will be called mxc_keypad.
+
 config KEYBOARD_NEWTON
        tristate "Newton keyboard"
        select SERIO
diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile
index babad5e..9c7133a 100644
--- a/drivers/input/keyboard/Makefile
+++ b/drivers/input/keyboard/Makefile
@@ -23,6 +23,7 @@ obj-$(CONFIG_KEYBOARD_LOCOMO)         += locomokbd.o
 obj-$(CONFIG_KEYBOARD_MAPLE)           += maple_keyb.o
 obj-$(CONFIG_KEYBOARD_MATRIX)          += matrix_keypad.o
 obj-$(CONFIG_KEYBOARD_MAX7359)         += max7359_keypad.o
+obj-$(CONFIG_KEYBOARD_MXC)             += mxc_keypad.o
 obj-$(CONFIG_KEYBOARD_NEWTON)          += newtonkbd.o
 obj-$(CONFIG_KEYBOARD_OMAP)            += omap-keypad.o
 obj-$(CONFIG_KEYBOARD_OPENCORES)       += opencores-kbd.o
diff --git a/drivers/input/keyboard/mxc_keypad.c b/drivers/input/keyboard/mxc_keypad.c
new file mode 100644
index 0000000..13e8823
--- /dev/null
+++ b/drivers/input/keyboard/mxc_keypad.c
@@ -0,0 +1,494 @@
+/*
+ * linux/drivers/input/keyboard/mxc_keypad.c
+ *
+ * Driver for the MXC keypad port.
+ * Copyright (C) 2009 Alberto Panizzo <maramaopercheseimorto@gmail.com>
+ *  based on Rodolfo Giometti work in pxa27x_keypad.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.
+ *
+ * >>Power management need to be implemented<<.
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/input/matrix_keypad.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#include <mach/mxc_keypad.h>
+
+/*
+ * Keypad Controller registers (halfword)
+ */
+#define KPCR           0x00 /* Keypad Control Register */
+
+#define KPSR           0x02 /* Keypad Status Register */
+#define KBD_STAT_KPKD  (0x1 << 0) /* Key Press Interrupt Status bit */
+#define KBD_STAT_KPKR  (0x1 << 1) /* Key Release Interrupt Status bit */
+#define KBD_STAT_KDSC  (0x1 << 2) /* Key Depress Synch Chain Status bit */
+#define KBD_STAT_KRSS  (0x1 << 3) /* Key Release Synch Status bit */
+#define KBD_STAT_KDIE  (0x1 << 8) /* Key Depress Interrupt Enable Status bit */
+#define KBD_STAT_KRIE  (0x1 << 9) /* Key Release Interrupt Enable */
+#define KBD_STAT_KPPEN (0x1 << 10) /* Keypad Clock Enable */
+
+#define KDDR           0x04 /* Keypad Data Direction Register */
+#define KPDR           0x06 /* Keypad Data Register */
+
+#define MAX_MATRIX_KEY_NUM     (MAX_MATRIX_KEY_ROWS * MAX_MATRIX_KEY_COLS)
+
+struct mxc_keypad {
+       struct mxc_keypad_platform_data *pdata;
+
+       struct clk *clk;
+       struct input_dev *input_dev;
+       void __iomem *mmio_base;
+
+       int                     irq;
+
+#define MXC_IRQ_KDI            1
+#define MXC_IRQ_KRI            2
+       unsigned int            irq_type;
+       int                     irq_since_last_change;
+
+       /* Masks for enabled rows/cols */
+       unsigned short          rows_en_mask;
+       unsigned short          cols_en_mask;
+
+       unsigned short          keycodes[MAX_MATRIX_KEY_NUM];
+
+       /* state row bits of each column scan */
+       unsigned short          matrix_key_state[MAX_MATRIX_KEY_COLS];
+};
+
+/* Return 0 if no changes are detected in matrix */
+static int mxc_keypad_scan_matrix(struct mxc_keypad *keypad)
+{
+       struct input_dev *input_dev = keypad->input_dev;
+       int row, col, changed = 0;
+       unsigned short new_state[MAX_MATRIX_KEY_COLS];
+       unsigned short reg_val;
+
+       memset(new_state, 0, sizeof(new_state));
+
+       for (col = 0; col < MAX_MATRIX_KEY_COLS; col++) {
+               if ((keypad->cols_en_mask & (1 << col)) == 0)
+                       continue;
+               /* Discharge keypad capacitance:
+                * 2. write 1s on column data.
+                * 3. configure columns as totem-pole to discharge capacitance.
+                * 4. configure columns as open-drain.*/
+               reg_val = readw(keypad->mmio_base + KPDR);
+               reg_val |= 0xff00;
+               writew(reg_val, keypad->mmio_base + KPDR);
+
+               reg_val = readw(keypad->mmio_base + KPCR);
+               reg_val &= ~((keypad->cols_en_mask & 0xff) << 8);
+               writew(reg_val, keypad->mmio_base + KPCR);
+
+               udelay(2);
+
+               reg_val = readw(keypad->mmio_base + KPCR);
+               reg_val |= (keypad->cols_en_mask & 0xff) << 8;
+               writew(reg_val, keypad->mmio_base + KPCR);
+
+               /*
+                * 5. Write a single column to 0, others to 1.
+                * 6. Sample row inputs and save data.
+                * 7. Repeat steps 2 - 6 for remaining columns.
+                */
+               reg_val = readw(keypad->mmio_base + KPDR);
+               reg_val &= ~(1 << (8 + col));
+               writew(reg_val, keypad->mmio_base + KPDR);
+
+               /* Delay added to avoid propagating the 0 from column to row
+                * when scanning. */
+               udelay(5);
+
+               /* 1s in state detect a key pressure */
+               new_state[col] = (~readw(keypad->mmio_base + KPDR)) & 0x00ff;
+       }
+
+       /* Test the state changes */
+       for (col = 0; col < MAX_MATRIX_KEY_COLS; col++) {
+               unsigned short bits_changed;
+               int code;
+
+               if ((keypad->cols_en_mask & (1 << col)) == 0)
+                       continue;
+
+               bits_changed = keypad->matrix_key_state[col] ^ new_state[col];
+               if (bits_changed == 0)
+                       continue;
+
+               changed = 1;
+               for (row = 0; row < MAX_MATRIX_KEY_ROWS; row++) {
+                       if ((keypad->rows_en_mask & (1 << row)) == 0)
+                               continue;
+                       if ((bits_changed & (1 << row)) == 0)
+                               continue;
+
+                       code = MATRIX_SCAN_CODE(row, col, MATRIX_ROW_SHIFT);
+                       input_event(input_dev, EV_MSC, MSC_SCAN, code);
+                       input_report_key(input_dev, keypad->keycodes[code],
+                                        new_state[col] & (1 << row));
+                       dev_dbg(&input_dev->dev, "Event code: %d, val: %d",
+                                       keypad->keycodes[code],
+                                       new_state[col] & (1 << row));
+               }
+       }
+       input_sync(input_dev);
+       memcpy(keypad->matrix_key_state, new_state, sizeof(new_state));
+
+       /* Return in standby mode:
+        * 9. write 0s to columns */
+       reg_val = readw(keypad->mmio_base + KPDR);
+       reg_val &= 0x00ff;
+       writew(reg_val, keypad->mmio_base + KPDR);
+
+       return changed;
+}
+
+static irqreturn_t mxc_keypad_irq_handler_thread(int irq, void *dev_id)
+{
+       struct mxc_keypad *keypad = dev_id;
+       struct mxc_keypad_platform_data *pdata = keypad->pdata;
+       struct input_dev *idev = keypad->input_dev;
+       unsigned short reg_val;
+
+       dev_dbg(&idev->dev, "Handling Interrupt of type %x\n",
+                                               keypad->irq_type);
+
+       /* Wait till debounce */
+       msleep(pdata->debounce_ms);
+
+       /* Do the scan routine and Keep track for how many time we got
+        * interrupt that make no change */
+       if (mxc_keypad_scan_matrix(keypad))
+               keypad->irq_since_last_change = 0;
+       else
+               keypad->irq_since_last_change++;
+
+       /* If the key is pressed since too many time, relax the update period */
+       if (keypad->irq_since_last_change > 2)
+               msleep(100);
+
+       /* 10. Clear KPKD and KPKR status bits
+        *     Set the KPKR sync chain and clear the KPKD sync chain */
+       reg_val = readw(keypad->mmio_base + KPSR);
+       reg_val |= KBD_STAT_KPKD | KBD_STAT_KPKR |
+                  KBD_STAT_KDSC | KBD_STAT_KRSS;
+       writew(reg_val, keypad->mmio_base + KPSR);
+
+       /* Re enable interrupts and clear sync reset bits.
+        * Next KDI is used for detect multiple pressures. */
+       reg_val = readw(keypad->mmio_base + KPSR);
+       reg_val &= ~(KBD_STAT_KDSC | KBD_STAT_KRSS);
+       writew(reg_val, keypad->mmio_base + KPSR);
+
+       reg_val |= KBD_STAT_KDIE | KBD_STAT_KRIE;
+       if (keypad->irq_type == MXC_IRQ_KRI)
+               reg_val &= ~KBD_STAT_KRIE;
+       writew(reg_val, keypad->mmio_base + KPSR);
+
+       return IRQ_HANDLED;
+}
+
+static irqreturn_t mxc_keypad_irq_handler(int irq, void *dev_id)
+{
+       struct mxc_keypad *keypad = dev_id;
+       unsigned short reg_val;
+
+       /* Disable every interrupt */
+       reg_val = readw(keypad->mmio_base + KPSR);
+       reg_val &= ~(KBD_STAT_KRIE | KBD_STAT_KDIE);
+       writew(reg_val, keypad->mmio_base + KPSR);
+
+       keypad->irq_type = reg_val & KBD_STAT_KPKD ?
+                          MXC_IRQ_KDI : MXC_IRQ_KRI;
+
+       return IRQ_WAKE_THREAD;
+}
+
+static void mxc_keypad_config(struct mxc_keypad *keypad)
+{
+       unsigned short reg_val;
+
+       /* Enable number of rows in keypad (KPCR[7:0])
+        * Configure keypad columns as open-drain (KPCR[15:8])
+        */
+       reg_val = readw(keypad->mmio_base + KPCR);
+       reg_val |= keypad->rows_en_mask & 0xff;         /* rows */
+       reg_val |= (keypad->cols_en_mask & 0xff) << 8;  /* cols */
+       writew(reg_val, keypad->mmio_base + KPCR);
+
+       /* Write 0's to KPDR[15:8] (Colums)*/
+       reg_val = readw(keypad->mmio_base + KPDR);
+       reg_val &= 0x00ff;
+       writew(reg_val, keypad->mmio_base + KPDR);
+
+       /* Configure columns as output, rows as input (KDDR[15:0]) */
+       reg_val = readw(keypad->mmio_base + KDDR);
+       reg_val |= 0xff00;
+       reg_val &= 0xff00;
+       writew(reg_val, keypad->mmio_base + KDDR);
+
+       /* Clear Key Depress and Key Release status bit.
+        * Clear synchronizer chain.
+        * */
+       reg_val = readw(keypad->mmio_base + KPSR);
+       reg_val |= KBD_STAT_KPKR | KBD_STAT_KPKD |
+                  KBD_STAT_KDSC | KBD_STAT_KRSS;
+       writew(reg_val, keypad->mmio_base + KPSR);
+
+       /* Set the KDIE control bit, and clear the KRIE control bit
+        * (avoid false release events). */
+       reg_val |= KBD_STAT_KDIE;
+       reg_val &= ~KBD_STAT_KRIE;
+       writew(reg_val, keypad->mmio_base + KPSR);
+}
+
+static void mxc_keypad_inhibit(struct mxc_keypad *keypad)
+{
+       unsigned short reg_val;
+
+       /* Colums as open drain and disable rows */
+       writew(0xff00, keypad->mmio_base + KPCR);
+
+       /* Clear the KPKD status flag and synchronizer chain.
+        * Clear KDIE control bit and KRIE control bit.
+        */
+       reg_val = readw(keypad->mmio_base + KPSR);
+       reg_val |= KBD_STAT_KPKD | KBD_STAT_KDSC;
+       reg_val &= ~(KBD_STAT_KRIE | KBD_STAT_KDIE);
+       writew(reg_val, keypad->mmio_base + KPSR);
+}
+
+static int mxc_keypad_open(struct input_dev *dev)
+{
+       struct mxc_keypad *keypad = input_get_drvdata(dev);
+
+       dev_dbg(&dev->dev, "%s\n", __func__);
+
+       /* Enable unit clock */
+       clk_enable(keypad->clk);
+       mxc_keypad_config(keypad);
+
+       /* Sanity control, not all the rows must be to 0s now. */
+       if ((readw(keypad->mmio_base + KPDR) & keypad->rows_en_mask) == 0) {
+               dev_err(&dev->dev, "Too much keys pressed for now, "
+                               "control pins initialisation\n");
+               goto open_err;
+       }
+
+       return 0;
+
+open_err:
+       mxc_keypad_inhibit(keypad);
+       return -EIO;
+}
+
+static void mxc_keypad_close(struct input_dev *dev)
+{
+       struct mxc_keypad *keypad = input_get_drvdata(dev);
+
+       mxc_keypad_inhibit(keypad);
+
+       /* Disable clock unit */
+       clk_disable(keypad->clk);
+}
+
+static int __devinit mxc_keypad_probe(struct platform_device *pdev)
+{
+       struct mxc_keypad_platform_data *pdata = pdev->dev.platform_data;
+       struct mxc_keypad *keypad;
+       struct input_dev *input_dev;
+       struct resource *res;
+       int irq, error, i;
+       struct matrix_keymap_data *keymap_data;
+
+       if (pdata == NULL) {
+               dev_err(&pdev->dev, "no platform data defined\n");
+               return -EINVAL;
+       }
+
+       irq = platform_get_irq(pdev, 0);
+       if (irq < 0) {
+               dev_err(&pdev->dev, "failed to get keypad irq\n");
+               return -ENXIO;
+       }
+
+       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       if (res == NULL) {
+               dev_err(&pdev->dev, "failed to get I/O memory\n");
+               return -ENXIO;
+       }
+
+       res = request_mem_region(res->start, resource_size(res), pdev->name);
+       if (res == NULL) {
+               dev_err(&pdev->dev, "failed to request I/O memory\n");
+               return -EBUSY;
+       }
+
+       input_dev = input_allocate_device();
+       if (!input_dev) {
+               dev_err(&pdev->dev, "failed to allocate the input device\n");
+               error = -ENOMEM;
+               goto failed_rel_mem;
+       }
+
+       keypad = kzalloc(sizeof(struct mxc_keypad), GFP_KERNEL);
+       if (!keypad) {
+               dev_err(&pdev->dev, "not enough memory for driver data\n");
+               error = -ENOMEM;
+               goto failed_free_input;
+       }
+
+       keypad->pdata = pdata;
+       keypad->input_dev = input_dev;
+       keypad->irq = irq;
+
+       keypad->mmio_base = ioremap(res->start, resource_size(res));
+       if (keypad->mmio_base == NULL) {
+               dev_err(&pdev->dev, "failed to remap I/O memory\n");
+               error = -ENXIO;
+               goto failed_free_priv;
+       }
+
+       keypad->clk = clk_get(NULL, "kpp");
+       if (IS_ERR(keypad->clk)) {
+               dev_err(&pdev->dev, "failed to get keypad clock\n");
+               error = PTR_ERR(keypad->clk);
+               goto failed_unmap;
+       }
+
+       input_dev->name = pdev->name;
+       input_dev->id.bustype = BUS_HOST;
+       input_dev->dev.parent = &pdev->dev;
+       input_dev->open = mxc_keypad_open;
+       input_dev->close = mxc_keypad_close;
+       input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);
+
+       /*
+        * Search for rows and cols enabled
+        */
+       keymap_data = (struct matrix_keymap_data *) pdata->keymap_data;
+       for (i = 0; i < keymap_data->keymap_size; i++) {
+               keypad->rows_en_mask |= 1 << KEY_ROW(keymap_data->keymap[i]);
+               keypad->cols_en_mask |= 1 << KEY_COL(keymap_data->keymap[i]);
+       }
+
+       if (keypad->rows_en_mask > ((1 << MAX_MATRIX_KEY_ROWS) - 1) ||
+          keypad->cols_en_mask > ((1 << MAX_MATRIX_KEY_COLS) - 1)) {
+               dev_err(&pdev->dev, "Invalid key data (too rows or colums)\n");
+               error = -EINVAL;
+               goto failed_clock_put;
+       }
+       dev_dbg(&pdev->dev, "enabled rows mask: %x\n", keypad->rows_en_mask);
+       dev_dbg(&pdev->dev, "enabled cols mask: %x\n", keypad->cols_en_mask);
+
+       input_dev->keycode = keypad->keycodes;
+       input_dev->keycodesize = sizeof(keypad->keycodes[0]);
+       input_dev->keycodemax = ARRAY_SIZE(keypad->keycodes);
+
+       matrix_keypad_build_keymap(pdata->keymap_data, MATRIX_ROW_SHIFT,
+                               keypad->keycodes, input_dev->keybit);
+
+       input_set_capability(input_dev, EV_MSC, MSC_SCAN);
+       input_set_drvdata(input_dev, keypad);
+
+       error = request_threaded_irq(irq, mxc_keypad_irq_handler,
+                       mxc_keypad_irq_handler_thread, IRQF_DISABLED,
+                       pdev->name, keypad);
+       if (error) {
+               dev_err(&pdev->dev, "failed to request IRQ\n");
+               goto failed_clock_put;
+       }
+
+       /* Register the input device */
+       error = input_register_device(input_dev);
+       if (error) {
+               dev_err(&pdev->dev, "failed to register input device\n");
+               goto failed_free_irq;
+       }
+
+       platform_set_drvdata(pdev, keypad);
+       device_init_wakeup(&pdev->dev, 1);
+
+       dev_info(&pdev->dev, "device probed\n");
+
+       return 0;
+
+failed_free_irq:
+       free_irq(irq, pdev);
+failed_clock_put:
+       clk_put(keypad->clk);
+failed_unmap:
+       iounmap(keypad->mmio_base);
+failed_free_priv:
+       kfree(keypad);
+failed_free_input:
+       input_free_device(input_dev);
+failed_rel_mem:
+       release_mem_region(res->start, resource_size(res));
+       return error;
+}
+
+static int __devexit mxc_keypad_remove(struct platform_device *pdev)
+{
+       struct mxc_keypad *keypad = platform_get_drvdata(pdev);
+       struct resource *res;
+
+       platform_set_drvdata(pdev, NULL);
+
+       free_irq(keypad->irq, keypad);
+       clk_put(keypad->clk);
+
+       iounmap(keypad->mmio_base);
+
+       input_unregister_device(keypad->input_dev);
+
+       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       release_mem_region(res->start, resource_size(res));
+
+       kfree(keypad);
+
+       dev_info(&pdev->dev, "device removed\n");
+
+       return 0;
+}
+
+static struct platform_driver mxc_keypad_driver = {
+       .driver         = {
+               .name   = "mxc-keypad",
+               .owner  = THIS_MODULE,
+       },
+       .probe          = mxc_keypad_probe,
+       .remove         = __devexit_p(mxc_keypad_remove),
+};
+
+static int __init mxc_keypad_init(void)
+{
+       return platform_driver_register(&mxc_keypad_driver);
+}
+
+static void __exit mxc_keypad_exit(void)
+{
+       platform_driver_unregister(&mxc_keypad_driver);
+}
+
+module_init(mxc_keypad_init);
+module_exit(mxc_keypad_exit);
+
+MODULE_AUTHOR("Alberto Panizzo <maramaopercheseimorto@gmail.com>");
+MODULE_DESCRIPTION("MXC Keypad Port Driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:mxc-keypad");
-- 
1.6.3.3


^ permalink raw reply related	[flat|nested] 16+ messages in thread

* Re: [PATCH v2] input: MXC: add mxc-keypad driver to support the Keypad Port present in the mxc application processors family.
@ 2010-01-23 10:46       ` Alberto Panizzo
  0 siblings, 0 replies; 16+ messages in thread
From: Alberto Panizzo @ 2010-01-23 10:46 UTC (permalink / raw)
  To: Dmitry Torokhov
  Cc: H Hartley Sweeten, Sascha linux-arm, linux-kernel,
	linux-arm-kernel-infradead, linux-input

Ping.. Any comments on this Dmitry?
This should be applied to input next branch.

Regards.
Alberto!

Patch below..
--------------------------------------------------------------------------------
The MXC family of Application Processors is shipped with a Keypad Port 
supported now by this driver.

The peripheral can control up to an 8x8 matrix key pad where all the scanning 
procedure is done via software.

The hardware provide two interrupts: one for a key pressed (KDI) and one for 
all key releases (KRI). There is also a simple circuit for glitch reduction 
(said for synchronization) made by two series of 3 D-latches clocked by the 
keypad-clock that stabilize the interrupts sources.
KDI and KRI are fired only if the respective conditions are maintained for at 
last 4 keypad-clock cycle.

Those simple synchronization circuits are used also for multiple key pressures: 
between a KDI and a KRI the driver reset the sync circuit and re-enable the KDI
interrupt so after 3 keypad-clock cycle another KDI is fired making possible to
repeat the matrix scan operation.

This algorithm is done via a threaded management of the keypad interrupt source
and delayed by a proper (and longer) debounce interval controlled by the 
platform initialization.
If a key is pressed for a lot of time, the driver relaxes the interrupt repeat 
interval to not over load the cpu in a long time keypad interaction.

Changes in v2:
-This driver completely apply to matrix_keypad interface.
-Rearranged remove procedure.
-Automated hardware configuration, based on matrix_keypad_data.
-Removed useless macro.

This driver is tested to build in kernel or as a module and follow the 
specification of Freescale Application processors:
i.MX25 i.MX27 i.MX31 i.MX35 i.MX51 especially tested on i.MX31.

Signed-off-by: Alberto Panizzo <maramaopercheseimorto@gmail.com>
---
 arch/arm/plat-mxc/include/mach/mxc_keypad.h |   18 +
 drivers/input/keyboard/Kconfig              |    9 +
 drivers/input/keyboard/Makefile             |    1 +
 drivers/input/keyboard/mxc_keypad.c         |  496 +++++++++++++++++++++++++++
 4 files changed, 524 insertions(+), 0 deletions(-)
 create mode 100644 arch/arm/plat-mxc/include/mach/mxc_keypad.h
 create mode 100644 drivers/input/keyboard/mxc_keypad.c

diff --git a/arch/arm/plat-mxc/include/mach/mxc_keypad.h b/arch/arm/plat-mxc/include/mach/mxc_keypad.h
new file mode 100644
index 0000000..4509103
--- /dev/null
+++ b/arch/arm/plat-mxc/include/mach/mxc_keypad.h
@@ -0,0 +1,18 @@
+#ifndef __MACH_MXC_KEYPAD_H
+#define __MACH_MXC_KEYPAD_H
+
+#include <linux/input/matrix_keypad.h>
+
+#define MAX_MATRIX_KEY_ROWS    (8)
+#define MAX_MATRIX_KEY_COLS    (8)
+#define MATRIX_ROW_SHIFT       (3)
+
+struct mxc_keypad_platform_data {
+
+       const struct matrix_keymap_data *keymap_data;
+
+       /* key debounce interval */
+       unsigned int    debounce_ms;
+};
+
+#endif /* __MACH_MXC_KEYPAD_H */
diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig
index ee98b1b..eff32fe 100644
--- a/drivers/input/keyboard/Kconfig
+++ b/drivers/input/keyboard/Kconfig
@@ -282,6 +282,15 @@ config KEYBOARD_MAX7359
          To compile this driver as a module, choose M here: the
          module will be called max7359_keypad.
 
+config KEYBOARD_MXC
+       tristate "MXC keypad support"
+       depends on ARCH_MXC
+       help
+         Enable support for MXC keypad port.
+
+         To compile this driver as a module, choose M here: the
+         module will be called mxc_keypad.
+
 config KEYBOARD_NEWTON
        tristate "Newton keyboard"
        select SERIO
diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile
index babad5e..9c7133a 100644
--- a/drivers/input/keyboard/Makefile
+++ b/drivers/input/keyboard/Makefile
@@ -23,6 +23,7 @@ obj-$(CONFIG_KEYBOARD_LOCOMO)         += locomokbd.o
 obj-$(CONFIG_KEYBOARD_MAPLE)           += maple_keyb.o
 obj-$(CONFIG_KEYBOARD_MATRIX)          += matrix_keypad.o
 obj-$(CONFIG_KEYBOARD_MAX7359)         += max7359_keypad.o
+obj-$(CONFIG_KEYBOARD_MXC)             += mxc_keypad.o
 obj-$(CONFIG_KEYBOARD_NEWTON)          += newtonkbd.o
 obj-$(CONFIG_KEYBOARD_OMAP)            += omap-keypad.o
 obj-$(CONFIG_KEYBOARD_OPENCORES)       += opencores-kbd.o
diff --git a/drivers/input/keyboard/mxc_keypad.c b/drivers/input/keyboard/mxc_keypad.c
new file mode 100644
index 0000000..13e8823
--- /dev/null
+++ b/drivers/input/keyboard/mxc_keypad.c
@@ -0,0 +1,494 @@
+/*
+ * linux/drivers/input/keyboard/mxc_keypad.c
+ *
+ * Driver for the MXC keypad port.
+ * Copyright (C) 2009 Alberto Panizzo <maramaopercheseimorto@gmail.com>
+ *  based on Rodolfo Giometti work in pxa27x_keypad.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.
+ *
+ * >>Power management need to be implemented<<.
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/input/matrix_keypad.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#include <mach/mxc_keypad.h>
+
+/*
+ * Keypad Controller registers (halfword)
+ */
+#define KPCR           0x00 /* Keypad Control Register */
+
+#define KPSR           0x02 /* Keypad Status Register */
+#define KBD_STAT_KPKD  (0x1 << 0) /* Key Press Interrupt Status bit */
+#define KBD_STAT_KPKR  (0x1 << 1) /* Key Release Interrupt Status bit */
+#define KBD_STAT_KDSC  (0x1 << 2) /* Key Depress Synch Chain Status bit */
+#define KBD_STAT_KRSS  (0x1 << 3) /* Key Release Synch Status bit */
+#define KBD_STAT_KDIE  (0x1 << 8) /* Key Depress Interrupt Enable Status bit */
+#define KBD_STAT_KRIE  (0x1 << 9) /* Key Release Interrupt Enable */
+#define KBD_STAT_KPPEN (0x1 << 10) /* Keypad Clock Enable */
+
+#define KDDR           0x04 /* Keypad Data Direction Register */
+#define KPDR           0x06 /* Keypad Data Register */
+
+#define MAX_MATRIX_KEY_NUM     (MAX_MATRIX_KEY_ROWS * MAX_MATRIX_KEY_COLS)
+
+struct mxc_keypad {
+       struct mxc_keypad_platform_data *pdata;
+
+       struct clk *clk;
+       struct input_dev *input_dev;
+       void __iomem *mmio_base;
+
+       int                     irq;
+
+#define MXC_IRQ_KDI            1
+#define MXC_IRQ_KRI            2
+       unsigned int            irq_type;
+       int                     irq_since_last_change;
+
+       /* Masks for enabled rows/cols */
+       unsigned short          rows_en_mask;
+       unsigned short          cols_en_mask;
+
+       unsigned short          keycodes[MAX_MATRIX_KEY_NUM];
+
+       /* state row bits of each column scan */
+       unsigned short          matrix_key_state[MAX_MATRIX_KEY_COLS];
+};
+
+/* Return 0 if no changes are detected in matrix */
+static int mxc_keypad_scan_matrix(struct mxc_keypad *keypad)
+{
+       struct input_dev *input_dev = keypad->input_dev;
+       int row, col, changed = 0;
+       unsigned short new_state[MAX_MATRIX_KEY_COLS];
+       unsigned short reg_val;
+
+       memset(new_state, 0, sizeof(new_state));
+
+       for (col = 0; col < MAX_MATRIX_KEY_COLS; col++) {
+               if ((keypad->cols_en_mask & (1 << col)) == 0)
+                       continue;
+               /* Discharge keypad capacitance:
+                * 2. write 1s on column data.
+                * 3. configure columns as totem-pole to discharge capacitance.
+                * 4. configure columns as open-drain.*/
+               reg_val = readw(keypad->mmio_base + KPDR);
+               reg_val |= 0xff00;
+               writew(reg_val, keypad->mmio_base + KPDR);
+
+               reg_val = readw(keypad->mmio_base + KPCR);
+               reg_val &= ~((keypad->cols_en_mask & 0xff) << 8);
+               writew(reg_val, keypad->mmio_base + KPCR);
+
+               udelay(2);
+
+               reg_val = readw(keypad->mmio_base + KPCR);
+               reg_val |= (keypad->cols_en_mask & 0xff) << 8;
+               writew(reg_val, keypad->mmio_base + KPCR);
+
+               /*
+                * 5. Write a single column to 0, others to 1.
+                * 6. Sample row inputs and save data.
+                * 7. Repeat steps 2 - 6 for remaining columns.
+                */
+               reg_val = readw(keypad->mmio_base + KPDR);
+               reg_val &= ~(1 << (8 + col));
+               writew(reg_val, keypad->mmio_base + KPDR);
+
+               /* Delay added to avoid propagating the 0 from column to row
+                * when scanning. */
+               udelay(5);
+
+               /* 1s in state detect a key pressure */
+               new_state[col] = (~readw(keypad->mmio_base + KPDR)) & 0x00ff;
+       }
+
+       /* Test the state changes */
+       for (col = 0; col < MAX_MATRIX_KEY_COLS; col++) {
+               unsigned short bits_changed;
+               int code;
+
+               if ((keypad->cols_en_mask & (1 << col)) == 0)
+                       continue;
+
+               bits_changed = keypad->matrix_key_state[col] ^ new_state[col];
+               if (bits_changed == 0)
+                       continue;
+
+               changed = 1;
+               for (row = 0; row < MAX_MATRIX_KEY_ROWS; row++) {
+                       if ((keypad->rows_en_mask & (1 << row)) == 0)
+                               continue;
+                       if ((bits_changed & (1 << row)) == 0)
+                               continue;
+
+                       code = MATRIX_SCAN_CODE(row, col, MATRIX_ROW_SHIFT);
+                       input_event(input_dev, EV_MSC, MSC_SCAN, code);
+                       input_report_key(input_dev, keypad->keycodes[code],
+                                        new_state[col] & (1 << row));
+                       dev_dbg(&input_dev->dev, "Event code: %d, val: %d",
+                                       keypad->keycodes[code],
+                                       new_state[col] & (1 << row));
+               }
+       }
+       input_sync(input_dev);
+       memcpy(keypad->matrix_key_state, new_state, sizeof(new_state));
+
+       /* Return in standby mode:
+        * 9. write 0s to columns */
+       reg_val = readw(keypad->mmio_base + KPDR);
+       reg_val &= 0x00ff;
+       writew(reg_val, keypad->mmio_base + KPDR);
+
+       return changed;
+}
+
+static irqreturn_t mxc_keypad_irq_handler_thread(int irq, void *dev_id)
+{
+       struct mxc_keypad *keypad = dev_id;
+       struct mxc_keypad_platform_data *pdata = keypad->pdata;
+       struct input_dev *idev = keypad->input_dev;
+       unsigned short reg_val;
+
+       dev_dbg(&idev->dev, "Handling Interrupt of type %x\n",
+                                               keypad->irq_type);
+
+       /* Wait till debounce */
+       msleep(pdata->debounce_ms);
+
+       /* Do the scan routine and Keep track for how many time we got
+        * interrupt that make no change */
+       if (mxc_keypad_scan_matrix(keypad))
+               keypad->irq_since_last_change = 0;
+       else
+               keypad->irq_since_last_change++;
+
+       /* If the key is pressed since too many time, relax the update period */
+       if (keypad->irq_since_last_change > 2)
+               msleep(100);
+
+       /* 10. Clear KPKD and KPKR status bits
+        *     Set the KPKR sync chain and clear the KPKD sync chain */
+       reg_val = readw(keypad->mmio_base + KPSR);
+       reg_val |= KBD_STAT_KPKD | KBD_STAT_KPKR |
+                  KBD_STAT_KDSC | KBD_STAT_KRSS;
+       writew(reg_val, keypad->mmio_base + KPSR);
+
+       /* Re enable interrupts and clear sync reset bits.
+        * Next KDI is used for detect multiple pressures. */
+       reg_val = readw(keypad->mmio_base + KPSR);
+       reg_val &= ~(KBD_STAT_KDSC | KBD_STAT_KRSS);
+       writew(reg_val, keypad->mmio_base + KPSR);
+
+       reg_val |= KBD_STAT_KDIE | KBD_STAT_KRIE;
+       if (keypad->irq_type == MXC_IRQ_KRI)
+               reg_val &= ~KBD_STAT_KRIE;
+       writew(reg_val, keypad->mmio_base + KPSR);
+
+       return IRQ_HANDLED;
+}
+
+static irqreturn_t mxc_keypad_irq_handler(int irq, void *dev_id)
+{
+       struct mxc_keypad *keypad = dev_id;
+       unsigned short reg_val;
+
+       /* Disable every interrupt */
+       reg_val = readw(keypad->mmio_base + KPSR);
+       reg_val &= ~(KBD_STAT_KRIE | KBD_STAT_KDIE);
+       writew(reg_val, keypad->mmio_base + KPSR);
+
+       keypad->irq_type = reg_val & KBD_STAT_KPKD ?
+                          MXC_IRQ_KDI : MXC_IRQ_KRI;
+
+       return IRQ_WAKE_THREAD;
+}
+
+static void mxc_keypad_config(struct mxc_keypad *keypad)
+{
+       unsigned short reg_val;
+
+       /* Enable number of rows in keypad (KPCR[7:0])
+        * Configure keypad columns as open-drain (KPCR[15:8])
+        */
+       reg_val = readw(keypad->mmio_base + KPCR);
+       reg_val |= keypad->rows_en_mask & 0xff;         /* rows */
+       reg_val |= (keypad->cols_en_mask & 0xff) << 8;  /* cols */
+       writew(reg_val, keypad->mmio_base + KPCR);
+
+       /* Write 0's to KPDR[15:8] (Colums)*/
+       reg_val = readw(keypad->mmio_base + KPDR);
+       reg_val &= 0x00ff;
+       writew(reg_val, keypad->mmio_base + KPDR);
+
+       /* Configure columns as output, rows as input (KDDR[15:0]) */
+       reg_val = readw(keypad->mmio_base + KDDR);
+       reg_val |= 0xff00;
+       reg_val &= 0xff00;
+       writew(reg_val, keypad->mmio_base + KDDR);
+
+       /* Clear Key Depress and Key Release status bit.
+        * Clear synchronizer chain.
+        * */
+       reg_val = readw(keypad->mmio_base + KPSR);
+       reg_val |= KBD_STAT_KPKR | KBD_STAT_KPKD |
+                  KBD_STAT_KDSC | KBD_STAT_KRSS;
+       writew(reg_val, keypad->mmio_base + KPSR);
+
+       /* Set the KDIE control bit, and clear the KRIE control bit
+        * (avoid false release events). */
+       reg_val |= KBD_STAT_KDIE;
+       reg_val &= ~KBD_STAT_KRIE;
+       writew(reg_val, keypad->mmio_base + KPSR);
+}
+
+static void mxc_keypad_inhibit(struct mxc_keypad *keypad)
+{
+       unsigned short reg_val;
+
+       /* Colums as open drain and disable rows */
+       writew(0xff00, keypad->mmio_base + KPCR);
+
+       /* Clear the KPKD status flag and synchronizer chain.
+        * Clear KDIE control bit and KRIE control bit.
+        */
+       reg_val = readw(keypad->mmio_base + KPSR);
+       reg_val |= KBD_STAT_KPKD | KBD_STAT_KDSC;
+       reg_val &= ~(KBD_STAT_KRIE | KBD_STAT_KDIE);
+       writew(reg_val, keypad->mmio_base + KPSR);
+}
+
+static int mxc_keypad_open(struct input_dev *dev)
+{
+       struct mxc_keypad *keypad = input_get_drvdata(dev);
+
+       dev_dbg(&dev->dev, "%s\n", __func__);
+
+       /* Enable unit clock */
+       clk_enable(keypad->clk);
+       mxc_keypad_config(keypad);
+
+       /* Sanity control, not all the rows must be to 0s now. */
+       if ((readw(keypad->mmio_base + KPDR) & keypad->rows_en_mask) == 0) {
+               dev_err(&dev->dev, "Too much keys pressed for now, "
+                               "control pins initialisation\n");
+               goto open_err;
+       }
+
+       return 0;
+
+open_err:
+       mxc_keypad_inhibit(keypad);
+       return -EIO;
+}
+
+static void mxc_keypad_close(struct input_dev *dev)
+{
+       struct mxc_keypad *keypad = input_get_drvdata(dev);
+
+       mxc_keypad_inhibit(keypad);
+
+       /* Disable clock unit */
+       clk_disable(keypad->clk);
+}
+
+static int __devinit mxc_keypad_probe(struct platform_device *pdev)
+{
+       struct mxc_keypad_platform_data *pdata = pdev->dev.platform_data;
+       struct mxc_keypad *keypad;
+       struct input_dev *input_dev;
+       struct resource *res;
+       int irq, error, i;
+       struct matrix_keymap_data *keymap_data;
+
+       if (pdata == NULL) {
+               dev_err(&pdev->dev, "no platform data defined\n");
+               return -EINVAL;
+       }
+
+       irq = platform_get_irq(pdev, 0);
+       if (irq < 0) {
+               dev_err(&pdev->dev, "failed to get keypad irq\n");
+               return -ENXIO;
+       }
+
+       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       if (res == NULL) {
+               dev_err(&pdev->dev, "failed to get I/O memory\n");
+               return -ENXIO;
+       }
+
+       res = request_mem_region(res->start, resource_size(res), pdev->name);
+       if (res == NULL) {
+               dev_err(&pdev->dev, "failed to request I/O memory\n");
+               return -EBUSY;
+       }
+
+       input_dev = input_allocate_device();
+       if (!input_dev) {
+               dev_err(&pdev->dev, "failed to allocate the input device\n");
+               error = -ENOMEM;
+               goto failed_rel_mem;
+       }
+
+       keypad = kzalloc(sizeof(struct mxc_keypad), GFP_KERNEL);
+       if (!keypad) {
+               dev_err(&pdev->dev, "not enough memory for driver data\n");
+               error = -ENOMEM;
+               goto failed_free_input;
+       }
+
+       keypad->pdata = pdata;
+       keypad->input_dev = input_dev;
+       keypad->irq = irq;
+
+       keypad->mmio_base = ioremap(res->start, resource_size(res));
+       if (keypad->mmio_base == NULL) {
+               dev_err(&pdev->dev, "failed to remap I/O memory\n");
+               error = -ENXIO;
+               goto failed_free_priv;
+       }
+
+       keypad->clk = clk_get(NULL, "kpp");
+       if (IS_ERR(keypad->clk)) {
+               dev_err(&pdev->dev, "failed to get keypad clock\n");
+               error = PTR_ERR(keypad->clk);
+               goto failed_unmap;
+       }
+
+       input_dev->name = pdev->name;
+       input_dev->id.bustype = BUS_HOST;
+       input_dev->dev.parent = &pdev->dev;
+       input_dev->open = mxc_keypad_open;
+       input_dev->close = mxc_keypad_close;
+       input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);
+
+       /*
+        * Search for rows and cols enabled
+        */
+       keymap_data = (struct matrix_keymap_data *) pdata->keymap_data;
+       for (i = 0; i < keymap_data->keymap_size; i++) {
+               keypad->rows_en_mask |= 1 << KEY_ROW(keymap_data->keymap[i]);
+               keypad->cols_en_mask |= 1 << KEY_COL(keymap_data->keymap[i]);
+       }
+
+       if (keypad->rows_en_mask > ((1 << MAX_MATRIX_KEY_ROWS) - 1) ||
+          keypad->cols_en_mask > ((1 << MAX_MATRIX_KEY_COLS) - 1)) {
+               dev_err(&pdev->dev, "Invalid key data (too rows or colums)\n");
+               error = -EINVAL;
+               goto failed_clock_put;
+       }
+       dev_dbg(&pdev->dev, "enabled rows mask: %x\n", keypad->rows_en_mask);
+       dev_dbg(&pdev->dev, "enabled cols mask: %x\n", keypad->cols_en_mask);
+
+       input_dev->keycode = keypad->keycodes;
+       input_dev->keycodesize = sizeof(keypad->keycodes[0]);
+       input_dev->keycodemax = ARRAY_SIZE(keypad->keycodes);
+
+       matrix_keypad_build_keymap(pdata->keymap_data, MATRIX_ROW_SHIFT,
+                               keypad->keycodes, input_dev->keybit);
+
+       input_set_capability(input_dev, EV_MSC, MSC_SCAN);
+       input_set_drvdata(input_dev, keypad);
+
+       error = request_threaded_irq(irq, mxc_keypad_irq_handler,
+                       mxc_keypad_irq_handler_thread, IRQF_DISABLED,
+                       pdev->name, keypad);
+       if (error) {
+               dev_err(&pdev->dev, "failed to request IRQ\n");
+               goto failed_clock_put;
+       }
+
+       /* Register the input device */
+       error = input_register_device(input_dev);
+       if (error) {
+               dev_err(&pdev->dev, "failed to register input device\n");
+               goto failed_free_irq;
+       }
+
+       platform_set_drvdata(pdev, keypad);
+       device_init_wakeup(&pdev->dev, 1);
+
+       dev_info(&pdev->dev, "device probed\n");
+
+       return 0;
+
+failed_free_irq:
+       free_irq(irq, pdev);
+failed_clock_put:
+       clk_put(keypad->clk);
+failed_unmap:
+       iounmap(keypad->mmio_base);
+failed_free_priv:
+       kfree(keypad);
+failed_free_input:
+       input_free_device(input_dev);
+failed_rel_mem:
+       release_mem_region(res->start, resource_size(res));
+       return error;
+}
+
+static int __devexit mxc_keypad_remove(struct platform_device *pdev)
+{
+       struct mxc_keypad *keypad = platform_get_drvdata(pdev);
+       struct resource *res;
+
+       platform_set_drvdata(pdev, NULL);
+
+       free_irq(keypad->irq, keypad);
+       clk_put(keypad->clk);
+
+       iounmap(keypad->mmio_base);
+
+       input_unregister_device(keypad->input_dev);
+
+       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       release_mem_region(res->start, resource_size(res));
+
+       kfree(keypad);
+
+       dev_info(&pdev->dev, "device removed\n");
+
+       return 0;
+}
+
+static struct platform_driver mxc_keypad_driver = {
+       .driver         = {
+               .name   = "mxc-keypad",
+               .owner  = THIS_MODULE,
+       },
+       .probe          = mxc_keypad_probe,
+       .remove         = __devexit_p(mxc_keypad_remove),
+};
+
+static int __init mxc_keypad_init(void)
+{
+       return platform_driver_register(&mxc_keypad_driver);
+}
+
+static void __exit mxc_keypad_exit(void)
+{
+       platform_driver_unregister(&mxc_keypad_driver);
+}
+
+module_init(mxc_keypad_init);
+module_exit(mxc_keypad_exit);
+
+MODULE_AUTHOR("Alberto Panizzo <maramaopercheseimorto@gmail.com>");
+MODULE_DESCRIPTION("MXC Keypad Port Driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:mxc-keypad");
-- 
1.6.3.3

^ permalink raw reply related	[flat|nested] 16+ messages in thread

* [PATCH v2] input: MXC: add mxc-keypad driver to support the Keypad Port present in the mxc application processors family.
@ 2010-01-23 10:46       ` Alberto Panizzo
  0 siblings, 0 replies; 16+ messages in thread
From: Alberto Panizzo @ 2010-01-23 10:46 UTC (permalink / raw)
  To: linux-arm-kernel

Ping.. Any comments on this Dmitry?
This should be applied to input next branch.

Regards.
Alberto!

Patch below..
--------------------------------------------------------------------------------
The MXC family of Application Processors is shipped with a Keypad Port 
supported now by this driver.

The peripheral can control up to an 8x8 matrix key pad where all the scanning 
procedure is done via software.

The hardware provide two interrupts: one for a key pressed (KDI) and one for 
all key releases (KRI). There is also a simple circuit for glitch reduction 
(said for synchronization) made by two series of 3 D-latches clocked by the 
keypad-clock that stabilize the interrupts sources.
KDI and KRI are fired only if the respective conditions are maintained for at 
last 4 keypad-clock cycle.

Those simple synchronization circuits are used also for multiple key pressures: 
between a KDI and a KRI the driver reset the sync circuit and re-enable the KDI
interrupt so after 3 keypad-clock cycle another KDI is fired making possible to
repeat the matrix scan operation.

This algorithm is done via a threaded management of the keypad interrupt source
and delayed by a proper (and longer) debounce interval controlled by the 
platform initialization.
If a key is pressed for a lot of time, the driver relaxes the interrupt repeat 
interval to not over load the cpu in a long time keypad interaction.

Changes in v2:
-This driver completely apply to matrix_keypad interface.
-Rearranged remove procedure.
-Automated hardware configuration, based on matrix_keypad_data.
-Removed useless macro.

This driver is tested to build in kernel or as a module and follow the 
specification of Freescale Application processors:
i.MX25 i.MX27 i.MX31 i.MX35 i.MX51 especially tested on i.MX31.

Signed-off-by: Alberto Panizzo <maramaopercheseimorto@gmail.com>
---
 arch/arm/plat-mxc/include/mach/mxc_keypad.h |   18 +
 drivers/input/keyboard/Kconfig              |    9 +
 drivers/input/keyboard/Makefile             |    1 +
 drivers/input/keyboard/mxc_keypad.c         |  496 +++++++++++++++++++++++++++
 4 files changed, 524 insertions(+), 0 deletions(-)
 create mode 100644 arch/arm/plat-mxc/include/mach/mxc_keypad.h
 create mode 100644 drivers/input/keyboard/mxc_keypad.c

diff --git a/arch/arm/plat-mxc/include/mach/mxc_keypad.h b/arch/arm/plat-mxc/include/mach/mxc_keypad.h
new file mode 100644
index 0000000..4509103
--- /dev/null
+++ b/arch/arm/plat-mxc/include/mach/mxc_keypad.h
@@ -0,0 +1,18 @@
+#ifndef __MACH_MXC_KEYPAD_H
+#define __MACH_MXC_KEYPAD_H
+
+#include <linux/input/matrix_keypad.h>
+
+#define MAX_MATRIX_KEY_ROWS    (8)
+#define MAX_MATRIX_KEY_COLS    (8)
+#define MATRIX_ROW_SHIFT       (3)
+
+struct mxc_keypad_platform_data {
+
+       const struct matrix_keymap_data *keymap_data;
+
+       /* key debounce interval */
+       unsigned int    debounce_ms;
+};
+
+#endif /* __MACH_MXC_KEYPAD_H */
diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig
index ee98b1b..eff32fe 100644
--- a/drivers/input/keyboard/Kconfig
+++ b/drivers/input/keyboard/Kconfig
@@ -282,6 +282,15 @@ config KEYBOARD_MAX7359
          To compile this driver as a module, choose M here: the
          module will be called max7359_keypad.
 
+config KEYBOARD_MXC
+       tristate "MXC keypad support"
+       depends on ARCH_MXC
+       help
+         Enable support for MXC keypad port.
+
+         To compile this driver as a module, choose M here: the
+         module will be called mxc_keypad.
+
 config KEYBOARD_NEWTON
        tristate "Newton keyboard"
        select SERIO
diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile
index babad5e..9c7133a 100644
--- a/drivers/input/keyboard/Makefile
+++ b/drivers/input/keyboard/Makefile
@@ -23,6 +23,7 @@ obj-$(CONFIG_KEYBOARD_LOCOMO)         += locomokbd.o
 obj-$(CONFIG_KEYBOARD_MAPLE)           += maple_keyb.o
 obj-$(CONFIG_KEYBOARD_MATRIX)          += matrix_keypad.o
 obj-$(CONFIG_KEYBOARD_MAX7359)         += max7359_keypad.o
+obj-$(CONFIG_KEYBOARD_MXC)             += mxc_keypad.o
 obj-$(CONFIG_KEYBOARD_NEWTON)          += newtonkbd.o
 obj-$(CONFIG_KEYBOARD_OMAP)            += omap-keypad.o
 obj-$(CONFIG_KEYBOARD_OPENCORES)       += opencores-kbd.o
diff --git a/drivers/input/keyboard/mxc_keypad.c b/drivers/input/keyboard/mxc_keypad.c
new file mode 100644
index 0000000..13e8823
--- /dev/null
+++ b/drivers/input/keyboard/mxc_keypad.c
@@ -0,0 +1,494 @@
+/*
+ * linux/drivers/input/keyboard/mxc_keypad.c
+ *
+ * Driver for the MXC keypad port.
+ * Copyright (C) 2009 Alberto Panizzo <maramaopercheseimorto@gmail.com>
+ *  based on Rodolfo Giometti work in pxa27x_keypad.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.
+ *
+ * >>Power management need to be implemented<<.
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/input/matrix_keypad.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#include <mach/mxc_keypad.h>
+
+/*
+ * Keypad Controller registers (halfword)
+ */
+#define KPCR           0x00 /* Keypad Control Register */
+
+#define KPSR           0x02 /* Keypad Status Register */
+#define KBD_STAT_KPKD  (0x1 << 0) /* Key Press Interrupt Status bit */
+#define KBD_STAT_KPKR  (0x1 << 1) /* Key Release Interrupt Status bit */
+#define KBD_STAT_KDSC  (0x1 << 2) /* Key Depress Synch Chain Status bit */
+#define KBD_STAT_KRSS  (0x1 << 3) /* Key Release Synch Status bit */
+#define KBD_STAT_KDIE  (0x1 << 8) /* Key Depress Interrupt Enable Status bit */
+#define KBD_STAT_KRIE  (0x1 << 9) /* Key Release Interrupt Enable */
+#define KBD_STAT_KPPEN (0x1 << 10) /* Keypad Clock Enable */
+
+#define KDDR           0x04 /* Keypad Data Direction Register */
+#define KPDR           0x06 /* Keypad Data Register */
+
+#define MAX_MATRIX_KEY_NUM     (MAX_MATRIX_KEY_ROWS * MAX_MATRIX_KEY_COLS)
+
+struct mxc_keypad {
+       struct mxc_keypad_platform_data *pdata;
+
+       struct clk *clk;
+       struct input_dev *input_dev;
+       void __iomem *mmio_base;
+
+       int                     irq;
+
+#define MXC_IRQ_KDI            1
+#define MXC_IRQ_KRI            2
+       unsigned int            irq_type;
+       int                     irq_since_last_change;
+
+       /* Masks for enabled rows/cols */
+       unsigned short          rows_en_mask;
+       unsigned short          cols_en_mask;
+
+       unsigned short          keycodes[MAX_MATRIX_KEY_NUM];
+
+       /* state row bits of each column scan */
+       unsigned short          matrix_key_state[MAX_MATRIX_KEY_COLS];
+};
+
+/* Return 0 if no changes are detected in matrix */
+static int mxc_keypad_scan_matrix(struct mxc_keypad *keypad)
+{
+       struct input_dev *input_dev = keypad->input_dev;
+       int row, col, changed = 0;
+       unsigned short new_state[MAX_MATRIX_KEY_COLS];
+       unsigned short reg_val;
+
+       memset(new_state, 0, sizeof(new_state));
+
+       for (col = 0; col < MAX_MATRIX_KEY_COLS; col++) {
+               if ((keypad->cols_en_mask & (1 << col)) == 0)
+                       continue;
+               /* Discharge keypad capacitance:
+                * 2. write 1s on column data.
+                * 3. configure columns as totem-pole to discharge capacitance.
+                * 4. configure columns as open-drain.*/
+               reg_val = readw(keypad->mmio_base + KPDR);
+               reg_val |= 0xff00;
+               writew(reg_val, keypad->mmio_base + KPDR);
+
+               reg_val = readw(keypad->mmio_base + KPCR);
+               reg_val &= ~((keypad->cols_en_mask & 0xff) << 8);
+               writew(reg_val, keypad->mmio_base + KPCR);
+
+               udelay(2);
+
+               reg_val = readw(keypad->mmio_base + KPCR);
+               reg_val |= (keypad->cols_en_mask & 0xff) << 8;
+               writew(reg_val, keypad->mmio_base + KPCR);
+
+               /*
+                * 5. Write a single column to 0, others to 1.
+                * 6. Sample row inputs and save data.
+                * 7. Repeat steps 2 - 6 for remaining columns.
+                */
+               reg_val = readw(keypad->mmio_base + KPDR);
+               reg_val &= ~(1 << (8 + col));
+               writew(reg_val, keypad->mmio_base + KPDR);
+
+               /* Delay added to avoid propagating the 0 from column to row
+                * when scanning. */
+               udelay(5);
+
+               /* 1s in state detect a key pressure */
+               new_state[col] = (~readw(keypad->mmio_base + KPDR)) & 0x00ff;
+       }
+
+       /* Test the state changes */
+       for (col = 0; col < MAX_MATRIX_KEY_COLS; col++) {
+               unsigned short bits_changed;
+               int code;
+
+               if ((keypad->cols_en_mask & (1 << col)) == 0)
+                       continue;
+
+               bits_changed = keypad->matrix_key_state[col] ^ new_state[col];
+               if (bits_changed == 0)
+                       continue;
+
+               changed = 1;
+               for (row = 0; row < MAX_MATRIX_KEY_ROWS; row++) {
+                       if ((keypad->rows_en_mask & (1 << row)) == 0)
+                               continue;
+                       if ((bits_changed & (1 << row)) == 0)
+                               continue;
+
+                       code = MATRIX_SCAN_CODE(row, col, MATRIX_ROW_SHIFT);
+                       input_event(input_dev, EV_MSC, MSC_SCAN, code);
+                       input_report_key(input_dev, keypad->keycodes[code],
+                                        new_state[col] & (1 << row));
+                       dev_dbg(&input_dev->dev, "Event code: %d, val: %d",
+                                       keypad->keycodes[code],
+                                       new_state[col] & (1 << row));
+               }
+       }
+       input_sync(input_dev);
+       memcpy(keypad->matrix_key_state, new_state, sizeof(new_state));
+
+       /* Return in standby mode:
+        * 9. write 0s to columns */
+       reg_val = readw(keypad->mmio_base + KPDR);
+       reg_val &= 0x00ff;
+       writew(reg_val, keypad->mmio_base + KPDR);
+
+       return changed;
+}
+
+static irqreturn_t mxc_keypad_irq_handler_thread(int irq, void *dev_id)
+{
+       struct mxc_keypad *keypad = dev_id;
+       struct mxc_keypad_platform_data *pdata = keypad->pdata;
+       struct input_dev *idev = keypad->input_dev;
+       unsigned short reg_val;
+
+       dev_dbg(&idev->dev, "Handling Interrupt of type %x\n",
+                                               keypad->irq_type);
+
+       /* Wait till debounce */
+       msleep(pdata->debounce_ms);
+
+       /* Do the scan routine and Keep track for how many time we got
+        * interrupt that make no change */
+       if (mxc_keypad_scan_matrix(keypad))
+               keypad->irq_since_last_change = 0;
+       else
+               keypad->irq_since_last_change++;
+
+       /* If the key is pressed since too many time, relax the update period */
+       if (keypad->irq_since_last_change > 2)
+               msleep(100);
+
+       /* 10. Clear KPKD and KPKR status bits
+        *     Set the KPKR sync chain and clear the KPKD sync chain */
+       reg_val = readw(keypad->mmio_base + KPSR);
+       reg_val |= KBD_STAT_KPKD | KBD_STAT_KPKR |
+                  KBD_STAT_KDSC | KBD_STAT_KRSS;
+       writew(reg_val, keypad->mmio_base + KPSR);
+
+       /* Re enable interrupts and clear sync reset bits.
+        * Next KDI is used for detect multiple pressures. */
+       reg_val = readw(keypad->mmio_base + KPSR);
+       reg_val &= ~(KBD_STAT_KDSC | KBD_STAT_KRSS);
+       writew(reg_val, keypad->mmio_base + KPSR);
+
+       reg_val |= KBD_STAT_KDIE | KBD_STAT_KRIE;
+       if (keypad->irq_type == MXC_IRQ_KRI)
+               reg_val &= ~KBD_STAT_KRIE;
+       writew(reg_val, keypad->mmio_base + KPSR);
+
+       return IRQ_HANDLED;
+}
+
+static irqreturn_t mxc_keypad_irq_handler(int irq, void *dev_id)
+{
+       struct mxc_keypad *keypad = dev_id;
+       unsigned short reg_val;
+
+       /* Disable every interrupt */
+       reg_val = readw(keypad->mmio_base + KPSR);
+       reg_val &= ~(KBD_STAT_KRIE | KBD_STAT_KDIE);
+       writew(reg_val, keypad->mmio_base + KPSR);
+
+       keypad->irq_type = reg_val & KBD_STAT_KPKD ?
+                          MXC_IRQ_KDI : MXC_IRQ_KRI;
+
+       return IRQ_WAKE_THREAD;
+}
+
+static void mxc_keypad_config(struct mxc_keypad *keypad)
+{
+       unsigned short reg_val;
+
+       /* Enable number of rows in keypad (KPCR[7:0])
+        * Configure keypad columns as open-drain (KPCR[15:8])
+        */
+       reg_val = readw(keypad->mmio_base + KPCR);
+       reg_val |= keypad->rows_en_mask & 0xff;         /* rows */
+       reg_val |= (keypad->cols_en_mask & 0xff) << 8;  /* cols */
+       writew(reg_val, keypad->mmio_base + KPCR);
+
+       /* Write 0's to KPDR[15:8] (Colums)*/
+       reg_val = readw(keypad->mmio_base + KPDR);
+       reg_val &= 0x00ff;
+       writew(reg_val, keypad->mmio_base + KPDR);
+
+       /* Configure columns as output, rows as input (KDDR[15:0]) */
+       reg_val = readw(keypad->mmio_base + KDDR);
+       reg_val |= 0xff00;
+       reg_val &= 0xff00;
+       writew(reg_val, keypad->mmio_base + KDDR);
+
+       /* Clear Key Depress and Key Release status bit.
+        * Clear synchronizer chain.
+        * */
+       reg_val = readw(keypad->mmio_base + KPSR);
+       reg_val |= KBD_STAT_KPKR | KBD_STAT_KPKD |
+                  KBD_STAT_KDSC | KBD_STAT_KRSS;
+       writew(reg_val, keypad->mmio_base + KPSR);
+
+       /* Set the KDIE control bit, and clear the KRIE control bit
+        * (avoid false release events). */
+       reg_val |= KBD_STAT_KDIE;
+       reg_val &= ~KBD_STAT_KRIE;
+       writew(reg_val, keypad->mmio_base + KPSR);
+}
+
+static void mxc_keypad_inhibit(struct mxc_keypad *keypad)
+{
+       unsigned short reg_val;
+
+       /* Colums as open drain and disable rows */
+       writew(0xff00, keypad->mmio_base + KPCR);
+
+       /* Clear the KPKD status flag and synchronizer chain.
+        * Clear KDIE control bit and KRIE control bit.
+        */
+       reg_val = readw(keypad->mmio_base + KPSR);
+       reg_val |= KBD_STAT_KPKD | KBD_STAT_KDSC;
+       reg_val &= ~(KBD_STAT_KRIE | KBD_STAT_KDIE);
+       writew(reg_val, keypad->mmio_base + KPSR);
+}
+
+static int mxc_keypad_open(struct input_dev *dev)
+{
+       struct mxc_keypad *keypad = input_get_drvdata(dev);
+
+       dev_dbg(&dev->dev, "%s\n", __func__);
+
+       /* Enable unit clock */
+       clk_enable(keypad->clk);
+       mxc_keypad_config(keypad);
+
+       /* Sanity control, not all the rows must be to 0s now. */
+       if ((readw(keypad->mmio_base + KPDR) & keypad->rows_en_mask) == 0) {
+               dev_err(&dev->dev, "Too much keys pressed for now, "
+                               "control pins initialisation\n");
+               goto open_err;
+       }
+
+       return 0;
+
+open_err:
+       mxc_keypad_inhibit(keypad);
+       return -EIO;
+}
+
+static void mxc_keypad_close(struct input_dev *dev)
+{
+       struct mxc_keypad *keypad = input_get_drvdata(dev);
+
+       mxc_keypad_inhibit(keypad);
+
+       /* Disable clock unit */
+       clk_disable(keypad->clk);
+}
+
+static int __devinit mxc_keypad_probe(struct platform_device *pdev)
+{
+       struct mxc_keypad_platform_data *pdata = pdev->dev.platform_data;
+       struct mxc_keypad *keypad;
+       struct input_dev *input_dev;
+       struct resource *res;
+       int irq, error, i;
+       struct matrix_keymap_data *keymap_data;
+
+       if (pdata == NULL) {
+               dev_err(&pdev->dev, "no platform data defined\n");
+               return -EINVAL;
+       }
+
+       irq = platform_get_irq(pdev, 0);
+       if (irq < 0) {
+               dev_err(&pdev->dev, "failed to get keypad irq\n");
+               return -ENXIO;
+       }
+
+       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       if (res == NULL) {
+               dev_err(&pdev->dev, "failed to get I/O memory\n");
+               return -ENXIO;
+       }
+
+       res = request_mem_region(res->start, resource_size(res), pdev->name);
+       if (res == NULL) {
+               dev_err(&pdev->dev, "failed to request I/O memory\n");
+               return -EBUSY;
+       }
+
+       input_dev = input_allocate_device();
+       if (!input_dev) {
+               dev_err(&pdev->dev, "failed to allocate the input device\n");
+               error = -ENOMEM;
+               goto failed_rel_mem;
+       }
+
+       keypad = kzalloc(sizeof(struct mxc_keypad), GFP_KERNEL);
+       if (!keypad) {
+               dev_err(&pdev->dev, "not enough memory for driver data\n");
+               error = -ENOMEM;
+               goto failed_free_input;
+       }
+
+       keypad->pdata = pdata;
+       keypad->input_dev = input_dev;
+       keypad->irq = irq;
+
+       keypad->mmio_base = ioremap(res->start, resource_size(res));
+       if (keypad->mmio_base == NULL) {
+               dev_err(&pdev->dev, "failed to remap I/O memory\n");
+               error = -ENXIO;
+               goto failed_free_priv;
+       }
+
+       keypad->clk = clk_get(NULL, "kpp");
+       if (IS_ERR(keypad->clk)) {
+               dev_err(&pdev->dev, "failed to get keypad clock\n");
+               error = PTR_ERR(keypad->clk);
+               goto failed_unmap;
+       }
+
+       input_dev->name = pdev->name;
+       input_dev->id.bustype = BUS_HOST;
+       input_dev->dev.parent = &pdev->dev;
+       input_dev->open = mxc_keypad_open;
+       input_dev->close = mxc_keypad_close;
+       input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);
+
+       /*
+        * Search for rows and cols enabled
+        */
+       keymap_data = (struct matrix_keymap_data *) pdata->keymap_data;
+       for (i = 0; i < keymap_data->keymap_size; i++) {
+               keypad->rows_en_mask |= 1 << KEY_ROW(keymap_data->keymap[i]);
+               keypad->cols_en_mask |= 1 << KEY_COL(keymap_data->keymap[i]);
+       }
+
+       if (keypad->rows_en_mask > ((1 << MAX_MATRIX_KEY_ROWS) - 1) ||
+          keypad->cols_en_mask > ((1 << MAX_MATRIX_KEY_COLS) - 1)) {
+               dev_err(&pdev->dev, "Invalid key data (too rows or colums)\n");
+               error = -EINVAL;
+               goto failed_clock_put;
+       }
+       dev_dbg(&pdev->dev, "enabled rows mask: %x\n", keypad->rows_en_mask);
+       dev_dbg(&pdev->dev, "enabled cols mask: %x\n", keypad->cols_en_mask);
+
+       input_dev->keycode = keypad->keycodes;
+       input_dev->keycodesize = sizeof(keypad->keycodes[0]);
+       input_dev->keycodemax = ARRAY_SIZE(keypad->keycodes);
+
+       matrix_keypad_build_keymap(pdata->keymap_data, MATRIX_ROW_SHIFT,
+                               keypad->keycodes, input_dev->keybit);
+
+       input_set_capability(input_dev, EV_MSC, MSC_SCAN);
+       input_set_drvdata(input_dev, keypad);
+
+       error = request_threaded_irq(irq, mxc_keypad_irq_handler,
+                       mxc_keypad_irq_handler_thread, IRQF_DISABLED,
+                       pdev->name, keypad);
+       if (error) {
+               dev_err(&pdev->dev, "failed to request IRQ\n");
+               goto failed_clock_put;
+       }
+
+       /* Register the input device */
+       error = input_register_device(input_dev);
+       if (error) {
+               dev_err(&pdev->dev, "failed to register input device\n");
+               goto failed_free_irq;
+       }
+
+       platform_set_drvdata(pdev, keypad);
+       device_init_wakeup(&pdev->dev, 1);
+
+       dev_info(&pdev->dev, "device probed\n");
+
+       return 0;
+
+failed_free_irq:
+       free_irq(irq, pdev);
+failed_clock_put:
+       clk_put(keypad->clk);
+failed_unmap:
+       iounmap(keypad->mmio_base);
+failed_free_priv:
+       kfree(keypad);
+failed_free_input:
+       input_free_device(input_dev);
+failed_rel_mem:
+       release_mem_region(res->start, resource_size(res));
+       return error;
+}
+
+static int __devexit mxc_keypad_remove(struct platform_device *pdev)
+{
+       struct mxc_keypad *keypad = platform_get_drvdata(pdev);
+       struct resource *res;
+
+       platform_set_drvdata(pdev, NULL);
+
+       free_irq(keypad->irq, keypad);
+       clk_put(keypad->clk);
+
+       iounmap(keypad->mmio_base);
+
+       input_unregister_device(keypad->input_dev);
+
+       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       release_mem_region(res->start, resource_size(res));
+
+       kfree(keypad);
+
+       dev_info(&pdev->dev, "device removed\n");
+
+       return 0;
+}
+
+static struct platform_driver mxc_keypad_driver = {
+       .driver         = {
+               .name   = "mxc-keypad",
+               .owner  = THIS_MODULE,
+       },
+       .probe          = mxc_keypad_probe,
+       .remove         = __devexit_p(mxc_keypad_remove),
+};
+
+static int __init mxc_keypad_init(void)
+{
+       return platform_driver_register(&mxc_keypad_driver);
+}
+
+static void __exit mxc_keypad_exit(void)
+{
+       platform_driver_unregister(&mxc_keypad_driver);
+}
+
+module_init(mxc_keypad_init);
+module_exit(mxc_keypad_exit);
+
+MODULE_AUTHOR("Alberto Panizzo <maramaopercheseimorto@gmail.com>");
+MODULE_DESCRIPTION("MXC Keypad Port Driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:mxc-keypad");
-- 
1.6.3.3

^ permalink raw reply related	[flat|nested] 16+ messages in thread

* Re: [PATCH v2] input: MXC: add mxc-keypad driver to support the Keypad Port present in the mxc application processors family.
  2010-01-16 17:48     ` Alberto Panizzo
@ 2010-01-26  9:52       ` Dmitry Torokhov
  -1 siblings, 0 replies; 16+ messages in thread
From: Dmitry Torokhov @ 2010-01-26  9:52 UTC (permalink / raw)
  To: Alberto Panizzo
  Cc: H Hartley Sweeten, linux-input, linux-kernel,
	linux-arm-kernel-infradead, Sascha linux-arm

Hi Alberto,

On Sat, Jan 16, 2010 at 06:48:45PM +0100, Alberto Panizzo wrote:
> Version 2 for this driver proposal.
> --------------------------------------------------------------------------------
> The MXC family of Application Processors is shipped with a Keypad Port 
> supported now by this driver.
> 
> The peripheral can control up to an 8x8 matrix key pad where all the scanning 
> procedure is done via software.
> 
> The hardware provide two interrupts: one for a key pressed (KDI) and one for 
> all key releases (KRI). There is also a simple circuit for glitch reduction 
> (said for synchronization) made by two series of 3 D-latches clocked by the 
> keypad-clock that stabilize the interrupts sources.
> KDI and KRI are fired only if the respective conditions are maintained for at 
> last 4 keypad-clock cycle.
> 
> Those simple synchronization circuits are used also for multiple key pressures: 
> between a KDI and a KRI the driver reset the sync circuit and re-enable the KDI
> interrupt so after 3 keypad-clock cycle another KDI is fired making possible to
> repeat the matrix scan operation.
> 

Nicely looking driver, thank you.

> This algorithm is done via a threaded management of the keypad interrupt source
> and delayed by a proper (and longer) debounce interval controlled by the 
> platform initialization.

This I am not so sure about - the core of the matrix scan routine does
not sleep so I wonder if starting a separate thread is not too wasteful
in this case - you can easily do whan you want with a timer, no?

> +
> +	/*
> +	 * Search for rows and cols enabled
> +	 */
> +	keymap_data = (struct matrix_keymap_data *) pdata->keymap_data;

Why do you need to cast away constness instead of declaring keymap_data
as const pointer?

-- 
Dmitry

^ permalink raw reply	[flat|nested] 16+ messages in thread

* [PATCH v2] input: MXC: add mxc-keypad driver to support the Keypad Port present in the mxc application processors family.
@ 2010-01-26  9:52       ` Dmitry Torokhov
  0 siblings, 0 replies; 16+ messages in thread
From: Dmitry Torokhov @ 2010-01-26  9:52 UTC (permalink / raw)
  To: linux-arm-kernel

Hi Alberto,

On Sat, Jan 16, 2010 at 06:48:45PM +0100, Alberto Panizzo wrote:
> Version 2 for this driver proposal.
> --------------------------------------------------------------------------------
> The MXC family of Application Processors is shipped with a Keypad Port 
> supported now by this driver.
> 
> The peripheral can control up to an 8x8 matrix key pad where all the scanning 
> procedure is done via software.
> 
> The hardware provide two interrupts: one for a key pressed (KDI) and one for 
> all key releases (KRI). There is also a simple circuit for glitch reduction 
> (said for synchronization) made by two series of 3 D-latches clocked by the 
> keypad-clock that stabilize the interrupts sources.
> KDI and KRI are fired only if the respective conditions are maintained for at 
> last 4 keypad-clock cycle.
> 
> Those simple synchronization circuits are used also for multiple key pressures: 
> between a KDI and a KRI the driver reset the sync circuit and re-enable the KDI
> interrupt so after 3 keypad-clock cycle another KDI is fired making possible to
> repeat the matrix scan operation.
> 

Nicely looking driver, thank you.

> This algorithm is done via a threaded management of the keypad interrupt source
> and delayed by a proper (and longer) debounce interval controlled by the 
> platform initialization.

This I am not so sure about - the core of the matrix scan routine does
not sleep so I wonder if starting a separate thread is not too wasteful
in this case - you can easily do whan you want with a timer, no?

> +
> +	/*
> +	 * Search for rows and cols enabled
> +	 */
> +	keymap_data = (struct matrix_keymap_data *) pdata->keymap_data;

Why do you need to cast away constness instead of declaring keymap_data
as const pointer?

-- 
Dmitry

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH v2] input: MXC: add mxc-keypad driver to support the Keypad Port present in the mxc application processors family.
  2010-01-26  9:52       ` Dmitry Torokhov
@ 2010-01-26 10:29         ` Alberto Panizzo
  -1 siblings, 0 replies; 16+ messages in thread
From: Alberto Panizzo @ 2010-01-26 10:29 UTC (permalink / raw)
  To: Dmitry Torokhov
  Cc: H Hartley Sweeten, linux-input, linux-kernel,
	linux-arm-kernel-infradead, Sascha linux-arm

Hi Dmitry! Thanks for reviewing!

On mar, 2010-01-26 at 01:52 -0800, Dmitry Torokhov wrote:
> > This algorithm is done via a threaded management of the keypad interrupt source
> > and delayed by a proper (and longer) debounce interval controlled by the 
> > platform initialization.
> 
> This I am not so sure about - the core of the matrix scan routine does
> not sleep so I wonder if starting a separate thread is not too wasteful
> in this case - you can easily do whan you want with a timer, no?

I'm pretty new to kernel programming and, from the university, the 
threaded way looked the better (and unique) for me..
Let me find some documentation on timers and I will restructure the 
interrupt management. Yes I need only to delay the matrix-scan activity
without waste cpu time.

> 
> > +
> > +     /*
> > +      * Search for rows and cols enabled
> > +      */
> > +     keymap_data = (struct matrix_keymap_data *) pdata->keymap_data;
> 
> Why do you need to cast away constness instead of declaring keymap_data
> as const pointer?

In next version will be fixed.

Alberto!



^ permalink raw reply	[flat|nested] 16+ messages in thread

* [PATCH v2] input: MXC: add mxc-keypad driver to support the Keypad Port present in the mxc application processors family.
@ 2010-01-26 10:29         ` Alberto Panizzo
  0 siblings, 0 replies; 16+ messages in thread
From: Alberto Panizzo @ 2010-01-26 10:29 UTC (permalink / raw)
  To: linux-arm-kernel

Hi Dmitry! Thanks for reviewing!

On mar, 2010-01-26 at 01:52 -0800, Dmitry Torokhov wrote:
> > This algorithm is done via a threaded management of the keypad interrupt source
> > and delayed by a proper (and longer) debounce interval controlled by the 
> > platform initialization.
> 
> This I am not so sure about - the core of the matrix scan routine does
> not sleep so I wonder if starting a separate thread is not too wasteful
> in this case - you can easily do whan you want with a timer, no?

I'm pretty new to kernel programming and, from the university, the 
threaded way looked the better (and unique) for me..
Let me find some documentation on timers and I will restructure the 
interrupt management. Yes I need only to delay the matrix-scan activity
without waste cpu time.

> 
> > +
> > +     /*
> > +      * Search for rows and cols enabled
> > +      */
> > +     keymap_data = (struct matrix_keymap_data *) pdata->keymap_data;
> 
> Why do you need to cast away constness instead of declaring keymap_data
> as const pointer?

In next version will be fixed.

Alberto!

^ permalink raw reply	[flat|nested] 16+ messages in thread

end of thread, other threads:[~2010-01-26 10:29 UTC | newest]

Thread overview: 16+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2010-01-08 18:58 MXC: input: add mxc-keypad driver to support the keypad interface present in the mxc application processors family Alberto Panizzo
2010-01-08 18:58 ` Alberto Panizzo
2010-01-08 20:33 ` H Hartley Sweeten
2010-01-08 20:33   ` H Hartley Sweeten
2010-01-08 20:33   ` H Hartley Sweeten
2010-01-16 17:48   ` [PATCH v2] input: MXC: add mxc-keypad driver to support the Keypad Port " Alberto Panizzo
2010-01-16 17:48     ` Alberto Panizzo
2010-01-21 16:17     ` Alberto Panizzo
2010-01-21 16:17       ` Alberto Panizzo
2010-01-23 10:46     ` Alberto Panizzo
2010-01-23 10:46       ` Alberto Panizzo
2010-01-23 10:46       ` Alberto Panizzo
2010-01-26  9:52     ` Dmitry Torokhov
2010-01-26  9:52       ` Dmitry Torokhov
2010-01-26 10:29       ` Alberto Panizzo
2010-01-26 10:29         ` Alberto Panizzo

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.