From patchwork Mon Jun 21 06:26:45 2010 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Joonyoung Shim X-Patchwork-Id: 107134 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by demeter.kernel.org (8.14.3/8.14.3) with ESMTP id o5L6R5mp021108 for ; Mon, 21 Jun 2010 06:27:05 GMT Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1751142Ab0FUG1E (ORCPT ); Mon, 21 Jun 2010 02:27:04 -0400 Received: from mailout4.samsung.com ([203.254.224.34]:40050 "EHLO mailout4.samsung.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1750816Ab0FUG1C (ORCPT ); Mon, 21 Jun 2010 02:27:02 -0400 Received: from epmmp1 (mailout4.samsung.com [203.254.224.34]) by mailout4.samsung.com (Sun Java(tm) System Messaging Server 7u3-15.01 64bit (built Feb 12 2010)) with ESMTP id <0L4C008DDPWZEX90@mailout4.samsung.com>; Mon, 21 Jun 2010 15:26:59 +0900 (KST) Received: from TNRNDGASPAPP1.tn.corp.samsungelectronics.net ([165.213.149.150]) by mmp1.samsung.com (iPlanet Messaging Server 5.2 Patch 2 (built Jul 14 2004)) with ESMTPA id <0L4C00ADLPWZGX@mmp1.samsung.com>; Mon, 21 Jun 2010 15:26:59 +0900 (KST) Received: from localhost.localdomain ([165.213.115.179]) by TNRNDGASPAPP1.tn.corp.samsungelectronics.net with Microsoft SMTPSVC(6.0.3790.4675); Mon, 21 Jun 2010 15:26:50 +0900 Date: Mon, 21 Jun 2010 15:26:45 +0900 From: Joonyoung Shim Subject: [PATCH v5 3/3] input: samsung-keypad - Add samsung keypad driver In-reply-to: <1277101605-2435-1-git-send-email-jy0922.shim@samsung.com> To: linux-arm-kernel@lists.infradead.org Cc: linux-samsung-soc@vger.kernel.org, linux-input@vger.kernel.org, ben-linux@fluff.org, dmitry.torokhov@gmail.com, kyungmin.park@samsung.com, kgene.kim@samsung.com Message-id: <1277101605-2435-3-git-send-email-jy0922.shim@samsung.com> X-Mailer: git-send-email 1.7.0.4 Content-transfer-encoding: 7BIT References: <1277101605-2435-1-git-send-email-jy0922.shim@samsung.com> X-OriginalArrivalTime: 21 Jun 2010 06:26:50.0620 (UTC) FILETIME=[BBACCBC0:01CB110A] Sender: linux-input-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-input@vger.kernel.org X-Greylist: IP, sender and recipient auto-whitelisted, not delayed by milter-greylist-4.2.3 (demeter.kernel.org [140.211.167.41]); Mon, 21 Jun 2010 06:27:05 +0000 (UTC) diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig index d8fa5d7..bf6a50f 100644 --- a/drivers/input/keyboard/Kconfig +++ b/drivers/input/keyboard/Kconfig @@ -342,6 +342,15 @@ config KEYBOARD_PXA930_ROTARY To compile this driver as a module, choose M here: the module will be called pxa930_rotary. +config KEYBOARD_SAMSUNG + tristate "Samsung keypad support" + depends on SAMSUNG_DEV_KEYPAD + help + Say Y here if you want to use the Samsung keypad. + + To compile this driver as a module, choose M here: the + module will be called samsung-keypad. + config KEYBOARD_STOWAWAY tristate "Stowaway keyboard" select SERIO diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile index 4596d0c..8f973ed 100644 --- a/drivers/input/keyboard/Makefile +++ b/drivers/input/keyboard/Makefile @@ -32,6 +32,7 @@ obj-$(CONFIG_KEYBOARD_OPENCORES) += opencores-kbd.o obj-$(CONFIG_KEYBOARD_PXA27x) += pxa27x_keypad.o obj-$(CONFIG_KEYBOARD_PXA930_ROTARY) += pxa930_rotary.o obj-$(CONFIG_KEYBOARD_QT2160) += qt2160.o +obj-$(CONFIG_KEYBOARD_SAMSUNG) += samsung-keypad.o obj-$(CONFIG_KEYBOARD_SH_KEYSC) += sh_keysc.o obj-$(CONFIG_KEYBOARD_STOWAWAY) += stowaway.o obj-$(CONFIG_KEYBOARD_SUNKBD) += sunkbd.o diff --git a/drivers/input/keyboard/samsung-keypad.c b/drivers/input/keyboard/samsung-keypad.c new file mode 100644 index 0000000..4b56e6f --- /dev/null +++ b/drivers/input/keyboard/samsung-keypad.c @@ -0,0 +1,400 @@ +/* + * samsung-keypad.c -- Samsung keypad driver + * + * Copyright (C) 2010 Samsung Electronics Co.Ltd + * Author: Joonyoung Shim + * Author: Donghwa Lee + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +enum samsung_keypad_type { + KEYPAD_TYPE_SAMSUNG, + KEYPAD_TYPE_S5PV210, +}; + +struct samsung_keypad { + struct input_dev *input_dev; + struct clk *clk; + struct delayed_work work; + void __iomem *base; + unsigned short *keycodes; + unsigned int row_shift; + unsigned int rows; + unsigned int cols; + unsigned int row_state[SAMSUNG_MAX_COLS]; + int irq; +}; + +static int samsung_keypad_is_s5pv210(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + enum samsung_keypad_type type; + + type = platform_get_device_id(pdev)->driver_data; + return type == KEYPAD_TYPE_S5PV210; +} + +static void samsung_keypad_scan(struct samsung_keypad *keypad, + unsigned int *row_state) +{ + struct device *dev = keypad->input_dev->dev.parent; + unsigned int col; + unsigned int val; + + for (col = 0; col < keypad->cols; col++) { + if (samsung_keypad_is_s5pv210(dev)) { + val = S5PV210_KEYIFCOLEN_MASK; + val &= ~(1 << col) << 8; + } else { + val = SAMSUNG_KEYIFCOL_MASK; + val &= ~(1 << col); + } + + writel(val, keypad->base + SAMSUNG_KEYIFCOL); + mdelay(1); + + val = readl(keypad->base + SAMSUNG_KEYIFROW); + row_state[col] = ~val & ((1 << keypad->rows) - 1); + } + + /* KEYIFCOL reg clear */ + writel(0, keypad->base + SAMSUNG_KEYIFCOL); +} + +static void samsung_keypad_worker(struct work_struct *work) +{ + struct samsung_keypad *keypad = container_of(work, + struct samsung_keypad, work.work); + unsigned int row_state[SAMSUNG_MAX_COLS]; + unsigned int val; + unsigned int changed; + unsigned int pressed; + unsigned int key_down = 0; + int col, row; + + clk_enable(keypad->clk); + + val = readl(keypad->base + SAMSUNG_KEYIFSTSCLR); + + /* interrupt clear */ + writel(~0x0, keypad->base + SAMSUNG_KEYIFSTSCLR); + + val = readl(keypad->base + SAMSUNG_KEYIFCON); + val &= ~(SAMSUNG_KEYIFCON_INT_F_EN | SAMSUNG_KEYIFCON_INT_R_EN); + writel(val, keypad->base + SAMSUNG_KEYIFCON); + + samsung_keypad_scan(keypad, row_state); + + for (col = 0; col < keypad->cols; col++) { + changed = row_state[col] ^ keypad->row_state[col]; + key_down |= row_state[col]; + if (!changed) + continue; + + for (row = 0; row < keypad->rows; row++) { + if (!(changed & (1 << row))) + continue; + + pressed = row_state[col] & (1 << row); + + dev_dbg(&keypad->input_dev->dev, + "key %s, row: %d, col: %d\n", + pressed ? "pressed" : "released", row, col); + + val = MATRIX_SCAN_CODE(row, col, keypad->row_shift); + + input_event(keypad->input_dev, EV_MSC, MSC_SCAN, val); + input_report_key(keypad->input_dev, + keypad->keycodes[val], pressed); + input_sync(keypad->input_dev); + } + } + memcpy(keypad->row_state, row_state, sizeof(row_state)); + + if (key_down) + schedule_delayed_work(&keypad->work, HZ / 20); + else { + /* enable interrupt bit */ + val = readl(keypad->base + SAMSUNG_KEYIFCON); + val |= (SAMSUNG_KEYIFCON_INT_F_EN | SAMSUNG_KEYIFCON_INT_R_EN); + writel(val, keypad->base + SAMSUNG_KEYIFCON); + enable_irq(keypad->irq); + } + clk_disable(keypad->clk); +} + +static irqreturn_t samsung_keypad_interrupt(int irq, void *dev_id) +{ + struct samsung_keypad *keypad = dev_id; + + if (!work_pending(&keypad->work.work)) { + disable_irq_nosync(keypad->irq); + schedule_delayed_work(&keypad->work, 0); + } + + return IRQ_HANDLED; +} + +static int __devinit samsung_keypad_probe(struct platform_device *pdev) +{ + const struct samsung_keypad_platdata *pdata; + const struct matrix_keymap_data *keymap_data; + struct samsung_keypad *keypad; + struct resource *res; + struct input_dev *input_dev; + unsigned short *keycodes; + unsigned int row_shift; + unsigned int val; + int ret; + + pdata = pdev->dev.platform_data; + if (!pdata) { + dev_err(&pdev->dev, "no platform data defined\n"); + return -EINVAL; + } + + keymap_data = pdata->keymap_data; + if (!keymap_data) { + dev_err(&pdev->dev, "no keymap data defined\n"); + return -EINVAL; + } + + if (!pdata->rows || (pdata->rows > SAMSUNG_MAX_ROWS)) + return -EINVAL; + + if (!pdata->cols || (pdata->cols > SAMSUNG_MAX_COLS)) + return -EINVAL; + + /* initialize the gpio */ + if (pdata->cfg_gpio) + pdata->cfg_gpio(pdata->rows, pdata->cols); + + row_shift = get_count_order(pdata->cols); + /* alloc with keycodes memory */ + keypad = kzalloc(sizeof(*keypad) + sizeof(*keycodes) * + (pdata->rows << row_shift), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!keypad || !input_dev) { + ret = -ENOMEM; + goto err_free_mem; + } + keycodes = (unsigned short *)(keypad + 1); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + ret = -ENODEV; + goto err_free_mem; + } + + keypad->base = ioremap(res->start, resource_size(res)); + if (!keypad->base) { + ret = -EBUSY; + goto err_free_mem; + } + + keypad->clk = clk_get(&pdev->dev, "keypad"); + if (IS_ERR(keypad->clk)) { + dev_err(&pdev->dev, "failed to get keypad clk\n"); + ret = PTR_ERR(keypad->clk); + goto err_unmap_base; + } + clk_enable(keypad->clk); + + keypad->input_dev = input_dev; + keypad->keycodes = keycodes; + keypad->row_shift = row_shift; + keypad->rows = pdata->rows; + keypad->cols = pdata->cols; + + INIT_DELAYED_WORK(&keypad->work, samsung_keypad_worker); + + /* enable interrupt and wakeup bit */ + val = SAMSUNG_KEYIFCON_INT_F_EN | SAMSUNG_KEYIFCON_INT_R_EN | + SAMSUNG_KEYIFCON_WAKEUPEN; + writel(val, keypad->base + SAMSUNG_KEYIFCON); + + /* KEYIFCOL reg clear */ + writel(0, keypad->base + SAMSUNG_KEYIFCOL); + + keypad->irq = platform_get_irq(pdev, 0); + if (keypad->irq < 0) { + ret = keypad->irq; + goto err_disable_clk; + } + + ret = request_irq(keypad->irq, samsung_keypad_interrupt, 0, + dev_name(&pdev->dev), keypad); + + if (ret) { + dev_err(&pdev->dev, "failed to register keypad interrupt\n"); + goto err_disable_clk; + } + + input_dev->name = pdev->name; + input_dev->id.bustype = BUS_HOST; + input_dev->dev.parent = &pdev->dev; + + input_dev->evbit[0] = BIT_MASK(EV_KEY); + if (!pdata->no_autorepeat) + input_dev->evbit[0] |= BIT_MASK(EV_REP); + + input_dev->keycode = keycodes; + input_dev->keycodesize = sizeof(*keycodes); + input_dev->keycodemax = pdata->rows << row_shift; + + matrix_keypad_build_keymap(keymap_data, row_shift, + input_dev->keycode, input_dev->keybit); + + input_set_capability(input_dev, EV_MSC, MSC_SCAN); + + ret = input_register_device(keypad->input_dev); + if (ret) + goto err_free_irq; + + device_init_wakeup(&pdev->dev, pdata->wakeup); + platform_set_drvdata(pdev, keypad); + clk_disable(keypad->clk); + + return 0; + +err_free_irq: + free_irq(keypad->irq, keypad); +err_disable_clk: + clk_disable(keypad->clk); + clk_put(keypad->clk); +err_unmap_base: + iounmap(keypad->base); +err_free_mem: + input_free_device(input_dev); + kfree(keypad); + + return ret; +} + +static int __devexit samsung_keypad_remove(struct platform_device *pdev) +{ + struct samsung_keypad *keypad = platform_get_drvdata(pdev); + + device_init_wakeup(&pdev->dev, 0); + + free_irq(keypad->irq, keypad); + cancel_delayed_work_sync(&keypad->work); + + platform_set_drvdata(pdev, NULL); + input_unregister_device(keypad->input_dev); + + clk_disable(keypad->clk); + clk_put(keypad->clk); + + iounmap(keypad->base); + kfree(keypad); + + return 0; +} + +#ifdef CONFIG_PM +static int samsung_keypad_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct samsung_keypad *keypad = platform_get_drvdata(pdev); + + disable_irq(keypad->irq); + + if (device_may_wakeup(&pdev->dev)) + enable_irq_wake(keypad->irq); + + return 0; +} + +static int samsung_keypad_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct samsung_keypad *keypad = platform_get_drvdata(pdev); + unsigned int val; + + if (device_may_wakeup(&pdev->dev)) + disable_irq_wake(keypad->irq); + + clk_enable(keypad->clk); + + /* enable interrupt and wakeup bit */ + val = SAMSUNG_KEYIFCON_INT_F_EN | SAMSUNG_KEYIFCON_INT_R_EN | + SAMSUNG_KEYIFCON_WAKEUPEN; + writel(val, keypad->base + SAMSUNG_KEYIFCON); + + /* KEYIFCOL reg clear */ + writel(0, keypad->base + SAMSUNG_KEYIFCOL); + + clk_disable(keypad->clk); + + enable_irq(keypad->irq); + + return 0; +} + +static const struct dev_pm_ops samsung_keypad_pm_ops = { + .suspend = samsung_keypad_suspend, + .resume = samsung_keypad_resume, +}; +#endif + +static struct platform_device_id samsung_keypad_driver_ids[] = { + { + .name = "samsung-keypad", + .driver_data = KEYPAD_TYPE_SAMSUNG, + }, { + .name = "s5pv210-keypad", + .driver_data = KEYPAD_TYPE_S5PV210, + }, + { }, +}; +MODULE_DEVICE_TABLE(platform, samsung_keypad_driver_ids); + +static struct platform_driver samsung_keypad_driver = { + .probe = samsung_keypad_probe, + .remove = __devexit_p(samsung_keypad_remove), + .driver = { + .name = "samsung-keypad", + .owner = THIS_MODULE, +#ifdef CONFIG_PM + .pm = &samsung_keypad_pm_ops, +#endif + }, + .id_table = samsung_keypad_driver_ids, +}; + +static int __init samsung_keypad_init(void) +{ + return platform_driver_register(&samsung_keypad_driver); +} + +static void __exit samsung_keypad_exit(void) +{ + platform_driver_unregister(&samsung_keypad_driver); +} + +module_init(samsung_keypad_init); +module_exit(samsung_keypad_exit); + +MODULE_DESCRIPTION("Samsung keypad driver"); +MODULE_AUTHOR("Joonyoung Shim "); +MODULE_AUTHOR("Donghwa Lee "); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:samsung-keypad");