Commit 0fffed27f92d9d7a34de9fe017b7082b5958bb93

Authored by Joonyoung Shim
Committed by Dmitry Torokhov
1 parent 1ca56e513a

Input: samsung-keypad - Add samsung keypad driver

This patch adds support for keypad driver running on Samsung cpus. This
driver is tested on GONI and Aquila board using S5PC110 cpu.

[ch.naveen@samsung.com: tested on SMDK6410, SMDKC100, and SMDKV210]
Signed-off-by: Joonyoung Shim <jy0922.shim@samsung.com>
Signed-off-by: Kyungmin Park <kyungmin.park@samsung.com>
Tested-by: Naveen Krishna Ch <ch.naveen@samsung.com>
Acked-by: Kukjin Kim <kgene.kim@samsung.com>
Signed-off-by: Dmitry Torokhov <dtor@mail.ru>

Showing 4 changed files with 544 additions and 0 deletions Side-by-side Diff

arch/arm/plat-samsung/include/plat/keypad.h
  1 +/*
  2 + * Samsung Platform - Keypad platform data definitions
  3 + *
  4 + * Copyright (C) 2010 Samsung Electronics Co.Ltd
  5 + * Author: Joonyoung Shim <jy0922.shim@samsung.com>
  6 + *
  7 + * This program is free software; you can redistribute it and/or modify it
  8 + * under the terms of the GNU General Public License as published by the
  9 + * Free Software Foundation; either version 2 of the License, or (at your
  10 + * option) any later version.
  11 + */
  12 +
  13 +#ifndef __PLAT_SAMSUNG_KEYPAD_H
  14 +#define __PLAT_SAMSUNG_KEYPAD_H
  15 +
  16 +#include <linux/input/matrix_keypad.h>
  17 +
  18 +#define SAMSUNG_MAX_ROWS 8
  19 +#define SAMSUNG_MAX_COLS 8
  20 +
  21 +/**
  22 + * struct samsung_keypad_platdata - Platform device data for Samsung Keypad.
  23 + * @keymap_data: pointer to &matrix_keymap_data.
  24 + * @rows: number of keypad row supported.
  25 + * @cols: number of keypad col supported.
  26 + * @no_autorepeat: disable key autorepeat.
  27 + * @wakeup: controls whether the device should be set up as wakeup source.
  28 + * @cfg_gpio: configure the GPIO.
  29 + *
  30 + * Initialisation data specific to either the machine or the platform
  31 + * for the device driver to use or call-back when configuring gpio.
  32 + */
  33 +struct samsung_keypad_platdata {
  34 + const struct matrix_keymap_data *keymap_data;
  35 + unsigned int rows;
  36 + unsigned int cols;
  37 + bool no_autorepeat;
  38 + bool wakeup;
  39 +
  40 + void (*cfg_gpio)(unsigned int rows, unsigned int cols);
  41 +};
  42 +
  43 +#endif /* __PLAT_SAMSUNG_KEYPAD_H */
drivers/input/keyboard/Kconfig
... ... @@ -354,6 +354,15 @@
354 354 To compile this driver as a module, choose M here: the
355 355 module will be called pxa930_rotary.
356 356  
  357 +config KEYBOARD_SAMSUNG
  358 + tristate "Samsung keypad support"
  359 + depends on SAMSUNG_DEV_KEYPAD
  360 + help
  361 + Say Y here if you want to use the Samsung keypad.
  362 +
  363 + To compile this driver as a module, choose M here: the
  364 + module will be called samsung-keypad.
  365 +
357 366 config KEYBOARD_STOWAWAY
358 367 tristate "Stowaway keyboard"
359 368 select SERIO
drivers/input/keyboard/Makefile
... ... @@ -33,6 +33,7 @@
33 33 obj-$(CONFIG_KEYBOARD_PXA27x) += pxa27x_keypad.o
34 34 obj-$(CONFIG_KEYBOARD_PXA930_ROTARY) += pxa930_rotary.o
35 35 obj-$(CONFIG_KEYBOARD_QT2160) += qt2160.o
  36 +obj-$(CONFIG_KEYBOARD_SAMSUNG) += samsung-keypad.o
