diff mbox

[1/4,INPUT,KEYBOARD] Samsung keypad driver support

Message ID 1252494663-17624-1-git-send-email-jsgood.yang@samsung.com (mailing list archive)
State New, archived
Headers show

Commit Message

양진성 Sept. 9, 2009, 11:11 a.m. UTC
From: Jinsung Yang <jsgood.yang@samsung.com>

Add support for Samsung matrix keypad driver

Signed-off-by: Jinsung Yang <jsgood.yang@samsung.com>
Signed-off-by: Kyeongil Kim <ki0351.kim@samsung.com>
---
 drivers/input/keyboard/Kconfig      |    6 +
 drivers/input/keyboard/Makefile     |    1 +
 drivers/input/keyboard/s3c-keypad.c |  446 +++++++++++++++++++++++++++++++++++
 3 files changed, 453 insertions(+), 0 deletions(-)
 create mode 100644 drivers/input/keyboard/s3c-keypad.c

Comments

Kyungmin Park Sept. 9, 2009, 11:21 a.m. UTC | #1
Hi,

Simple typo at CONFIG_PM parameters. see below

Thank you,
Kyungmin Park

On Wed, Sep 9, 2009 at 8:11 PM,  <jsgood.yang@samsung.com> wrote:
> From: Jinsung Yang <jsgood.yang@samsung.com>
>
> Add support for Samsung matrix keypad driver
>
> Signed-off-by: Jinsung Yang <jsgood.yang@samsung.com>
> Signed-off-by: Kyeongil Kim <ki0351.kim@samsung.com>
> ---
>  drivers/input/keyboard/Kconfig      |    6 +
>  drivers/input/keyboard/Makefile     |    1 +
>  drivers/input/keyboard/s3c-keypad.c |  446 +++++++++++++++++++++++++++++++++++
>  3 files changed, 453 insertions(+), 0 deletions(-)
>  create mode 100644 drivers/input/keyboard/s3c-keypad.c
>
> diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig
> index a6b989a..4a04553 100644
> --- a/drivers/input/keyboard/Kconfig
> +++ b/drivers/input/keyboard/Kconfig
> @@ -361,4 +361,10 @@ config KEYBOARD_XTKBD
>          To compile this driver as a module, choose M here: the
>          module will be called xtkbd.
>
> +config KEYBOARD_S3C
> +       tristate "Samsung S3C keypad support"
> +       depends on PLAT_S3C
> +       help
> +         Enable support for Samsung S3C keypad interface.
> +
>  endif
> diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile
> index b5b5eae..21331b8 100644
> --- a/drivers/input/keyboard/Makefile
> +++ b/drivers/input/keyboard/Makefile
> @@ -31,3 +31,4 @@ obj-$(CONFIG_KEYBOARD_STOWAWAY)               += stowaway.o
>  obj-$(CONFIG_KEYBOARD_SUNKBD)          += sunkbd.o
>  obj-$(CONFIG_KEYBOARD_TOSA)            += tosakbd.o
>  obj-$(CONFIG_KEYBOARD_XTKBD)           += xtkbd.o
> +obj-$(CONFIG_KEYBOARD_S3C)             += s3c-keypad.o
> diff --git a/drivers/input/keyboard/s3c-keypad.c b/drivers/input/keyboard/s3c-keypad.c
> new file mode 100644
> index 0000000..9436a4c
> --- /dev/null
> +++ b/drivers/input/keyboard/s3c-keypad.c
> @@ -0,0 +1,446 @@
> +/*
> + * linux/drivers/input/keyboard/s3c-keypad.c
> + *
> + * Copyright (C) 2009 Samsung Electronics
> + *
> + * Author: Jinsung Yang <jsgood.yang@samsung.com>
> + * Author: Kyeongil Kim <ki0351.kim@samsung.com>
> + *
> + * Driver for Samsung SoC matrix keypad controller.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + */
> +
> +#include <linux/module.h>
> +#include <linux/kernel.h>
> +#include <linux/init.h>
> +#include <linux/interrupt.h>
> +#include <linux/input.h>
> +#include <linux/platform_device.h>
> +#include <linux/miscdevice.h>
> +#include <linux/clk.h>
> +#include <linux/delay.h>
> +#include <linux/io.h>
> +
> +#include <mach/hardware.h>
> +#include <asm/irq.h>
> +
> +#include <plat/keypad.h>
> +#include <plat/regs-keypad.h>
> +
> +#undef DEBUG
> +
> +#define S3C_KEYPAD_MAX_ROWS 16
> +#define S3C_KEYPAD_MAX_COLS 8
> +
> +struct s3c_keypad {
> +       struct s3c_platform_keypad *pdata;
> +       unsigned short keycodes[S3C_KEYPAD_MAX_ROWS * S3C_KEYPAD_MAX_COLS];
> +       struct input_dev *dev;
> +       struct timer_list timer;
> +       void __iomem *regs;
> +       struct clk *clk;
> +       int irq;
> +       unsigned int prevmask_low;
> +       unsigned int prevmask_high;
> +       unsigned int keyifcon;
> +       unsigned int keyiffc;
> +};
> +
> +static int s3c_keypad_scan(struct s3c_keypad *keypad, u32 *keymask_low,
> +                               u32 *keymask_high)
> +{
> +       struct s3c_platform_keypad *pdata = keypad->pdata;
> +       int i, j = 0;
> +       u32 cval, rval, cfg;
> +
> +       for (i = 0; i < pdata->nr_cols; i++) {
> +               cval = readl(keypad->regs + S3C_KEYIFCOL);
> +               cval |= S3C_KEYIF_COL_DMASK;
> +               cval &= ~(1 << i);
> +               writel(cval, keypad->regs + S3C_KEYIFCOL);
> +               udelay(pdata->delay);
> +
> +               rval = ~(readl(keypad->regs + S3C_KEYIFROW)) &
> +                       S3C_KEYIF_ROW_DMASK;
> +
> +               if ((i * pdata->nr_rows) < pdata->max_masks)
> +                       *keymask_low |= (rval << (i * pdata->nr_rows));
> +               else {
> +                       *keymask_high |= (rval << (j * pdata->nr_rows));
> +                       j++;
> +               }
> +       }
> +
> +       cfg = readl(keypad->regs + S3C_KEYIFCOL);
> +       cfg &= ~S3C_KEYIF_COL_MASK_ALL;
> +       writel(cfg, keypad->regs + S3C_KEYIFCOL);
> +
> +       return 0;
> +}
> +
> +static void s3c_keypad_timer_handler(unsigned long data)
> +{
> +       struct s3c_keypad *keypad = (struct s3c_keypad *)data;
> +       struct s3c_platform_keypad *pdata = keypad->pdata;
> +       struct input_dev *input = keypad->dev;
> +       u32 keymask_low = 0, keymask_high = 0;
> +       u32 press_mask_low, press_mask_high;
> +       u32 release_mask_low, release_mask_high, code, cfg;
> +       int i;
> +
> +       s3c_keypad_scan(keypad, &keymask_low, &keymask_high);
> +
> +       if (keymask_low != keypad->prevmask_low) {
> +               press_mask_low = ((keymask_low ^ keypad->prevmask_low) &
> +                                       keymask_low);
> +               release_mask_low = ((keymask_low ^ keypad->prevmask_low) &
> +                                       keypad->prevmask_low);
> +
> +               i = 0;
> +               while (press_mask_low) {
> +                       if (press_mask_low & 1) {
> +                               code = keypad->keycodes[i];
> +                               input_report_key(input, code, 1);
> +                               dev_dbg(&input->dev, "low pressed: %d\n", i);
> +                       }
> +                       press_mask_low >>= 1;
> +                       i++;
> +               }
> +
> +               i = 0;
> +               while (release_mask_low) {
> +                       if (release_mask_low & 1) {
> +                               code = keypad->keycodes[i];
> +                               input_report_key(input, code, 0);
> +                               dev_dbg(&input->dev, "low released: %d\n", i);
> +                       }
> +                       release_mask_low >>= 1;
> +                       i++;
> +               }
> +               keypad->prevmask_low = keymask_low;
> +       }
> +
> +       if (keymask_high != keypad->prevmask_high) {
> +               press_mask_high = ((keymask_high ^ keypad->prevmask_high) &
> +                                       keymask_high);
> +               release_mask_high = ((keymask_high ^ keypad->prevmask_high) &
> +                                       keypad->prevmask_high);
> +
> +               i = 0;
> +               while (press_mask_high) {
> +                       if (press_mask_high & 1) {
> +                               code = keypad->keycodes[i + pdata->max_masks];
> +                               input_report_key(input, code, 1);
> +                               dev_dbg(&input->dev, "high pressed: %d %d\n",
> +                                       keypad->keycodes[i + pdata->max_masks],
> +                                       i);
> +                       }
> +                       press_mask_high >>= 1;
> +                       i++;
> +               }
> +
> +               i = 0;
> +               while (release_mask_high) {
> +                       if (release_mask_high & 1) {
> +                               code = keypad->keycodes[i + pdata->max_masks];
> +                               input_report_key(input, code, 0);
> +                               dev_dbg(&input->dev, "high released: %d\n",
> +                                       keypad->keycodes[i + pdata->max_masks]);
> +                       }
> +                       release_mask_high >>= 1;
> +                       i++;
> +               }
> +               keypad->prevmask_high = keymask_high;
> +       }
> +
> +       if (keymask_low | keymask_high) {
> +               mod_timer(&keypad->timer, jiffies + HZ / 10);
> +       } else {
> +               cfg = readl(keypad->regs + S3C_KEYIFCON);
> +               cfg &= ~S3C_KEYIF_CON_MASK_ALL;
> +               cfg |= (S3C_KEYIF_INT_F_EN | S3C_KEYIF_INT_R_EN |
> +                               S3C_KEYIF_DF_EN | S3C_KEYIF_FC_EN);
> +               writel(cfg, keypad->regs + S3C_KEYIFCON);
> +       }
> +}
> +
> +static irqreturn_t s3c_keypad_irq(int irq, void *dev_id)
> +{
> +       struct s3c_keypad *keypad = dev_id;
> +       u32 cfg;
> +
> +       /* disable keypad interrupt and schedule for keypad timer handler */
> +       cfg = readl(keypad->regs + S3C_KEYIFCON);
> +       cfg &= ~(S3C_KEYIF_INT_F_EN | S3C_KEYIF_INT_R_EN);
> +       writel(cfg, keypad->regs + S3C_KEYIFCON);
> +
> +       keypad->timer.expires = jiffies + (HZ / 100);
> +       mod_timer(&keypad->timer, keypad->timer.expires);
> +
> +       /* clear the keypad interrupt status */
> +       writel(S3C_KEYIF_STSCLR_CLEAR, keypad->regs + S3C_KEYIFSTSCLR);
> +
> +       return IRQ_HANDLED;
> +}
> +
> +static int s3c_keypad_open(struct input_dev *dev)
> +{
> +       struct s3c_keypad *keypad = input_get_drvdata(dev);
> +       u32 cfg;
> +
> +       clk_enable(keypad->clk);
> +
> +       /* init keypad h/w block */
> +       cfg = readl(keypad->regs + S3C_KEYIFCON);
> +       cfg &= ~S3C_KEYIF_CON_MASK_ALL;
> +       cfg |= (S3C_KEYIF_INT_F_EN | S3C_KEYIF_INT_R_EN |
> +                       S3C_KEYIF_DF_EN | S3C_KEYIF_FC_EN);
> +       writel(cfg, keypad->regs + S3C_KEYIFCON);
> +
> +       cfg = readl(keypad->regs + S3C_KEYIFFC);
> +       cfg |= S3C_KEYIF_FILTER_VAL;
> +       writel(cfg, keypad->regs + S3C_KEYIFFC);
> +
> +       cfg = readl(keypad->regs + S3C_KEYIFCOL);
> +       cfg &= ~S3C_KEYIF_COL_MASK_ALL;
> +       writel(cfg, keypad->regs + S3C_KEYIFCOL);
> +
> +       return 0;
> +}
> +
> +static void s3c_keypad_close(struct input_dev *dev)
> +{
> +       struct s3c_keypad *keypad = input_get_drvdata(dev);
> +
> +       clk_disable(keypad->clk);
> +}
> +
> +static int __devinit s3c_keypad_probe(struct platform_device *pdev)
> +{
> +       struct s3c_platform_keypad *pdata;
> +       struct s3c_keypad *keypad;
> +       struct input_dev *input;
> +       struct resource *res;
> +       int error = 0, irq, i;
> +
> +       pdata = pdev->dev.platform_data;
> +       if (pdata == NULL) {
> +               dev_err(&pdev->dev, "no platform data\n");
> +               return -EINVAL;
> +       }
> +
> +       keypad = kzalloc(sizeof(struct s3c_keypad), GFP_KERNEL);
> +       if (keypad == NULL) {
> +               dev_err(&pdev->dev, "failed to allocate driver data\n");
> +               return -ENOMEM;
> +       }
> +
> +       keypad->pdata = pdata;
> +       if (!pdata->keymap) {
> +               dev_err(&pdev->dev, "failed to get keymap array\n");
> +               goto err_keymap;
> +       }
> +
> +       memcpy(keypad->keycodes, pdata->keymap,
> +                       sizeof(pdata->keymap[0]) * pdata->max_keys);
> +
> +       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +       if (res == NULL) {
> +               dev_err(&pdev->dev, "failed to get I/O memory\n");
> +               error = -ENXIO;
> +               goto err_keymap;
> +       }
> +
> +       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 err_keymap;
> +       }
> +
> +       keypad->regs = ioremap(res->start, resource_size(res));
> +       if (keypad->regs == NULL) {
> +               dev_err(&pdev->dev, "failed to remap I/O memory\n");
> +               error = -ENXIO;
> +               goto err_map_io;
> +       }
> +
> +       keypad->clk = clk_get(&pdev->dev, "keypad");
> +       if (IS_ERR(keypad->clk)) {
> +               dev_err(&pdev->dev, "failed to get keypad clock\n");
> +               error = PTR_ERR(keypad->clk);
> +               goto err_clk;
> +       }
> +
> +       /* Create and register the input driver. */
> +       input = input_allocate_device();
> +       if (!input) {
> +               dev_err(&pdev->dev, "failed to allocate input device\n");
> +               error = -ENOMEM;
> +               goto err_alloc_input;
> +       }
> +
> +       input->name = pdev->name;
> +       input->id.bustype = BUS_HOST;
> +       input->open = s3c_keypad_open;
> +       input->close = s3c_keypad_close;
> +       input->dev.parent = &pdev->dev;
> +       input->keycode = keypad->keycodes;
> +       input->keycodesize = sizeof(keypad->keycodes[0]);
> +       input->keycodemax = ARRAY_SIZE(keypad->keycodes);
> +
> +       keypad->dev = input;
> +       input_set_drvdata(input, keypad);
> +
> +       __set_bit(EV_KEY, input->evbit);
> +       __set_bit(EV_REP, input->evbit);
> +
> +       for (i = 0; i < pdata->max_keys; i++)
> +               __set_bit(keypad->keycodes[i] & KEY_MAX, input->keybit);
> +
> +       __clear_bit(KEY_RESERVED, input->keybit);
> +
> +       irq = platform_get_irq(pdev, 0);
> +       if (irq < 0) {
> +               dev_err(&pdev->dev, "failed to get keypad irq\n");
> +               error = -ENXIO;
> +               goto err_get_irq;
> +       }
> +
> +       platform_set_drvdata(pdev, keypad);
> +
> +       error = request_irq(irq, s3c_keypad_irq, IRQF_DISABLED,
> +                           pdev->name, keypad);
> +       if (error) {
> +               dev_err(&pdev->dev, "failed to request IRQ\n");
> +               goto err_req_irq;
> +       }
> +
> +       keypad->irq = irq;
> +       setup_timer(&keypad->timer, s3c_keypad_timer_handler,
> +                       (unsigned long)keypad);
> +
> +       /* Register the input device */
> +       error = input_register_device(input);
> +       if (error) {
> +               dev_err(&pdev->dev, "failed to register input device\n");
> +               goto err_reg_input;
> +       }
> +
> +       device_init_wakeup(&pdev->dev, 1);
> +       dev_info(&pdev->dev, "Samsung Keypad Interface driver loaded\n");
> +
> +       return 0;
> +
> +err_reg_input:
> +       free_irq(irq, pdev);
> +
> +err_req_irq:
> +       platform_set_drvdata(pdev, NULL);
> +
> +err_get_irq:
> +       input_free_device(input);
> +
> +err_alloc_input:
> +       clk_put(keypad->clk);
> +
> +err_clk:
> +       iounmap(keypad->regs);
> +
> +err_map_io:
> +       release_mem_region(res->start, resource_size(res));
> +
> +err_keymap:
> +       kfree(keypad);
> +
> +       return error;
> +}
> +
> +static int __devexit s3c_keypad_remove(struct platform_device *pdev)
> +{
> +       struct s3c_keypad *keypad = platform_get_drvdata(pdev);
> +       struct resource *res;
> +
> +       free_irq(keypad->irq, pdev);
> +       clk_put(keypad->clk);
> +
> +       input_unregister_device(keypad->dev);
> +       iounmap(keypad->regs);
> +
> +       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;
> +}
> +
> +#ifdef CONFIG_PM
> +static int s3c_keypad_suspend(struct platform_device *dev, pm_message_t state)
> +{
> +       struct s3c_keypad *keypad = platform_get_drvdata(pdev);
> +
> +       keypad->keyifcon = readl(keypad->regs + S3C_KEYIFCON);
> +       keypad->keyiffc = readl(keypad->regs + S3C_KEYIFFC);
> +
> +       disable_irq(IRQ_KEYPAD);
> +       clk_disable(keypad->clk);
> +
> +       return 0;
> +}
> +
> +static int s3c_keypad_resume(struct platform_device *dev)
> +{
> +       struct s3c_keypad *keypad = platform_get_drvdata(pdev);

Where's pdev? maybe you should use 'pdev' instead of 'dev' at parameter.

> +
> +       clk_enable(keypad->clock);
> +
> +       writel(keypad->keyifcon, keypad->regs + S3C_KEYIFCON);
> +       writel(keypad->keyiffc, keypad->regs + S3C_KEYIFFC);
> +
> +       enable_irq(IRQ_KEYPAD);
> +
> +       return 0;
> +}
> +#else
> +#define s3c_keypad_suspend NULL
> +#define s3c_keypad_resume  NULL
> +#endif /* CONFIG_PM */
> +
> +static struct dev_pm_ops s3c_keypad_dev_pm_ops = {
> +       .suspend        = s3c_keypad_suspend,
> +       .resume         = s3c_keypad_resume,
> +};
> +
> +static struct platform_driver s3c_keypad_driver = {
> +       .probe          = s3c_keypad_probe,
> +       .remove         = s3c_keypad_remove,
> +       .driver         = {
> +               .owner  = THIS_MODULE,
> +               .name   = "s3c-keypad",
> +               .pm     = &s3c_keypad_dev_pm_ops,
> +       },
> +};
> +
> +static int __init s3c_keypad_init(void)
> +{
> +       return platform_driver_register(&s3c_keypad_driver);
> +}
> +
> +static void __exit s3c_keypad_exit(void)
> +{
> +       platform_driver_unregister(&s3c_keypad_driver);
> +}
> +
> +module_init(s3c_keypad_init);
> +module_exit(s3c_keypad_exit);
> +
> +MODULE_AUTHOR("Kyeongil, Kim <ki0351.kim@samsung.com>");
> +MODULE_AUTHOR("Jinsung, Yang <jsgood.yang@samsung.com>");
> +MODULE_LICENSE("GPL v2");
> +MODULE_DESCRIPTION("Keypad Interface Driver for Samsung SoC");
> +
> --
> 1.6.2.5
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-input" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
>
--
To unsubscribe from this list: send the line "unsubscribe linux-input" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
양진성 Sept. 9, 2009, 11:31 a.m. UTC | #2
> Simple typo at CONFIG_PM parameters. see below

