Path: blob/master/drivers/input/keyboard/samsung-keypad.c
15111 views
/*1* Samsung keypad driver2*3* Copyright (C) 2010 Samsung Electronics Co.Ltd4* Author: Joonyoung Shim <[email protected]>5* Author: Donghwa Lee <[email protected]>6*7* This program is free software; you can redistribute it and/or modify it8* under the terms of the GNU General Public License as published by the9* Free Software Foundation; either version 2 of the License, or (at your10* option) any later version.11*/1213#include <linux/clk.h>14#include <linux/delay.h>15#include <linux/err.h>16#include <linux/init.h>17#include <linux/input.h>18#include <linux/interrupt.h>19#include <linux/io.h>20#include <linux/module.h>21#include <linux/platform_device.h>22#include <linux/slab.h>23#include <linux/sched.h>24#include <plat/keypad.h>2526#define SAMSUNG_KEYIFCON 0x0027#define SAMSUNG_KEYIFSTSCLR 0x0428#define SAMSUNG_KEYIFCOL 0x0829#define SAMSUNG_KEYIFROW 0x0c30#define SAMSUNG_KEYIFFC 0x103132/* SAMSUNG_KEYIFCON */33#define SAMSUNG_KEYIFCON_INT_F_EN (1 << 0)34#define SAMSUNG_KEYIFCON_INT_R_EN (1 << 1)35#define SAMSUNG_KEYIFCON_DF_EN (1 << 2)36#define SAMSUNG_KEYIFCON_FC_EN (1 << 3)37#define SAMSUNG_KEYIFCON_WAKEUPEN (1 << 4)3839/* SAMSUNG_KEYIFSTSCLR */40#define SAMSUNG_KEYIFSTSCLR_P_INT_MASK (0xff << 0)41#define SAMSUNG_KEYIFSTSCLR_R_INT_MASK (0xff << 8)42#define SAMSUNG_KEYIFSTSCLR_R_INT_OFFSET 843#define S5PV210_KEYIFSTSCLR_P_INT_MASK (0x3fff << 0)44#define S5PV210_KEYIFSTSCLR_R_INT_MASK (0x3fff << 16)45#define S5PV210_KEYIFSTSCLR_R_INT_OFFSET 164647/* SAMSUNG_KEYIFCOL */48#define SAMSUNG_KEYIFCOL_MASK (0xff << 0)49#define S5PV210_KEYIFCOLEN_MASK (0xff << 8)5051/* SAMSUNG_KEYIFROW */52#define SAMSUNG_KEYIFROW_MASK (0xff << 0)53#define S5PV210_KEYIFROW_MASK (0x3fff << 0)5455/* SAMSUNG_KEYIFFC */56#define SAMSUNG_KEYIFFC_MASK (0x3ff << 0)5758enum samsung_keypad_type {59KEYPAD_TYPE_SAMSUNG,60KEYPAD_TYPE_S5PV210,61};6263struct samsung_keypad {64struct input_dev *input_dev;65struct clk *clk;66void __iomem *base;67wait_queue_head_t wait;68bool stopped;69int irq;70unsigned int row_shift;71unsigned int rows;72unsigned int cols;73unsigned int row_state[SAMSUNG_MAX_COLS];74unsigned short keycodes[];75};7677static int samsung_keypad_is_s5pv210(struct device *dev)78{79struct platform_device *pdev = to_platform_device(dev);80enum samsung_keypad_type type =81platform_get_device_id(pdev)->driver_data;8283return type == KEYPAD_TYPE_S5PV210;84}8586static void samsung_keypad_scan(struct samsung_keypad *keypad,87unsigned int *row_state)88{89struct device *dev = keypad->input_dev->dev.parent;90unsigned int col;91unsigned int val;9293for (col = 0; col < keypad->cols; col++) {94if (samsung_keypad_is_s5pv210(dev)) {95val = S5PV210_KEYIFCOLEN_MASK;96val &= ~(1 << col) << 8;97} else {98val = SAMSUNG_KEYIFCOL_MASK;99val &= ~(1 << col);100}101102writel(val, keypad->base + SAMSUNG_KEYIFCOL);103mdelay(1);104105val = readl(keypad->base + SAMSUNG_KEYIFROW);106row_state[col] = ~val & ((1 << keypad->rows) - 1);107}108109/* KEYIFCOL reg clear */110writel(0, keypad->base + SAMSUNG_KEYIFCOL);111}112113static bool samsung_keypad_report(struct samsung_keypad *keypad,114unsigned int *row_state)115{116struct input_dev *input_dev = keypad->input_dev;117unsigned int changed;118unsigned int pressed;119unsigned int key_down = 0;120unsigned int val;121unsigned int col, row;122123for (col = 0; col < keypad->cols; col++) {124changed = row_state[col] ^ keypad->row_state[col];125key_down |= row_state[col];126if (!changed)127continue;128129for (row = 0; row < keypad->rows; row++) {130if (!(changed & (1 << row)))131continue;132133pressed = row_state[col] & (1 << row);134135dev_dbg(&keypad->input_dev->dev,136"key %s, row: %d, col: %d\n",137pressed ? "pressed" : "released", row, col);138139val = MATRIX_SCAN_CODE(row, col, keypad->row_shift);140141input_event(input_dev, EV_MSC, MSC_SCAN, val);142input_report_key(input_dev,143keypad->keycodes[val], pressed);144}145input_sync(keypad->input_dev);146}147148memcpy(keypad->row_state, row_state, sizeof(keypad->row_state));149150return key_down;151}152153static irqreturn_t samsung_keypad_irq(int irq, void *dev_id)154{155struct samsung_keypad *keypad = dev_id;156unsigned int row_state[SAMSUNG_MAX_COLS];157unsigned int val;158bool key_down;159160do {161val = readl(keypad->base + SAMSUNG_KEYIFSTSCLR);162/* Clear interrupt. */163writel(~0x0, keypad->base + SAMSUNG_KEYIFSTSCLR);164165samsung_keypad_scan(keypad, row_state);166167key_down = samsung_keypad_report(keypad, row_state);168if (key_down)169wait_event_timeout(keypad->wait, keypad->stopped,170msecs_to_jiffies(50));171172} while (key_down && !keypad->stopped);173174return IRQ_HANDLED;175}176177static void samsung_keypad_start(struct samsung_keypad *keypad)178{179unsigned int val;180181/* Tell IRQ thread that it may poll the device. */182keypad->stopped = false;183184clk_enable(keypad->clk);185186/* Enable interrupt bits. */187val = readl(keypad->base + SAMSUNG_KEYIFCON);188val |= SAMSUNG_KEYIFCON_INT_F_EN | SAMSUNG_KEYIFCON_INT_R_EN;189writel(val, keypad->base + SAMSUNG_KEYIFCON);190191/* KEYIFCOL reg clear. */192writel(0, keypad->base + SAMSUNG_KEYIFCOL);193}194195static void samsung_keypad_stop(struct samsung_keypad *keypad)196{197unsigned int val;198199/* Signal IRQ thread to stop polling and disable the handler. */200keypad->stopped = true;201wake_up(&keypad->wait);202disable_irq(keypad->irq);203204/* Clear interrupt. */205writel(~0x0, keypad->base + SAMSUNG_KEYIFSTSCLR);206207/* Disable interrupt bits. */208val = readl(keypad->base + SAMSUNG_KEYIFCON);209val &= ~(SAMSUNG_KEYIFCON_INT_F_EN | SAMSUNG_KEYIFCON_INT_R_EN);210writel(val, keypad->base + SAMSUNG_KEYIFCON);211212clk_disable(keypad->clk);213214/*215* Now that chip should not generate interrupts we can safely216* re-enable the handler.217*/218enable_irq(keypad->irq);219}220221static int samsung_keypad_open(struct input_dev *input_dev)222{223struct samsung_keypad *keypad = input_get_drvdata(input_dev);224225samsung_keypad_start(keypad);226227return 0;228}229230static void samsung_keypad_close(struct input_dev *input_dev)231{232struct samsung_keypad *keypad = input_get_drvdata(input_dev);233234samsung_keypad_stop(keypad);235}236237static int __devinit samsung_keypad_probe(struct platform_device *pdev)238{239const struct samsung_keypad_platdata *pdata;240const struct matrix_keymap_data *keymap_data;241struct samsung_keypad *keypad;242struct resource *res;243struct input_dev *input_dev;244unsigned int row_shift;245unsigned int keymap_size;246int error;247248pdata = pdev->dev.platform_data;249if (!pdata) {250dev_err(&pdev->dev, "no platform data defined\n");251return -EINVAL;252}253254keymap_data = pdata->keymap_data;255if (!keymap_data) {256dev_err(&pdev->dev, "no keymap data defined\n");257return -EINVAL;258}259260if (!pdata->rows || pdata->rows > SAMSUNG_MAX_ROWS)261return -EINVAL;262263if (!pdata->cols || pdata->cols > SAMSUNG_MAX_COLS)264return -EINVAL;265266/* initialize the gpio */267if (pdata->cfg_gpio)268pdata->cfg_gpio(pdata->rows, pdata->cols);269270row_shift = get_count_order(pdata->cols);271keymap_size = (pdata->rows << row_shift) * sizeof(keypad->keycodes[0]);272273keypad = kzalloc(sizeof(*keypad) + keymap_size, GFP_KERNEL);274input_dev = input_allocate_device();275if (!keypad || !input_dev) {276error = -ENOMEM;277goto err_free_mem;278}279280res = platform_get_resource(pdev, IORESOURCE_MEM, 0);281if (!res) {282error = -ENODEV;283goto err_free_mem;284}285286keypad->base = ioremap(res->start, resource_size(res));287if (!keypad->base) {288error = -EBUSY;289goto err_free_mem;290}291292keypad->clk = clk_get(&pdev->dev, "keypad");293if (IS_ERR(keypad->clk)) {294dev_err(&pdev->dev, "failed to get keypad clk\n");295error = PTR_ERR(keypad->clk);296goto err_unmap_base;297}298299keypad->input_dev = input_dev;300keypad->row_shift = row_shift;301keypad->rows = pdata->rows;302keypad->cols = pdata->cols;303init_waitqueue_head(&keypad->wait);304305input_dev->name = pdev->name;306input_dev->id.bustype = BUS_HOST;307input_dev->dev.parent = &pdev->dev;308input_set_drvdata(input_dev, keypad);309310input_dev->open = samsung_keypad_open;311input_dev->close = samsung_keypad_close;312313input_dev->evbit[0] = BIT_MASK(EV_KEY);314if (!pdata->no_autorepeat)315input_dev->evbit[0] |= BIT_MASK(EV_REP);316317input_set_capability(input_dev, EV_MSC, MSC_SCAN);318319input_dev->keycode = keypad->keycodes;320input_dev->keycodesize = sizeof(keypad->keycodes[0]);321input_dev->keycodemax = pdata->rows << row_shift;322323matrix_keypad_build_keymap(keymap_data, row_shift,324input_dev->keycode, input_dev->keybit);325326keypad->irq = platform_get_irq(pdev, 0);327if (keypad->irq < 0) {328error = keypad->irq;329goto err_put_clk;330}331332error = request_threaded_irq(keypad->irq, NULL, samsung_keypad_irq,333IRQF_ONESHOT, dev_name(&pdev->dev), keypad);334if (error) {335dev_err(&pdev->dev, "failed to register keypad interrupt\n");336goto err_put_clk;337}338339error = input_register_device(keypad->input_dev);340if (error)341goto err_free_irq;342343device_init_wakeup(&pdev->dev, pdata->wakeup);344platform_set_drvdata(pdev, keypad);345return 0;346347err_free_irq:348free_irq(keypad->irq, keypad);349err_put_clk:350clk_put(keypad->clk);351err_unmap_base:352iounmap(keypad->base);353err_free_mem:354input_free_device(input_dev);355kfree(keypad);356357return error;358}359360static int __devexit samsung_keypad_remove(struct platform_device *pdev)361{362struct samsung_keypad *keypad = platform_get_drvdata(pdev);363364device_init_wakeup(&pdev->dev, 0);365platform_set_drvdata(pdev, NULL);366367input_unregister_device(keypad->input_dev);368369/*370* It is safe to free IRQ after unregistering device because371* samsung_keypad_close will shut off interrupts.372*/373free_irq(keypad->irq, keypad);374375clk_put(keypad->clk);376377iounmap(keypad->base);378kfree(keypad);379380return 0;381}382383#ifdef CONFIG_PM384static void samsung_keypad_toggle_wakeup(struct samsung_keypad *keypad,385bool enable)386{387struct device *dev = keypad->input_dev->dev.parent;388unsigned int val;389390clk_enable(keypad->clk);391392val = readl(keypad->base + SAMSUNG_KEYIFCON);393if (enable) {394val |= SAMSUNG_KEYIFCON_WAKEUPEN;395if (device_may_wakeup(dev))396enable_irq_wake(keypad->irq);397} else {398val &= ~SAMSUNG_KEYIFCON_WAKEUPEN;399if (device_may_wakeup(dev))400disable_irq_wake(keypad->irq);401}402writel(val, keypad->base + SAMSUNG_KEYIFCON);403404clk_disable(keypad->clk);405}406407static int samsung_keypad_suspend(struct device *dev)408{409struct platform_device *pdev = to_platform_device(dev);410struct samsung_keypad *keypad = platform_get_drvdata(pdev);411struct input_dev *input_dev = keypad->input_dev;412413mutex_lock(&input_dev->mutex);414415if (input_dev->users)416samsung_keypad_stop(keypad);417418samsung_keypad_toggle_wakeup(keypad, true);419420mutex_unlock(&input_dev->mutex);421422return 0;423}424425static int samsung_keypad_resume(struct device *dev)426{427struct platform_device *pdev = to_platform_device(dev);428struct samsung_keypad *keypad = platform_get_drvdata(pdev);429struct input_dev *input_dev = keypad->input_dev;430431mutex_lock(&input_dev->mutex);432433samsung_keypad_toggle_wakeup(keypad, false);434435if (input_dev->users)436samsung_keypad_start(keypad);437438mutex_unlock(&input_dev->mutex);439440return 0;441}442443static const struct dev_pm_ops samsung_keypad_pm_ops = {444.suspend = samsung_keypad_suspend,445.resume = samsung_keypad_resume,446};447#endif448449static struct platform_device_id samsung_keypad_driver_ids[] = {450{451.name = "samsung-keypad",452.driver_data = KEYPAD_TYPE_SAMSUNG,453}, {454.name = "s5pv210-keypad",455.driver_data = KEYPAD_TYPE_S5PV210,456},457{ },458};459MODULE_DEVICE_TABLE(platform, samsung_keypad_driver_ids);460461static struct platform_driver samsung_keypad_driver = {462.probe = samsung_keypad_probe,463.remove = __devexit_p(samsung_keypad_remove),464.driver = {465.name = "samsung-keypad",466.owner = THIS_MODULE,467#ifdef CONFIG_PM468.pm = &samsung_keypad_pm_ops,469#endif470},471.id_table = samsung_keypad_driver_ids,472};473474static int __init samsung_keypad_init(void)475{476return platform_driver_register(&samsung_keypad_driver);477}478module_init(samsung_keypad_init);479480static void __exit samsung_keypad_exit(void)481{482platform_driver_unregister(&samsung_keypad_driver);483}484module_exit(samsung_keypad_exit);485486MODULE_DESCRIPTION("Samsung keypad driver");487MODULE_AUTHOR("Joonyoung Shim <[email protected]>");488MODULE_AUTHOR("Donghwa Lee <[email protected]>");489MODULE_LICENSE("GPL");490MODULE_ALIAS("platform:samsung-keypad");491492493