36 37 obj-$(CONFIG_KEYBOARD_SH_KEYSC) += sh_keysc.o
37 38 obj-$(CONFIG_KEYBOARD_STOWAWAY) += stowaway.o
38 39 obj-$(CONFIG_KEYBOARD_SUNKBD) += sunkbd.o
drivers/input/keyboard/samsung-keypad.c
  1 +/*
  2 + * Samsung keypad driver
  3 + *
  4 + * Copyright (C) 2010 Samsung Electronics Co.Ltd
  5 + * Author: Joonyoung Shim <jy0922.shim@samsung.com>
  6 + * Author: Donghwa Lee <dh09.lee@samsung.com>
  7 + *
  8 + * This program is free software; you can redistribute it and/or modify it
  9 + * under the terms of the GNU General Public License as published by the
  10 + * Free Software Foundation; either version 2 of the License, or (at your
  11 + * option) any later version.
  12 + */
  13 +
  14 +#include <linux/clk.h>
  15 +#include <linux/delay.h>
  16 +#include <linux/err.h>
  17 +#include <linux/init.h>
  18 +#include <linux/input.h>
  19 +#include <linux/interrupt.h>
  20 +#include <linux/io.h>
  21 +#include <linux/module.h>
  22 +#include <linux/platform_device.h>
  23 +#include <linux/slab.h>
  24 +#include <linux/sched.h>
  25 +#include <plat/keypad.h>
  26 +
  27 +#define SAMSUNG_KEYIFCON 0x00
  28 +#define SAMSUNG_KEYIFSTSCLR 0x04
  29 +#define SAMSUNG_KEYIFCOL 0x08
  30 +#define SAMSUNG_KEYIFROW 0x0c
  31 +#define SAMSUNG_KEYIFFC 0x10
  32 +
  33 +/* SAMSUNG_KEYIFCON */
  34 +#define SAMSUNG_KEYIFCON_INT_F_EN (1 << 0)
  35 +#define SAMSUNG_KEYIFCON_INT_R_EN (1 << 1)
  36 +#define SAMSUNG_KEYIFCON_DF_EN (1 << 2)
  37 +#define SAMSUNG_KEYIFCON_FC_EN (1 << 3)
  38 +#define SAMSUNG_KEYIFCON_WAKEUPEN (1 << 4)
  39 +
  40 +/* SAMSUNG_KEYIFSTSCLR */
  41 +#define SAMSUNG_KEYIFSTSCLR_P_INT_MASK (0xff << 0)
  42 +#define SAMSUNG_KEYIFSTSCLR_R_INT_MASK (0xff << 8)
  43 +#define SAMSUNG_KEYIFSTSCLR_R_INT_OFFSET 8
  44 +#define S5PV210_KEYIFSTSCLR_P_INT_MASK (0x3fff << 0)
  45 +#define S5PV210_KEYIFSTSCLR_R_INT_MASK (0x3fff << 16)
  46 +#define S5PV210_KEYIFSTSCLR_R_INT_OFFSET 16
  47 +
  48 +/* SAMSUNG_KEYIFCOL */
  49 +#define SAMSUNG_KEYIFCOL_MASK (0xff << 0)
  50 +#define S5PV210_KEYIFCOLEN_MASK (0xff << 8)
  51 +
  52 +/* SAMSUNG_KEYIFROW */
  53 +#define SAMSUNG_KEYIFROW_MASK (0xff << 0)
  54 +#define S5PV210_KEYIFROW_MASK (0x3fff << 0)
  55 +
  56 +/* SAMSUNG_KEYIFFC */
  57 +#define SAMSUNG_KEYIFFC_MASK (0x3ff << 0)
  58 +
  59 +enum samsung_keypad_type {
  60 + KEYPAD_TYPE_SAMSUNG,
  61 + KEYPAD_TYPE_S5PV210,
  62 +};
  63 +
  64 +struct samsung_keypad {
  65 + struct input_dev *input_dev;
  66 + struct clk *clk;
  67 + void __iomem *base;
  68 + wait_queue_head_t wait;
  69 + bool stopped;
  70 + int irq;
  71 + unsigned int row_shift;
  72 + unsigned int rows;
  73 + unsigned int cols;
  74 + unsigned int row_state[SAMSUNG_MAX_COLS];
  75 + unsigned short keycodes[];
  76 +};
  77 +
  78 +static int samsung_keypad_is_s5pv210(struct device *dev)
  79 +{
  80 + struct platform_device *pdev = to_platform_device(dev);
  81 + enum samsung_keypad_type type =
  82 + platform_get_device_id(pdev)->driver_data;
  83 +
  84 + return type == KEYPAD_TYPE_S5PV210;
  85 +}
  86 +
  87 +static void samsung_keypad_scan(struct samsung_keypad *keypad,
  88 + unsigned int *row_state)
  89 +{
  90 + struct device *dev = keypad->input_dev->dev.parent;
  91 + unsigned int col;
  92 + unsigned int val;
  93 +
  94 + for (col = 0; col < keypad->cols; col++) {
  95 + if (samsung_keypad_is_s5pv210(dev)) {
  96 + val = S5PV210_KEYIFCOLEN_MASK;
  97 + val &= ~(1 << col) << 8;
  98 + } else {
  99 + val = SAMSUNG_KEYIFCOL_MASK;
  100 + val &= ~(1 << col);
  101 + }
  102 +
  103 + writel(val, keypad->base + SAMSUNG_KEYIFCOL);
  104 + mdelay(1);
  105 +
  106 + val = readl(keypad->base + SAMSUNG_KEYIFROW);
  107 + row_state[col] = ~val & ((1 << keypad->rows) - 1);
  108 + }
  109 +
  110 + /* KEYIFCOL reg clear */
  111 + writel(0, keypad->base + SAMSUNG_KEYIFCOL);
  112 +}
  113 +
  114 +static bool samsung_keypad_report(struct samsung_keypad *keypad,
  115 + unsigned int *row_state)
  116 +{
  117 + struct input_dev *input_dev = keypad->input_dev;
  118 + unsigned int changed;
  119 + unsigned int pressed;
  120 + unsigned int key_down = 0;
  121 + unsigned int val;
  122 + unsigned int col, row;
  123 +
  124 + for (col = 0; col < keypad->cols; col++) {
  125 + changed = row_state[col] ^ keypad->row_state[col];
  126 + key_down |= row_state[col];
  127 + if (!changed)
  128 + continue;
  129 +
  130 + for (row = 0; row < keypad->rows; row++) {
  131 + if (!(changed & (1 << row)))
  132 + continue;
  133 +
  134 + pressed = row_state[col] & (1 << row);
  135 +
  136 + dev_dbg(&keypad->input_dev->dev,
  137 + "key %s, row: %d, col: %d\n",
  138 + pressed ? "pressed" : "released", row, col);
  139 +
  140 + val = MATRIX_SCAN_CODE(row, col, keypad->row_shift);
  141 +
  142 + input_event(input_dev, EV_MSC, MSC_SCAN, val);
  143 + input_report_key(input_dev,
  144 + keypad->keycodes[val], pressed);
  145 + }
  146 + input_sync(keypad->input_dev);
  147 + }
  148 +
  149 + memcpy(keypad->row_state, row_state, sizeof(keypad->row_state));
  150 +
  151 + return key_down;
  152 +}
  153 +
  154 +static irqreturn_t samsung_keypad_irq(int irq, void *dev_id)
  155 +{
  156 + struct samsung_keypad *keypad = dev_id;
  157 + unsigned int row_state[SAMSUNG_MAX_COLS];
  158 + unsigned int val;
  159 + bool key_down;
  160 +
  161 + do {
  162 + val = readl(keypad->base + SAMSUNG_KEYIFSTSCLR);
  163 + /* Clear interrupt. */
  164 + writel(~0x0, keypad->base + SAMSUNG_KEYIFSTSCLR);
  165 +
  166 + samsung_keypad_scan(keypad, row_state);
  167 +
  168 + key_down = samsung_keypad_report(keypad, row_state);
  169 + if (key_down)
  170 + wait_event_timeout(keypad->wait, keypad->stopped,
  171 + msecs_to_jiffies(50));
  172 +
  173 + } while (key_down && !keypad->stopped);
  174 +
  175 + return IRQ_HANDLED;
  176 +}
  177 +
  178 +static void samsung_keypad_start(struct samsung_keypad *keypad)
  179 +{
  180 + unsigned int val;
  181 +
  182 + /* Tell IRQ thread that it may poll the device. */
  183 + keypad->stopped = false;
  184 +
  185 + clk_enable(keypad->clk);
  186 +
  187 + /* Enable interrupt bits. */
  188 + val = readl(keypad->base + SAMSUNG_KEYIFCON);
  189 + val |= SAMSUNG_KEYIFCON_INT_F_EN | SAMSUNG_KEYIFCON_INT_R_EN;
  190 + writel(val, keypad->base + SAMSUNG_KEYIFCON);
  191 +
  192 + /* KEYIFCOL reg clear. */
  193 + writel(0, keypad->base + SAMSUNG_KEYIFCOL);
  194 +}
  195 +
  196 +static void samsung_keypad_stop(struct samsung_keypad *keypad)
  197 +{
  198 + unsigned int val;
  199 +
  200 + /* Signal IRQ thread to stop polling and disable the handler. */
  201 + keypad->stopped = true;
  202 + wake_up(&keypad->wait);
  203 + disable_irq(keypad->irq);
  204 +
  205 + /* Clear interrupt. */
  206 + writel(~0x0, keypad->base + SAMSUNG_KEYIFSTSCLR);
  207 +
  208 + /* Disable interrupt bits. */
  209 + val = readl(keypad->base + SAMSUNG_KEYIFCON);
  210 + val &= ~(SAMSUNG_KEYIFCON_INT_F_EN | SAMSUNG_KEYIFCON_INT_R_EN);
  211 + writel(val, keypad->base + SAMSUNG_KEYIFCON);
  212 +
  213 + clk_disable(keypad->clk);
  214 +
  215 + /*
  216 + * Now that chip should not generate interrupts we can safely
  217 + * re-enable the handler.
  218 + */
  219 + enable_irq(keypad->irq);
  220 +}
  221 +
  222 +static int samsung_keypad_open(struct input_dev *input_dev)
  223 +{
  224 + struct samsung_keypad *keypad = input_get_drvdata(input_dev);
  225 +
  226 + samsung_keypad_start(keypad);
  227 +
  228 + return 0;
  229 +}
  230 +
  231 +static void samsung_keypad_close(struct input_dev *input_dev)
  232 +{
  233 + struct samsung_keypad *keypad = input_get_drvdata(input_dev);
  234 +
  235 + samsung_keypad_stop(keypad);
  236 +}
  237 +
  238 +static int __devinit samsung_keypad_probe(struct platform_device *pdev)
  239 +{
  240 + const struct samsung_keypad_platdata *pdata;
  241 + const struct matrix_keymap_data *keymap_data;
  242 + struct samsung_keypad *keypad;
  243 + struct resource *res;
  244 + struct input_dev *input_dev;
  245 + unsigned int row_shift;
  246 + unsigned int keymap_size;
  247 + int error;
  248 +
  249 + pdata = pdev->dev.platform_data;
  250 + if (!pdata) {
  251 + dev_err(&pdev->dev, "no platform data defined\n");
  252 + return -EINVAL;
  253 + }
  254 +
  255 + keymap_data = pdata->keymap_data;
  256 + if (!keymap_data) {
  257 + dev_err(&pdev->dev, "no keymap data defined\n");
  258 + return -EINVAL;
  259 + }
  260 +
  261 + if (!pdata->rows || pdata->rows > SAMSUNG_MAX_ROWS)
  262 + return -EINVAL;
  263 +
  264 + if (!pdata->cols || pdata->cols > SAMSUNG_MAX_COLS)
  265 + return -EINVAL;
  266 +
  267 + /* initialize the gpio */
  268 + if (pdata->cfg_gpio)
  269 + pdata->cfg_gpio(pdata->rows, pdata->cols);
  270 +
  271 + row_shift = get_count_order(pdata->cols);
  272 + keymap_size = (pdata->rows << row_shift) * sizeof(keypad->keycodes[0]);
  273 +
  274 + keypad = kzalloc(sizeof(*keypad) + keymap_size, GFP_KERNEL);
  275 + input_dev = input_allocate_device();
  276 + if (!keypad || !input_dev) {
  277 + error = -ENOMEM;
  278 + goto err_free_mem;
  279 + }
  280 +
  281 + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
  282 + if (!res) {
  283 + error = -ENODEV;
  284 + goto err_free_mem;
  285 + }
  286 +
  287 + keypad->base = ioremap(res->start, resource_size(res));
  288 + if (!keypad->base) {
  289 + error = -EBUSY;
  290 + goto err_free_mem;
  291 + }
  292 +
  293 + keypad->clk = clk_get(&pdev->dev, "keypad");
  294 + if (IS_ERR(keypad->clk)) {
  295 + dev_err(&pdev->dev, "failed to get keypad clk\n");
  296 + error = PTR_ERR(keypad->clk);
  297 + goto err_unmap_base;
  298 + }
  299 +
  300 + keypad->input_dev = input_dev;
  301 + keypad->row_shift = row_shift;
  302 + keypad->rows = pdata->rows;
  303 + keypad->cols = pdata->cols;
  304 + init_waitqueue_head(&keypad->wait);
  305 +
  306 + input_dev->name = pdev->name;
  307 + input_dev->id.bustype = BUS_HOST;
  308 + input_dev->dev.parent = &pdev->dev;
  309 + input_set_drvdata(input_dev, keypad);
  310 +
  311 + input_dev->open = samsung_keypad_open;
  312 + input_dev->close = samsung_keypad_close;
  313 +
  314 + input_dev->evbit[0] = BIT_MASK(EV_KEY);
  315 + if (!pdata->no_autorepeat)
  316 + input_dev->evbit[0] |= BIT_MASK(EV_REP);
  317 +
  318 + input_set_capability(input_dev, EV_MSC, MSC_SCAN);
  319 +
  320 + input_dev->keycode = keypad->keycodes;
  321 + input_dev->keycodesize = sizeof(keypad->keycodes[0]);
  322 + input_dev->keycodemax = pdata->rows << row_shift;
  323 +
  324 + matrix_keypad_build_keymap(keymap_data, row_shift,
  325 + input_dev->keycode, input_dev->keybit);
  326 +
  327 + keypad->irq = platform_get_irq(pdev, 0);
  328 + if (keypad->irq < 0) {
  329 + error = keypad->irq;
  330 + goto err_put_clk;
  331 + }
  332 +
  333 + error = request_threaded_irq(keypad->irq, NULL, samsung_keypad_irq,
  334 + IRQF_ONESHOT, dev_name(&pdev->dev), keypad);
  335 + if (error) {
  336 + dev_err(&pdev->dev, "failed to register keypad interrupt\n");
  337 + goto err_put_clk;
  338 + }
  339 +
  340 + error = input_register_device(keypad->input_dev);
  341 + if (error)
  342 + goto err_free_irq;
  343 +
  344 + device_init_wakeup(&pdev->dev, pdata->wakeup);
  345 + platform_set_drvdata(pdev, keypad);
  346 + return 0;
  347 +
  348 +err_free_irq:
  349 + free_irq(keypad->irq, keypad);
  350 +err_put_clk:
  351 + clk_put(keypad->clk);
  352 +err_unmap_base:
  353 + iounmap(keypad->base);
  354 +err_free_mem:
  355 + input_free_device(input_dev);
  356 + kfree(keypad);
  357 +
  358 + return error;
  359 +}
  360 +
  361 +static int __devexit samsung_keypad_remove(struct platform_device *pdev)
  362 +{
  363 + struct samsung_keypad *keypad = platform_get_drvdata(pdev);
  364 +
  365 + device_init_wakeup(&pdev->dev, 0);
  366 + platform_set_drvdata(pdev, NULL);
  367 +
  368 + input_unregister_device(keypad->input_dev);
  369 +
  370 + /*
  371 + * It is safe to free IRQ after unregistering device because
  372 + * samsung_keypad_close will shut off interrupts.
  373 + */
  374 + free_irq(keypad->irq, keypad);
  375 +
  376 + clk_put(keypad->clk);
  377 +
  378 + iounmap(keypad->base);
  379 + kfree(keypad);
  380 +
  381 + return 0;
  382 +}
  383 +
  384 +#ifdef CONFIG_PM
  385 +static void samsung_keypad_toggle_wakeup(struct samsung_keypad *keypad,
  386 + bool enable)
  387 +{
  388 + struct device *dev = keypad->input_dev->dev.parent;
  389 + unsigned int val;
  390 +
  391 + clk_enable(keypad->clk);
  392 +
  393 + val = readl(keypad->base + SAMSUNG_KEYIFCON);
  394 + if (enable) {
  395 + val |= SAMSUNG_KEYIFCON_WAKEUPEN;
  396 + if (device_may_wakeup(dev))
  397 + enable_irq_wake(keypad->irq);
  398 + } else {
  399 + val &= ~SAMSUNG_KEYIFCON_WAKEUPEN;
  400 + if (device_may_wakeup(dev))
  401 + disable_irq_wake(keypad->irq);
  402 + }
  403 + writel(val, keypad->base + SAMSUNG_KEYIFCON);
  404 +
  405 + clk_disable(keypad->clk);
  406 +}
  407 +
  408 +static int samsung_keypad_suspend(struct device *dev)
  409 +{
  410 + struct platform_device *pdev = to_platform_device(dev);
  411 + struct samsung_keypad *keypad = platform_get_drvdata(pdev);
  412 + struct input_dev *input_dev = keypad->input_dev;
  413 +
  414 + mutex_lock(&input_dev->mutex);
  415 +
  416 + if (input_dev->users)
  417 + samsung_keypad_stop(keypad);
  418 +
  419 + samsung_keypad_toggle_wakeup(keypad, true);
  420 +
  421 + mutex_unlock(&input_dev->mutex);
  422 +
  423 + return 0;
  424 +}
  425 +
  426 +static int samsung_keypad_resume(struct device *dev)
  427 +{
  428 + struct platform_device *pdev = to_platform_device(dev);
  429 + struct samsung_keypad *keypad = platform_get_drvdata(pdev);
  430 + struct input_dev *input_dev = keypad->input_dev;
  431 +
  432 + mutex_lock(&input_dev->mutex);
  433 +
  434 + samsung_keypad_toggle_wakeup(keypad, false);
  435 +
  436 + if (input_dev->users)
  437 + samsung_keypad_start(keypad);
  438 +
  439 + mutex_unlock(&input_dev->mutex);
  440 +
  441 + return 0;
  442 +}
  443 +
  444 +static const struct dev_pm_ops samsung_keypad_pm_ops = {
  445 + .suspend = samsung_keypad_suspend,
  446 + .resume = samsung_keypad_resume,
  447 +};
  448 +#endif
  449 +
  450 +static struct platform_device_id samsung_keypad_driver_ids[] = {
  451 + {
  452 + .name = "samsung-keypad",
  453 + .driver_data = KEYPAD_TYPE_SAMSUNG,
  454 + }, {
  455 + .name = "s5pv210-keypad",
  456 + .driver_data = KEYPAD_TYPE_S5PV210,
  457 + },
  458 + { },
  459 +};
  460 +MODULE_DEVICE_TABLE(platform, samsung_keypad_driver_ids);
  461 +
  462 +static struct platform_driver samsung_keypad_driver = {
  463 + .probe = samsung_keypad_probe,
  464 + .remove = __devexit_p(samsung_keypad_remove),
  465 + .driver = {
  466 + .name = "samsung-keypad",
  467 + .owner = THIS_MODULE,
  468 +#ifdef CONFIG_PM
  469 + .pm = &samsung_keypad_pm_ops,
  470 +#endif
  471 + },
  472 + .id_table = samsung_keypad_driver_ids,
  473 +};
  474 +
  475 +static int __init samsung_keypad_init(void)
  476 +{
  477 + return platform_driver_register(&samsung_keypad_driver);
  478 +}
  479 +module_init(samsung_keypad_init);
  480 +
  481 +static void __exit samsung_keypad_exit(void)
  482 +{
  483 + platform_driver_unregister(&samsung_keypad_driver);
  484 +}
  485 +module_exit(samsung_keypad_exit);
  486 +
  487 +MODULE_DESCRIPTION("Samsung keypad driver");
  488 +MODULE_AUTHOR("Joonyoung Shim <jy0922.shim@samsung.com>");
  489 +MODULE_AUTHOR("Donghwa Lee <dh09.lee@samsung.com>");
  490 +MODULE_LICENSE("GPL");
  491 +MODULE_ALIAS("platform:samsung-keypad");