Hi,
Thank you Kyungmin Park,
I will send again with fix my simple typo.

Best Regards
--
Jinsung, Yang <jsgood.yang@samsung.com>
AP Development Team
System LSI, Semiconductor Business
SAMSUNG Electronics Co., LTD


--
To unsubscribe from this list: send the line "unsubscribe linux-input" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Mark Brown Sept. 9, 2009, 11:52 a.m. UTC | #3
On Wed, Sep 09, 2009 at 08:11:03PM +0900, jsgood.yang@samsung.com wrote:

> +#ifdef CONFIG_PM
> +static int s3c_keypad_suspend(struct platform_device *dev, pm_message_t state)
> +{
> +	struct s3c_keypad *keypad = platform_get_drvdata(pdev);
> +
> +	keypad->keyifcon = readl(keypad->regs + S3C_KEYIFCON);
> +	keypad->keyiffc = readl(keypad->regs + S3C_KEYIFFC);
> +
> +	disable_irq(IRQ_KEYPAD);
> +	clk_disable(keypad->clk);
> +
> +	return 0;
> +}

This will unconditionally enable the clock but the clock is only enabled
while the input device is opened.  This may create confusion or generate
warnings with the clock API so it'd be better to avoid doing this.

A similar problem exists in the resume function.
--
To unsubscribe from this list: send the line "unsubscribe linux-input" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Pavel Machek Sept. 26, 2009, 9:54 a.m. UTC | #4
On Wed 2009-09-09 20:11:03, jsgood.yang@samsung.com wrote:
> From: Jinsung Yang <jsgood.yang@samsung.com>
> 
> Add support for Samsung matrix keypad driver

