The i.MX21 SoC has a built-in keypad controller, which this driver utilizes. As the keypad usually doesn't connect to a standard PC keyboard, but to the keyboard of a PDA-like device, I wrote the driver in a keyboard-layout agnostic way. Instead, the driver reports each row/column key press and release via a platform-data supplied function. Board-specific code can than decode the input events from this and submit them. See the comments at the top of drivers/input/keyboard/mxc_keypad.c Signed-off-by: Holger Schurig <hs4233@xxxxxxxxxxxxxxxxxxxx> --- Changes from v1: * call release_mem_region() in error path * no IRQF_SAMPLE_RANDOM, input.c does it * removed input_free_device() after unregister_device() * added device_init_wakeup(&pdev->dev, 0) in remove path (Thanks Trilok Soni) * added release_mem_region() to mxc_keypad_remove() * removed debugging call to mxc_keypad_open() Changes from v2: * fixed clkdev name back, as input-devices don't register with a stable device name --- linux.orig/arch/arm/mach-mx2/devices.c +++ linux/arch/arm/mach-mx2/devices.c @@ -36,6 +36,7 @@ #include <mach/hardware.h> #include <mach/common.h> #include <mach/mmc.h> +#include <mach/mxc_keypad.h> #include "devices.h" @@ -446,3 +447,31 @@ { return mxc_gpio_init(imx_gpio_ports, ARRAY_SIZE(imx_gpio_ports)); } + +#ifdef CONFIG_KEYBOARD_MXC +static struct resource mxc_resource_keypad[] = { + [0] = { + .start = 0x10008000, + .end = 0x10008014, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = MXC_INT_KPP, + .end = MXC_INT_KPP, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct platform_device mxc_device_keypad = { + .name = "mxc-keypad", + .id = -1, + .resource = mxc_resource_keypad, + .num_resources = ARRAY_SIZE(mxc_resource_keypad), +}; + +struct mxc_keypad_platform_data; +void __init mxc_set_keypad_info(struct mxc_keypad_platform_data *info) +{ + mxc_register_device(&mxc_device_keypad, info); +} +#endif --- linux.orig/arch/arm/mach-mx2/devices.h +++ linux/arch/arm/mach-mx2/devices.h @@ -20,3 +20,4 @@ extern struct platform_device mxc_i2c_device1; extern struct platform_device mxc_sdhc_device0; extern struct platform_device mxc_sdhc_device1; +extern struct platform_device mxc_keypad; --- /dev/null +++ linux/arch/arm/plat-mxc/include/mach/mxc_keypad.h @@ -0,0 +1,20 @@ +#ifndef __MACH_MXC_KEYPAD_H +#define __MACH_MXC_KEYPAD_H + +#include <linux/input.h> + +#define MAX_MATRIX_KEY_ROWS (8) +#define MAX_MATRIX_KEY_COLS (8) + +struct input_dev; +struct mxc_keypad_platform_data { + u16 output_pins; + u16 input_pins; + void (*init)(struct input_dev *idev, struct platform_device *pdev); + void (*exit)(struct platform_device *pdev); + void (*handle_key)(struct input_dev *, int col, int row, int down); +}; + +extern void mxc_set_keypad_info(struct mxc_keypad_platform_data *info); + +#endif --- linux.orig/drivers/input/keyboard/Kconfig +++ linux/drivers/input/keyboard/Kconfig @@ -250,6 +250,16 @@ To compile this driver as a module, choose M here: the module will be called jornada720_kbd. +config KEYBOARD_MXC + tristate "Freescale MXC/IMX keypad support" + depends on ARCH_MXC + help + Say Y here if you have a keypad connected to your MXC/i.MX + SoC. Note that you also need board-support for this. + + To compile this driver as a module, choose M here: the + module will be called mxc_keypad. + config KEYBOARD_OMAP tristate "TI OMAP keypad support" depends on (ARCH_OMAP1 || ARCH_OMAP2) --- linux.orig/drivers/input/keyboard/Makefile +++ linux/drivers/input/keyboard/Makefile @@ -18,6 +18,7 @@ obj-$(CONFIG_KEYBOARD_TOSA) += tosakbd.o obj-$(CONFIG_KEYBOARD_HIL) += hil_kbd.o obj-$(CONFIG_KEYBOARD_HIL_OLD) += hilkbd.o +obj-$(CONFIG_KEYBOARD_MXC) += mxc_keypad.o obj-$(CONFIG_KEYBOARD_OMAP) += omap-keypad.o obj-$(CONFIG_KEYBOARD_PXA27x) += pxa27x_keypad.o obj-$(CONFIG_KEYBOARD_PXA930_ROTARY) += pxa930_rotary.o --- /dev/null +++ linux/drivers/input/keyboard/mxc_keypad.c @@ -0,0 +1,395 @@ +/* + * Driver for the i.MX/MXC matrix keyboard controller. + * + * Copyright (c) 2009 by Holger Schurig <hs4233@xxxxxxxxxxxxxxxxxxxx> + * + * 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. + */ + +/* + +This driver is just a "generic" framework, it cannot produce input events on +it's own. That's because diverse board/devices have very different +keyboards (numeric, alphanumeric, cell phone like etc). + +Therefore, you'll do the actual scancode -> input event code conversion +in a function that you'll provide in your platform data: + +static struct mxc_keypad_platform_data tt8000_keypad_data = { + .output_pins = 0x0f00, + .input_pins = 0x001f, + .init = tt8000_keypad_init, + .exit = tt8000_keypad_exit, + .handle_key = tt8000_keypad_key, +}; + +static void tt8000_keypad_init(struct input_dev *input_dev, + struct platform_device *pdev); + +This function is called before the input device registers, it can setup +primary/alternate GPIO functions and modify it, e.g. by setting +the propriate bits in input_dev->keybit. + +static void tt8000_keypad_exit(struct platform_device *pdev); + +The opposite function. + +static void tt8000_keypad_key(struct input_dev *input_dev, + int row, int col, int down); + +Called for each key press or release event. Should determine +the input event code and sent it via input_report_key(). + +*/ + + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/input.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/clk.h> +#include <linux/err.h> + +#include <asm/mach/arch.h> +#include <asm/mach/map.h> + +#include <mach/hardware.h> +#include <mach/mxc_keypad.h> + +#define KPCR 0x00 + +#define KPSR 0x02 +#define KPSR_KPP_EN (1 << 10) +#define KPSR_KRIE (1 << 9) +#define KPSR_KDIE (1 << 8) +#define KPSR_KRSS (1 << 3) +#define KPSR_KDSC (1 << 2) +#define KPSR_KPKR (1 << 1) +#define KPSR_KPKD (1 << 0) +#define KPSR_INT_MASK (KPSR_KRIE|KPSR_KDIE) + +#define KDDR 0x04 + +#define KPDR 0x06 + + +#define MAX_INPUTS 8 +#define MAX_OUTPUTS 8 + + +struct mxc_keypad { + struct mxc_keypad_platform_data *pdata; + struct resource *res; + int irq; + void __iomem *base; + + struct clk *clk; + struct input_dev *input_dev; + + u16 keystate_prev[MAX_INPUTS]; +}; + + +static void mxc_keypad_scan(unsigned long data) +{ + struct mxc_keypad *kp = (struct mxc_keypad *)data; + u16 col_bit, reg; + int i, j; + int snum = 0; + u16 keystate_cur[MAX_INPUTS]; + u16 keystate_xor[MAX_INPUTS]; + + /* write ones to KPDR8:15, setting columns data */ + __raw_writew(kp->pdata->output_pins, kp->base + KPDR); + + /* Quick-discharge by setting to totem-pole, */ + /* then turn back to open-drain */ + __raw_writew(kp->pdata->output_pins, kp->base + KPCR); + wmb(); + udelay(2); + __raw_writew(kp->pdata->output_pins | + kp->pdata->input_pins, kp->base + KPCR); + + col_bit = 1; + while (col_bit) { + if (col_bit & kp->pdata->output_pins) { + __raw_writew(~col_bit, kp->base + KPDR); + wmb(); + udelay(2); + reg = ~__raw_readw(kp->base + KPDR); + keystate_cur[snum] = reg & kp->pdata->input_pins; + + if (snum++ >= MAX_INPUTS) + break; + } + col_bit <<= 1; + } + for (i = 0; i < snum; i++) { + keystate_xor[i] = kp->keystate_prev[i] ^ keystate_cur[i]; + for (j = 0; j < MAX_OUTPUTS; j++) { + if (keystate_xor[i] & (1 << j)) { + int down = !!(keystate_cur[i] & (1 << j)); + pr_debug("i,j: %d,%d, down %d\n", i, j, down); + kp->pdata->handle_key(kp->input_dev, + i, j, down); + } + } + kp->keystate_prev[i] = keystate_cur[i]; + } + + /* set columns back to 0 */ + __raw_writew(0, kp->base + KPDR); + + /* + * Clear KPKD and KPKR status bit(s) by writing to a "1", + * set the KPKR synchronizer chain by writing "1" to KRSS register, + * clear the KPKD synchronizer chain by writing "1" to KDSC register + */ + reg = __raw_readw(kp->base + KPSR); + reg |= KPSR_KPKD | KPSR_KPKR | KPSR_KRSS | KPSR_KDSC; + __raw_writew(reg, kp->base + KPSR); +} + + +static irqreturn_t mxc_keypad_irq_handler(int irq, void *data) +{ + struct mxc_keypad *kp = data; + u16 stat; + + stat = __raw_readw(kp->base + KPSR); + /* Toggle between depress- and release-interrupt */ + stat ^= KPSR_INT_MASK; + /* clear interrupt status */ + stat |= KPSR_KPKD; + stat |= KPSR_KPKR; + __raw_writew(stat | KPSR_KPKD | KPSR_KPKR, kp->base + KPSR); + + mxc_keypad_scan((unsigned long)kp); + + pr_debug("KPCR: %04x (0f1f)\n", readw(kp->base + KPCR)); + pr_debug("KPSR: %04x (0502)\n", readw(kp->base + KPSR)); + pr_debug("KDDR: %04x (0f00)\n", readw(kp->base + KDDR)); + pr_debug("KPDR: %04x (705f)\n", readw(kp->base + KPDR)); + + return IRQ_HANDLED; +} + +#if 0 +static int mxc_keypad_open(struct input_dev *dev) +{ + struct mxc_keypad *kp = input_get_drvdata(dev); + struct clk *clk; + + /* Enable unit clock */ + clk = clk_get(&pdev->dev, "kpp"); + if (IS_ERR(clk)) + return -ENODEV; + clk_enable(clk); + + /* configure keypad */ + __raw_writew(kp->pdata->output_pins | + kp->pdata->input_pins, kp->base + KPCR); + __raw_writew(0, kp->base + KPDR); + __raw_writew(kp->pdata->output_pins, kp->base + KDDR); + __raw_writew(KPSR_KPP_EN | KPSR_KDIE | + KPSR_KPKD | KPSR_KRSS | KPSR_KDSC, kp->base + KPSR); + + return 0; +} + +static void mxc_keypad_close(struct input_dev *dev) +{ + struct mxc_keypad *kp = input_get_drvdata(dev); + u16 stat; + struct clk *clk; + + /* Mask interrupts */ + stat = __raw_readw(kp->base + KPSR); + stat &= ~KPSR_INT_MASK; + __raw_writew(stat | KPSR_KPKD | KPSR_KPKR, kp->base + KPSR); + + clk = clk_get(&dev->dev, "kpp"); + if (clk) + clk_disable(clk); +} +#endif + +static int __devinit mxc_keypad_probe(struct platform_device *pdev) +{ + struct mxc_keypad *kp; + struct input_dev *input_dev; + struct mxc_keypad_platform_data *pdata; + struct resource *res; + struct clk *clk; + int irq, error; + + pdata = pdev->dev.platform_data; + if (!pdata || !pdata->handle_key) + return -EINVAL; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + irq = platform_get_irq(pdev, 0); + if (!res || !irq) + return -ENXIO; + + res = request_mem_region(res->start, resource_size(res), pdev->name); + if (!res) + return -EBUSY; + + kp = kzalloc(sizeof(struct mxc_keypad), GFP_KERNEL); + if (!kp) { + error = -ENOMEM; + goto failed_release; + } + + kp->res = res; + kp->pdata = pdata; + + /* Create and register the input driver. */ + input_dev = kp->input_dev = input_allocate_device(); + if (!input_dev) { + dev_err(&pdev->dev, "input_allocate_device\n"); + error = -ENOMEM; + goto failed_put_clk; + } + + kp->base = ioremap(res->start, resource_size(res)); + if (!kp->base) { + dev_err(&pdev->dev, "ioremap\n"); + error = -ENOMEM; + goto failed_free_dev; + } + + clk = clk_get(&pdev->dev, "kpp"); + if (IS_ERR(clk)) { + error = -ENODEV; + goto failed_unmap; + } + + input_dev->name = pdev->name; + input_dev->dev.parent = &pdev->dev; + input_dev->id.bustype = BUS_HOST; + input_dev->id.vendor = 0x0001; + input_dev->id.product = 0x0001; + input_dev->id.version = 0x0100; + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP); + input_set_drvdata(input_dev, kp); + + platform_set_drvdata(pdev, kp); + + /* should call input_set_capability(input_dev, EV_KEY, code); */ + if (kp->pdata->init) + kp->pdata->init(input_dev, pdev); + + error = request_irq(irq, mxc_keypad_irq_handler, + IRQF_DISABLED, pdev->name, kp); + if (error) { + dev_err(&pdev->dev, "request_irq\n"); + goto failed_clk_put; + } + + kp->irq = irq; + + /* 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; + } + + device_init_wakeup(&pdev->dev, 1); + + /* configure keypad */ + clk_enable(clk); + __raw_writew(kp->pdata->output_pins | + kp->pdata->input_pins, kp->base + KPCR); + __raw_writew(0, kp->base + KPDR); + __raw_writew(kp->pdata->output_pins, kp->base + KDDR); + __raw_writew(KPSR_KPP_EN | KPSR_KDIE | + KPSR_KPKD | KPSR_KRSS | KPSR_KDSC, kp->base + KPSR); + + return 0; + +failed_free_irq: + free_irq(irq, pdev); + platform_set_drvdata(pdev, NULL); +failed_clk_put: + clk_disable(clk); + clk_put(clk); +failed_unmap: + iounmap(kp->base); +failed_free_dev: + input_free_device(input_dev); +failed_put_clk: + kfree(kp); +failed_release: + release_mem_region(res->start, resource_size(res)); + return error; +} + +static int __devexit mxc_keypad_remove(struct platform_device *pdev) +{ + struct mxc_keypad *kp = platform_get_drvdata(pdev); + + device_init_wakeup(&pdev->dev, 0); + + if (kp) { + /* Mask interrupts */ + u16 stat = __raw_readw(kp->base + KPSR); + stat &= ~KPSR_INT_MASK; + __raw_writew(stat | KPSR_KPKD | KPSR_KPKR, kp->base + KPSR); + + free_irq(kp->irq, pdev); + + if (kp->pdata->exit) + kp->pdata->exit(pdev); + + if (kp->base) + iounmap(kp->base); + + clk_disable(kp->clk); + clk_put(kp->clk); + + input_unregister_device(kp->input_dev); + release_mem_region(kp->res->start, resource_size(kp->res)); + } + + platform_set_drvdata(pdev, NULL); + kfree(kp); + return 0; +} + +/* work with hotplug and coldplug */ +MODULE_ALIAS("platform:mxc-keypad"); + +static struct platform_driver mxc_keypad_driver = { + .probe = mxc_keypad_probe, + .remove = __devexit_p(mxc_keypad_remove), + .driver = { + .name = "mxc-keypad", + .owner = THIS_MODULE, + }, +}; + +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_LICENSE("GPL"); +MODULE_DESCRIPTION("i.MX Keypad Driver"); +MODULE_AUTHOR("Holger Schurig <hs4233@@mail.mn-solutions.de>"); --- linux.orig/arch/arm/mach-mx2/clock_imx21.c +++ linux/arch/arm/mach-mx2/clock_imx21.c @@ -932,7 +932,7 @@ _REGISTER_CLOCK("imx-wdt.0", NULL, wdog_clk) _REGISTER_CLOCK(NULL, "gpio", gpio_clk) _REGISTER_CLOCK(NULL, "i2c", i2c_clk) - _REGISTER_CLOCK("mxc-keypad", NULL, kpp_clk) + _REGISTER_CLOCK(NULL, "kpp", kpp_clk) _REGISTER_CLOCK(NULL, "owire", owire_clk) _REGISTER_CLOCK(NULL, "rtc", rtc_clk) }; -- To unsubscribe from this list: send the line "unsubscribe linux-input" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html