Subject: [PATCH RESEND v3 1/2] input: keyboard: Add keys driver for the LPC32xx SoC

From: [email-masked] (Rob Herring)
Date: Thu, 17 May 2012 18:31:08 -0500

On 05/16/2012 03:25 PM, Roland Stigge wrote:
> This patch adds a driver for the key scan interface of the LPC32xx SoC
> 
> Signed-off-by: Roland Stigge <[email-masked]>

For the binding,

Acked-by: Rob Herring <[email-masked]>

> 
> ---
> Applies to v3.4-rc7
> 
> Please pick this patch for the input subsystem.
> 
> Changes since v2:
> * Fixed case of lpc32XX_* -> lpc32xx_*
> * Removed unnecessary casts
> * Removed unnecessary unlikely()
> * Use unsigned for key bits
> * Included Russell's lpc32xx_mod_states() algorithm change suggestion
> * Replaced (res == NULL) with (!res)
> * clk_enable() -> clk_prepare_enable()
> * Moved clk_prepare_enable() and clk_disable_unprepare() to input's
>   open()/close() callbacks to save power
> * Handle clk_prepare_enable() return value appropriately
> * DT: Use "keypad,num-rows", "keypad,num-columns" and "linux,keymap" properties
> 
> Thanks to Axel Lin and Russell King for reviewing!
> 
>  Documentation/devicetree/bindings/input/lpc32xx-key.txt |   28 +
>  drivers/input/keyboard/Kconfig                          |   10 
>  drivers/input/keyboard/Makefile                         |    1 
>  drivers/input/keyboard/lpc32xx-keys.c                   |  382 ++++++++++++++++
>  4 files changed, 421 insertions(+)
> 
> --- /dev/null
> +++ linux-2.6/Documentation/devicetree/bindings/input/lpc32xx-key.txt
> @@ -0,0 +1,28 @@
> +NXP LPC32xx Key Scan Interface
> +
> +Required Properties:
> +- compatible: Should be "nxp,lpc3220-key"
> +- reg: Physical base address of the controller and length of memory mapped
> +  region.
> +- interrupts: The interrupt number to the cpu.
> +- keypad,num-rows: Number of rows and columns, e.g. 1: 1x1, 6: 6x6
> +- keypad,num-columns: Must be equal to keypad,num-rows since LPC32xx only
> +  supports square matrices
> +- nxp,debounce-delay-ms: Debounce delay in ms
> +- nxp,scan-delay-ms: Repeated scan period in ms
> +- linux,keymap: the key-code to be reported when the key is pressed
> +  and released, see also
> +  Documentation/devicetree/bindings/input/matrix-keymap.txt
> +
> +Example:
> +
> +	key at 40050000 {
> +		compatible = "nxp,lpc3220-key";
> +		reg = <0x40050000 0x1000>;
> +		interrupts = <54 0>;
> +		keypad,num-rows = <1>;
> +		keypad,num-columns = <1>;
> +		nxp,debounce-delay-ms = <3>;
> +		nxp,scan-delay-ms = <34>;
> +		linux,keymap = <0x00000002>;
> +	};
> --- linux-2.6.orig/drivers/input/keyboard/Kconfig
> +++ linux-2.6/drivers/input/keyboard/Kconfig
> @@ -318,6 +318,16 @@ config KEYBOARD_LOCOMO
>  	  To compile this driver as a module, choose M here: the
>  	  module will be called locomokbd.
>  
> +config KEYBOARD_LPC32XX
> +	tristate "LPC32XX matrix key scanner support"
> +	depends on ARCH_LPC32XX
> +	help
> +	  Say Y here if you want to use NXP LPC32XX SoC key scanner interface,
> +	  connected to a key matrix.
> +
> +	  To compile this driver as a module, choose M here: the
> +	  module will be called lpc32xx-keys.
> +
>  config KEYBOARD_MAPLE
>  	tristate "Maple bus keyboard"
>  	depends on SH_DREAMCAST && MAPLE
> --- linux-2.6.orig/drivers/input/keyboard/Makefile
> +++ linux-2.6/drivers/input/keyboard/Makefile
> @@ -25,6 +25,7 @@ obj-$(CONFIG_KEYBOARD_HP7XX)		+= jornada
>  obj-$(CONFIG_KEYBOARD_LKKBD)		+= lkkbd.o
>  obj-$(CONFIG_KEYBOARD_LM8323)		+= lm8323.o
>  obj-$(CONFIG_KEYBOARD_LOCOMO)		+= locomokbd.o
> +obj-$(CONFIG_KEYBOARD_LPC32XX)		+= lpc32xx-keys.o
>  obj-$(CONFIG_KEYBOARD_MAPLE)		+= maple_keyb.o
>  obj-$(CONFIG_KEYBOARD_MATRIX)		+= matrix_keypad.o
>  obj-$(CONFIG_KEYBOARD_MAX7359)		+= max7359_keypad.o
> --- /dev/null
> +++ linux-2.6/drivers/input/keyboard/lpc32xx-keys.c
> @@ -0,0 +1,382 @@
> +/*
> + * NXP LPC32xx SoC Key Scan Interface
> + *
> + * Authors:
> + *    Kevin Wells <[email-masked]>
> + *    Roland Stigge <[email-masked]>
> + *
> + * Copyright (C) 2010 NXP Semiconductors
> + * Copyright (C) 2012 Roland Stigge
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + */
> +
> +#include <linux/module.h>
> +#include <linux/interrupt.h>
> +#include <linux/slab.h>
> +#include <linux/irq.h>
> +#include <linux/pm.h>
> +#include <linux/platform_device.h>
> +#include <linux/input.h>
> +#include <linux/clk.h>
> +#include <linux/io.h>
> +#include <linux/of.h>
> +
> +/*
> + * Key scanner register offsets
> + */
> +#define LPC32XX_KS_DEB(x)			((x) + 0x00)
> +#define LPC32XX_KS_STATE_COND(x)		((x) + 0x04)
> +#define LPC32XX_KS_IRQ(x)			((x) + 0x08)
> +#define LPC32XX_KS_SCAN_CTL(x)			((x) + 0x0C)
> +#define LPC32XX_KS_FAST_TST(x)			((x) + 0x10)
> +#define LPC32XX_KS_MATRIX_DIM(x)		((x) + 0x14)
> +#define LPC32XX_KS_DATA(x, y)			((x) + 0x40 + ((y) << 2))
> +
> +#define LPC32XX_KSCAN_DEB_NUM_DEB_PASS(n)	((n) & 0xFF)
> +
> +#define LPC32XX_KSCAN_SCOND_IN_IDLE		0x0
> +#define LPC32XX_KSCAN_SCOND_IN_SCANONCE		0x1
> +#define LPC32XX_KSCAN_SCOND_IN_IRQGEN		0x2
> +#define LPC32XX_KSCAN_SCOND_IN_SCAN_MATRIX	0x3
> +
> +#define LPC32XX_KSCAN_IRQ_PENDING_CLR		0x1
> +
> +#define LPC32XX_KSCAN_SCTRL_SCAN_DELAY(n)	((n) & 0xFF)
> +
> +#define LPC32XX_KSCAN_FTST_FORCESCANONCE	0x1
> +#define LPC32XX_KSCAN_FTST_USE32K_CLK		0x2
> +
> +#define LPC32XX_KSCAN_MSEL_SELECT(n)		((n) & 0xF)
> +
> +/*
> + * Key scanner platform configuration structure
> + */
> +struct lpc32xx_kscan_cfg {
> +	u32	matrix_sz;	/* Size of matrix in XxY, ie. 3 = 3x3 */
> +	int	*keymap;	/* Pointer to key map for the scan matrix */
> +	u32	deb_clks;	/* Debounce clocks (based on 32KHz clock) */
> +	u32	scan_delay;	/* Scan delay (based on 32KHz clock) */
> +};
> +
> +struct lpc32xx_kscan_drv {
> +	struct input_dev *input;
> +	struct lpc32xx_kscan_cfg *kscancfg;
> +	struct clk *clk;
> +	void __iomem *kscan_base;
> +	int irq;
> +	u8 lastkeystates[8];
> +};
> +
> +static void lpc32xx_mod_states(struct lpc32xx_kscan_drv *kscandat, int off)
> +{
> +	u8 key;
> +	int j;
> +	unsigned changed, scancode, keycode;
> +
> +	key = readl(LPC32XX_KS_DATA(kscandat->kscan_base, off));
> +	changed = key ^ kscandat->lastkeystates[off];
> +	if (changed) {
> +		for (j = 0; j < kscandat->kscancfg->matrix_sz; j++) {
> +			if (changed & (1 << j)) {
> +				/* Key state changed, signal an event */
> +				scancode =
> +				    (j * kscandat->kscancfg->matrix_sz) + off;
> +				keycode = kscandat->kscancfg->keymap[scancode];
> +				input_report_key(kscandat->input, keycode,
> +						 key & (1 << j));
> +			}
> +		}
> +
> +		kscandat->lastkeystates[off] = key;
> +	}
> +}
> +
> +static irqreturn_t lpc32xx_kscan_irq(int irq, void *dev_id)
> +{
> +	int i;
> +	struct lpc32xx_kscan_drv *kscandat = dev_id;
> +
> +	for (i = 0; i < kscandat->kscancfg->matrix_sz; i++)
> +		lpc32xx_mod_states(kscandat, i);
> +
> +	writel(1, LPC32XX_KS_IRQ(kscandat->kscan_base));
> +
> +	input_sync(kscandat->input);
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static int lpc32xx_kscan_open(struct input_dev *dev)
> +{
> +	struct lpc32xx_kscan_drv *kscandat = input_get_drvdata(dev);
> +
> +	return clk_prepare_enable(kscandat->clk);
> +}
> +
> +static void lpc32xx_kscan_close(struct input_dev *dev)
> +{
> +	struct lpc32xx_kscan_drv *kscandat = input_get_drvdata(dev);
> +
> +	clk_disable_unprepare(kscandat->clk);
> +}
> +
> +#ifdef CONFIG_OF
> +static struct lpc32xx_kscan_cfg *lpc32xx_parse_dt(struct device *dev)
> +{
> +	struct lpc32xx_kscan_cfg *pdata;
> +	struct device_node *np = dev->of_node;
> +	int key_count;
> +	int i;
> +	u32 rows, columns;
> +	const __be32 *prop;
> +	int proplen;
> +
> +	pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
> +	if (!pdata) {
> +		dev_err(dev, "could not allocate memory for platform data\n");
> +		return NULL;
> +	}
> +
> +	of_property_read_u32(np, "keypad,num-rows", &rows);
> +	of_property_read_u32(np, "keypad,num-columns", &columns);
> +	if (!rows || rows != columns) {
> +		dev_err(dev, "rows and columns must be specified and be equal!\n");
> +		return NULL;
> +	}
> +
> +	pdata->matrix_sz = rows;
> +	key_count = rows * columns;
> +
> +	of_property_read_u32(np, "nxp,debounce-delay-ms", &pdata->deb_clks);
> +	of_property_read_u32(np, "nxp,scan-delay-ms", &pdata->scan_delay);
> +	if (!pdata->deb_clks || !pdata->scan_delay) {
> +		dev_err(dev, "debounce or scan delay not specified\n");
> +		return NULL;
> +	}
> +
> +	pdata->keymap = devm_kzalloc(dev, sizeof(int) * key_count, GFP_KERNEL);
> +	if (!pdata->keymap) {
> +		dev_err(dev, "could not allocate memory for keymap\n");
> +		return NULL;
> +	}
> +
> +	prop = of_get_property(np, "linux,keymap", &proplen);
> +	if (!prop) {
> +		dev_err(dev, "linux,keymap not specified\n");
> +		return NULL;
> +	}
> +
> +	if (proplen % sizeof(u32)) {
> +		dev_err(dev, "bad linux,keymap property size: %d\n", proplen);
> +		return NULL;
> +	}
> +	proplen /= sizeof(u32);
> +
> +	for (i = 0; i < proplen; i++) {
> +		u32 tmp = be32_to_cpup(prop + i);
> +		u32 row, column, key_code;
> +
> +		row = (tmp >> 24) & 0xff;
> +		column = (tmp >> 16) & 0xff;
> +		key_code = tmp & 0xffff;
> +		pdata->keymap[row * columns + column] = key_code;
> +	}
> +
> +	return pdata;
> +}
> +#else
> +static struct lpc32xx_kscan_cfg *lpc32xx_parse_dt(struct device *dev)
> +{
> +	return NULL;
> +}
> +#endif
> +
> +static int __devinit lpc32xx_kscan_probe(struct platform_device *pdev)
> +{
> +	struct lpc32xx_kscan_drv *kscandat;
> +	struct resource *res;
> +	int retval, i, keynum;
> +
> +	kscandat = kzalloc(sizeof(struct lpc32xx_kscan_drv), GFP_KERNEL);
> +	if (!kscandat) {
> +		dev_err(&pdev->dev, "failed to allocate memory\n");
> +		return -ENOMEM;
> +	}
> +
> +	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +	if (!res) {
> +		dev_err(&pdev->dev, "failed to get platform I/O memory\n");
> +		retval = -EBUSY;
> +		goto out1;
> +	}
> +
> +	kscandat->kscan_base = devm_request_and_ioremap(&pdev->dev, res);
> +	if (kscandat->kscan_base == NULL) {
> +		dev_err(&pdev->dev, "failed to request and remap I/O memory\n");
> +		retval = -EBUSY;
> +		goto out1;
> +	}
> +
> +	/* Get the key scanner clock */
> +	kscandat->clk = clk_get(&pdev->dev, NULL);
> +	if (IS_ERR(kscandat->clk)) {
> +		dev_err(&pdev->dev, "failed to get clock\n");
> +		retval = -ENODEV;
> +		goto out1;
> +	}
> +
> +	kscandat->irq = platform_get_irq(pdev, 0);
> +	if ((kscandat->irq < 0) || (kscandat->irq >= NR_IRQS)) {
> +		dev_err(&pdev->dev, "failed to get platform irq\n");
> +		retval = -EINVAL;
> +		goto out2;
> +	}
> +	retval = request_irq(kscandat->irq, lpc32xx_kscan_irq,
> +			     0, pdev->name, kscandat);
> +	if (retval) {
> +		dev_err(&pdev->dev, "failed to request irq\n");
> +		goto out2;
> +	}
> +
> +	kscandat->input = input_allocate_device();
> +	if (kscandat->input == NULL) {
> +		dev_err(&pdev->dev, "failed to allocate device\n");
> +		retval = -ENOMEM;
> +		goto out3;
> +	}
> +
> +	if (pdev->dev.of_node)
> +		kscandat->kscancfg = lpc32xx_parse_dt(&pdev->dev);
> +	else
> +		kscandat->kscancfg = pdev->dev.platform_data;
> +	if (!kscandat->kscancfg) {
> +		dev_err(&pdev->dev, "failed to get platform data\n");
> +		retval = -EINVAL;
> +		goto out4;
> +	}
> +
> +	platform_set_drvdata(pdev, kscandat);
> +
> +	/* Setup key input */
> +	kscandat->input->evbit[0]	= BIT_MASK(EV_KEY);
> +	kscandat->input->name		= pdev->name;
> +	kscandat->input->phys		= "matrix-keys/input0";
> +	kscandat->input->dev.parent	=  &pdev->dev;
> +	kscandat->input->id.vendor	= 0x0001;
> +	kscandat->input->id.product	= 0x0001;
> +	kscandat->input->id.version	= 0x0100;
> +	kscandat->input->open		= &lpc32xx_kscan_open;
> +	kscandat->input->close		= &lpc32xx_kscan_close;
> +	keynum = kscandat->kscancfg->matrix_sz * kscandat->kscancfg->matrix_sz;
> +	for (i = 0; i < keynum; i++)
> +		__set_bit(kscandat->kscancfg->keymap[i],
> +			kscandat->input->keybit);
> +
> +	input_set_drvdata(kscandat->input, kscandat);
> +	input_set_capability(kscandat->input, EV_MSC, MSC_SCAN);
> +
> +	retval = input_register_device(kscandat->input);
> +	if (retval) {
> +		dev_err(&pdev->dev, "failed to register input device\n");
> +		goto out4;
> +	}
> +
> +	/* Configure the key scanner */
> +	writel(kscandat->kscancfg->deb_clks,
> +		LPC32XX_KS_DEB(kscandat->kscan_base));
> +	writel(kscandat->kscancfg->scan_delay,
> +		LPC32XX_KS_SCAN_CTL(kscandat->kscan_base));
> +	writel(LPC32XX_KSCAN_FTST_USE32K_CLK,
> +		LPC32XX_KS_FAST_TST(kscandat->kscan_base));
> +	writel(kscandat->kscancfg->matrix_sz,
> +		LPC32XX_KS_MATRIX_DIM(kscandat->kscan_base));
> +	writel(1, LPC32XX_KS_IRQ(kscandat->kscan_base));
> +
> +	return 0;
> +
> +out4:
> +	input_free_device(kscandat->input);
> +out3:
> +	free_irq(kscandat->irq, pdev);
> +out2:
> +	clk_put(kscandat->clk);
> +out1:
> +	kfree(kscandat);
> +
> +	return retval;
> +}
> +
> +static int __devexit lpc32xx_kscan_remove(struct platform_device *pdev)
> +{
> +	struct lpc32xx_kscan_drv *kscandat = platform_get_drvdata(pdev);
> +
> +	free_irq(kscandat->irq, pdev);
> +	input_unregister_device(kscandat->input);
> +	clk_put(kscandat->clk);
> +	kfree(kscandat);
> +
> +	return 0;
> +}
> +
> +#ifdef CONFIG_PM
> +static int lpc32xx_kscan_suspend(struct platform_device *pdev,
> +	pm_message_t state)
> +{
> +	struct lpc32xx_kscan_drv *kscandat = platform_get_drvdata(pdev);
> +
> +	/* Clear IRQ and disable clock */
> +	writel(1, LPC32XX_KS_IRQ(kscandat->kscan_base));
> +	clk_disable_unprepare(kscandat->clk);
> +
> +	return 0;
> +}
> +
> +static int lpc32xx_kscan_resume(struct platform_device *pdev)
> +{
> +	struct lpc32xx_kscan_drv *kscandat = platform_get_drvdata(pdev);
> +
> +	/* Enable clock and clear IRQ */
> +	clk_prepare_enable(kscandat->clk);
> +	writel(1, LPC32XX_KS_IRQ(kscandat->kscan_base));
> +
> +	return 0;
> +}
> +#else
> +#define lpc32xx_kscan_suspend	NULL
> +#define lpc32xx_kscan_resume	NULL
> +#endif
> +
> +#ifdef CONFIG_OF
> +static const struct of_device_id lpc32xx_kscan_match[] = {
> +	{ .compatible = "nxp,lpc3220-key" },
> +	{},
> +};
> +MODULE_DEVICE_TABLE(of, lpc32xx_kscan_match);
> +#endif
> +
> +static struct platform_driver lpc32xx_kscan_driver = {
> +	.probe		= lpc32xx_kscan_probe,
> +	.remove		= __devexit_p(lpc32xx_kscan_remove),
> +	.suspend	= lpc32xx_kscan_suspend,
> +	.resume		= lpc32xx_kscan_resume,
> +	.driver		= {
> +		.name	= "lpc32xx_keys",
> +		.of_match_table = of_match_ptr(lpc32xx_kscan_match),
> +	}
> +};
> +
> +module_platform_driver(lpc32xx_kscan_driver);
> +
> +MODULE_LICENSE("GPL");
> +MODULE_AUTHOR("Kevin Wells <[email-masked]>");
> +MODULE_AUTHOR("Roland Stigge <[email-masked]>");
> +MODULE_DESCRIPTION("Key scanner driver for LPC32XX devices");
> _______________________________________________
> devicetree-discuss mailing list
> devicetree-discuss at lists.ozlabs.org
> https://lists.ozlabs.org/listinfo/devicetree-discuss