Could generic matrix keyboard driver be used instead?
							Pavel
Trilok Soni Sept. 26, 2009, 5:21 p.m. UTC | #5
Hi Pavel,

On Sat, Sep 26, 2009 at 3:24 PM, Pavel Machek <pavel@ucw.cz> wrote:
> On Wed 2009-09-09 20:11:03, jsgood.yang@samsung.com wrote:
>> From: Jinsung Yang <jsgood.yang@samsung.com>
>>
>> Add support for Samsung matrix keypad driver
>
> Could generic matrix keyboard driver be used instead?
>                                                        Pavel

Just looking at initial lines of code of this driver, matrix gpio
keypad driver doesn't fit the need, because this driver is based on
keypad controller on SoC and not through gpios.
diff mbox

Patch

diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig
index a6b989a..4a04553 100644
--- a/drivers/input/keyboard/Kconfig
+++ b/drivers/input/keyboard/Kconfig
@@ -361,4 +361,10 @@  config KEYBOARD_XTKBD
 	  To compile this driver as a module, choose M here: the
 	  module will be called xtkbd.
 
+config KEYBOARD_S3C
+	tristate "Samsung S3C keypad support"
+	depends on PLAT_S3C
+	help
+	  Enable support for Samsung S3C keypad interface.
+
 endif
diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile
index b5b5eae..21331b8 100644
--- a/drivers/input/keyboard/Makefile
+++ b/drivers/input/keyboard/Makefile
@@ -31,3 +31,4 @@  obj-$(CONFIG_KEYBOARD_STOWAWAY)		+= stowaway.o
 obj-$(CONFIG_KEYBOARD_SUNKBD)		+= sunkbd.o
 obj-$(CONFIG_KEYBOARD_TOSA)		+= tosakbd.o
 obj-$(CONFIG_KEYBOARD_XTKBD)		+= xtkbd.o
+obj-$(CONFIG_KEYBOARD_S3C)		+= s3c-keypad.o
diff --git a/drivers/input/keyboard/s3c-keypad.c b/drivers/input/keyboard/s3c-keypad.c
new file mode 100644
index 0000000..9436a4c
--- /dev/null
+++ b/drivers/input/keyboard/s3c-keypad.c
@@ -0,0 +1,446 @@ 
+/*
+ * linux/drivers/input/keyboard/s3c-keypad.c
+ *
+ * Copyright (C) 2009 Samsung Electronics
+ *
+ * Author: Jinsung Yang <jsgood.yang@samsung.com>
+ * Author: Kyeongil Kim <ki0351.kim@samsung.com>
+ *
+ * Driver for Samsung SoC matrix keypad controller.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/input.h>
+#include <linux/platform_device.h>
+#include <linux/miscdevice.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+
+#include <mach/hardware.h>
+#include <asm/irq.h>
+
+#include <plat/keypad.h>
+#include <plat/regs-keypad.h>
+
+#undef DEBUG
+
+#define S3C_KEYPAD_MAX_ROWS 16
+#define S3C_KEYPAD_MAX_COLS 8
+
+struct s3c_keypad {
+	struct s3c_platform_keypad *pdata;
+	unsigned short keycodes[S3C_KEYPAD_MAX_ROWS * S3C_KEYPAD_MAX_COLS];
+	struct input_dev *dev;
+	struct timer_list timer;
+	void __iomem *regs;
+	struct clk *clk;
+	int irq;
+	unsigned int prevmask_low;
+	unsigned int prevmask_high;
+	unsigned int keyifcon;
+	unsigned int keyiffc;
+};
+
+static int s3c_keypad_scan(struct s3c_keypad *keypad, u32 *keymask_low,
+				u32 *keymask_high)
+{
+	struct s3c_platform_keypad *pdata = keypad->pdata;
+	int i, j = 0;
+	u32 cval, rval, cfg;
+
+	for (i = 0; i < pdata->nr_cols; i++) {
+		cval = readl(keypad->regs + S3C_KEYIFCOL);
+		cval |= S3C_KEYIF_COL_DMASK;
+		cval &= ~(1 << i);
+		writel(cval, keypad->regs + S3C_KEYIFCOL);
+		udelay(pdata->delay);
+
+		rval = ~(readl(keypad->regs + S3C_KEYIFROW)) &
+			S3C_KEYIF_ROW_DMASK;
+
+		if ((i * pdata->nr_rows) < pdata->max_masks)
+			*keymask_low |= (rval << (i * pdata->nr_rows));
+		else {
+			*keymask_high |= (rval << (j * pdata->nr_rows));
+			j++;
+		}
+	}
+
+	cfg = readl(keypad->regs + S3C_KEYIFCOL);
+	cfg &= ~S3C_KEYIF_COL_MASK_ALL;
+	writel(cfg, keypad->regs + S3C_KEYIFCOL);
+
+	return 0;
+}
+
+static void s3c_keypad_timer_handler(unsigned long data)
+{
+	struct s3c_keypad *keypad = (struct s3c_keypad *)data;
+	struct s3c_platform_keypad *pdata = keypad->pdata;
+	struct input_dev *input = keypad->dev;
+	u32 keymask_low = 0, keymask_high = 0;
+	u32 press_mask_low, press_mask_high;
+	u32 release_mask_low, release_mask_high, code, cfg;
+	int i;
+
+	s3c_keypad_scan(keypad, &keymask_low, &keymask_high);
+
+	if (keymask_low != keypad->prevmask_low) {
+		press_mask_low = ((keymask_low ^ keypad->prevmask_low) &
+					keymask_low);
+		release_mask_low = ((keymask_low ^ keypad->prevmask_low) &
+					keypad->prevmask_low);
+
+		i = 0;
+		while (press_mask_low) {
+			if (press_mask_low & 1) {
+				code = keypad->keycodes[i];
+				input_report_key(input, code, 1);
+				dev_dbg(&input->dev, "low pressed: %d\n", i);
+			}
+			press_mask_low >>= 1;
+			i++;
+		}
+
+		i = 0;
+		while (release_mask_low) {
+			if (release_mask_low & 1) {
+				code = keypad->keycodes[i];
+				input_report_key(input, code, 0);
+				dev_dbg(&input->dev, "low released: %d\n", i);
+			}
+			release_mask_low >>= 1;
+			i++;
+		}
+		keypad->prevmask_low = keymask_low;
+	}
+
+	if (keymask_high != keypad->prevmask_high) {
+		press_mask_high = ((keymask_high ^ keypad->prevmask_high) &
+					keymask_high);
+		release_mask_high = ((keymask_high ^ keypad->prevmask_high) &
+					keypad->prevmask_high);
+
+		i = 0;
+		while (press_mask_high) {
+			if (press_mask_high & 1) {
+				code = keypad->keycodes[i + pdata->max_masks];
+				input_report_key(input, code, 1);
+				dev_dbg(&input->dev, "high pressed: %d %d\n",
+					keypad->keycodes[i + pdata->max_masks],
+					i);
+			}
+			press_mask_high >>= 1;
+			i++;
+		}
+
+		i = 0;
+		while (release_mask_high) {
+			if (release_mask_high & 1) {
+				code = keypad->keycodes[i + pdata->max_masks];
+				input_report_key(input, code, 0);
+				dev_dbg(&input->dev, "high released: %d\n",
+					keypad->keycodes[i + pdata->max_masks]);
+			}
+			release_mask_high >>= 1;
+			i++;
+		}
+		keypad->prevmask_high = keymask_high;
+	}
+
+	if (keymask_low | keymask_high) {
+		mod_timer(&keypad->timer, jiffies + HZ / 10);
+	} else {
+		cfg = readl(keypad->regs + S3C_KEYIFCON);
+		cfg &= ~S3C_KEYIF_CON_MASK_ALL;
+		cfg |= (S3C_KEYIF_INT_F_EN | S3C_KEYIF_INT_R_EN |
+				S3C_KEYIF_DF_EN | S3C_KEYIF_FC_EN);
+		writel(cfg, keypad->regs + S3C_KEYIFCON);
+	}
+}
+
+static irqreturn_t s3c_keypad_irq(int irq, void *dev_id)
+{
+	struct s3c_keypad *keypad = dev_id;
+	u32 cfg;
+
+	/* disable keypad interrupt and schedule for keypad timer handler */
+	cfg = readl(keypad->regs + S3C_KEYIFCON);
+	cfg &= ~(S3C_KEYIF_INT_F_EN | S3C_KEYIF_INT_R_EN);
+	writel(cfg, keypad->regs + S3C_KEYIFCON);
+
+	keypad->timer.expires = jiffies + (HZ / 100);
+	mod_timer(&keypad->timer, keypad->timer.expires);
+
+	/* clear the keypad interrupt status */
+	writel(S3C_KEYIF_STSCLR_CLEAR, keypad->regs + S3C_KEYIFSTSCLR);
+
+	return IRQ_HANDLED;
+}
+
+static int s3c_keypad_open(struct input_dev *dev)
+{
+	struct s3c_keypad *keypad = input_get_drvdata(dev);
+	u32 cfg;
+
+	clk_enable(keypad->clk);
+
+	/* init keypad h/w block */
+	cfg = readl(keypad->regs + S3C_KEYIFCON);
+	cfg &= ~S3C_KEYIF_CON_MASK_ALL;
+	cfg |= (S3C_KEYIF_INT_F_EN | S3C_KEYIF_INT_R_EN |
+			S3C_KEYIF_DF_EN | S3C_KEYIF_FC_EN);
+	writel(cfg, keypad->regs + S3C_KEYIFCON);
+
+	cfg = readl(keypad->regs + S3C_KEYIFFC);
+	cfg |= S3C_KEYIF_FILTER_VAL;
+	writel(cfg, keypad->regs + S3C_KEYIFFC);
+
+	cfg = readl(keypad->regs + S3C_KEYIFCOL);
+	cfg &= ~S3C_KEYIF_COL_MASK_ALL;
+	writel(cfg, keypad->regs + S3C_KEYIFCOL);
+
+	return 0;
+}
+
+static void s3c_keypad_close(struct input_dev *dev)
+{
+	struct s3c_keypad *keypad = input_get_drvdata(dev);
+
+	clk_disable(keypad->clk);
+}
+
+static int __devinit s3c_keypad_probe(struct platform_device *pdev)
+{
+	struct s3c_platform_keypad *pdata;
+	struct s3c_keypad *keypad;
+	struct input_dev *input;
+	struct resource *res;
+	int error = 0, irq, i;
+
+	pdata = pdev->dev.platform_data;
+	if (pdata == NULL) {
+		dev_err(&pdev->dev, "no platform data\n");
+		return -EINVAL;
+	}
+
+	keypad = kzalloc(sizeof(struct s3c_keypad), GFP_KERNEL);
+	if (keypad == NULL) {
+		dev_err(&pdev->dev, "failed to allocate driver data\n");
+		return -ENOMEM;
+	}
+
+	keypad->pdata = pdata;
+	if (!pdata->keymap) {
+		dev_err(&pdev->dev, "failed to get keymap array\n");
+		goto err_keymap;
+	}
+
+	memcpy(keypad->keycodes, pdata->keymap,
+			sizeof(pdata->keymap[0]) * pdata->max_keys);
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (res == NULL) {
+		dev_err(&pdev->dev, "failed to get I/O memory\n");
+		error = -ENXIO;
+		goto err_keymap;
+	}
+
+	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 err_keymap;
+	}
+
+	keypad->regs = ioremap(res->start, resource_size(res));
+	if (keypad->regs == NULL) {
+		dev_err(&pdev->dev, "failed to remap I/O memory\n");
+		error = -ENXIO;
+		goto err_map_io;
+	}
+
+	keypad->clk = clk_get(&pdev->dev, "keypad");
+	if (IS_ERR(keypad->clk)) {
+		dev_err(&pdev->dev, "failed to get keypad clock\n");
+		error = PTR_ERR(keypad->clk);
+		goto err_clk;
+	}
+
+	/* Create and register the input driver. */
+	input = input_allocate_device();
+	if (!input) {
+		dev_err(&pdev->dev, "failed to allocate input device\n");
+		error = -ENOMEM;
+		goto err_alloc_input;
+	}
+
+	input->name = pdev->name;
+	input->id.bustype = BUS_HOST;
+	input->open = s3c_keypad_open;
+	input->close = s3c_keypad_close;
+	input->dev.parent = &pdev->dev;
+	input->keycode = keypad->keycodes;
+	input->keycodesize = sizeof(keypad->keycodes[0]);
+	input->keycodemax = ARRAY_SIZE(keypad->keycodes);
+
+	keypad->dev = input;
+	input_set_drvdata(input, keypad);
+
+	__set_bit(EV_KEY, input->evbit);
+	__set_bit(EV_REP, input->evbit);
+
+	for (i = 0; i < pdata->max_keys; i++)
+		__set_bit(keypad->keycodes[i] & KEY_MAX, input->keybit);
+
+	__clear_bit(KEY_RESERVED, input->keybit);
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0) {
+		dev_err(&pdev->dev, "failed to get keypad irq\n");
+		error = -ENXIO;
+		goto err_get_irq;
+	}
+
+	platform_set_drvdata(pdev, keypad);
+
+	error = request_irq(irq, s3c_keypad_irq, IRQF_DISABLED,
+			    pdev->name, keypad);
+	if (error) {
+		dev_err(&pdev->dev, "failed to request IRQ\n");
+		goto err_req_irq;
+	}
+
+	keypad->irq = irq;
+	setup_timer(&keypad->timer, s3c_keypad_timer_handler,
+			(unsigned long)keypad);
+
+	/* Register the input device */
+	error = input_register_device(input);
+	if (error) {
+		dev_err(&pdev->dev, "failed to register input device\n");
+		goto err_reg_input;
+	}
+
+	device_init_wakeup(&pdev->dev, 1);
+	dev_info(&pdev->dev, "Samsung Keypad Interface driver loaded\n");
+
+	return 0;
+
+err_reg_input:
+	free_irq(irq, pdev);
+
+err_req_irq:
+	platform_set_drvdata(pdev, NULL);
+
+err_get_irq:
+	input_free_device(input);
+
+err_alloc_input:
+	clk_put(keypad->clk);
+
+err_clk:
+	iounmap(keypad->regs);
+
+err_map_io:
+	release_mem_region(res->start, resource_size(res));
+
+err_keymap:
+	kfree(keypad);
+
+	return error;
+}
+
+static int __devexit s3c_keypad_remove(struct platform_device *pdev)
+{
+	struct s3c_keypad *keypad = platform_get_drvdata(pdev);
+	struct resource *res;
+
+	free_irq(keypad->irq, pdev);
+	clk_put(keypad->clk);
+
+	input_unregister_device(keypad->dev);
+	iounmap(keypad->regs);
+
+	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;
+}
+
+#ifdef CONFIG_PM
+static int s3c_keypad_suspend(struct platform_device *dev, pm_message_t state)
+{
+	struct s3c_keypad *keypad = platform_get_drvdata(pdev);
+
+	keypad->keyifcon = readl(keypad->regs + S3C_KEYIFCON);
+	keypad->keyiffc = readl(keypad->regs + S3C_KEYIFFC);
+
+	disable_irq(IRQ_KEYPAD);
+	clk_disable(keypad->clk);
+
+	return 0;
+}
+
+static int s3c_keypad_resume(struct platform_device *dev)
+{
+	struct s3c_keypad *keypad = platform_get_drvdata(pdev);
+
+	clk_enable(keypad->clock);
+
+	writel(keypad->keyifcon, keypad->regs + S3C_KEYIFCON);
+	writel(keypad->keyiffc, keypad->regs + S3C_KEYIFFC);
+
+	enable_irq(IRQ_KEYPAD);
+
+	return 0;
+}
+#else
+#define s3c_keypad_suspend NULL
+#define s3c_keypad_resume  NULL
+#endif /* CONFIG_PM */
+
+static struct dev_pm_ops s3c_keypad_dev_pm_ops = {
+	.suspend	= s3c_keypad_suspend,
+	.resume		= s3c_keypad_resume,
+};
+
+static struct platform_driver s3c_keypad_driver = {
+	.probe		= s3c_keypad_probe,
+	.remove		= s3c_keypad_remove,
+	.driver		= {
+		.owner	= THIS_MODULE,
+		.name	= "s3c-keypad",
+		.pm	= &s3c_keypad_dev_pm_ops,
+	},
+};
+
+static int __init s3c_keypad_init(void)
+{
+	return platform_driver_register(&s3c_keypad_driver);
+}
+
+static void __exit s3c_keypad_exit(void)
+{
+	platform_driver_unregister(&s3c_keypad_driver);
+}
+
+module_init(s3c_keypad_init);
+module_exit(s3c_keypad_exit);
+
+MODULE_AUTHOR("Kyeongil, Kim <ki0351.kim@samsung.com>");
+MODULE_AUTHOR("Jinsung, Yang <jsgood.yang@samsung.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Keypad Interface Driver for Samsung SoC");
+