diff options
Diffstat (limited to 'drivers/input/keyboard')
-rw-r--r-- | drivers/input/keyboard/Kconfig | 37 | ||||
-rw-r--r-- | drivers/input/keyboard/Makefile | 6 | ||||
-rw-r--r-- | drivers/input/keyboard/mc9s08dz60_keyb.c | 248 | ||||
-rw-r--r-- | drivers/input/keyboard/mpr084.c | 500 | ||||
-rw-r--r-- | drivers/input/keyboard/mpr121.c | 305 | ||||
-rw-r--r-- | drivers/input/keyboard/mxc_keyb.c | 1203 | ||||
-rw-r--r-- | drivers/input/keyboard/mxc_pwrkey.c | 156 | ||||
-rw-r--r-- | drivers/input/keyboard/mxs-kbd.c | 365 |
8 files changed, 2820 insertions, 0 deletions
diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig index 1ba25145b333..c58bb7a49e7f 100644 --- a/drivers/input/keyboard/Kconfig +++ b/drivers/input/keyboard/Kconfig @@ -426,4 +426,41 @@ config KEYBOARD_W90P910 To compile this driver as a module, choose M here: the module will be called w90p910_keypad. +config KEYBOARD_MXC + tristate "MXC Keypad Driver" + depends on ARCH_MXC + help + This is the Keypad driver for the Freescale MXC application + processors. + +config KEYBOARD_MXS + tristate "MXS keyboard" + depends on ARCH_MXS + help + This is the Keypad driver for the Freescale mxs soc + + +config KEYBOARD_MC9S08DZ60 + tristate "mc9s08dz60 keyboard" + depends on MXC_PMIC_MC9S08DZ60 + help + -to be written- + +config KEYBOARD_MPR084 + tristate "Freescale MPR084 Touch Keypad Driver" + depends on ARCH_MX37 + help + This is the Keypad driver for the Freescale Proximity Capacitive + Touch Sensor controller chip. + +config KEYBOARD_MPR121 + tristate "Freescale MPR121 Touch Keypad Driver" + depends on I2C + help + Say Y here if you have the touchkey Freescale Proximity Capacitive + Touch Sensor controller chip in your system. + + If unsure, say N. + + To compile this driver as a module, choose M here endif diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile index 4596d0c6f922..e3344b875d81 100644 --- a/drivers/input/keyboard/Makefile +++ b/drivers/input/keyboard/Makefile @@ -38,3 +38,9 @@ obj-$(CONFIG_KEYBOARD_SUNKBD) += sunkbd.o obj-$(CONFIG_KEYBOARD_TWL4030) += twl4030_keypad.o obj-$(CONFIG_KEYBOARD_XTKBD) += xtkbd.o obj-$(CONFIG_KEYBOARD_W90P910) += w90p910_keypad.o +obj-$(CONFIG_KEYBOARD_MXC) += mxc_keyb.o +obj-$(CONFIG_KEYBOARD_MXC) += mxc_pwrkey.o +obj-$(CONFIG_KEYBOARD_MPR084) += mpr084.o +obj-$(CONFIG_KEYBOARD_MXS) += mxs-kbd.o +obj-$(CONFIG_KEYBOARD_MC9S08DZ60) += mc9s08dz60_keyb.o +obj-$(CONFIG_KEYBOARD_MPR121) += mpr121.o diff --git a/drivers/input/keyboard/mc9s08dz60_keyb.c b/drivers/input/keyboard/mc9s08dz60_keyb.c new file mode 100644 index 000000000000..87ab5d3eda0a --- /dev/null +++ b/drivers/input/keyboard/mc9s08dz60_keyb.c @@ -0,0 +1,248 @@ +/* + * Copyright 2009-2010 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mc9s08dz60keyb.c + * + * @brief Driver for the Freescale Semiconductor MXC keypad port. + * + * The keypad driver is designed as a standard Input driver which interacts + * with low level keypad port hardware. Upon opening, the Keypad driver + * initializes the keypad port. When the keypad interrupt happens the driver + * calles keypad polling timer and scans the keypad matrix for key + * press/release. If all key press/release happened it comes out of timer and + * waits for key press interrupt. The scancode for key press and release events + * are passed to Input subsytem. + * + * @ingroup keypad + */ + +#include <linux/module.h> +#include <linux/sched.h> +#include <linux/mm.h> +#include <linux/init.h> +#include <mach/hardware.h> +#include <linux/kd.h> +#include <linux/fs.h> +#include <linux/kbd_kern.h> +#include <linux/ioctl.h> +#include <linux/poll.h> +#include <linux/interrupt.h> +#include <linux/timer.h> +#include <linux/input.h> +#include <linux/miscdevice.h> +#include <linux/platform_device.h> +#include <linux/delay.h> +#include <linux/clk.h> +#include <linux/mfd/mc9s08dz60/pmic.h> +#include <asm/mach/keypad.h> + +#define MOD_NAME "mc9s08dz60-keyb" +/* + * Module header file + */ + +/*! Input device structure. */ +static struct input_dev *mc9s08dz60kbd_dev; +static unsigned int key_status; +static int keypad_irq; +static unsigned int key_code_map[8] = { + KEY_LEFT, + KEY_DOWN, + 0, + 0, + KEY_UP, + KEY_RIGHT, + 0, + 0, +}; +static unsigned int keycodes_size = 8; + +static void read_key_handler(struct work_struct *work); +static DECLARE_WORK(key_pad_event, read_key_handler); + + +static void read_key_handler(struct work_struct *work) +{ + unsigned int val1, val2; + int pre_val, curr_val, i; + val1 = val2 = 0xff; + mcu_pmic_read_reg(REG_MCU_KPD_1, &val1, 0xff); + mcu_pmic_read_reg(REG_MCU_KPD_2, &val2, 0xff); + pr_debug("key pressed, 0x%02x%02x\n", val2, val1); + for (i = 0; i < 8; i++) { + curr_val = (val1 >> i) & 0x1; + if (curr_val > 0) + input_event(mc9s08dz60kbd_dev, EV_KEY, + key_code_map[i], 1); + else { + pre_val = (key_status >> i) & 0x1; + if (pre_val > 0) + input_event(mc9s08dz60kbd_dev, EV_KEY, + key_code_map[i], 0); + } + } + key_status = val1; + +} + +static irqreturn_t mc9s08dz60kpp_interrupt(int irq, void *dev_id) +{ + schedule_work(&key_pad_event); + return IRQ_RETVAL(1); +} + +/*! + * This function is called when the keypad driver is opened. + * Since keypad initialization is done in __init, nothing is done in open. + * + * @param dev Pointer to device inode + * + * @result The function always return 0 + */ +static int mc9s08dz60kpp_open(struct input_dev *dev) +{ + return 0; +} + +/*! + * This function is called close the keypad device. + * Nothing is done in this function, since every thing is taken care in + * __exit function. + * + * @param dev Pointer to device inode + * + */ +static void mc9s08dz60kpp_close(struct input_dev *dev) +{ +} + + +/*! + * This function is called during the driver binding process. + * + * @param pdev the device structure used to store device specific + * information that is used by the suspend, resume and remove + * functions. + * + * @return The function returns 0 on successful registration. Otherwise returns + * specific error code. + */ +static int mc9s08dz60kpp_probe(struct platform_device *pdev) +{ + int i, irq; + int retval; + + retval = mcu_pmic_write_reg(REG_MCU_KPD_CONTROL, 0x1, 0x1); + if (retval != 0) { + pr_info("mc9s08dz60 keypad: mcu not detected!\n"); + return retval; + } + + irq = platform_get_irq(pdev, 0); + retval = request_irq(irq, mc9s08dz60kpp_interrupt, + 0, MOD_NAME, MOD_NAME); + if (retval) { + pr_debug("KPP: request_irq(%d) returned error %d\n", + irq, retval); + return retval; + } + keypad_irq = irq; + + mc9s08dz60kbd_dev = input_allocate_device(); + if (!mc9s08dz60kbd_dev) { + pr_info(KERN_ERR "mc9s08dz60kbd_dev: \ + not enough memory for input device\n"); + retval = -ENOMEM; + goto err1; + } + + mc9s08dz60kbd_dev->name = "mc9s08dz60kpd"; + mc9s08dz60kbd_dev->id.bustype = BUS_HOST; + mc9s08dz60kbd_dev->open = mc9s08dz60kpp_open; + mc9s08dz60kbd_dev->close = mc9s08dz60kpp_close; + + retval = input_register_device(mc9s08dz60kbd_dev); + if (retval < 0) { + pr_info(KERN_ERR + "mc9s08dz60kbd_dev: failed to register input device\n"); + goto err2; + } + + __set_bit(EV_KEY, mc9s08dz60kbd_dev->evbit); + + for (i = 0; i < keycodes_size; i++) + __set_bit(key_code_map[i], mc9s08dz60kbd_dev->keybit); + + device_init_wakeup(&pdev->dev, 1); + + pr_info("mc9s08dz60 keypad probed\n"); + + return 0; + +err2: + input_free_device(mc9s08dz60kbd_dev); +err1: + free_irq(irq, MOD_NAME); + return retval; +} + +/*! + * Dissociates the driver from the kpp device. + * + * @param pdev the device structure used to give information on which SDHC + * to remove + * + * @return The function always returns 0. + */ +static int mc9s08dz60kpp_remove(struct platform_device *pdev) +{ + free_irq(keypad_irq, MOD_NAME); + input_unregister_device(mc9s08dz60kbd_dev); + + if (mc9s08dz60kbd_dev) + input_free_device(mc9s08dz60kbd_dev); + + return 0; +} + +/*! + * This structure contains pointers to the power management callback functions. + */ +static struct platform_driver mc9s08dz60kpd_driver = { + .driver = { + .name = "mc9s08dz60keypad", + .bus = &platform_bus_type, + }, + .probe = mc9s08dz60kpp_probe, + .remove = mc9s08dz60kpp_remove +}; + +static int __init mc9s08dz60kpp_init(void) +{ + pr_info(KERN_INFO "mc9s08dz60 keypad loaded\n"); + platform_driver_register(&mc9s08dz60kpd_driver); + return 0; +} + +static void __exit mc9s08dz60kpp_cleanup(void) +{ + platform_driver_unregister(&mc9s08dz60kpd_driver); +} + +module_init(mc9s08dz60kpp_init); +module_exit(mc9s08dz60kpp_cleanup); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MXC Keypad Controller Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/keyboard/mpr084.c b/drivers/input/keyboard/mpr084.c new file mode 100644 index 000000000000..d0d5a9fb0809 --- /dev/null +++ b/drivers/input/keyboard/mpr084.c @@ -0,0 +1,500 @@ +/* + * Copyright 2008-2010 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file linux/drivers/input/keyboard/mpr084.c + * + * @brief Driver for the Freescale MPR084 I2C Touch Sensor KeyPad module. + * + * + * + * @ingroup Keypad + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/string.h> +#include <linux/bcd.h> +#include <linux/list.h> +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/kthread.h> +#include <linux/input.h> +#include <linux/delay.h> +#include <linux/regulator/consumer.h> +#include <asm/mach/irq.h> +#include <mach/gpio.h> + +/* + * Definitions + */ +#define DEBUG 0 + +#define KEY_COUNT 8 + +/* + *Registers in MPR084 + */ +#define MPR084_FIFO_ADDR 0x00 +#define MPR084_FAULT_ADDR 0x01 +#define MPR084_TPS_ADDR 0x02 +#define MPR084_TPC_ADDR 0x03 +#define MPR084_STR1_ADDR 0x04 +#define MPR084_STR2_ADDR 0x05 +#define MPR084_STR3_ADDR 0x06 +#define MPR084_STR4_ADDR 0x07 +#define MPR084_STR5_ADDR 0x08 +#define MPR084_STR6_ADDR 0x09 +#define MPR084_STR7_ADDR 0x0A +#define MPR084_STR8_ADDR 0x0B +#define MPR084_ECEM_ADDR 0x0C +#define MPR084_MNTP_ADDR 0x0D +#define MPR084_MTC_ADDR 0x0E +#define MPR084_TASP_ADDR 0x0F +#define MPR084_SC_ADDR 0x10 +#define MPR084_LPC_ADDR 0x11 +#define MPR084_SKT_ADDR 0x12 +#define MPR084_CONFIG_ADDR 0x13 +#define MPR084_SI_ADDR 0x14 +#define MPR084_ADDR_MINI MPR084_FIFO_ADDR +#define MPR084_ADDR_MAX MPR084_SI_ADDR + +/* FIFO registers */ +#define MPR084_FIFO_MORE_DATA_FLAG 0x80 +#define MPR084_FIFO_NO_DATA_FLAG 0x40 +#define MPR084_FIFO_OVERFLOW_FLAG 0x20 +#define MPR084_FIFO_PAD_IS_TOUCHED 0x10 +#define MPR084_FIFO_POSITION_MASK 0x0F + +#define DRIVER_NAME "mpr084" + +struct mpr084_data { + struct i2c_client *client; + struct device_driver driver; + struct input_dev *idev; + struct task_struct *tstask; + struct completion kpirq_completion; + int kpirq; + int kp_thread_cnt; + int opened; +}; + +static int kpstatus[KEY_COUNT]; +static struct mxc_keyp_platform_data *keypad; +static const unsigned short *mxckpd_keycodes; +static struct regulator *vdd_reg; + +static int mpr084_read_register(struct mpr084_data *data, + unsigned char regaddr, int *value) +{ + int ret = 0; + unsigned char regvalue; + + ret = i2c_master_send(data->client, ®addr, 1); + if (ret < 0) + goto err; + udelay(20); + ret = i2c_master_recv(data->client, ®value, 1); + if (ret < 0) + goto err; + *value = regvalue; + + return ret; +err: + return -ENODEV; +} + +static int mpr084_write_register(struct mpr084_data *data, + u8 regaddr, u8 regvalue) +{ + int ret = 0; + unsigned char msgbuf[2]; + + msgbuf[0] = regaddr; + msgbuf[1] = regvalue; + ret = i2c_master_send(data->client, msgbuf, 2); + if (ret < 0) { + printk(KERN_ERR "%s - Error in writing to I2C Register %d \n", + __func__, regaddr); + return ret; + } + + return ret; +} + + +static irqreturn_t mpr084_keypadirq(int irq, void *v) +{ + struct mpr084_data *d = v; + + disable_irq(d->kpirq); + complete(&d->kpirq_completion); + return IRQ_HANDLED; +} + +static int mpr084ts_thread(void *v) +{ + struct mpr084_data *d = v; + int ret = 0, fifo = 0; + int index = 0, currentstatus = 0; + + if (d->kp_thread_cnt) + return -EINVAL; + d->kp_thread_cnt = 1; + while (1) { + + if (kthread_should_stop()) + break; + /* Wait for keypad interrupt */ + if (wait_for_completion_interruptible_timeout + (&d->kpirq_completion, HZ) <= 0) + continue; + + ret = mpr084_read_register(d, MPR084_FIFO_ADDR, &fifo); + if (ret < 0) { + printk(KERN_ERR + "%s: Err in reading keypad FIFO register \n\n", + __func__); + } else { + if (fifo & MPR084_FIFO_OVERFLOW_FLAG) + printk(KERN_ERR + "%s: FIFO overflow \n\n", __func__); + while (!(fifo & MPR084_FIFO_NO_DATA_FLAG)) { + index = fifo & MPR084_FIFO_POSITION_MASK; + currentstatus = + fifo & MPR084_FIFO_PAD_IS_TOUCHED; + /*Scan key map for changes */ + if ((currentstatus) ^ (kpstatus[index])) { + if (!(currentstatus)) { + /*Key released. */ + input_event(d->idev, EV_KEY, + mxckpd_keycodes + [index], 0); + } else { + /* Key pressed. */ + input_event(d->idev, EV_KEY, + mxckpd_keycodes + [index], 1); + } + /*Store current keypad status */ + kpstatus[index] = currentstatus; + } + mpr084_read_register(d, MPR084_FIFO_ADDR, + &fifo); + if (fifo & MPR084_FIFO_OVERFLOW_FLAG) + printk(KERN_ERR + "%s: FIFO overflow \n\n", + __func__); + } + } + /* Re-enable interrupts */ + enable_irq(d->kpirq); + } + + d->kp_thread_cnt = 0; + return 0; +} + +/*! + * This function puts the Keypad controller in low-power mode/state. + * + * @param pdev the device structure used to give information on Keypad + * to suspend + * @param state the power state the device is entering + * + * @return The function always returns 0. + */ +static int mpr084_suspend(struct i2c_client *client, pm_message_t state) +{ + struct mpr084_data *d = i2c_get_clientdata(client); + + if (!IS_ERR(d->tstask) && d->opened) + kthread_stop(d->tstask); + + return 0; +} + +/*! + * This function brings the Keypad controller back from low-power state. + * + * @param pdev the device structure used to give information on Keypad + * to resume + * + * @return The function always returns 0. + */ +static int mpr084_resume(struct i2c_client *client) +{ + struct mpr084_data *d = i2c_get_clientdata(client); + + if (d->opened) + d->tstask = kthread_run(mpr084ts_thread, d, DRIVER_NAME "kpd"); + + return 0; +} + +static int mpr084_idev_open(struct input_dev *idev) +{ + struct mpr084_data *d = input_get_drvdata(idev); + int ret = 0; + + d->tstask = kthread_run(mpr084ts_thread, d, DRIVER_NAME "kpd"); + if (IS_ERR(d->tstask)) + ret = PTR_ERR(d->tstask); + else + d->opened++; + return ret; +} + +static void mpr084_idev_close(struct input_dev *idev) +{ + struct mpr084_data *d = input_get_drvdata(idev); + + if (!IS_ERR(d->tstask)) + kthread_stop(d->tstask); + if (d->opened > 0) + d->opened--; +} + +static int mpr084_driver_register(struct mpr084_data *data) +{ + struct input_dev *idev; + int ret = 0; + + if (data->kpirq) { + ret = + request_irq(data->kpirq, mpr084_keypadirq, + IRQF_TRIGGER_FALLING, DRIVER_NAME, data); + if (!ret) { + init_completion(&data->kpirq_completion); + set_irq_wake(data->kpirq, 1); + } else { + printk(KERN_ERR "%s: cannot grab irq %d\n", + __func__, data->kpirq); + } + + } + idev = input_allocate_device(); + data->idev = idev; + input_set_drvdata(idev, data); + idev->name = DRIVER_NAME; + idev->open = mpr084_idev_open; + idev->close = mpr084_idev_close; + if (!ret) + ret = input_register_device(idev); + + return ret; +} + +static int mpr084_i2c_remove(struct i2c_client *client) +{ + struct mpr084_data *d = i2c_get_clientdata(client); + + free_irq(d->kpirq, d); + input_unregister_device(d->idev); + if (keypad->inactive) + keypad->inactive(); + + /*Disable the Regulator*/ + if (keypad->vdd_reg) { + regulator_disable(vdd_reg); + regulator_put(vdd_reg); + } + + return 0; +} + +static int mpr084_configure(struct mpr084_data *data) +{ + int ret = 0, regValue = 0; + + ret = mpr084_write_register(data, MPR084_TPC_ADDR, 0x1d); + if (ret < 0) + goto err; + ret = mpr084_write_register(data, MPR084_STR1_ADDR, 0x10); + if (ret < 0) + goto err; + ret = mpr084_write_register(data, MPR084_STR2_ADDR, 0x10); + if (ret < 0) + goto err; + ret = mpr084_write_register(data, MPR084_STR3_ADDR, 0x10); + if (ret < 0) + goto err; + ret = mpr084_write_register(data, MPR084_STR4_ADDR, 0x10); + if (ret < 0) + goto err; + ret = mpr084_write_register(data, MPR084_STR5_ADDR, 0x10); + if (ret < 0) + goto err; + ret = mpr084_write_register(data, MPR084_STR6_ADDR, 0x10); + if (ret < 0) + goto err; + ret = mpr084_write_register(data, MPR084_STR7_ADDR, 0x10); + if (ret < 0) + goto err; + ret = mpr084_write_register(data, MPR084_STR8_ADDR, 0x10); + if (ret < 0) + goto err; + /* channel enable mask: enable all */ + ret = mpr084_write_register(data, MPR084_ECEM_ADDR, 0xff); + if (ret < 0) + goto err; + /*two conccurrent touch position allowed */ + ret = mpr084_write_register(data, MPR084_MNTP_ADDR, 0x02); + if (ret < 0) + goto err; + + /* master tick period*/ + ret = mpr084_write_register(data, MPR084_MTC_ADDR, 0x05); + if (ret < 0) + goto err; + + + /*Sample period */ + ret = mpr084_write_register(data, MPR084_TASP_ADDR, 0x02); + if (ret < 0) + goto err; + + + /* disable sournder*/ + ret = mpr084_write_register(data, MPR084_SC_ADDR, 0x00); + if (ret < 0) + goto err; + + /* stuck key timeout */ + ret = mpr084_write_register(data, MPR084_SKT_ADDR, 0x01); + if (ret < 0) + goto err; + + /*enabled IRQEN, RUNE, IRQR */ + ret = mpr084_read_register(data, MPR084_CONFIG_ADDR, ®Value); + if (ret < 0) { + printk(KERN_ERR + "%s: Err in reading keypad CONFIGADDR register \n\n", + __func__); + goto err; + } + regValue |= 0x03; + ret = mpr084_write_register(data, MPR084_CONFIG_ADDR, regValue); + if (ret < 0) + goto err; + return ret; +err: + return -ENODEV; +} + +static int mpr084_i2c_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + struct mpr084_data *data; + int err = 0, i = 0; +#if DEBUG + int regValue = 0; +#endif + data = kzalloc(sizeof(struct mpr084_data), GFP_KERNEL); + if (data == NULL) + return -ENOMEM; + i2c_set_clientdata(client, data); + data->client = client; + data->kpirq = client->irq; + err = mpr084_driver_register(data); + if (err < 0) + goto exit_free; + keypad = (struct mxc_keyp_platform_data *)(client->dev).platform_data; + if (keypad->active) + keypad->active(); + + /*Enable the Regulator*/ + if (keypad && keypad->vdd_reg) { + vdd_reg = regulator_get(&client->dev, keypad->vdd_reg); + if (!IS_ERR(vdd_reg)) + regulator_enable(vdd_reg); + else + vdd_reg = NULL; + } else + vdd_reg = NULL; + + mxckpd_keycodes = keypad->matrix; + data->idev->keycode = &mxckpd_keycodes; + data->idev->keycodesize = sizeof(unsigned char); + data->idev->keycodemax = KEY_COUNT; + data->idev->id.bustype = BUS_I2C; + __set_bit(EV_KEY, data->idev->evbit); + for (i = 0; i < 8; i++) + __set_bit(mxckpd_keycodes[i], data->idev->keybit); + err = mpr084_configure(data); + if (err == -ENODEV) { + free_irq(data->kpirq, data); + input_unregister_device(data->idev); + goto exit_free; + } + +#if DEBUG + for (i = MPR084_ADDR_MINI; i <= MPR084_ADDR_MAX; i++) { + err = mpr084_read_register(data, i, ®Value); + if (err < 0) { + printk(KERN_ERR + "%s: Err in reading keypad CONFIGADDR register \n\n", + __func__); + goto exit_free; + } + printk("MPR084 Register id: %d, Value:%d \n", i, regValue); + + } +#endif + memset(kpstatus, 0, sizeof(kpstatus)); + printk(KERN_INFO "%s: Device Attached\n", __func__); + return 0; +exit_free: + /*disable the Regulator*/ + if (vdd_reg) { + regulator_disable(vdd_reg); + regulator_put(vdd_reg); + vdd_reg = NULL; + } + kfree(data); + return err; +} + +static const struct i2c_device_id mpr084_id[] = { + { "mpr084", 0 }, + {}, +}; +MODULE_DEVICE_TABLE(i2c, mpr084_id); + +static struct i2c_driver mpr084_driver = { + .driver = { + .name = DRIVER_NAME, + }, + .probe = mpr084_i2c_probe, + .remove = mpr084_i2c_remove, + .suspend = mpr084_suspend, + .resume = mpr084_resume, + .command = NULL, + .id_table = mpr084_id, +}; +static int __init mpr084_init(void) +{ + return i2c_add_driver(&mpr084_driver); +} + +static void __exit mpr084_exit(void) +{ + i2c_del_driver(&mpr084_driver); +} + +MODULE_AUTHOR("Freescale Semiconductor Inc"); +MODULE_DESCRIPTION("MPR084 Touch KeyPad Controller driver"); +MODULE_LICENSE("GPL"); +module_init(mpr084_init); +module_exit(mpr084_exit); diff --git a/drivers/input/keyboard/mpr121.c b/drivers/input/keyboard/mpr121.c new file mode 100644 index 000000000000..6a723235805d --- /dev/null +++ b/drivers/input/keyboard/mpr121.c @@ -0,0 +1,305 @@ +/* + * Copyright (C) 2010-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * 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. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/* + * mpr121.c - Touchkey driver for Freescale MPR121 Capacitive Touch + * Sensor Controllor + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/i2c.h> +#include <linux/i2c/mpr.h> +#include <linux/interrupt.h> +#include <linux/input.h> +#include <linux/irq.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/bitops.h> + +struct mpr121_touchkey_data { + struct i2c_client *client; + struct input_dev *input_dev; + unsigned int key_val; + int statusbits; + int keycount; + u16 keycodes[MPR121_MAX_KEY_COUNT]; +}; + +struct mpr121_init_register { + int addr; + u8 val; +}; + +static struct mpr121_init_register init_reg_table[] = { + {MHD_RISING_ADDR, 0x1}, + {NHD_RISING_ADDR, 0x1}, + {NCL_RISING_ADDR, 0x0}, + {FDL_RISING_ADDR, 0x0}, + {MHD_FALLING_ADDR, 0x1}, + {NHD_FALLING_ADDR, 0x1}, + {NCL_FALLING_ADDR, 0xff}, + {FDL_FALLING_ADDR, 0x02}, + {FILTER_CONF_ADDR, 0x04}, + {AFE_CONF_ADDR, 0x0b}, + {AUTO_CONFIG_CTRL_ADDR, 0x0b}, +}; + +static irqreturn_t mpr_touchkey_interrupt(int irq, void *dev_id) +{ + struct mpr121_touchkey_data *data = dev_id; + struct i2c_client *client = data->client; + struct input_dev *input = data->input_dev; + unsigned int key_num, pressed; + int reg; + + reg = i2c_smbus_read_byte_data(client, ELE_TOUCH_STATUS_1_ADDR); + if (reg < 0) { + dev_err(&client->dev, "i2c read error [%d]\n", reg); + goto out; + } + + reg <<= 8; + reg |= i2c_smbus_read_byte_data(client, ELE_TOUCH_STATUS_0_ADDR); + if (reg < 0) { + dev_err(&client->dev, "i2c read error [%d]\n", reg); + goto out; + } + + reg &= TOUCH_STATUS_MASK; + /* use old press bit to figure out which bit changed */ + key_num = ffs(reg ^ data->statusbits) - 1; + /* use the bit check the press status */ + pressed = (reg & (1 << (key_num))) >> key_num; + data->statusbits = reg; + data->key_val = data->keycodes[key_num]; + + input_report_key(input, data->key_val, pressed); + input_sync(input); + + dev_dbg(&client->dev, "key %d %d %s\n", key_num, data->key_val, + pressed ? "pressed" : "released"); + +out: + return IRQ_HANDLED; +} + +static int mpr121_phys_init(struct mpr121_platform_data *pdata, + struct mpr121_touchkey_data *data, + struct i2c_client *client) +{ + struct mpr121_init_register *reg; + unsigned char usl, lsl, tl; + int i, t, vdd, ret; + + /* setup touch/release threshold for ele0-ele11 */ + for (i = 0; i <= MPR121_MAX_KEY_COUNT; i++) { + t = ELE0_TOUCH_THRESHOLD_ADDR + (i * 2); + ret = i2c_smbus_write_byte_data(client, t, TOUCH_THRESHOLD); + if (ret < 0) + goto err_i2c_write; + ret = i2c_smbus_write_byte_data(client, t + 1, + RELEASE_THRESHOLD); + if (ret < 0) + goto err_i2c_write; + } + /* setup init register */ + for (i = 0; i < ARRAY_SIZE(init_reg_table); i++) { + reg = &init_reg_table[i]; + ret = i2c_smbus_write_byte_data(client, reg->addr, reg->val); + if (ret < 0) + goto err_i2c_write; + } + /* setup auto-register by vdd,the formula please ref:AN3889 */ + vdd = pdata->vdd_uv / 1000; + usl = ((vdd - 700) * 256) / vdd; + lsl = (usl * 65) / 100; + tl = (usl * 90) / 100; + ret = i2c_smbus_write_byte_data(client, AUTO_CONFIG_USL_ADDR, usl); + if (ret < 0) + goto err_i2c_write; + ret = i2c_smbus_write_byte_data(client, AUTO_CONFIG_LSL_ADDR, lsl); + if (ret < 0) + goto err_i2c_write; + ret = i2c_smbus_write_byte_data(client, AUTO_CONFIG_TL_ADDR, tl); + if (ret < 0) + goto err_i2c_write; + ret = i2c_smbus_write_byte_data(client, ELECTRODE_CONF_ADDR, + data->keycount); + if (ret < 0) + goto err_i2c_write; + + dev_info(&client->dev, "mpr121: config as enable %x of electrode.\n", + data->keycount); + + return 0; + +err_i2c_write: + dev_err(&client->dev, "i2c write error[%d]\n", ret); + return ret; +} + +static int __devinit mpr_touchkey_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct mpr121_platform_data *pdata; + struct mpr121_touchkey_data *data; + struct input_dev *input_dev; + int error; + int i; + + pdata = client->dev.platform_data; + if (!pdata) { + dev_err(&client->dev, "no platform data defined\n"); + return -EINVAL; + } + + data = kzalloc(sizeof(struct mpr121_touchkey_data), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!data || !input_dev) { + dev_err(&client->dev, "Falied to allocate memory\n"); + error = -ENOMEM; + goto err_free_mem; + } + + data->client = client; + data->input_dev = input_dev; + data->keycount = pdata->keycount; + + if (data->keycount > MPR121_MAX_KEY_COUNT) { + dev_err(&client->dev, "Too many key defined\n"); + error = -EINVAL; + goto err_free_mem; + } + + error = mpr121_phys_init(pdata, data, client); + if (error < 0) { + dev_err(&client->dev, "Failed to init register\n"); + goto err_free_mem; + } + + i2c_set_clientdata(client, data); + + input_dev->name = "FSL MPR121 Touchkey"; + input_dev->id.bustype = BUS_I2C; + input_dev->dev.parent = &client->dev; + input_dev->keycode = pdata->matrix; + input_dev->keycodesize = sizeof(pdata->matrix[0]); + input_dev->keycodemax = data->keycount; + + for (i = 0; i < input_dev->keycodemax; i++) { + __set_bit(pdata->matrix[i], input_dev->keybit); + data->keycodes[i] = pdata->matrix[i]; + input_set_capability(input_dev, EV_KEY, pdata->matrix[i]); + } + input_set_drvdata(input_dev, data); + + error = request_threaded_irq(client->irq, NULL, + mpr_touchkey_interrupt, + IRQF_TRIGGER_FALLING, + client->dev.driver->name, data); + if (error) { + dev_err(&client->dev, "Failed to register interrupt\n"); + goto err_free_mem; + } + + error = input_register_device(input_dev); + if (error) + goto err_free_irq; + i2c_set_clientdata(client, data); + device_init_wakeup(&client->dev, pdata->wakeup); + dev_info(&client->dev, "Mpr121 touch keyboard init success.\n"); + return 0; + +err_free_irq: + free_irq(client->irq, data); +err_free_mem: + input_free_device(input_dev); + kfree(data); + return error; +} + +static int __devexit mpr_touchkey_remove(struct i2c_client *client) +{ + struct mpr121_touchkey_data *data = i2c_get_clientdata(client); + + free_irq(client->irq, data); + input_unregister_device(data->input_dev); + kfree(data); + + return 0; +} + +#ifdef CONFIG_PM +static int mpr_suspend(struct i2c_client *client, pm_message_t mesg) +{ + if (device_may_wakeup(&client->dev)) + enable_irq_wake(client->irq); + + i2c_smbus_write_byte_data(client, ELECTRODE_CONF_ADDR, 0x00); + + return 0; +} + +static int mpr_resume(struct i2c_client *client) +{ + struct mpr121_touchkey_data *data = i2c_get_clientdata(client); + + if (device_may_wakeup(&client->dev)) + disable_irq_wake(client->irq); + + i2c_smbus_write_byte_data(client, ELECTRODE_CONF_ADDR, data->keycount); + + return 0; +} +#else +static int mpr_suspend(struct i2c_client *client, pm_message_t mesg) {} +static int mpr_resume(struct i2c_client *client) {} +#endif + +static const struct i2c_device_id mpr121_id[] = { + {"mpr121_touchkey", 0}, + { } +}; + +static struct i2c_driver mpr_touchkey_driver = { + .driver = { + .name = "mpr121", + .owner = THIS_MODULE, + }, + .id_table = mpr121_id, + .probe = mpr_touchkey_probe, + .remove = __devexit_p(mpr_touchkey_remove), + .suspend = mpr_suspend, + .resume = mpr_resume, +}; + +static int __init mpr_touchkey_init(void) +{ + return i2c_add_driver(&mpr_touchkey_driver); +} + +static void __exit mpr_touchkey_exit(void) +{ + i2c_del_driver(&mpr_touchkey_driver); +} + +module_init(mpr_touchkey_init); +module_exit(mpr_touchkey_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("Touch Key driver for FSL MPR121 Chip"); diff --git a/drivers/input/keyboard/mxc_keyb.c b/drivers/input/keyboard/mxc_keyb.c new file mode 100644 index 000000000000..740140f3f0e2 --- /dev/null +++ b/drivers/input/keyboard/mxc_keyb.c @@ -0,0 +1,1203 @@ +/* + * Copyright 2004-2010 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mxc_keyb.c + * + * @brief Driver for the Freescale Semiconductor MXC keypad port. + * + * The keypad driver is designed as a standard Input driver which interacts + * with low level keypad port hardware. Upon opening, the Keypad driver + * initializes the keypad port. When the keypad interrupt happens the driver + * calles keypad polling timer and scans the keypad matrix for key + * press/release. If all key press/release happened it comes out of timer and + * waits for key press interrupt. The scancode for key press and release events + * are passed to Input subsytem. + * + * @ingroup keypad + */ + +/*! + * Comment KPP_DEBUG to disable debug messages + */ +#define KPP_DEBUG 0 + +#if KPP_DEBUG +#define DEBUG +#include <linux/kernel.h> +#endif + +#include <linux/module.h> +#include <linux/sched.h> +#include <linux/mm.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/uaccess.h> +#include <linux/kd.h> +#include <linux/fs.h> +#include <linux/kbd_kern.h> +#include <linux/ioctl.h> +#include <linux/poll.h> +#include <linux/interrupt.h> +#include <linux/timer.h> +#include <linux/input.h> +#include <linux/miscdevice.h> +#include <linux/platform_device.h> +#include <linux/delay.h> +#include <linux/clk.h> +#include <linux/fsl_devices.h> +#include <linux/slab.h> +#include <asm/mach/keypad.h> + +/*! + * Keypad Module Name + */ +#define MOD_NAME "mxckpd" + +/*! + * XLATE mode selection + */ +#define KEYPAD_XLATE 0 + +/*! + * RAW mode selection + */ +#define KEYPAD_RAW 1 + +/*! + * Maximum number of keys. + */ +#define MAXROW 8 +#define MAXCOL 8 +#define MXC_MAXKEY (MAXROW * MAXCOL) + +/*! + * This define indicates break scancode for every key release. A constant + * of 128 is added to the key press scancode. + */ +#define MXC_KEYRELEASE 128 + +/* + * _reg_KPP_KPCR _reg_KPP_KPSR _reg_KPP_KDDR _reg_KPP_KPDR + * The offset of Keypad Control Register Address + */ +#define KPCR 0x00 + +/* + * The offset of Keypad Status Register Address + */ +#define KPSR 0x02 + +/* + * The offset of Keypad Data Direction Address + */ +#define KDDR 0x04 + +/* + * The offset of Keypad Data Register + */ +#define KPDR 0x06 + +/* + * Key Press Interrupt Status bit + */ +#define KBD_STAT_KPKD 0x01 + +/* + * Key Release Interrupt Status bit + */ +#define KBD_STAT_KPKR 0x02 + +/* + * Key Depress Synchronizer Chain Status bit + */ +#define KBD_STAT_KDSC 0x04 + +/* + * Key Release Synchronizer Status bit + */ +#define KBD_STAT_KRSS 0x08 + +/* + * Key Depress Interrupt Enable Status bit + */ +#define KBD_STAT_KDIE 0x100 + +/* + * Key Release Interrupt Enable + */ +#define KBD_STAT_KRIE 0x200 + +/* + * Keypad Clock Enable + */ +#define KBD_STAT_KPPEN 0x400 + +/*! + * Buffer size of keypad queue. Should be a power of 2. + */ +#define KPP_BUF_SIZE 128 + +/*! + * Test whether bit is set for integer c + */ +#define TEST_BIT(c, n) ((c) & (0x1 << (n))) + +/*! + * Set nth bit in the integer c + */ +#define BITSET(c, n) ((c) | (1 << (n))) + +/*! + * Reset nth bit in the integer c + */ +#define BITRESET(c, n) ((c) & ~(1 << (n))) + +/*! + * This enum represents the keypad state machine to maintain debounce logic + * for key press/release. + */ +enum KeyState { + + /*! + * Key press state. + */ + KStateUp, + + /*! + * Key press debounce state. + */ + KStateFirstDown, + + /*! + * Key release state. + */ + KStateDown, + + /*! + * Key release debounce state. + */ + KStateFirstUp +}; + +/*! + * Keypad Private Data Structure + */ +struct keypad_priv { + + /*! + * Keypad state machine. + */ + enum KeyState iKeyState; + + /*! + * Number of rows configured in the keypad matrix + */ + unsigned long kpp_rows; + + /*! + * Number of Columns configured in the keypad matrix + */ + unsigned long kpp_cols; + + /*! + * Timer used for Keypad polling. + */ + struct timer_list poll_timer; + + /*! + * The base address + */ + void __iomem *base; +}; +/*! + * This structure holds the keypad private data structure. + */ +static struct keypad_priv kpp_dev; + +/*! Indicates if the key pad device is enabled. */ +static unsigned int key_pad_enabled; + +/*! Input device structure. */ +static struct input_dev *mxckbd_dev; + +/*! KPP clock handle. */ +static struct clk *kpp_clk; + +/*! This static variable indicates whether a key event is pressed/released. */ +static unsigned short KPress; + +/*! cur_rcmap and prev_rcmap array is used to detect key press and release. */ +static unsigned short *cur_rcmap; /* max 64 bits (8x8 matrix) */ +static unsigned short *prev_rcmap; + +/*! + * Debounce polling period(10ms) in system ticks. + */ +static unsigned short KScanRate = (10 * HZ) / 1000; + +static struct keypad_data *keypad; + +static int has_leaning_key; +/*! + * These arrays are used to store press and release scancodes. + */ +static short **press_scancode; +static short **release_scancode; + +static const unsigned short *mxckpd_keycodes; +static unsigned short mxckpd_keycodes_size; + +#define press_left_code 30 +#define press_right_code 29 +#define press_up_code 28 +#define press_down_code 27 + +#define rel_left_code 158 +#define rel_right_code 157 +#define rel_up_code 156 +#define rel_down_code 155 +/*! + * These functions are used to configure and the GPIO pins for keypad to + * activate and deactivate it. + */ +extern void gpio_keypad_active(void); +extern void gpio_keypad_inactive(void); + +/*! + * This function is called for generating scancodes for key press and + * release on keypad for the board. + * + * @param row Keypad row pressed on the keypad matrix. + * @param col Keypad col pressed on the keypad matrix. + * @param press Indicated key press/release. + * + * @return Key press/release Scancode. + */ +static signed short mxc_scan_matrix_leaning_key(int row, int col, int press) +{ + static unsigned first_row; + static unsigned first_set, flag; + signed short scancode = -1; + + if (press) { + if ((3 == col) && ((3 == row) || + (4 == row) || (5 == row) || (6 == row))) { + if (first_set == 0) { + first_set = 1; + first_row = row; + } else { + first_set = 0; + if (((first_row == 6) || (first_row == 3)) + && ((row == 6) || (row == 3))) + scancode = press_down_code; + else if (((first_row == 3) || (first_row == 5)) + && ((row == 3) || (row == 5))) + scancode = press_left_code; + else if (((first_row == 6) || (first_row == 4)) + && ((row == 6) || (row == 4))) + scancode = press_right_code; + else if (((first_row == 4) || (first_row == 5)) + && ((row == 4) || (row == 5))) + scancode = press_up_code; + KPress = 1; + kpp_dev.iKeyState = KStateUp; + pr_debug("Press (%d, %d) scan=%d Kpress=%d\n", + row, col, scancode, KPress); + } + } else { + /* + * check for other keys only + * if the cursor key presses + * are not detected may be + * this needs better logic + */ + if ((0 == (cur_rcmap[3] & BITSET(0, 3))) && + (0 == (cur_rcmap[4] & BITSET(0, 3))) && + (0 == (cur_rcmap[5] & BITSET(0, 3))) && + (0 == (cur_rcmap[6] & BITSET(0, 3)))) { + scancode = ((col * kpp_dev.kpp_rows) + row); + KPress = 1; + kpp_dev.iKeyState = KStateUp; + flag = 1; + pr_debug("Press (%d, %d) scan=%d Kpress=%d\n", + row, col, scancode, KPress); + } + } + } else { + if ((flag == 0) && (3 == col) + && ((3 == row) || (4 == row) || (5 == row) + || (6 == row))) { + if (first_set == 0) { + first_set = 1; + first_row = row; + } else { + first_set = 0; + if (((first_row == 6) || (first_row == 3)) + && ((row == 6) || (row == 3))) + scancode = rel_down_code; + else if (((first_row == 3) || (first_row == 5)) + && ((row == 3) || (row == 5))) + scancode = rel_left_code; + else if (((first_row == 6) || (first_row == 4)) + && ((row == 6) || (row == 4))) + scancode = rel_right_code; + else if (((first_row == 4) || (first_row == 5)) + && ((row == 4) || (row == 5))) + scancode = rel_up_code; + KPress = 0; + kpp_dev.iKeyState = KStateDown; + pr_debug("Release (%d, %d) scan=%d Kpress=%d\n", + row, col, scancode, KPress); + } + } else { + /* + * check for other keys only + * if the cursor key presses + * are not detected may be + * this needs better logic + */ + if ((0 == (prev_rcmap[3] & BITSET(0, 3))) && + (0 == (prev_rcmap[4] & BITSET(0, 3))) && + (0 == (cur_rcmap[5] & BITSET(0, 3))) && + (0 == (cur_rcmap[6] & BITSET(0, 3)))) { + scancode = ((col * kpp_dev.kpp_rows) + row) + + MXC_KEYRELEASE; + KPress = 0; + flag = 0; + kpp_dev.iKeyState = KStateDown; + pr_debug("Release (%d, %d) scan=%d Kpress=%d\n", + row, col, scancode, KPress); + } + } + } + return scancode; +} + +/*! + * This function is called to scan the keypad matrix to find out the key press + * and key release events. Make scancode and break scancode are generated for + * key press and key release events. + * + * The following scanning sequence are done for + * keypad row and column scanning, + * -# Write 1's to KPDR[15:8], setting column data to 1's + * -# Configure columns as totem pole outputs(for quick discharging of keypad + * capacitance) + * -# Configure columns as open-drain + * -# Write a single column to 0, others to 1. + * -# Sample row inputs and save data. Multiple key presses can be detected on + * a single column. + * -# Repeat steps the above steps for remaining columns. + * -# Return all columns to 0 in preparation for standby mode. + * -# Clear KPKD and KPKR status bit(s) by writing to a 1, + * Set the KPKR synchronizer chain by writing "1" to KRSS register, + * Clear the KPKD synchronizer chain by writing "1" to KDSC register + * + * @result Number of key pressed/released. + */ +static int mxc_kpp_scan_matrix(void) +{ + unsigned short reg_val; + int col, row; + short scancode = 0; + int keycnt = 0; /* How many keys are still pressed */ + + /* + * wmb() linux kernel function which guarantees orderings in write + * operations + */ + wmb(); + + /* save cur keypad matrix to prev */ + + memcpy(prev_rcmap, cur_rcmap, kpp_dev.kpp_rows * sizeof(prev_rcmap[0])); + memset(cur_rcmap, 0, kpp_dev.kpp_rows * sizeof(cur_rcmap[0])); + + for (col = 0; col < kpp_dev.kpp_cols; col++) { /* Col */ + /* 2. Write 1.s to KPDR[15:8] setting column data to 1.s */ + reg_val = __raw_readw(kpp_dev.base + KPDR); + reg_val |= 0xff00; + __raw_writew(reg_val, kpp_dev.base + KPDR); + + /* + * 3. Configure columns as totem pole outputs(for quick + * discharging of keypad capacitance) + */ + reg_val = __raw_readw(kpp_dev.base + KPCR); + reg_val &= 0x00ff; + __raw_writew(reg_val, kpp_dev.base + KPCR); + + udelay(2); + + /* + * 4. Configure columns as open-drain + */ + reg_val = __raw_readw(kpp_dev.base + KPCR); + reg_val |= ((1 << kpp_dev.kpp_cols) - 1) << 8; + __raw_writew(reg_val, kpp_dev.base + KPCR); + + /* + * 5. Write a single column to 0, others to 1. + * 6. Sample row inputs and save data. Multiple key presses + * can be detected on a single column. + * 7. Repeat steps 2 - 6 for remaining columns. + */ + + /* Col bit starts at 8th bit in KPDR */ + reg_val = __raw_readw(kpp_dev.base + KPDR); + reg_val &= ~(1 << (8 + col)); + __raw_writew(reg_val, kpp_dev.base + KPDR); + + /* Delay added to avoid propagating the 0 from column to row + * when scanning. */ + + udelay(5); + + /* Read row input */ + reg_val = __raw_readw(kpp_dev.base + KPDR); + for (row = 0; row < kpp_dev.kpp_rows; row++) { /* sample row */ + if (TEST_BIT(reg_val, row) == 0) { + cur_rcmap[row] = BITSET(cur_rcmap[row], col); + keycnt++; + } + } + } + + /* + * 8. Return all columns to 0 in preparation for standby mode. + * 9. Clear KPKD and KPKR status bit(s) by writing to a .1., + * set the KPKR synchronizer chain by writing "1" to KRSS register, + * clear the KPKD synchronizer chain by writing "1" to KDSC register + */ + reg_val = 0x00; + __raw_writew(reg_val, kpp_dev.base + KPDR); + reg_val = __raw_readw(kpp_dev.base + KPDR); + reg_val = __raw_readw(kpp_dev.base + KPSR); + reg_val |= KBD_STAT_KPKD | KBD_STAT_KPKR | KBD_STAT_KRSS | + KBD_STAT_KDSC; + __raw_writew(reg_val, kpp_dev.base + KPSR); + + /* Check key press status change */ + + /* + * prev_rcmap array will contain the previous status of the keypad + * matrix. cur_rcmap array will contains the present status of the + * keypad matrix. If a bit is set in the array, that (row, col) bit is + * pressed, else it is not pressed. + * + * XORing these two variables will give us the change in bit for + * particular row and column. If a bit is set in XOR output, then that + * (row, col) has a change of status from the previous state. From + * the diff variable the key press and key release of row and column + * are found out. + * + * If the key press is determined then scancode for key pressed + * can be generated using the following statement: + * scancode = ((row * 8) + col); + * + * If the key release is determined then scancode for key release + * can be generated using the following statement: + * scancode = ((row * 8) + col) + MXC_KEYRELEASE; + */ + for (row = 0; row < kpp_dev.kpp_rows; row++) { + unsigned char diff; + + /* + * Calculate the change in the keypad row status + */ + diff = prev_rcmap[row] ^ cur_rcmap[row]; + + for (col = 0; col < kpp_dev.kpp_cols; col++) { + if ((diff >> col) & 0x1) { + /* There is a status change on col */ + if ((prev_rcmap[row] & BITSET(0, col)) == 0) { + /* + * Previous state is 0, so now + * a key is pressed + */ + if (has_leaning_key) { + scancode = + mxc_scan_matrix_leaning_key + (row, col, 1); + } else { + scancode = + ((row * kpp_dev.kpp_cols) + + col); + KPress = 1; + kpp_dev.iKeyState = KStateUp; + } + pr_debug("Press (%d, %d) scan=%d " + "Kpress=%d\n", + row, col, scancode, KPress); + press_scancode[row][col] = + (short)scancode; + } else { + /* + * Previous state is not 0, so + * now a key is released + */ + if (has_leaning_key) { + scancode = + mxc_scan_matrix_leaning_key + (row, col, 0); + } else { + scancode = + (row * kpp_dev.kpp_cols) + + col + MXC_KEYRELEASE; + KPress = 0; + kpp_dev.iKeyState = KStateDown; + } + + pr_debug + ("Release (%d, %d) scan=%d Kpress=%d\n", + row, col, scancode, KPress); + release_scancode[row][col] = + (short)scancode; + keycnt++; + } + } + } + } + + /* + * This switch case statement is the + * implementation of state machine of debounce + * logic for key press/release. + * The explaination of state machine is as + * follows: + * + * KStateUp State: + * This is in intial state of the state machine + * this state it checks for any key presses. + * The key press can be checked using the + * variable KPress. If KPress is set, then key + * press is identified and switches the to + * KStateFirstDown state for key press to + * debounce. + * + * KStateFirstDown: + * After debounce delay(10ms), if the KPress is + * still set then pass scancode generated to + * input device and change the state to + * KStateDown, else key press debounce is not + * satisfied so change the state to KStateUp. + * + * KStateDown: + * In this state it checks for any key release. + * If KPress variable is cleared, then key + * release is indicated and so, switch the + * state to KStateFirstUp else to state + * KStateDown. + * + * KStateFirstUp: + * After debounce delay(10ms), if the KPress is + * still reset then pass the key release + * scancode to input device and change + * the state to KStateUp else key release is + * not satisfied so change the state to + * KStateDown. + */ + switch (kpp_dev.iKeyState) { + case KStateUp: + if (KPress) { + /* First Down (must debounce). */ + kpp_dev.iKeyState = KStateFirstDown; + } else { + /* Still UP.(NO Changes) */ + kpp_dev.iKeyState = KStateUp; + } + break; + + case KStateFirstDown: + if (KPress) { + for (row = 0; row < kpp_dev.kpp_rows; row++) { + for (col = 0; col < kpp_dev.kpp_cols; col++) { + if ((press_scancode[row][col] != -1)) { + /* Still Down, so add scancode */ + scancode = + press_scancode[row][col]; + input_event(mxckbd_dev, EV_KEY, + mxckpd_keycodes + [scancode], 1); + if (mxckpd_keycodes[scancode] == + KEY_LEFTSHIFT) { + input_event(mxckbd_dev, + EV_KEY, + KEY_3, 1); + } + kpp_dev.iKeyState = KStateDown; + press_scancode[row][col] = -1; + } + } + } + } else { + /* Just a bounce */ + kpp_dev.iKeyState = KStateUp; + } + break; + + case KStateDown: + if (KPress) { + /* Still down (no change) */ + kpp_dev.iKeyState = KStateDown; + } else { + /* First Up. Must debounce */ + kpp_dev.iKeyState = KStateFirstUp; + } + break; + + case KStateFirstUp: + if (KPress) { + /* Just a bounce */ + kpp_dev.iKeyState = KStateDown; + } else { + for (row = 0; row < kpp_dev.kpp_rows; row++) { + for (col = 0; col < kpp_dev.kpp_cols; col++) { + if ((release_scancode[row][col] != -1)) { + scancode = + release_scancode[row][col]; + scancode = + scancode - MXC_KEYRELEASE; + input_event(mxckbd_dev, EV_KEY, + mxckpd_keycodes + [scancode], 0); + if (mxckpd_keycodes[scancode] == + KEY_LEFTSHIFT) { + input_event(mxckbd_dev, + EV_KEY, + KEY_3, 0); + } + kpp_dev.iKeyState = KStateUp; + release_scancode[row][col] = -1; + } + } + } + } + break; + + default: + return -EBADRQC; + break; + } + + return keycnt; +} + +/*! + * This function is called to start the timer for scanning the keypad if there + * is any key press. Currently this interval is set to 10 ms. When there are + * no keys pressed on the keypad we return back, waiting for a keypad key + * press interrupt. + * + * @param data Opaque data passed back by kernel. Not used. + */ +static void mxc_kpp_handle_timer(unsigned long data) +{ + unsigned short reg_val; + int i; + + if (key_pad_enabled == 0) { + return; + } + if (mxc_kpp_scan_matrix() == 0) { + /* + * Stop scanning and wait for interrupt. + * Enable press interrupt and disable release interrupt. + */ + __raw_writew(0x00FF, kpp_dev.base + KPDR); + reg_val = __raw_readw(kpp_dev.base + KPSR); + reg_val |= (KBD_STAT_KPKR | KBD_STAT_KPKD); + reg_val |= KBD_STAT_KRSS | KBD_STAT_KDSC; + __raw_writew(reg_val, kpp_dev.base + KPSR); + reg_val |= KBD_STAT_KDIE; + reg_val &= ~KBD_STAT_KRIE; + __raw_writew(reg_val, kpp_dev.base + KPSR); + + /* + * No more keys pressed... make sure unwanted key codes are + * not given upstairs + */ + for (i = 0; i < kpp_dev.kpp_rows; i++) { + memset(press_scancode[i], -1, + sizeof(press_scancode[0][0]) * kpp_dev.kpp_cols); + memset(release_scancode[i], -1, + sizeof(release_scancode[0][0]) * + kpp_dev.kpp_cols); + } + return; + } + + /* + * There are still some keys pressed, continue to scan. + * We shall scan again in 10 ms. This has to be tuned according + * to the requirement. + */ + kpp_dev.poll_timer.expires = jiffies + KScanRate; + kpp_dev.poll_timer.function = mxc_kpp_handle_timer; + add_timer(&kpp_dev.poll_timer); +} + +/*! + * This function is the keypad Interrupt handler. + * This function checks for keypad status register (KPSR) for key press + * and interrupt. If key press interrupt has occurred, then the key + * press interrupt in the KPSR are disabled. + * It then calls mxc_kpp_scan_matrix to check for any key pressed/released. + * If any key is found to be pressed, then a timer is set to call + * mxc_kpp_scan_matrix function for every 10 ms. + * + * @param irq The Interrupt number + * @param dev_id Driver private data + * + * @result The function returns \b IRQ_RETVAL(1) if interrupt was handled, + * returns \b IRQ_RETVAL(0) if the interrupt was not handled. + * \b IRQ_RETVAL is defined in include/linux/interrupt.h. + */ +static irqreturn_t mxc_kpp_interrupt(int irq, void *dev_id) +{ + unsigned short reg_val; + + /* Delete the polling timer */ + del_timer(&kpp_dev.poll_timer); + reg_val = __raw_readw(kpp_dev.base + KPSR); + + /* Check if it is key press interrupt */ + if (reg_val & KBD_STAT_KPKD) { + /* + * Disable key press(KDIE status bit) interrupt + */ + reg_val &= ~KBD_STAT_KDIE; + __raw_writew(reg_val, kpp_dev.base + KPSR); + } else { + /* spurious interrupt */ + return IRQ_RETVAL(0); + } + /* + * Check if any keys are pressed, if so start polling. + */ + mxc_kpp_handle_timer(0); + + return IRQ_RETVAL(1); +} + +/*! + * This function is called when the keypad driver is opened. + * Since keypad initialization is done in __init, nothing is done in open. + * + * @param dev Pointer to device inode + * + * @result The function always return 0 + */ +static int mxc_kpp_open(struct input_dev *dev) +{ + return 0; +} + +/*! + * This function is called close the keypad device. + * Nothing is done in this function, since every thing is taken care in + * __exit function. + * + * @param dev Pointer to device inode + * + */ +static void mxc_kpp_close(struct input_dev *dev) +{ +} + +#ifdef CONFIG_PM +/*! + * This function puts the Keypad controller in low-power mode/state. + * If Keypad is enabled as a wake source(i.e. it can resume the system + * from suspend mode), the Keypad controller doesn't enter low-power state. + * + * @param pdev the device structure used to give information on Keypad + * to suspend + * @param state the power state the device is entering + * + * @return return -1 when the keypad is pressed. Otherwise, return 0 + */ +static int mxc_kpp_suspend(struct platform_device *pdev, pm_message_t state) +{ + /* When the keypad is still pressed, clean up registers and timers */ + if (timer_pending(&kpp_dev.poll_timer)) + return -1; + + if (device_may_wakeup(&pdev->dev)) { + enable_irq_wake(keypad->irq); + } else { + disable_irq(keypad->irq); + key_pad_enabled = 0; + clk_disable(kpp_clk); + gpio_keypad_inactive(); + } + + return 0; +} + +/*! + * This function brings the Keypad controller back from low-power state. + * If Keypad is enabled as a wake source(i.e. it can resume the system + * from suspend mode), the Keypad controller doesn't enter low-power state. + * + * @param pdev the device structure used to give information on Keypad + * to resume + * + * @return The function always returns 0. + */ +static int mxc_kpp_resume(struct platform_device *pdev) +{ + if (device_may_wakeup(&pdev->dev)) { + disable_irq_wake(keypad->irq); + } else { + gpio_keypad_active(); + clk_enable(kpp_clk); + key_pad_enabled = 1; + enable_irq(keypad->irq); + } + + return 0; +} + +#else +#define mxc_kpp_suspend NULL +#define mxc_kpp_resume NULL +#endif /* CONFIG_PM */ + +/*! + * This function is called to free the allocated memory for local arrays + */ +static void mxc_kpp_free_allocated(void) +{ + + int i; + + if (press_scancode) { + for (i = 0; i < kpp_dev.kpp_rows; i++) { + if (press_scancode[i]) + kfree(press_scancode[i]); + } + kfree(press_scancode); + } + + if (release_scancode) { + for (i = 0; i < kpp_dev.kpp_rows; i++) { + if (release_scancode[i]) + kfree(release_scancode[i]); + } + kfree(release_scancode); + } + + if (cur_rcmap) + kfree(cur_rcmap); + + if (prev_rcmap) + kfree(prev_rcmap); + + if (mxckbd_dev) + input_free_device(mxckbd_dev); +} + +/*! + * This function is called during the driver binding process. + * + * @param pdev the device structure used to store device specific + * information that is used by the suspend, resume and remove + * functions. + * + * @return The function returns 0 on successful registration. Otherwise returns + * specific error code. + */ +static int mxc_kpp_probe(struct platform_device *pdev) +{ + int i, irq; + int retval; + unsigned int reg_val; + struct resource *res; + + keypad = (struct keypad_data *)pdev->dev.platform_data; + + kpp_dev.kpp_cols = keypad->colmax; + kpp_dev.kpp_rows = keypad->rowmax; + key_pad_enabled = 0; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENODEV; + + kpp_dev.base = ioremap(res->start, res->end - res->start + 1); + if (!kpp_dev.base) + return -ENOMEM; + + irq = platform_get_irq(pdev, 0); + keypad->irq = irq; + + /* Enable keypad clock */ + kpp_clk = clk_get(&pdev->dev, "kpp_clk"); + clk_enable(kpp_clk); + + /* IOMUX configuration for keypad */ + gpio_keypad_active(); + + /* Configure keypad */ + + /* Enable number of rows in keypad (KPCR[7:0]) + * Configure keypad columns as open-drain (KPCR[15:8]) + * + * Configure the rows/cols in KPP + * LSB nibble in KPP is for 8 rows + * MSB nibble in KPP is for 8 cols + */ + reg_val = __raw_readw(kpp_dev.base + KPCR); + reg_val |= (1 << keypad->rowmax) - 1; /* LSB */ + reg_val |= ((1 << keypad->colmax) - 1) << 8; /* MSB */ + __raw_writew(reg_val, kpp_dev.base + KPCR); + + /* Write 0's to KPDR[15:8] */ + reg_val = __raw_readw(kpp_dev.base + KPDR); + reg_val &= 0x00ff; + __raw_writew(reg_val, kpp_dev.base + KPDR); + + /* Configure columns as output, rows as input (KDDR[15:0]) */ + reg_val = __raw_readw(kpp_dev.base + KDDR); + reg_val |= 0xff00; + reg_val &= 0xff00; + __raw_writew(reg_val, kpp_dev.base + KDDR); + + reg_val = __raw_readw(kpp_dev.base + KPSR); + reg_val &= ~(KBD_STAT_KPKR | KBD_STAT_KPKD); + reg_val |= KBD_STAT_KPKD; + reg_val |= KBD_STAT_KRSS | KBD_STAT_KDSC; + __raw_writew(reg_val, kpp_dev.base + KPSR); + reg_val |= KBD_STAT_KDIE; + reg_val &= ~KBD_STAT_KRIE; + __raw_writew(reg_val, kpp_dev.base + KPSR); + + has_leaning_key = keypad->learning; + mxckpd_keycodes = keypad->matrix; + mxckpd_keycodes_size = keypad->rowmax * keypad->colmax; + + if ((keypad->matrix == (void *)0) + || (mxckpd_keycodes_size == 0)) { + retval = -ENODEV; + goto err1; + } + + mxckbd_dev = input_allocate_device(); + if (!mxckbd_dev) { + printk(KERN_ERR + "mxckbd_dev: not enough memory for input device\n"); + retval = -ENOMEM; + goto err1; + } + + mxckbd_dev->keycode = (void *)mxckpd_keycodes; + mxckbd_dev->keycodesize = sizeof(mxckpd_keycodes[0]); + mxckbd_dev->keycodemax = mxckpd_keycodes_size; + mxckbd_dev->name = "mxckpd"; + mxckbd_dev->id.bustype = BUS_HOST; + mxckbd_dev->open = mxc_kpp_open; + mxckbd_dev->close = mxc_kpp_close; + + retval = input_register_device(mxckbd_dev); + if (retval < 0) { + printk(KERN_ERR + "mxckbd_dev: failed to register input device\n"); + goto err2; + } + + /* allocate required memory */ + press_scancode = kmalloc(kpp_dev.kpp_rows * sizeof(press_scancode[0]), + GFP_KERNEL); + release_scancode = + kmalloc(kpp_dev.kpp_rows * sizeof(release_scancode[0]), GFP_KERNEL); + + if (!press_scancode || !release_scancode) { + retval = -ENOMEM; + goto err3; + } + + for (i = 0; i < kpp_dev.kpp_rows; i++) { + press_scancode[i] = kmalloc(kpp_dev.kpp_cols + * sizeof(press_scancode[0][0]), + GFP_KERNEL); + release_scancode[i] = + kmalloc(kpp_dev.kpp_cols * sizeof(release_scancode[0][0]), + GFP_KERNEL); + + if (!press_scancode[i] || !release_scancode[i]) { + retval = -ENOMEM; + goto err3; + } + } + + cur_rcmap = + kmalloc(kpp_dev.kpp_rows * sizeof(cur_rcmap[0]), GFP_KERNEL); + prev_rcmap = + kmalloc(kpp_dev.kpp_rows * sizeof(prev_rcmap[0]), GFP_KERNEL); + + if (!cur_rcmap || !prev_rcmap) { + retval = -ENOMEM; + goto err3; + } + + __set_bit(EV_KEY, mxckbd_dev->evbit); + + for (i = 0; i < mxckpd_keycodes_size; i++) + __set_bit(mxckpd_keycodes[i], mxckbd_dev->keybit); + + for (i = 0; i < kpp_dev.kpp_rows; i++) { + memset(press_scancode[i], -1, + sizeof(press_scancode[0][0]) * kpp_dev.kpp_cols); + memset(release_scancode[i], -1, + sizeof(release_scancode[0][0]) * kpp_dev.kpp_cols); + } + memset(cur_rcmap, 0, kpp_dev.kpp_rows * sizeof(cur_rcmap[0])); + memset(prev_rcmap, 0, kpp_dev.kpp_rows * sizeof(prev_rcmap[0])); + + key_pad_enabled = 1; + /* Initialize the polling timer */ + init_timer(&kpp_dev.poll_timer); + + /* + * Request for IRQ number for keypad port. The Interrupt handler + * function (mxc_kpp_interrupt) is called when ever interrupt occurs on + * keypad port. + */ + retval = request_irq(irq, mxc_kpp_interrupt, 0, MOD_NAME, MOD_NAME); + if (retval) { + pr_debug("KPP: request_irq(%d) returned error %d\n", + irq, retval); + goto err3; + } + + /* By default, devices should wakeup if they can */ + /* So keypad is set as "should wakeup" as it can */ + device_init_wakeup(&pdev->dev, 1); + + return 0; + + err3: + mxc_kpp_free_allocated(); + err2: + input_free_device(mxckbd_dev); + err1: + free_irq(irq, MOD_NAME); + clk_disable(kpp_clk); + clk_put(kpp_clk); + return retval; +} + +/*! + * Dissociates the driver from the kpp device. + * + * @param pdev the device structure used to give information on which SDHC + * to remove + * + * @return The function always returns 0. + */ +static int mxc_kpp_remove(struct platform_device *pdev) +{ + unsigned short reg_val; + + /* + * Clear the KPKD status flag (write 1 to it) and synchronizer chain. + * Set KDIE control bit, clear KRIE control bit (avoid false release + * events. Disable the keypad GPIO pins. + */ + __raw_writew(0x00, kpp_dev.base + KPCR); + __raw_writew(0x00, kpp_dev.base + KPDR); + __raw_writew(0x00, kpp_dev.base + KDDR); + + reg_val = __raw_readw(kpp_dev.base + KPSR); + reg_val |= KBD_STAT_KPKD; + reg_val &= ~KBD_STAT_KRSS; + reg_val |= KBD_STAT_KDIE; + reg_val &= ~KBD_STAT_KRIE; + __raw_writew(reg_val, kpp_dev.base + KPSR); + + gpio_keypad_inactive(); + clk_disable(kpp_clk); + clk_put(kpp_clk); + + KPress = 0; + + del_timer(&kpp_dev.poll_timer); + + free_irq(keypad->irq, MOD_NAME); + input_unregister_device(mxckbd_dev); + + mxc_kpp_free_allocated(); + + return 0; +} + +/*! + * This structure contains pointers to the power management callback functions. + */ +static struct platform_driver mxc_kpd_driver = { + .driver = { + .name = "mxc_keypad", + .bus = &platform_bus_type, + }, + .suspend = mxc_kpp_suspend, + .resume = mxc_kpp_resume, + .probe = mxc_kpp_probe, + .remove = mxc_kpp_remove +}; + +/*! + * This function is called for module initialization. + * It registers keypad char driver and requests for KPP irq number. This + * function does the initialization of the keypad device. + * + * The following steps are used for keypad configuration,\n + * -# Enable number of rows in the keypad control register (KPCR[7:0}).\n + * -# Write 0's to KPDR[15:8]\n + * -# Configure keypad columns as open-drain (KPCR[15:8])\n + * -# Configure columns as output, rows as input (KDDR[15:0])\n + * -# Clear the KPKD status flag (write 1 to it) and synchronizer chain\n + * -# Set KDIE control bit, clear KRIE control bit\n + * In this function the keypad queue initialization is done. + * The keypad IOMUX configuration are done here.* + + * + * @return 0 on success and a non-zero value on failure. + */ +static int __init mxc_kpp_init(void) +{ + printk(KERN_INFO "MXC keypad loaded\n"); + platform_driver_register(&mxc_kpd_driver); + return 0; +} + +/*! + * This function is called whenever the module is removed from the kernel. It + * unregisters the keypad driver from kernel and frees the irq number. + * This function puts the keypad to standby mode. The keypad interrupts are + * disabled. It calls gpio_keypad_inactive function to switch gpio + * configuration into default state. + * + */ +static void __exit mxc_kpp_cleanup(void) +{ + platform_driver_unregister(&mxc_kpd_driver); +} + +module_init(mxc_kpp_init); +module_exit(mxc_kpp_cleanup); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MXC Keypad Controller Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/keyboard/mxc_pwrkey.c b/drivers/input/keyboard/mxc_pwrkey.c new file mode 100644 index 000000000000..7e8ae02c8fee --- /dev/null +++ b/drivers/input/keyboard/mxc_pwrkey.c @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2010-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * 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. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ +#include <linux/module.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/mm.h> +#include <linux/init.h> +#include <asm/io.h> +#include <asm/uaccess.h> +#include <linux/kd.h> +#include <linux/fs.h> +#include <linux/ioctl.h> +#include <linux/input.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/powerkey.h> + +struct mxc_pwrkey_priv { + + struct input_dev *input; + int value; + int (*get_status) (int); +}; + +static struct mxc_pwrkey_priv *mxc_pwrkey; + +static void pwrkey_event_handler(void *param) +{ + int pressed; + + pressed = mxc_pwrkey->get_status((int)param); + + if (pressed) { + input_report_key( + mxc_pwrkey->input, mxc_pwrkey->value, 1); + pr_info("%s_Keydown\n", __func__); + } else { + input_report_key( + mxc_pwrkey->input, mxc_pwrkey->value, 0); + pr_info("%s_Keyup\n", __func__); + } +} + +static int mxcpwrkey_probe(struct platform_device *pdev) +{ + int retval; + struct input_dev *input; + struct power_key_platform_data *pdata = pdev->dev.platform_data; + + if (mxc_pwrkey) { + dev_warn(&pdev->dev, "two power key??\n"); + return -EBUSY; + } + + if (!pdata || !pdata->get_key_status) { + dev_err(&pdev->dev, "can not get platform data\n"); + return -EINVAL; + } + + mxc_pwrkey = kmalloc(sizeof(struct mxc_pwrkey_priv), GFP_KERNEL); + if (!mxc_pwrkey) { + dev_err(&pdev->dev, "can not allocate private data"); + return -ENOMEM; + } + + input = input_allocate_device(); + if (!input) { + dev_err(&pdev->dev, "no memory for input device\n"); + retval = -ENOMEM; + goto err1; + } + + input->name = "mxc_power_key"; + input->phys = "mxcpwrkey/input0"; + input->id.bustype = BUS_HOST; + input->evbit[0] = BIT_MASK(EV_KEY); + + mxc_pwrkey->value = pdata->key_value; + mxc_pwrkey->get_status = pdata->get_key_status; + mxc_pwrkey->input = input; + pdata->register_pwrkey(pwrkey_event_handler); + + input_set_capability(input, EV_KEY, mxc_pwrkey->value); + + retval = input_register_device(input); + if (retval < 0) { + dev_err(&pdev->dev, "failed to register input device\n"); + goto err2; + } + + printk(KERN_INFO "PMIC powerkey probe\n"); + + return 0; + +err2: + input_free_device(input); +err1: + kfree(mxc_pwrkey); + mxc_pwrkey = NULL; + + return retval; +} + +static int mxcpwrkey_remove(struct platform_device *pdev) +{ + input_unregister_device(mxc_pwrkey->input); + input_free_device(mxc_pwrkey->input); + kfree(mxc_pwrkey); + mxc_pwrkey = NULL; + + return 0; +} + +static struct platform_driver mxcpwrkey_driver = { + .driver = { + .name = "mxcpwrkey", + }, + .probe = mxcpwrkey_probe, + .remove = mxcpwrkey_remove, +}; + +static int __init mxcpwrkey_init(void) +{ + return platform_driver_register(&mxcpwrkey_driver); +} + +static void __exit mxcpwrkey_exit(void) +{ + platform_driver_unregister(&mxcpwrkey_driver); +} + +module_init(mxcpwrkey_init); +module_exit(mxcpwrkey_exit); + + +MODULE_AUTHOR("Freescale Semiconductor"); +MODULE_DESCRIPTION("MXC board power key Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/keyboard/mxs-kbd.c b/drivers/input/keyboard/mxs-kbd.c new file mode 100644 index 000000000000..20daee01aae4 --- /dev/null +++ b/drivers/input/keyboard/mxs-kbd.c @@ -0,0 +1,365 @@ +/* + * Keypad ladder driver for Freescale MXS boards + * + * Author: dmitry pervushin <dimka@embeddedalley.com> + * + * Copyright 2008-2010 Freescale Semiconductor, Inc. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/slab.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/input.h> +#include <linux/io.h> +#include <linux/ioport.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/pm.h> + +#include <mach/device.h> +#include <mach/hardware.h> +#include <mach/regs-lradc.h> +#include <mach/lradc.h> + +#define BUTTON_PRESS_THRESHOLD 3300 +#define LRADC_NOISE_MARGIN 100 + +/* this value represents the the lradc value at 3.3V ( 3.3V / 0.000879 V/b ) */ +#define TARGET_VDDIO_LRADC_VALUE 3754 + +struct mxskbd_data { + struct input_dev *input; + int last_button; + int irq; + int btn_irq; + struct mxskbd_keypair *keycodes; + unsigned int base; + int chan; + unsigned int btn_enable; /* detect enable bits */ + unsigned int btn_irq_stat; /* detect irq status bits */ + unsigned int btn_irq_ctrl; /* detect irq enable bits */ +}; + +static int delay1 = 500; +static int delay2 = 200; + +static int mxskbd_open(struct input_dev *dev); +static void mxskbd_close(struct input_dev *dev); + +static struct mxskbd_data *mxskbd_data_alloc(struct platform_device *pdev, + struct mxskbd_keypair *keys) +{ + struct mxskbd_data *d = kzalloc(sizeof(*d), GFP_KERNEL); + + if (!d) + return NULL; + + if (!keys) { + dev_err(&pdev->dev, + "No keycodes in platform_data, bailing out.\n"); + kfree(d); + return NULL; + } + d->keycodes = keys; + + d->input = input_allocate_device(); + if (!d->input) { + kfree(d); + return NULL; + } + + d->input->phys = "onboard"; + d->input->uniq = "0000'0000"; + d->input->name = pdev->name; + d->input->id.bustype = BUS_HOST; + d->input->open = mxskbd_open; + d->input->close = mxskbd_close; + d->input->dev.parent = &pdev->dev; + + set_bit(EV_KEY, d->input->evbit); + set_bit(EV_REL, d->input->evbit); + set_bit(EV_REP, d->input->evbit); + + + d->last_button = -1; + + while (keys->raw >= 0) { + set_bit(keys->kcode, d->input->keybit); + keys++; + } + + return d; +} + +static inline struct input_dev *GET_INPUT_DEV(struct mxskbd_data *d) +{ + BUG_ON(!d); + return d->input; +} + +static void mxskbd_data_free(struct mxskbd_data *d) +{ + if (!d) + return; + if (d->input) + input_free_device(d->input); + kfree(d); +} + +static unsigned mxskbd_decode_button(struct mxskbd_keypair *codes, + int raw_button) + +{ + pr_debug("Decoding %d\n", raw_button); + while (codes->raw != -1) { + if ((raw_button >= (codes->raw - LRADC_NOISE_MARGIN)) && + (raw_button < (codes->raw + LRADC_NOISE_MARGIN))) { + pr_debug("matches code 0x%x = %d\n", + codes->kcode, codes->kcode); + return codes->kcode; + } + codes++; + } + return (unsigned)-1; /* invalid key */ +} + + +static irqreturn_t mxskbd_irq_handler(int irq, void *dev_id) +{ + struct platform_device *pdev = dev_id; + struct mxskbd_data *devdata = platform_get_drvdata(pdev); + u16 raw_button, normalized_button, vddio; + unsigned btn; + + if (devdata->btn_irq == irq) { + __raw_writel(devdata->btn_irq_stat, + devdata->base + HW_LRADC_CTRL1_CLR); + __raw_writel(BM_LRADC_CTRL1_LRADC0_IRQ << devdata->chan, + devdata->base + HW_LRADC_CTRL1_CLR); + __raw_writel(BM_LRADC_CTRL1_LRADC0_IRQ_EN << devdata->chan, + devdata->base + HW_LRADC_CTRL1_SET); + return IRQ_HANDLED; + } + + raw_button = __raw_readl(devdata->base + HW_LRADC_CHn(devdata->chan)) & + BM_LRADC_CHn_VALUE; + vddio = hw_lradc_vddio(); + BUG_ON(vddio == 0); + + normalized_button = (raw_button * TARGET_VDDIO_LRADC_VALUE) / + vddio; + + if (normalized_button < BUTTON_PRESS_THRESHOLD && + devdata->last_button < 0) { + btn = mxskbd_decode_button(devdata->keycodes, + normalized_button); + if (btn < KEY_MAX) { + devdata->last_button = btn; + input_report_key(GET_INPUT_DEV(devdata), + devdata->last_button, !0); + } else + dev_err(&pdev->dev, "Invalid button: raw = %d, " + "normalized = %d, vddio = %d\n", + raw_button, normalized_button, vddio); + } else if (devdata->last_button > 0 && + normalized_button >= BUTTON_PRESS_THRESHOLD) { + input_report_key(GET_INPUT_DEV(devdata), + devdata->last_button, 0); + devdata->last_button = -1; + if (devdata->btn_irq > 0) + __raw_writel(BM_LRADC_CTRL1_LRADC0_IRQ_EN << + devdata->chan, + devdata->base + HW_LRADC_CTRL1_CLR); + } + + __raw_writel(BM_LRADC_CTRL1_LRADC0_IRQ << devdata->chan, + devdata->base + HW_LRADC_CTRL1_CLR); + return IRQ_HANDLED; +} + +static int mxskbd_open(struct input_dev *dev) +{ + /* enable clock */ + return 0; +} + +static void mxskbd_close(struct input_dev *dev) +{ + /* disable clock */ +} + +static void mxskbd_hwinit(struct platform_device *pdev) +{ + struct mxskbd_data *d = platform_get_drvdata(pdev); + + hw_lradc_init_ladder(d->chan, LRADC_DELAY_TRIGGER_BUTTON, 200); + if (d->btn_irq > 0) { + __raw_writel(d->btn_enable, d->base + HW_LRADC_CTRL0_SET); + __raw_writel(d->btn_irq_ctrl, d->base + HW_LRADC_CTRL1_SET); + } else { + __raw_writel(BM_LRADC_CTRL1_LRADC0_IRQ << d->chan, + d->base + HW_LRADC_CTRL1_CLR); + __raw_writel(BM_LRADC_CTRL1_LRADC0_IRQ_EN << d->chan, + d->base + HW_LRADC_CTRL1_SET); + } + hw_lradc_set_delay_trigger_kick(LRADC_DELAY_TRIGGER_BUTTON, !0); +} + +#ifdef CONFIG_PM +static int mxskbd_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct mxskbd_data *d = platform_get_drvdata(pdev); + + hw_lradc_stop_ladder(d->chan, LRADC_DELAY_TRIGGER_BUTTON); + hw_lradc_set_delay_trigger_kick(LRADC_DELAY_TRIGGER_BUTTON, 0); + hw_lradc_unuse_channel(d->chan); + __raw_writel(BM_LRADC_CTRL1_LRADC0_IRQ_EN << d->chan, + d->base + HW_LRADC_CTRL1_CLR); + mxskbd_close(d->input); + return 0; +} + +static int mxskbd_resume(struct platform_device *pdev) +{ + struct mxskbd_data *d = platform_get_drvdata(pdev); + + __raw_writel(BM_LRADC_CTRL1_LRADC0_IRQ_EN << d->chan, + d->base + HW_LRADC_CTRL1_SET); + mxskbd_open(d->input); + hw_lradc_use_channel(d->chan); + mxskbd_hwinit(pdev); + return 0; +} +#endif + +static int __devinit mxskbd_probe(struct platform_device *pdev) +{ + int err = 0; + struct resource *res; + struct mxskbd_data *d; + struct mxs_kbd_plat_data *plat_data; + + plat_data = (struct mxs_kbd_plat_data *)pdev->dev.platform_data; + if (plat_data == NULL) + return -ENODEV; + + /* Create and register the input driver. */ + d = mxskbd_data_alloc(pdev, plat_data->keypair); + if (!d) { + dev_err(&pdev->dev, "Cannot allocate driver structures\n"); + err = -ENOMEM; + goto err_out; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + err = -ENODEV; + goto err_out; + } + d->base = (unsigned int)IO_ADDRESS(res->start); + d->chan = plat_data->channel; + d->irq = platform_get_irq(pdev, 0); + d->btn_irq = platform_get_irq(pdev, 1); + d->btn_enable = plat_data->btn_enable; + d->btn_irq_stat = plat_data->btn_irq_stat; + d->btn_irq_ctrl = plat_data->btn_irq_ctrl; + + platform_set_drvdata(pdev, d); + + err = request_irq(d->irq, mxskbd_irq_handler, + IRQF_DISABLED, pdev->name, pdev); + if (err) { + dev_err(&pdev->dev, "Cannot request keypad IRQ\n"); + goto err_free_dev; + } + + if (d->btn_irq > 0) { + err = request_irq(d->btn_irq, mxskbd_irq_handler, + IRQF_DISABLED, pdev->name, pdev); + if (err) { + dev_err(&pdev->dev, + "Cannot request keybad detect IRQ\n"); + goto err_free_irq; + } + } + + /* Register the input device */ + err = input_register_device(GET_INPUT_DEV(d)); + if (err) + goto err_free_irq2; + + /* these two have to be set after registering the input device */ + d->input->rep[REP_DELAY] = delay1; + d->input->rep[REP_PERIOD] = delay2; + + hw_lradc_use_channel(d->chan); + mxskbd_hwinit(pdev); + + return 0; + +err_free_irq2: + platform_set_drvdata(pdev, NULL); + if (d->btn_irq > 0) + free_irq(d->btn_irq, pdev); +err_free_irq: + free_irq(d->irq, pdev); +err_free_dev: + mxskbd_data_free(d); +err_out: + return err; +} + +static int __devexit mxskbd_remove(struct platform_device *pdev) +{ + struct mxskbd_data *d = platform_get_drvdata(pdev); + + hw_lradc_unuse_channel(d->chan); + input_unregister_device(GET_INPUT_DEV(d)); + free_irq(d->irq, pdev); + if (d->btn_irq > 0) + free_irq(d->btn_irq, pdev); + mxskbd_data_free(d); + + platform_set_drvdata(pdev, NULL); + + return 0; +} + +static struct platform_driver mxskbd_driver = { + .probe = mxskbd_probe, + .remove = __devexit_p(mxskbd_remove), +#ifdef CONFIG_PM + .suspend = mxskbd_suspend, + .resume = mxskbd_resume, +#endif + .driver = { + .name = "mxs-kbd", + }, +}; + +static int __init mxskbd_init(void) +{ + return platform_driver_register(&mxskbd_driver); +} + +static void __exit mxskbd_exit(void) +{ + platform_driver_unregister(&mxskbd_driver); +} + +module_init(mxskbd_init); +module_exit(mxskbd_exit); +MODULE_DESCRIPTION("Freescale keyboard driver for mxs family"); +MODULE_AUTHOR("dmitry pervushin <dimka@embeddedalley.com>") +MODULE_LICENSE("GPL"); |