diff options
author | Naveen Kumar Gaddipati <naveen.gaddipati@stericsson.com> | 2010-06-16 10:34:44 +0530 |
---|---|---|
committer | John Rigby <john.rigby@linaro.org> | 2010-09-02 22:45:44 -0600 |
commit | e8704ac96d42a4f3093dc253b7f2b44cf8c150e8 (patch) | |
tree | 059f5e17a3ba6265cf2001fab7d3e91b80b3ef2f /drivers/input/keyboard | |
parent | 1b7470b38cbbc8ef57ead27b2362703fb139ab92 (diff) |
ske_driver: support for internal keypad
Added the driver to support the internal keypad on
ux500 platform
ST-Ericsson Id:CR 256008
Signed-off-by: Naveen Kumar Gaddipati <naveen.gaddipati@stericsson.com>
Signed-off-by: Mian Yousaf Kaukab <mian.yousaf.kaukab@stericsson.com>
Change-Id: I8f776d4ff1e8ac4bc3ad0b629c1144b7e5daefb8
Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/2541
Reviewed-by: Jonas ABERG <jonas.aberg@stericsson.com>
Diffstat (limited to 'drivers/input/keyboard')
-rw-r--r-- | drivers/input/keyboard/Kconfig | 7 | ||||
-rw-r--r-- | drivers/input/keyboard/Makefile | 1 | ||||
-rwxr-xr-x | drivers/input/keyboard/ske_keypad.c | 533 |
3 files changed, 541 insertions, 0 deletions
diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig index 922f72715ca..478c6487ffa 100644 --- a/drivers/input/keyboard/Kconfig +++ b/drivers/input/keyboard/Kconfig @@ -444,6 +444,13 @@ config KEYPAD_U8500 Say Y here if you want to use a keypad provided on UIB board which is to be plugged on top of 8500 core platform. +config KEYPAD_SKE + tristate "SKE keypad support" + depends on ARCH_U8500 && !KEYPAD_U8500 && !TC35893_KEYPAD + default y + help + Say Y here if you want to use a keypad provided on Ux500 platform + config TC35893_KEYPAD tristate "TC35893 keypad controller" depends on I2C diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile index cc4ef677cea..f233a9ada5f 100644 --- a/drivers/input/keyboard/Makefile +++ b/drivers/input/keyboard/Makefile @@ -42,3 +42,4 @@ obj-$(CONFIG_KEYPAD_U8500) += u8500-kpd.o obj-$(CONFIG_TC35893_KEYPAD) += tc35893-keypad.o obj-$(CONFIG_KEYBOARD_XTKBD) += xtkbd.o obj-$(CONFIG_KEYBOARD_W90P910) += w90p910_keypad.o +obj-$(CONFIG_KEYPAD_SKE) += ske_keypad.o diff --git a/drivers/input/keyboard/ske_keypad.c b/drivers/input/keyboard/ske_keypad.c new file mode 100755 index 00000000000..4e00e12a890 --- /dev/null +++ b/drivers/input/keyboard/ske_keypad.c @@ -0,0 +1,533 @@ +/* + * Copyright (C) ST-Ericsson SA 2009 + * Author: Naveen Kumar G <naveen.gaddipati@stericsson.com> for ST-Ericsson + * License terms:GNU General Public License (GPL) version 2 + * + * This driver is used for SKE(Scroll Key Encoder) controller in + * all Ux500 platform. + */ + +#include <linux/spinlock.h> +#include <linux/platform_device.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/workqueue.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <mach/kpd.h> + +#define SKE_CR 0x00 +#define SKE_VAL0 0x04 +#define SKE_VAL1 0x08 +#define SKE_DBCR 0x0C +#define SKE_IMSC 0x10 +#define SKE_RIS 0x14 +#define SKE_MIS 0x18 +#define SKE_ICR 0x1C +#define SKE_ASR0 0x20 +#define SKE_ASR1 0x24 +#define SKE_ASR2 0x28 +#define SKE_ASR3 0x2C + +#define SKE_KEYPAD_VER_MAJOR 1 +#define SKE_KEYPAD_VER_MINOR 0 +#define SKE_KEYPAD_VER_RELEASE 0 +#define KP_ASDIS 0x00000000 +#define KP_ASEN 0x00000004 +#define KP_SOFTSCAN_INTR_EN 0x00000008 +#define KP_SOFTSCAN_INTR_CLR 0x00000008 +#define KP_HI_ALLCOLS 0x0000FF00 +#define KP_LOW_ALLCOLS 0x00000000 +#define KP_AS_INTR_EN 0x04 + +#define KP_MULT 0x40 +#define KP_MULT_SCAN_ALL 0x38 +#define KP_ASCANON 0x80 +#define KP_DBCR_PERIOD 0x1000 + +#define CLR_KP_AUTOSCAN_INTR_MASK 0x00 +#define CLR_ALL_INTR 0xF +#define KP_AUTOSCAN_INTR_CLR 0x04 +#define KP_AUTOSCAN_INTR_RIS 0x04 +#define DISABLE_AUTOSCAN 0x00000000 +#define DEFAULT_ASCAN_VALUE 0x00000000 + +#define LOWER_BYTE 0xFF +#define UPPER_BYTE 0xFF00 +#define SHIFT_8 8 +#define REG_OFFSET 0x04 + +static void ske_kp_wq_kscan(struct work_struct *work); + +/** + * ske_kp_autoscan_en - enables the autoscan mode + * @kp: keypad device data pointer + * This function enables the autoscan mode +*/ +static void ske_kp_autoscan_en(struct keypad_t *kp) +{ + int value; + unsigned long flags; + spin_lock_irqsave(&kp->cr_lock, flags); + writel(KP_MULT, (kp->ske_regs + SKE_CR)); + value = readl(kp->ske_regs + SKE_CR); + value = (value | KP_ASEN | KP_LOW_ALLCOLS | KP_MULT_SCAN_ALL); + writel(value, (kp->ske_regs + SKE_CR)); + writel(KP_DBCR_PERIOD, (kp->ske_regs + SKE_DBCR)); + writel(CLR_ALL_INTR, (kp->ske_regs + SKE_ICR)); + spin_unlock_irqrestore(&kp->cr_lock, flags); +} + +/** + * ske_kp_autoscan_disable - Disables the autoscan mode + * @kp: keypad device data pointer + * This function disables the autoscan mode +*/ +static void ske_kp_autoscan_disable(struct keypad_t *kp) +{ + unsigned long ske_cr_reg_value = KP_ASCANON; + unsigned long flags; + spin_lock_irqsave(&kp->cr_lock, flags); + while (ske_cr_reg_value & KP_ASCANON) + ske_cr_reg_value = readl(kp->ske_regs + SKE_CR); + writel(DISABLE_AUTOSCAN, (kp->ske_regs + SKE_CR)); + spin_unlock_irqrestore(&kp->cr_lock, flags); +} + +/** + * ske_kp_autoscan_intr_enable - Enables the autoscan mode + * @kp: keypad device data pointer + * This function enables the interrupt in autoscan mode +*/ +static void ske_kp_autoscan_intr_enable(struct keypad_t *kp) +{ + unsigned long flags; + spin_lock_irqsave(&kp->cr_lock, flags); + writel(KP_AS_INTR_EN, (kp->ske_regs + SKE_IMSC)); + kp->board->int_status = KP_INT_ENABLED; + spin_unlock_irqrestore(&kp->cr_lock, flags); +} + +/** + * ske_kp_autoscan_intr_disable - Disables the autoscan mode + * @kp: keypad device data pointer + * This function disables the interrupt in autoscan mode +*/ +static void ske_kp_autoscan_intr_disable(struct keypad_t *kp) +{ + unsigned long flags; + spin_lock_irqsave(&kp->cr_lock, flags); + writel(CLR_KP_AUTOSCAN_INTR_MASK, (kp->ske_regs + SKE_IMSC)); + writel(KP_AUTOSCAN_INTR_CLR, (kp->ske_regs + SKE_ICR)); + kp->board->int_status = KP_INT_DISABLED; + spin_unlock_irqrestore(&kp->cr_lock, flags); +} + +/** + * ske_kp_autoscan_check - check the autoscan mode + * @kp: keypad device data pointer + * This function used to check the autoscan mode +*/ +static bool ske_kp_autoscan_check(struct keypad_t *kp) +{ + unsigned long ske_cr_reg_value = 0; + unsigned long flags = 0; + bool ret; + spin_lock_irqsave(&kp->cr_lock, flags); + ske_cr_reg_value = readl(kp->ske_regs + SKE_CR); + spin_unlock_irqrestore(&kp->cr_lock, flags); + ret = (ske_cr_reg_value & KP_ASCANON); + return ret; +} +/** + * ske_kp_key_pressed - report key pressed + * @kp: keypad device data pointer + * @row: row number + * @col: col number + * @scancode: scane code for the key + * This function is used to report key pressed +*/ +static void ske_kp_key_pressed(struct keypad_t *kp, int row, int col, + u8 *scancode) +{ + if (kp->key_state[row][col] == KEYPAD_STATE_DEFAULT) { + kp->key_cnt++; + input_report_key(kp->inp_dev, *scancode, true); + kp->key_state[row][col] = KEYPAD_STATE_PRESSACK; + } +} + +/** + * ske_kp_key_released - report key released + * @kp: keypad device data pointer + * @row: row number + * @col: col number + * @scancode: scane code for the key + * This function is used to report key released +*/ +static void ske_kp_key_released(struct keypad_t *kp, int row, int col, + u8 *scancode) +{ + int value; + if (kp->key_state[row][col] == KEYPAD_STATE_PRESSACK) { + value = KP_AUTOSCAN_INTR_RIS; + while (value && KP_AUTOSCAN_INTR_RIS) + value = readl(kp->ske_regs + SKE_RIS); + kp->key_cnt--; + input_report_key(kp->inp_dev, *scancode, false); + kp->key_state[row][col] = KEYPAD_STATE_DEFAULT; + } +} + +/** + * ske_kp_autoscan_results - check the results of autoscan + * @kp: keypad device data pointer + * This function is used to check the results of autoscan +*/ +static void ske_kp_autoscan_results(struct keypad_t *kp) +{ + unsigned int col_val[MAX_KPCOL]; + int col = 0, row = 0, mask; + unsigned long add = 0; + u8 *p_kcode; + int key_pressed; + unsigned long flags; + spin_lock_irqsave(&kp->cr_lock, flags); + while (col < MAX_KPCOL) { + col_val[col] = + readl(kp->ske_regs + SKE_ASR0 + add) & LOWER_BYTE; + col_val[col + 1] = + (readl(kp->ske_regs + SKE_ASR0 + add) & UPPER_BYTE) + >> SHIFT_8; + col += 2; + add = add + REG_OFFSET; + } + spin_unlock_irqrestore(&kp->cr_lock, flags); + for (col = 0; col < MAX_KPCOL; col++) { + if (col_val[col] != 0) { + p_kcode = kp->board->kcode_tbl + col; + for (row = 0; row < MAX_KPROW; row++) { + mask = (1 << row); + key_pressed = 0; + key_pressed = (col_val[col] & mask); + if (key_pressed != 0) { + ske_kp_key_pressed(kp, row, col, + p_kcode); + ske_kp_key_released(kp, row, col, + p_kcode); + } + p_kcode += MAX_KPROW; + mask = 0; + } + } + } + input_sync(kp->inp_dev); +} +/** + * ske_kp_key_irqen- enables keypad interrupt + * @kp: keypad device data pointer + * + * enables keypad interrupt + */ +static int ske_kp_key_irqen(struct keypad_t *kp) +{ + if (kp->board->int_status != KP_INT_ENABLED) + ske_kp_autoscan_intr_enable(kp); + return 0; +} + +/** + * ske_kp_key_irqdis- disables keypad interrupt + * @kp: keypad device data pointer + * + * disables keypad interrupt + */ +static int ske_kp_key_irqdis(struct keypad_t *kp) +{ + if (kp->board->int_status != KP_INT_DISABLED) + ske_kp_autoscan_intr_disable(kp); + return 0; +} +/** + * ske_kp_intrhandler - keypad interrupt handler + * @irq: irq number for keypad + * @dev_id: pointer to keypad device id + * + * checks for valid interrupt, disables interrupt to avoid any nested interrupt + * starts work queue for further key processing with debouncing logic + */ +static irqreturn_t ske_kp_intrhandler(int irq, void *dev_id) +{ + struct keypad_t *kp = (struct keypad_t *)dev_id; + if (!(test_bit(KPINTR_LKBIT, &kp->lockbits))) { + set_bit(KPINTR_LKBIT, &kp->lockbits); + ske_kp_key_irqdis(kp); + schedule_delayed_work(&kp->kscan_work, + kp->board->debounce_period); + } + return IRQ_HANDLED; +} + +/** + * ske_kp_wq_kscan - work queue for keypad scanning + * @work: pointer to keypad data + * + * Executes at each scan tick, execute the key press/release function, + * Generates key press/release event message for input subsystem for valid key + * events, enables keypad interrupts (for int mode) + */ + +static void ske_kp_wq_kscan(struct work_struct *work) +{ + bool ret = true; + struct keypad_t *kp = + container_of((struct delayed_work *)work, + struct keypad_t, kscan_work); + + while (ret) { + udelay(100); + ret = ske_kp_autoscan_check(kp); + } + kp->key_cnt = 0; + ske_kp_autoscan_results(kp); + clear_bit(KPINTR_LKBIT, &kp->lockbits); + ske_kp_key_irqen(kp); +} + +/** + * ske_kp_init_keypad - keypad parameter initialization + * @kp: keypad device data pointer + * + * Initializes Keybits to enable keyevents + * Initializes Initial keypress status to default + * Calls the keypad platform specific init function. + */ +int __init ske_kp_init_keypad(struct keypad_t *kp) +{ + int row, column, err; + u8 *p_kcode = kp->board->kcode_tbl; + if (kp->board->init) { + err = kp->board->init(kp); + if (err) + return err; + } + for (row = 0; row < MAX_KPROW; row++) { + for (column = 0; column < MAX_KPCOL; column++) { + set_bit(*p_kcode, kp->inp_dev->keybit); + kp->key_state[row][column] = + KEYPAD_STATE_DEFAULT; + p_kcode++; + } + } + return err; +} + +#ifdef CONFIG_PM +/** + * ske_kp_suspend - suspend keypad + * @pdev: platform data + * @state: power down level + * This function is used to suspend the keypad + */ +static int ske_kp_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct keypad_t *kp = platform_get_drvdata(pdev); + dev_dbg(&pdev->dev, "suspend start\n"); + if (!device_may_wakeup(&pdev->dev)) + ske_kp_key_irqdis(kp); + dev_dbg(&pdev->dev, "suspend done\n"); + return 0; +} + +/** + * ske_kp_resume - resumes keypad + * @pdev: platform data + * This function is used to resume the keypad + */ + +static int ske_kp_resume(struct platform_device *pdev) +{ + struct keypad_t *kp = platform_get_drvdata(pdev); + + dev_dbg(&pdev->dev, "resume start\n"); + if (!device_may_wakeup(&pdev->dev)) + ske_kp_key_irqen(kp); + dev_dbg(&pdev->dev, "resume done\n"); + return 0; +} + +#endif + +/** + * ske_kp_probe - keypad module probe function + * @pdev: driver platform data + * + * Allocates data memory, registers the module with input subsystem, + * initializes keypad default condition, initializes keypad interrupt handler + * for interrupt mode operation. + */ +static int __init ske_kp_probe(struct platform_device *pdev) +{ + struct keypad_t *kp; + struct resource *res = NULL; + int err = 0; + struct keypad_device *keypad_board; + + if (!pdev) + return -EINVAL; + dev_dbg(&pdev->dev, "probe start\n"); + + keypad_board = pdev->dev.platform_data; + kp = kzalloc(sizeof(struct keypad_t), GFP_KERNEL); + if (!kp) { + err = -ENOMEM; + goto err_kzalloc; + } + platform_set_drvdata(pdev, kp); + kp = platform_get_drvdata(pdev); + if (!keypad_board) { + dev_err(&pdev->dev, "platform data not defined\n"); + err = -1; + goto err_board; + } + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res == NULL) { + dev_err(&pdev->dev, "memory resources not defined\n"); + err = -EINVAL; + goto err_board; + } else { + kp->ske_regs = ioremap(res->start, resource_size(res)); + if (kp->ske_regs == NULL) { + dev_err(&pdev->dev, "alloc reg space failed\n"); + err = -ENOMEM; + goto err_board; + } + } + kp->irq = platform_get_irq(pdev, 0); + if (!kp->irq) { + dev_err(&pdev->dev, "irq not defined\n"); + err = -EINVAL; + goto err_board; + } + kp->clk = clk_get(&pdev->dev, NULL); + if (IS_ERR(kp->clk)) { + dev_err(&pdev->dev, "clk error\n"); + err = PTR_ERR(kp->clk); + goto err_board; + } else { + clk_enable(kp->clk); + } + kp->board = keypad_board; + kp->inp_dev = input_allocate_device(); + if (!kp->inp_dev) { + dev_err(&pdev->dev, "alloc memory failed\n"); + err = -EINVAL; + goto err_inp_devalloc; + } + clear_bit(KPINTR_LKBIT, &kp->lockbits); + spin_lock_init(&kp->cr_lock); + err = ske_kp_init_keypad(kp); + if (err) { + dev_err(&pdev->dev, "init hardware failed\n"); + goto err_init_kpd; + } + INIT_DELAYED_WORK(&kp->kscan_work, ske_kp_wq_kscan); + ske_kp_autoscan_en(kp); + err = request_irq(kp->irq, ske_kp_intrhandler, + kp->board->irqtype, "ske", kp); + if (err) { + dev_err(&pdev->dev, "allocate irq %d failed\n", kp->irq); + goto err_req_irq; + } + ske_kp_key_irqen(kp); + + set_bit(EV_KEY, kp->inp_dev->evbit); + set_bit(EV_REP, kp->inp_dev->evbit); + kp->inp_dev->id.bustype = BUS_HOST; + kp->inp_dev->name = "ske-kp"; + kp->inp_dev->phys = "ske-kp/input0"; + kp->inp_dev->id.product = SKE_KEYPAD_VER_MAJOR; + kp->inp_dev->id.version = SKE_KEYPAD_VER_MINOR * 0x0ff + SKE_KEYPAD_VER_RELEASE; + kp->inp_dev->dev.parent = &pdev->dev; + kp->inp_dev->keycode = kp->board->kcode_tbl; + kp->inp_dev->keycodesize = sizeof(unsigned char); + kp->inp_dev->keycodemax = MAX_KEYS; + clear_bit(0, kp->inp_dev->keybit); + err = input_register_device(kp->inp_dev); + if (err) { + dev_err(&pdev->dev, "could not register input device\n"); + err = -EINVAL; + goto err_inp_reg; + } + dev_dbg(&pdev->dev, "Module initialized Ver(%d.%d.%d)\n", + SKE_KEYPAD_VER_MAJOR, SKE_KEYPAD_VER_MINOR, SKE_KEYPAD_VER_RELEASE); + return 0; + +err_req_irq: + free_irq(kp->irq, kp); +err_inp_reg: + input_unregister_device(kp->inp_dev); +err_inp_devalloc: +err_init_kpd: + input_free_device(kp->inp_dev); +err_board: + kfree(kp); +err_kzalloc: + return err; +} + +/** + * ske_kp_remove - keypad module remove function + * @pdev: driver platform data + * + * Disables Keypad interrupt if any, frees allocated keypad interrupt if any, + * cancels keypad work queues if any, deallocate used GPIO pin, unregisters the + * module, frees used memory + */ +static int ske_kp_remove(struct platform_device *pdev) +{ + struct keypad_t *kp = platform_get_drvdata(pdev); + + dev_dbg(&pdev->dev, "keypad remove start\n"); + ske_kp_autoscan_disable(kp); + if (kp->board->exit) + kp->board->exit(kp); + free_irq(kp->irq, kp); + cancel_delayed_work(&kp->kscan_work); + cancel_delayed_work_sync(&kp->kscan_work); + input_unregister_device(kp->inp_dev); + input_free_device(kp->inp_dev); + kp->clk = 0; + kfree(kp); + dev_dbg(&pdev->dev, "keypad removed\n"); + return 0; +} + +struct platform_driver ske_driver = { + .probe = ske_kp_probe, + .remove = ske_kp_remove, + .driver = { + .name = "ske-kp", + }, +#ifdef CONFIG_PM + .suspend = ske_kp_suspend, + .resume = ske_kp_resume, +#endif +}; + +static int __devinit ske_kp_init(void) +{ + return platform_driver_register(&ske_driver); +} + +static void __exit ske_kp_exit(void) +{ + platform_driver_unregister(&ske_driver); +} + +module_init(ske_kp_init); +module_exit(ske_kp_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("NAVEEN KUMAR G"); +MODULE_DESCRIPTION("SKE keyboard driver"); |