diff options
author | Benn Pörscke <benn.porscke@stericsson.com> | 2011-10-07 15:31:57 +0200 |
---|---|---|
committer | Benn Pörscke <benn.porscke@stericsson.com> | 2011-10-07 15:31:57 +0200 |
commit | 47a4dbf83a75014d6b3467be18997894f1c617db (patch) | |
tree | 7f5d116db48205309fbc4ae0954f20ab8a651e46 /drivers/input | |
parent | ea8a52f9f4bcc3420c38ae07f8378a2f18443970 (diff) |
Squashandroid-20111012
Change-Id: If0ae9fa8067740ab2ede33703c79ec134f204a5e
Diffstat (limited to 'drivers/input')
26 files changed, 9162 insertions, 2 deletions
diff --git a/drivers/input/Kconfig b/drivers/input/Kconfig index 07c2cd43109..dd62135e704 100644 --- a/drivers/input/Kconfig +++ b/drivers/input/Kconfig @@ -161,6 +161,15 @@ config INPUT_APMPOWER To compile this driver as a module, choose M here: the module will be called apm-power. +config INPUT_KEYRESET + tristate "Reset key" + depends on INPUT + ---help--- + Say Y here if you want to reboot when some keys are pressed; + + To compile this driver as a module, choose M here: the + module will be called keyreset. + config XEN_KBDDEV_FRONTEND tristate "Xen virtual keyboard and mouse support" depends on XEN_FBDEV_FRONTEND diff --git a/drivers/input/Makefile b/drivers/input/Makefile index 7ad212d31f9..3c7125758b7 100644 --- a/drivers/input/Makefile +++ b/drivers/input/Makefile @@ -24,5 +24,6 @@ obj-$(CONFIG_INPUT_TOUCHSCREEN) += touchscreen/ obj-$(CONFIG_INPUT_MISC) += misc/ obj-$(CONFIG_INPUT_APMPOWER) += apm-power.o +obj-$(CONFIG_INPUT_KEYRESET) += keyreset.o obj-$(CONFIG_XEN_KBDDEV_FRONTEND) += xen-kbdfront.o diff --git a/drivers/input/evdev.c b/drivers/input/evdev.c index 2ee6c7a68bd..1fcd6a2e3dc 100644 --- a/drivers/input/evdev.c +++ b/drivers/input/evdev.c @@ -20,6 +20,7 @@ #include <linux/input.h> #include <linux/major.h> #include <linux/device.h> +#include <linux/wakelock.h> #include "input-compat.h" struct evdev { @@ -43,6 +44,8 @@ struct evdev_client { struct fasync_struct *fasync; struct evdev *evdev; struct list_head node; + struct wake_lock wake_lock; + char name[28]; }; static struct evdev *evdev_table[EVDEV_MINORS]; @@ -55,6 +58,7 @@ static void evdev_pass_event(struct evdev_client *client, * Interrupts are disabled, just acquire the lock */ spin_lock(&client->buffer_lock); + wake_lock_timeout(&client->wake_lock, 5 * HZ); client->buffer[client->head++] = *event; client->head &= EVDEV_BUFFER_SIZE - 1; spin_unlock(&client->buffer_lock); @@ -72,8 +76,11 @@ static void evdev_event(struct input_handle *handle, struct evdev *evdev = handle->private; struct evdev_client *client; struct input_event event; + struct timespec ts; - do_gettimeofday(&event.time); + ktime_get_ts(&ts); + event.time.tv_sec = ts.tv_sec; + event.time.tv_usec = ts.tv_nsec / NSEC_PER_USEC; event.type = type; event.code = code; event.value = value; @@ -234,6 +241,7 @@ static int evdev_release(struct inode *inode, struct file *file) mutex_unlock(&evdev->mutex); evdev_detach_client(evdev, client); + wake_lock_destroy(&client->wake_lock); kfree(client); evdev_close_device(evdev); @@ -270,6 +278,9 @@ static int evdev_open(struct inode *inode, struct file *file) } spin_lock_init(&client->buffer_lock); + snprintf(client->name, sizeof(client->name), "%s-%d", + dev_name(&evdev->dev), task_tgid_vnr(current)); + wake_lock_init(&client->wake_lock, WAKE_LOCK_SUSPEND, client->name); client->evdev = evdev; evdev_attach_client(evdev, client); @@ -335,6 +346,8 @@ static int evdev_fetch_next_event(struct evdev_client *client, if (have_event) { *event = client->buffer[client->tail++]; client->tail &= EVDEV_BUFFER_SIZE - 1; + if (client->head == client->tail) + wake_unlock(&client->wake_lock); } spin_unlock_irq(&client->buffer_lock); diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig index 1ba25145b33..3012138fade 100644 --- a/drivers/input/keyboard/Kconfig +++ b/drivers/input/keyboard/Kconfig @@ -143,6 +143,16 @@ config KEYBOARD_BFIN To compile this driver as a module, choose M here: the module will be called bf54x-keys. +config KEYBOARD_DB5500 + tristate "DB5500 keyboard" + depends on UX500_SOC_DB5500 + help + Say Y here to enable the on-chip keypad controller on the + ST-Ericsson U5500 platform. + + To compile this driver as a module, choose M here: the + module will be called db5500_keypad. + config KEYBOARD_LKKBD tristate "DECstation/VAXstation LK201/LK401 keyboard" select SERIO @@ -315,6 +325,16 @@ config KEYBOARD_NEWTON To compile this driver as a module, choose M here: the module will be called newtonkbd. +config KEYBOARD_NOMADIK + tristate "ST-Ericsson Nomadik SKE keyboard" + depends on PLAT_NOMADIK + help + Say Y here if you want to use a keypad provided on the SKE controller + used on the Ux500 and Nomadik platforms + + To compile this driver as a module, choose M here: the + module will be called nmk-ske-keypad. + config KEYBOARD_OPENCORES tristate "OpenCores Keyboard Controller" help @@ -374,6 +394,16 @@ config KEYBOARD_SH_KEYSC To compile this driver as a module, choose M here: the module will be called sh_keysc. +config KEYBOARD_STMPE + tristate "STMPE keypad support" + depends on MFD_STMPE + help + Say Y here if you want to use the keypad controller on STMPE I/O + expanders. + + To compile this driver as a module, choose M here: the module will be + called stmpe-keypad. + config KEYBOARD_DAVINCI tristate "TI DaVinci Key Scan" depends on ARCH_DAVINCI_DM365 @@ -404,6 +434,46 @@ config KEYBOARD_TWL4030 To compile this driver as a module, choose M here: the module will be called twl4030_keypad. +config KEYBOARD_TOSA + tristate "Tosa keyboard (deprecated)" + depends on MACH_TOSA + help + Say Y here to enable the keyboard on the Sharp Zaurus SL-6000x (Tosa) + + This driver is now deprecated, use generic GPIO based matrix + keyboard driver instead. + + To compile this driver as a module, choose M here: the + module will be called tosakbd. + +config KEYBOARD_TOSA_USE_EXT_KEYCODES + bool "Tosa keyboard: use extended keycodes" + depends on KEYBOARD_TOSA + help + Say Y here to enable the tosa keyboard driver to generate extended + (>= 127) keycodes. Be aware, that they can't be correctly interpreted + by either console keyboard driver or by Kdrive keybd driver. + + Say Y only if you know, what you are doing! + +config KEYBOARD_NOMADIK_SKE + tristate "Nomadik SKE Keypad support" + depends on ARCH_U8500 && !KEYBOARD_STMPE && !KEYBOARD_TC35893 + default y + help + Say Y here if you want to use a keypad provided on the SKE controller + used on the Ux500 and Nomadik platforms + +config KEYBOARD_TC35893 + tristate "TC35893 Keypad support" + depends on I2C + help + Say Y here if you want to use the keypad controller on + TC35893 I/O expander on the U8500 NUIB + + To compile this driver as a module, choose M here: the + module will be called tc35893-keypad + config KEYBOARD_XTKBD tristate "XT keyboard" select SERIO diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile index 4596d0c6f92..a72a957e136 100644 --- a/drivers/input/keyboard/Makefile +++ b/drivers/input/keyboard/Makefile @@ -12,6 +12,7 @@ obj-$(CONFIG_KEYBOARD_ATARI) += atakbd.o obj-$(CONFIG_KEYBOARD_ATKBD) += atkbd.o obj-$(CONFIG_KEYBOARD_BFIN) += bf54x-keys.o obj-$(CONFIG_KEYBOARD_DAVINCI) += davinci_keyscan.o +obj-$(CONFIG_KEYBOARD_DB5500) += db5500_keypad.o obj-$(CONFIG_KEYBOARD_EP93XX) += ep93xx_keypad.o obj-$(CONFIG_KEYBOARD_GPIO) += gpio_keys.o obj-$(CONFIG_KEYBOARD_TCA6416) += tca6416-keypad.o @@ -27,14 +28,17 @@ obj-$(CONFIG_KEYBOARD_MAPLE) += maple_keyb.o obj-$(CONFIG_KEYBOARD_MATRIX) += matrix_keypad.o obj-$(CONFIG_KEYBOARD_MAX7359) += max7359_keypad.o obj-$(CONFIG_KEYBOARD_NEWTON) += newtonkbd.o +obj-$(CONFIG_KEYBOARD_NOMADIK_SKE) += nomadik-ske-keypad.o obj-$(CONFIG_KEYBOARD_OMAP) += omap-keypad.o obj-$(CONFIG_KEYBOARD_OPENCORES) += opencores-kbd.o obj-$(CONFIG_KEYBOARD_PXA27x) += pxa27x_keypad.o obj-$(CONFIG_KEYBOARD_PXA930_ROTARY) += pxa930_rotary.o obj-$(CONFIG_KEYBOARD_QT2160) += qt2160.o obj-$(CONFIG_KEYBOARD_SH_KEYSC) += sh_keysc.o +obj-$(CONFIG_KEYBOARD_STMPE) += stmpe-keypad.o obj-$(CONFIG_KEYBOARD_STOWAWAY) += stowaway.o obj-$(CONFIG_KEYBOARD_SUNKBD) += sunkbd.o obj-$(CONFIG_KEYBOARD_TWL4030) += twl4030_keypad.o +obj-$(CONFIG_KEYBOARD_TC35893) += tc35893-keypad.o obj-$(CONFIG_KEYBOARD_XTKBD) += xtkbd.o obj-$(CONFIG_KEYBOARD_W90P910) += w90p910_keypad.o diff --git a/drivers/input/keyboard/db5500_keypad.c b/drivers/input/keyboard/db5500_keypad.c new file mode 100644 index 00000000000..8767b7f81ba --- /dev/null +++ b/drivers/input/keyboard/db5500_keypad.c @@ -0,0 +1,449 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * License terms: GNU General Public License, version 2 + * Author: Sundar Iyer <sundar.iyer@stericsson.com> for ST-Ericsson + */ + +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/gpio.h> +#include <linux/io.h> +#include <linux/input.h> +#include <linux/input/matrix_keypad.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/clk.h> +#include <mach/db5500-keypad.h> + +#define KEYPAD_CTR 0x0 +#define KEYPAD_IRQ_CLEAR 0x4 +#define KEYPAD_INT_ENABLE 0x8 +#define KEYPAD_INT_STATUS 0xC +#define KEYPAD_ARRAY_01 0x18 + +#define KEYPAD_NUM_ARRAY_REGS 5 + +#define KEYPAD_CTR_WRITE_IRQ_ENABLE (1 << 10) +#define KEYPAD_CTR_WRITE_CONTROL (1 << 8) +#define KEYPAD_CTR_SCAN_ENABLE (1 << 7) + +#define KEYPAD_ARRAY_CHANGEBIT (1 << 15) + +#define KEYPAD_DEBOUNCE_PERIOD_MIN 5 /* ms */ +#define KEYPAD_DEBOUNCE_PERIOD_MAX 80 /* ms */ + +#define KEYPAD_GND_ROW 8 + +#define KEYPAD_MAX_ROWS 9 +#define KEYPAD_MAX_COLS 8 +#define KEYPAD_ROW_SHIFT 4 +#define KEYPAD_KEYMAP_SIZE \ + (KEYPAD_MAX_ROWS * KEYPAD_MAX_COLS) + +/** + * struct db5500_keypad - data structure used by keypad driver + * @irq: irq number + * @base: keypad registers base address + * @input: pointer to input device object + * @board: keypad platform data + * @keymap: matrix scan code table for keycodes + * @clk: clock structure pointer + * @previous_set: previous set of registers + */ +struct db5500_keypad { + int irq; + void __iomem *base; + struct input_dev *input; + const struct db5500_keypad_platform_data *board; + unsigned short keymap[KEYPAD_KEYMAP_SIZE]; + struct clk *clk; + u8 previous_set[KEYPAD_MAX_ROWS]; +}; + +/* + * By default all column reads are 1111 1111b. Any press will pull the column + * down, leading to a 0 in any of these locations. We invert these values so + * that a 1 means means "column pressed". + * + * If curr changes from the previous from 0 to 1, we report it as a key press. + * If curr changes from the previous from 1 to 0, we report it as a key + * release. + */ +static void db5500_keypad_report(struct db5500_keypad *keypad, int row, + u8 curr, u8 previous) +{ + struct input_dev *input = keypad->input; + u8 changed = curr ^ previous; + + while (changed) { + int col = __ffs(changed); + bool press = curr & BIT(col); + int code = MATRIX_SCAN_CODE(row, col, KEYPAD_ROW_SHIFT); + + input_event(input, EV_MSC, MSC_SCAN, code); + input_report_key(input, keypad->keymap[code], press); + input_sync(input); + + changed &= ~BIT(col); + } +} + +static irqreturn_t db5500_keypad_irq(int irq, void *dev_id) +{ + struct db5500_keypad *keypad = dev_id; + u8 current_set[ARRAY_SIZE(keypad->previous_set)]; + int tries = 100; + bool changebit; + u32 data_reg; + u8 allrows; + u8 common; + int i; + + writel(0x1, keypad->base + KEYPAD_IRQ_CLEAR); + +again: + if (!tries--) { + dev_warn(&keypad->input->dev, "values failed to stabilize\n"); + return IRQ_HANDLED; + } + + changebit = readl(keypad->base + KEYPAD_ARRAY_01) + & KEYPAD_ARRAY_CHANGEBIT; + + for (i = 0; i < KEYPAD_NUM_ARRAY_REGS; i++) { + data_reg = readl(keypad->base + KEYPAD_ARRAY_01 + 4 * i); + + /* If the change bit changed, we need to reread the data */ + if (changebit != !!(data_reg & KEYPAD_ARRAY_CHANGEBIT)) + goto again; + + current_set[2 * i] = ~(data_reg & 0xff); + + /* Last array reg has only one valid set of columns */ + if (i != KEYPAD_NUM_ARRAY_REGS - 1) + current_set[2 * i + 1] = ~((data_reg & 0xff0000) >> 16); + } + + allrows = current_set[KEYPAD_GND_ROW]; + + /* + * Sometimes during a GND row release, an incorrect report is received + * where the ARRAY8 all rows setting does not match the other ARRAY* + * rows. Ignore this report; the correct one has been observed to + * follow it. + */ + common = 0xff; + for (i = 0; i < KEYPAD_GND_ROW; i++) + common &= current_set[i]; + + if ((allrows & common) != common) + return IRQ_HANDLED; + + for (i = 0; i < ARRAY_SIZE(current_set); i++) { + /* + * If there is an allrows press (GND row), we need to ignore + * the allrows values from the reset of the ARRAYs. + */ + if (i < KEYPAD_GND_ROW && allrows) + current_set[i] &= ~allrows; + + if (keypad->previous_set[i] == current_set[i]) + continue; + + db5500_keypad_report(keypad, i, current_set[i], + keypad->previous_set[i]); + } + + /* update the reference set of array registers */ + memcpy(keypad->previous_set, current_set, sizeof(keypad->previous_set)); + + return IRQ_HANDLED; +} + +static void db5500_keypad_writel(struct db5500_keypad *keypad, u32 val, u32 reg) +{ + int timeout = 4; + int allowedbit; + + switch (reg) { + case KEYPAD_CTR: + allowedbit = KEYPAD_CTR_WRITE_CONTROL; + break; + case KEYPAD_INT_ENABLE: + allowedbit = KEYPAD_CTR_WRITE_IRQ_ENABLE; + break; + default: + BUG(); + } + + do { + u32 ctr = readl(keypad->base + KEYPAD_CTR); + + if (ctr & allowedbit) + break; + + udelay(50); + } while (--timeout); + + /* Five 32k clk cycles (~150us) required, we waited 200us */ + WARN_ON(!timeout); + + writel(val, keypad->base + reg); +} + +static int db5500_keypad_chip_init(struct db5500_keypad *keypad) +{ + int debounce = keypad->board->debounce_ms; + int debounce_hits = 0; + + if (debounce < KEYPAD_DEBOUNCE_PERIOD_MIN) + debounce = KEYPAD_DEBOUNCE_PERIOD_MIN; + + if (debounce > KEYPAD_DEBOUNCE_PERIOD_MAX) { + debounce_hits = DIV_ROUND_UP(debounce, + KEYPAD_DEBOUNCE_PERIOD_MAX) - 1; + debounce = KEYPAD_DEBOUNCE_PERIOD_MAX; + } + + /* Convert the milliseconds to the bit mask */ + debounce = DIV_ROUND_UP(debounce, KEYPAD_DEBOUNCE_PERIOD_MIN) - 1; + + clk_enable(keypad->clk); + + db5500_keypad_writel(keypad, + KEYPAD_CTR_SCAN_ENABLE + | ((debounce_hits & 0x7) << 4) + | debounce, + KEYPAD_CTR); + + db5500_keypad_writel(keypad, 0x1, KEYPAD_INT_ENABLE); + + return 0; +} + +static void db5500_keypad_close(struct db5500_keypad *keypad) +{ + db5500_keypad_writel(keypad, 0, KEYPAD_CTR); + db5500_keypad_writel(keypad, 0, KEYPAD_INT_ENABLE); + + clk_disable(keypad->clk); +} + +static int __devinit db5500_keypad_probe(struct platform_device *pdev) +{ + const struct db5500_keypad_platform_data *plat; + struct db5500_keypad *keypad; + struct resource *res; + struct input_dev *input; + void __iomem *base; + struct clk *clk; + int ret; + int irq; + + plat = pdev->dev.platform_data; + if (!plat) { + dev_err(&pdev->dev, "invalid keypad platform data\n"); + ret = -EINVAL; + goto out_ret; + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "failed to get keypad irq\n"); + ret = -EINVAL; + goto out_ret; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res == NULL) { + dev_err(&pdev->dev, "missing platform resources\n"); + ret = -EINVAL; + goto out_ret; + } + + res = request_mem_region(res->start, resource_size(res), pdev->name); + if (!res) { + dev_err(&pdev->dev, "failed to request I/O memory\n"); + ret = -EBUSY; + goto out_ret; + } + + base = ioremap(res->start, resource_size(res)); + if (!base) { + dev_err(&pdev->dev, "failed to remap I/O memory\n"); + ret = -ENXIO; + goto out_freerequest_memregions; + } + + clk = clk_get(&pdev->dev, NULL); + if (IS_ERR(clk)) { + dev_err(&pdev->dev, "failed to clk_get\n"); + ret = PTR_ERR(clk); + goto out_iounmap; + } + + keypad = kzalloc(sizeof(struct db5500_keypad), GFP_KERNEL); + if (!keypad) { + dev_err(&pdev->dev, "failed to allocate keypad memory\n"); + ret = -ENOMEM; + goto out_freeclk; + } + + input = input_allocate_device(); + if (!input) { + dev_err(&pdev->dev, "failed to input_allocate_device\n"); + ret = -ENOMEM; + goto out_freekeypad; + } + + input->id.bustype = BUS_HOST; + input->name = "db5500-keypad"; + input->dev.parent = &pdev->dev; + + input->keycode = keypad->keymap; + input->keycodesize = sizeof(keypad->keymap[0]); + input->keycodemax = ARRAY_SIZE(keypad->keymap); + + input_set_capability(input, EV_MSC, MSC_SCAN); + + __set_bit(EV_KEY, input->evbit); + if (!plat->no_autorepeat) + __set_bit(EV_REP, input->evbit); + + matrix_keypad_build_keymap(plat->keymap_data, KEYPAD_ROW_SHIFT, + input->keycode, input->keybit); + + ret = input_register_device(input); + if (ret) { + dev_err(&pdev->dev, + "unable to register input device: %d\n", ret); + goto out_freeinput; + } + + keypad->irq = irq; + keypad->board = plat; + keypad->input = input; + keypad->base = base; + keypad->clk = clk; + + ret = db5500_keypad_chip_init(keypad); + if (ret < 0) { + dev_err(&pdev->dev, "unable to init keypad hardware\n"); + goto out_unregisterinput; + } + + ret = request_threaded_irq(keypad->irq, NULL, db5500_keypad_irq, + IRQF_ONESHOT, "db5500-keypad", keypad); + if (ret) { + dev_err(&pdev->dev, "allocate irq %d failed\n", keypad->irq); + goto out_unregisterinput; + } + + platform_set_drvdata(pdev, keypad); + + return 0; + +out_unregisterinput: + input_unregister_device(input); + input = NULL; + clk_disable(keypad->clk); +out_freeinput: + input_free_device(input); +out_freekeypad: + kfree(keypad); +out_freeclk: + clk_put(clk); +out_iounmap: + iounmap(base); +out_freerequest_memregions: + release_mem_region(res->start, resource_size(res)); +out_ret: + return ret; +} + +static int __devexit db5500_keypad_remove(struct platform_device *pdev) +{ + struct db5500_keypad *keypad = platform_get_drvdata(pdev); + struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + free_irq(keypad->irq, keypad); + input_unregister_device(keypad->input); + + clk_disable(keypad->clk); + clk_put(keypad->clk); + + iounmap(keypad->base); + release_mem_region(res->start, resource_size(res)); + kfree(keypad); + + return 0; +} + +#ifdef CONFIG_PM +static int db5500_keypad_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct db5500_keypad *keypad = platform_get_drvdata(pdev); + int irq = platform_get_irq(pdev, 0); + + if (device_may_wakeup(dev)) + enable_irq_wake(irq); + else { + disable_irq(irq); + db5500_keypad_close(keypad); + } + + return 0; +} + +static int db5500_keypad_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct db5500_keypad *keypad = platform_get_drvdata(pdev); + int irq = platform_get_irq(pdev, 0); + + if (device_may_wakeup(dev)) + disable_irq_wake(irq); + else { + db5500_keypad_chip_init(keypad); + enable_irq(irq); + } + + return 0; +} + +static const struct dev_pm_ops db5500_keypad_dev_pm_ops = { + .suspend = db5500_keypad_suspend, + .resume = db5500_keypad_resume, +}; +#endif + +static struct platform_driver db5500_keypad_driver = { + .driver = { + .name = "db5500-keypad", + .owner = THIS_MODULE, +#ifdef CONFIG_PM + .pm = &db5500_keypad_dev_pm_ops, +#endif + }, + .probe = db5500_keypad_probe, + .remove = __devexit_p(db5500_keypad_remove), +}; + +static int __init db5500_keypad_init(void) +{ + return platform_driver_register(&db5500_keypad_driver); +} +module_init(db5500_keypad_init); + +static void __exit db5500_keypad_exit(void) +{ + platform_driver_unregister(&db5500_keypad_driver); +} +module_exit(db5500_keypad_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Sundar Iyer <sundar.iyer@stericsson.com>"); +MODULE_DESCRIPTION("DB5500 Keypad Driver"); +MODULE_ALIAS("platform:db5500-keypad"); diff --git a/drivers/input/keyboard/nomadik-ske-keypad.c b/drivers/input/keyboard/nomadik-ske-keypad.c new file mode 100644 index 00000000000..49b393b74fb --- /dev/null +++ b/drivers/input/keyboard/nomadik-ske-keypad.c @@ -0,0 +1,756 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * Author: Naveen Kumar G <naveen.gaddipati@stericsson.com> for ST-Ericsson + * Author: Sundar Iyer <sundar.iyer@stericsson.com> for ST-Ericsson + * + * License terms:GNU General Public License (GPL) version 2 + * + * Keypad controller driver for the SKE (Scroll Key Encoder) module used in + * the Nomadik 8815 and Ux500 platforms. + */ + +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/workqueue.h> +#include <linux/spinlock.h> +#include <linux/io.h> +#include <linux/delay.h> +#include <linux/input.h> +#include <linux/slab.h> +#include <linux/clk.h> +#include <linux/regulator/consumer.h> + +#include <plat/ske.h> +#include <plat/gpio.h> + +/* SKE_CR bits */ +#define SKE_KPMLT (0x1 << 6) +#define SKE_KPCN (0x7 << 3) +#define SKE_KPASEN (0x1 << 2) +#define SKE_KPASON (0x1 << 7) + +/* SKE_IMSC bits */ +#define SKE_KPIMA (0x1 << 2) + +/* SKE_ICR bits */ +#define SKE_KPICS (0x1 << 3) +#define SKE_KPICA (0x1 << 2) + +/* SKE_RIS bits */ +#define SKE_KPRISA (0x1 << 2) + +#define SKE_KEYPAD_ROW_SHIFT 3 +#define SKE_KPD_KEYMAP_SIZE (8 * 8) + +/* keypad auto scan registers */ +#define SKE_ASR0 0x20 +#define SKE_ASR1 0x24 +#define SKE_ASR2 0x28 +#define SKE_ASR3 0x2C + +#define SKE_NUM_ASRX_REGISTERS (4) +#define KEY_PRESSED_DELAY 10 + +/** + * struct ske_keypad - data structure used by keypad driver + * @irq: irq no + * @reg_base: ske regsiters base address + * @input: pointer to input device object + * @board: keypad platform device + * @keymap: matrix scan code table for keycodes + * @clk: clock structure pointer + * @enable: flag to enable the driver event + * @regulator: pointer to the regulator used for ske kyepad + * @gpio_input_irq: array for gpio irqs + * @key_pressed: hold the key state + * @work: delayed work variable for gpio switch + * @ske_rows: rows gpio array for ske + * @ske_cols: columns gpio array for ske + * @gpio_row: gpio row + * @gpio_col: gpio column + * @gpio_work: delayed work variable for release gpio key + */ +struct ske_keypad { + int irq; + void __iomem *reg_base; + struct input_dev *input; + const struct ske_keypad_platform_data *board; + unsigned short keymap[SKE_KPD_KEYMAP_SIZE]; + struct clk *clk; + spinlock_t ske_keypad_lock; + bool enable; + struct regulator *regulator; + int gpio_input_irq[SKE_KPD_MAX_ROWS]; + int key_pressed; + struct delayed_work work; + int ske_rows[SKE_KPD_MAX_ROWS]; + int ske_cols[SKE_KPD_MAX_COLS]; + int gpio_row; + int gpio_col; + struct delayed_work gpio_work; +}; + +static void ske_keypad_set_bits(struct ske_keypad *keypad, u16 addr, + u8 mask, u8 data) +{ + u32 ret; + + spin_lock(&keypad->ske_keypad_lock); + + ret = readl(keypad->reg_base + addr); + ret &= ~mask; + ret |= data; + writel(ret, keypad->reg_base + addr); + + spin_unlock(&keypad->ske_keypad_lock); +} + +/* + * ske_keypad_chip_init: init keypad controller configuration + * + * Enable Multi key press detection, auto scan mode + */ +static int __devinit ske_keypad_chip_init(struct ske_keypad *keypad) +{ + u32 value; + int timeout = keypad->board->debounce_ms; + + /* check SKE_RIS to be 0 */ + while ((readl(keypad->reg_base + SKE_RIS) != 0x00000000) && timeout--) + cpu_relax(); + + if (!timeout) + return -EINVAL; + + /* + * set debounce value + * keypad dbounce is configured in DBCR[15:8] + * dbounce value in steps of 32/32.768 ms + */ + spin_lock(&keypad->ske_keypad_lock); + value = readl(keypad->reg_base + SKE_DBCR); + value = value & 0xff; + value |= ((keypad->board->debounce_ms * 32000)/32768) << 8; + writel(value, keypad->reg_base + SKE_DBCR); + spin_unlock(&keypad->ske_keypad_lock); + + /* enable multi key detection */ + ske_keypad_set_bits(keypad, SKE_CR, 0x0, SKE_KPMLT); + + /* + * set up the number of columns + * KPCN[5:3] defines no. of keypad columns to be auto scanned + */ + value = (keypad->board->kcol - 1) << 3; + ske_keypad_set_bits(keypad, SKE_CR, SKE_KPCN, value); + + /* clear keypad interrupt for auto(and pending SW) scans */ + ske_keypad_set_bits(keypad, SKE_ICR, 0x0, SKE_KPICA | SKE_KPICS); + + /* un-mask keypad interrupts */ + ske_keypad_set_bits(keypad, SKE_IMSC, 0x0, SKE_KPIMA); + + /* enable automatic scan */ + ske_keypad_set_bits(keypad, SKE_CR, 0x0, SKE_KPASEN); + + return 0; +} + +static void ske_mode_enable(struct ske_keypad *keypad, bool enable) +{ + int i; + + if (!enable) { + writel(0, keypad->reg_base + SKE_CR); + if (keypad->board->exit) + keypad->board->exit(); + for (i = 0; i < keypad->board->krow; i++) { + enable_irq(keypad->gpio_input_irq[i]); + enable_irq_wake(keypad->gpio_input_irq[i]); + } + clk_disable(keypad->clk); + regulator_disable(keypad->regulator); + } else { + regulator_enable(keypad->regulator); + clk_enable(keypad->clk); + for (i = 0; i < keypad->board->krow; i++) { + disable_irq_nosync(keypad->gpio_input_irq[i]); + disable_irq_wake(keypad->gpio_input_irq[i]); + } + if (keypad->board->init) + keypad->board->init(); + ske_keypad_chip_init(keypad); + } +} +static void ske_enable(struct ske_keypad *keypad, bool enable) +{ + keypad->enable = enable; + if (keypad->enable) { + enable_irq(keypad->irq); + ske_mode_enable(keypad, true); + } else { + ske_mode_enable(keypad, false); + disable_irq(keypad->irq); + } +} + +static ssize_t ske_show_attr_enable(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct ske_keypad *keypad = platform_get_drvdata(pdev); + return sprintf(buf, "%d\n", keypad->enable); +} + +static ssize_t ske_store_attr_enable(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct platform_device *pdev = to_platform_device(dev); + struct ske_keypad *keypad = platform_get_drvdata(pdev); + unsigned long val; + + if (strict_strtoul(buf, 0, &val)) + return -EINVAL; + + if ((val != 0) && (val != 1)) + return -EINVAL; + + if (keypad->enable != val) { + keypad->enable = val ? true : false; + ske_enable(keypad, keypad->enable); + } + return count; +} + +static DEVICE_ATTR(enable, S_IWUSR | S_IRUGO, + ske_show_attr_enable, ske_store_attr_enable); + +static struct attribute *ske_keypad_attrs[] = { + &dev_attr_enable.attr, + NULL, +}; + +static struct attribute_group ske_attr_group = { + .attrs = ske_keypad_attrs, +}; + +static void ske_keypad_report(struct ske_keypad *keypad, u8 status, int col) +{ + int row = 0, code, pos; + struct input_dev *input = keypad->input; + u32 ske_ris; + int num_of_rows; + + /* find out the row */ + num_of_rows = hweight8(status); + do { + pos = __ffs(status); + row = pos; + status &= ~(1 << pos); + + code = MATRIX_SCAN_CODE(row, col, SKE_KEYPAD_ROW_SHIFT); + ske_ris = readl(keypad->reg_base + SKE_RIS); + keypad->key_pressed = ske_ris & SKE_KPRISA; + + input_event(input, EV_MSC, MSC_SCAN, code); + input_report_key(input, keypad->keymap[code], + keypad->key_pressed); + input_sync(input); + num_of_rows--; + } while (num_of_rows); +} + +static void ske_keypad_read_data(struct ske_keypad *keypad) +{ + u8 status; + int col = 0; + int ske_asr, i; + + /* + * Read the auto scan registers + * + * Each SKE_ASRx (x=0 to x=3) contains two row values. + * lower byte contains row value for column 2*x, + * upper byte contains row value for column 2*x + 1 + */ + for (i = 0; i < SKE_NUM_ASRX_REGISTERS; i++) { + ske_asr = readl(keypad->reg_base + SKE_ASR0 + (4 * i)); + if (!ske_asr) + continue; + + /* now that ASRx is zero, find out the coloumn x and row y */ + status = ske_asr & 0xff; + if (status) { + col = i * 2; + ske_keypad_report(keypad, status, col); + } + status = (ske_asr & 0xff00) >> 8; + if (status) { + col = (i * 2) + 1; + ske_keypad_report(keypad, status, col); + } + } +} +static void ske_keypad_scan(struct ske_keypad *keypad) +{ + int timeout = keypad->board->debounce_ms; + + /* disable auto scan interrupt; mask the interrupt generated */ + ske_keypad_set_bits(keypad, SKE_IMSC, ~SKE_KPIMA, 0x0); + ske_keypad_set_bits(keypad, SKE_ICR, 0x0, SKE_KPICA); + + while ((readl(keypad->reg_base + SKE_CR) & SKE_KPASON) && --timeout) + cpu_relax(); + + /* SKEx registers are stable and can be read */ + ske_keypad_read_data(keypad); + + /* wait until raw interrupt is clear */ + while ((readl(keypad->reg_base + SKE_RIS)) && --timeout) + msleep(KEY_PRESSED_DELAY); + + /* enable auto scan interrupts */ + ske_keypad_set_bits(keypad, SKE_IMSC, 0x0, SKE_KPIMA); +} + +static void ske_gpio_switch_work(struct work_struct *work) +{ + struct ske_keypad *keypad = container_of(work, + struct ske_keypad, work.work); + + ske_mode_enable(keypad, false); + keypad->enable = false; +} + +static void ske_gpio_release_work(struct work_struct *work) +{ + int code; + struct ske_keypad *keypad = container_of(work, + struct ske_keypad, gpio_work.work); + struct input_dev *input = keypad->input; + + code = MATRIX_SCAN_CODE(keypad->gpio_row, keypad->gpio_col, + SKE_KEYPAD_ROW_SHIFT); + input_event(input, EV_MSC, MSC_SCAN, code); + input_report_key(input, keypad->keymap[code], 1); + input_sync(input); + input_report_key(input, keypad->keymap[code], 0); + input_sync(input); +} + +static int ske_read_get_gpio_row(struct ske_keypad *keypad) +{ + int row; + int value = 0; + int ret; + + /* read all rows GPIO data register values */ + for (row = 0; row < SKE_KPD_MAX_ROWS ; row++) { + ret = gpio_get_value(keypad->ske_rows[row]); + value += (1 << row) * ret; + } + + /* get the exact row */ + for (row = 0; row < keypad->board->krow; row++) { + if (((1 << row) & value) == 0) + return row; + } + + return -1; +} + +static void ske_set_cols(struct ske_keypad *keypad, int col) +{ + int i ; + int value; + + /* + * Set all columns except the requested column + * output pin as high + */ + for (i = 0; i < SKE_KPD_MAX_COLS; i++) { + if (i == col) + value = 0; + else + value = 1; + gpio_request(keypad->ske_cols[i], "ske-kp"); + gpio_direction_output(keypad->ske_cols[i], value); + gpio_free(keypad->ske_cols[i]); + } +} + +static void ske_free_cols(struct ske_keypad *keypad) +{ + int i ; + + for (i = 0; i < SKE_KPD_MAX_COLS; i++) { + gpio_request(keypad->ske_cols[i], "ske-kp"); + gpio_direction_output(keypad->ske_cols[i], 0); + gpio_free(keypad->ske_cols[i]); + } +} + +static void ske_manual_scan(struct ske_keypad *keypad) +{ + int row; + int col; + + for (col = 0; col < keypad->board->kcol; col++) { + ske_set_cols(keypad, col); + row = ske_read_get_gpio_row(keypad); + if (row >= 0) { + keypad->key_pressed = 1; + keypad->gpio_row = row; + keypad->gpio_col = col; + break; + } + } + ske_free_cols(keypad); +} + +static irqreturn_t ske_keypad_gpio_irq(int irq, void *dev_id) +{ + struct ske_keypad *keypad = dev_id; + + if (!gpio_get_value(IRQ_TO_GPIO(irq))) { + ske_manual_scan(keypad); + if (!keypad->enable) { + keypad->enable = true; + ske_mode_enable(keypad, true); + } + /* + * Schedule the work queue to change it to + * report the key pressed, if it is not detected in SKE mode. + */ + if (keypad->key_pressed) + schedule_delayed_work(&keypad->gpio_work, + KEY_PRESSED_DELAY); + } + + return IRQ_HANDLED; +} +static irqreturn_t ske_keypad_irq(int irq, void *dev_id) +{ + struct ske_keypad *keypad = dev_id; + + cancel_delayed_work_sync(&keypad->gpio_work); + cancel_delayed_work_sync(&keypad->work); + ske_keypad_scan(keypad); + + /* + * Schedule the work queue to change it to + * GPIO mode, if there is no activity in SKE mode + */ + if (!keypad->key_pressed && keypad->enable) + schedule_delayed_work(&keypad->work, + keypad->board->switch_delay); + + return IRQ_HANDLED; +} + +static int __devinit ske_keypad_probe(struct platform_device *pdev) +{ + struct ske_keypad *keypad; + struct resource *res = NULL; + struct input_dev *input; + struct clk *clk; + void __iomem *reg_base; + int ret = 0; + int irq; + int i; + struct ske_keypad_platform_data *plat = pdev->dev.platform_data; + + if (!plat) { + dev_err(&pdev->dev, "invalid keypad platform data\n"); + return -EINVAL; + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "failed to get keypad irq\n"); + return -EINVAL; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res == NULL) { + dev_err(&pdev->dev, "missing platform resources\n"); + return -ENXIO; + } + + res = request_mem_region(res->start, resource_size(res), pdev->name); + if (!res) { + dev_err(&pdev->dev, "failed to request I/O memory\n"); + return -EBUSY; + } + + reg_base = ioremap(res->start, resource_size(res)); + if (!reg_base) { + dev_err(&pdev->dev, "failed to remap I/O memory\n"); + ret = -ENXIO; + goto out_freerequest_memregions; + } + + clk = clk_get(&pdev->dev, NULL); + if (IS_ERR(clk)) { + dev_err(&pdev->dev, "failed to clk_get\n"); + ret = PTR_ERR(clk); + goto out_freeioremap; + } + + /* resources are sane; we begin allocation */ + keypad = kzalloc(sizeof(struct ske_keypad), GFP_KERNEL); + if (!keypad) { + dev_err(&pdev->dev, "failed to allocate keypad memory\n"); + goto out_freeclk; + } + + input = input_allocate_device(); + if (!input) { + dev_err(&pdev->dev, "failed to input_allocate_device\n"); + ret = -ENOMEM; + goto out_freekeypad; + } + keypad->regulator = regulator_get(&pdev->dev, "v-ape"); + if (IS_ERR(keypad->regulator)) { + dev_err(&pdev->dev, "regulator_get failed\n"); + keypad->regulator = NULL; + goto out_regulator_get; + } else { + ret = regulator_enable(keypad->regulator); + if (ret < 0) { + dev_err(&pdev->dev, "regulator_enable failed\n"); + goto out_regulator_enable; + } + } + + input->id.bustype = BUS_HOST; + input->name = "ux500-ske-keypad"; + input->dev.parent = &pdev->dev; + + input->keycode = keypad->keymap; + input->keycodesize = sizeof(keypad->keymap[0]); + input->keycodemax = ARRAY_SIZE(keypad->keymap); + + input_set_capability(input, EV_MSC, MSC_SCAN); + input_set_drvdata(input, keypad); + + __set_bit(EV_KEY, input->evbit); + if (!plat->no_autorepeat) + __set_bit(EV_REP, input->evbit); + + matrix_keypad_build_keymap(plat->keymap_data, SKE_KEYPAD_ROW_SHIFT, + input->keycode, input->keybit); + + ret = input_register_device(input); + if (ret) { + dev_err(&pdev->dev, + "unable to register input device: %d\n", ret); + goto out_freeinput; + } + + keypad->irq = irq; + keypad->board = plat; + keypad->input = input; + keypad->reg_base = reg_base; + keypad->clk = clk; + INIT_DELAYED_WORK(&keypad->work, ske_gpio_switch_work); + INIT_DELAYED_WORK(&keypad->gpio_work, ske_gpio_release_work); + + /* allocations are sane, we begin HW initialization */ + clk_enable(keypad->clk); + + if (!keypad->board->init) { + dev_err(&pdev->dev, "init funtion not defined\n"); + ret = -EINVAL; + goto out_unregisterinput; + } + + if (keypad->board->init() < 0) { + dev_err(&pdev->dev, "keyboard init config failed\n"); + ret = -EINVAL; + goto out_unregisterinput; + } + + if (!keypad->board->exit) { + dev_err(&pdev->dev, "exit funtion not defined\n"); + ret = -EINVAL; + goto out_unregisterinput; + } + + if (keypad->board->exit() < 0) { + dev_err(&pdev->dev, "keyboard exit config failed\n"); + ret = -EINVAL; + goto out_unregisterinput; + } + for (i = 0; i < SKE_KPD_MAX_ROWS; i++) { + keypad->ske_rows[i] = *plat->gpio_input_pins; + keypad->ske_cols[i] = *plat->gpio_output_pins; + keypad->gpio_input_irq[i] = + GPIO_TO_IRQ(keypad->ske_rows[i]); + plat->gpio_input_pins++; + plat->gpio_output_pins++; + } + + for (i = 0; i < keypad->board->krow; i++) { + ret = request_threaded_irq(keypad->gpio_input_irq[i], + NULL, ske_keypad_gpio_irq, + IRQF_TRIGGER_FALLING | IRQF_NO_SUSPEND, + "ske-keypad-gpio", keypad); + if (ret) { + dev_err(&pdev->dev, "allocate gpio irq %d failed\n", + keypad->gpio_input_irq[i]); + goto out_unregisterinput; + } + enable_irq_wake(keypad->gpio_input_irq[i]); + } + + ret = request_threaded_irq(keypad->irq, NULL, ske_keypad_irq, + IRQF_ONESHOT, "ske-keypad", keypad); + if (ret) { + dev_err(&pdev->dev, "allocate irq %d failed\n", keypad->irq); + goto out_unregisterinput; + } + + /* sysfs implementation for dynamic enable/disable the input event */ + ret = sysfs_create_group(&pdev->dev.kobj, &ske_attr_group); + if (ret) { + dev_err(&pdev->dev, "failed to create sysfs entries\n"); + goto out_free_irq; + } + + if (plat->wakeup_enable) + device_init_wakeup(&pdev->dev, true); + + platform_set_drvdata(pdev, keypad); + + clk_disable(keypad->clk); + regulator_disable(keypad->regulator); + + return 0; + +out_free_irq: + free_irq(keypad->irq, keypad); +out_unregisterinput: + input_unregister_device(input); + input = NULL; + clk_disable(keypad->clk); +out_freeinput: + regulator_disable(keypad->regulator); +out_regulator_enable: + regulator_put(keypad->regulator); +out_regulator_get: + input_free_device(input); +out_freekeypad: + kfree(keypad); +out_freeclk: + clk_put(keypad->clk); +out_freeioremap: + iounmap(reg_base); +out_freerequest_memregions: + release_mem_region(res->start, resource_size(res)); + return ret; +} + +static int __devexit ske_keypad_remove(struct platform_device *pdev) +{ + struct ske_keypad *keypad = platform_get_drvdata(pdev); + struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + cancel_delayed_work_sync(&keypad->gpio_work); + cancel_delayed_work_sync(&keypad->work); + free_irq(keypad->irq, keypad); + + input_unregister_device(keypad->input); + + sysfs_remove_group(&pdev->dev.kobj, &ske_attr_group); + clk_disable(keypad->clk); + clk_put(keypad->clk); + + if (keypad->board->exit) + keypad->board->exit(); + + regulator_put(keypad->regulator); + + iounmap(keypad->reg_base); + release_mem_region(res->start, resource_size(res)); + kfree(keypad); + + return 0; +} + +#ifdef CONFIG_PM +static int ske_keypad_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct ske_keypad *keypad = platform_get_drvdata(pdev); + int irq = platform_get_irq(pdev, 0); + + if (device_may_wakeup(dev)) + enable_irq_wake(irq); + else { + cancel_delayed_work_sync(&keypad->gpio_work); + cancel_delayed_work_sync(&keypad->work); + disable_irq(irq); + if (keypad->enable) { + ske_mode_enable(keypad, false); + keypad->enable = false; + } + } + + return 0; +} + +static int ske_keypad_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct ske_keypad *keypad = platform_get_drvdata(pdev); + int irq = platform_get_irq(pdev, 0); + + if (device_may_wakeup(dev)) + disable_irq_wake(irq); + else { + if (!keypad->enable) { + keypad->enable = true; + ske_mode_enable(keypad, true); + } + enable_irq(irq); + } + + return 0; +} + +static const struct dev_pm_ops ske_keypad_dev_pm_ops = { + .suspend = ske_keypad_suspend, + .resume = ske_keypad_resume, +}; +#endif + +struct platform_driver ske_keypad_driver = { + .driver = { + .name = "nmk-ske-keypad", + .owner = THIS_MODULE, +#ifdef CONFIG_PM + .pm = &ske_keypad_dev_pm_ops, +#endif + }, + .probe = ske_keypad_probe, + .remove = __devexit_p(ske_keypad_remove), +}; + +static int __init ske_keypad_init(void) +{ + return platform_driver_register(&ske_keypad_driver); +} +module_init(ske_keypad_init); + +static void __exit ske_keypad_exit(void) +{ + platform_driver_unregister(&ske_keypad_driver); +} +module_exit(ske_keypad_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Naveen Kumar <naveen.gaddipati@stericsson.com>"); +MODULE_DESCRIPTION("Nomadik Scroll-Key-Encoder Keypad Driver"); +MODULE_ALIAS("platform:nomadik-ske-keypad"); diff --git a/drivers/input/keyboard/stmpe-keypad.c b/drivers/input/keyboard/stmpe-keypad.c new file mode 100644 index 00000000000..9b3d3b408dc --- /dev/null +++ b/drivers/input/keyboard/stmpe-keypad.c @@ -0,0 +1,473 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * License Terms: GNU General Public License, version 2 + * Author: Rabin Vincent <rabin.vincent@stericsson.com> for ST-Ericsson + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/input/matrix_keypad.h> +#include <linux/mfd/stmpe.h> + +/* These are at the same addresses in all STMPE variants */ +#define STMPE_KPC_COL 0x60 +#define STMPE_KPC_ROW_MSB 0x61 +#define STMPE_KPC_ROW_LSB 0x62 +#define STMPE_KPC_CTRL_MSB 0x63 +#define STMPE_KPC_CTRL_LSB 0x64 +#define STMPE_KPC_COMBI_KEY_0 0x65 +#define STMPE_KPC_COMBI_KEY_1 0x66 +#define STMPE_KPC_COMBI_KEY_2 0x67 +#define STMPE_KPC_DATA_BYTE0 0x68 +#define STMPE_KPC_DATA_BYTE1 0x69 +#define STMPE_KPC_DATA_BYTE2 0x6a +#define STMPE_KPC_DATA_BYTE3 0x6b +#define STMPE_KPC_DATA_BYTE4 0x6c + +#define STMPE_KPC_CTRL_LSB_SCAN (0x1 << 0) +#define STMPE_KPC_CTRL_LSB_DEBOUNCE (0x7f << 1) +#define STMPE_KPC_CTRL_MSB_SCAN_COUNT (0xf << 4) + +#define STMPE_KPC_ROW_MSB_ROWS 0xff + +#define STMPE_KPC_DATA_UP (0x1 << 7) +#define STMPE_KPC_DATA_ROW (0xf << 3) +#define STMPE_KPC_DATA_COL (0x7 << 0) +#define STMPE_KPC_DATA_NOKEY_MASK 0x78 + +#define STMPE_KEYPAD_MAX_DEBOUNCE 127 +#define STMPE_KEYPAD_MAX_SCAN_COUNT 15 + +#define STMPE_KEYPAD_MAX_ROWS 8 +#define STMPE_KEYPAD_MAX_COLS 8 +#define STMPE_KEYPAD_ROW_SHIFT 3 +#define STMPE_KEYPAD_KEYMAP_SIZE \ + (STMPE_KEYPAD_MAX_ROWS * STMPE_KEYPAD_MAX_COLS) + +/** + * struct stmpe_keypad_variant - model-specific attributes + * @auto_increment: whether the KPC_DATA_BYTE register address + * auto-increments on multiple read + * @num_data: number of data bytes + * @num_normal_data: number of normal keys' data bytes + * @max_cols: maximum number of columns supported + * @max_rows: maximum number of rows supported + * @col_gpios: bitmask of gpios which can be used for columns + * @row_gpios: bitmask of gpios which can be used for rows + */ +struct stmpe_keypad_variant { + bool auto_increment; + int num_data; + int num_normal_data; + int max_cols; + int max_rows; + unsigned int col_gpios; + unsigned int row_gpios; +}; + +static const struct stmpe_keypad_variant stmpe_keypad_variants[] = { + [STMPE1601] = { + .auto_increment = true, + .num_data = 5, + .num_normal_data = 3, + .max_cols = 8, + .max_rows = 8, + .col_gpios = 0x000ff, /* GPIO 0 - 7 */ + .row_gpios = 0x0ff00, /* GPIO 8 - 15 */ + }, + [STMPE2401] = { + .auto_increment = false, + .num_data = 3, + .num_normal_data = 2, + .max_cols = 8, + .max_rows = 12, + .col_gpios = 0x0000ff, /* GPIO 0 - 7*/ + .row_gpios = 0x1fef00, /* GPIO 8-14, 16-20 */ + }, + [STMPE2403] = { + .auto_increment = true, + .num_data = 5, + .num_normal_data = 3, + .max_cols = 8, + .max_rows = 12, + .col_gpios = 0x0000ff, /* GPIO 0 - 7*/ + .row_gpios = 0x1fef00, /* GPIO 8-14, 16-20 */ + }, +}; + +struct stmpe_keypad { + struct stmpe *stmpe; + struct input_dev *input; + const struct stmpe_keypad_variant *variant; + const struct stmpe_keypad_platform_data *plat; + + unsigned int rows; + unsigned int cols; + bool enable; + + unsigned short keymap[STMPE_KEYPAD_KEYMAP_SIZE]; +}; + +static ssize_t stmpe_show_attr_enable(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct stmpe_keypad *keypad = platform_get_drvdata(pdev); + return sprintf(buf, "%d\n", keypad->enable); +} + +static ssize_t stmpe_store_attr_enable(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct platform_device *pdev = to_platform_device(dev); + struct stmpe_keypad *keypad = platform_get_drvdata(pdev); + struct stmpe *stmpe = keypad->stmpe; + unsigned long val; + + if (strict_strtoul(buf, 0, &val)) + return -EINVAL; + + if (keypad->enable != val) { + keypad->enable = val; + if (!val) + stmpe_disable(stmpe, STMPE_BLOCK_KEYPAD); + else + stmpe_enable(stmpe, STMPE_BLOCK_KEYPAD); + } + return count; +} + +static DEVICE_ATTR(enable, S_IWUSR | S_IRUGO, + stmpe_show_attr_enable, stmpe_store_attr_enable); + +static struct attribute *stmpe_keypad_attrs[] = { + &dev_attr_enable.attr, + NULL, +}; + +static struct attribute_group stmpe_attr_group = { + .attrs = stmpe_keypad_attrs, +}; + +static int stmpe_keypad_read_data(struct stmpe_keypad *keypad, u8 *data) +{ + const struct stmpe_keypad_variant *variant = keypad->variant; + struct stmpe *stmpe = keypad->stmpe; + int ret; + int i; + + if (variant->auto_increment) + return stmpe_block_read(stmpe, STMPE_KPC_DATA_BYTE0, + variant->num_data, data); + + for (i = 0; i < variant->num_data; i++) { + ret = stmpe_reg_read(stmpe, STMPE_KPC_DATA_BYTE0 + i); + if (ret < 0) + return ret; + + data[i] = ret; + } + + return 0; +} + +static irqreturn_t stmpe_keypad_irq(int irq, void *dev) +{ + struct stmpe_keypad *keypad = dev; + struct input_dev *input = keypad->input; + const struct stmpe_keypad_variant *variant = keypad->variant; + u8 fifo[variant->num_data]; + int ret; + int i; + + ret = stmpe_keypad_read_data(keypad, fifo); + if (ret < 0) + return IRQ_NONE; + + for (i = 0; i < variant->num_normal_data; i++) { + u8 data = fifo[i]; + int row = (data & STMPE_KPC_DATA_ROW) >> 3; + int col = data & STMPE_KPC_DATA_COL; + int code = MATRIX_SCAN_CODE(row, col, STMPE_KEYPAD_ROW_SHIFT); + bool up = data & STMPE_KPC_DATA_UP; + + if ((data & STMPE_KPC_DATA_NOKEY_MASK) + == STMPE_KPC_DATA_NOKEY_MASK) + continue; + + input_event(input, EV_MSC, MSC_SCAN, code); + input_report_key(input, keypad->keymap[code], !up); + input_sync(input); + } + + return IRQ_HANDLED; +} + +static int __devinit stmpe_keypad_altfunc_init(struct stmpe_keypad *keypad) +{ + const struct stmpe_keypad_variant *variant = keypad->variant; + unsigned int col_gpios = variant->col_gpios; + unsigned int row_gpios = variant->row_gpios; + struct stmpe *stmpe = keypad->stmpe; + unsigned int pins = 0; + int i; + + /* + * Figure out which pins need to be set to the keypad alternate + * function. + * + * {cols,rows}_gpios are bitmasks of which pins on the chip can be used + * for the keypad. + * + * keypad->{cols,rows} are a bitmask of which pins (of the ones useable + * for the keypad) are used on the board. + */ + + for (i = 0; i < variant->max_cols; i++) { + int num = __ffs(col_gpios); + + if (keypad->cols & (1 << i)) + pins |= 1 << num; + + col_gpios &= ~(1 << num); + } + + for (i = 0; i < variant->max_rows; i++) { + int num = __ffs(row_gpios); + + if (keypad->rows & (1 << i)) + pins |= 1 << num; + + row_gpios &= ~(1 << num); + } + + return stmpe_set_altfunc(stmpe, pins, STMPE_BLOCK_KEYPAD); +} + +static int __devinit stmpe_keypad_chip_init(struct stmpe_keypad *keypad) +{ + const struct stmpe_keypad_platform_data *plat = keypad->plat; + const struct stmpe_keypad_variant *variant = keypad->variant; + struct stmpe *stmpe = keypad->stmpe; + int ret; + + if (plat->debounce_ms > STMPE_KEYPAD_MAX_DEBOUNCE) + return -EINVAL; + + if (plat->scan_count > STMPE_KEYPAD_MAX_SCAN_COUNT) + return -EINVAL; + + ret = stmpe_enable(stmpe, STMPE_BLOCK_KEYPAD); + if (ret < 0) + return ret; + + ret = stmpe_keypad_altfunc_init(keypad); + if (ret < 0) + return ret; + + ret = stmpe_reg_write(stmpe, STMPE_KPC_COL, keypad->cols); + if (ret < 0) + return ret; + + ret = stmpe_reg_write(stmpe, STMPE_KPC_ROW_LSB, keypad->rows); + if (ret < 0) + return ret; + + if (variant->max_rows > 8) { + ret = stmpe_set_bits(stmpe, STMPE_KPC_ROW_MSB, + STMPE_KPC_ROW_MSB_ROWS, + keypad->rows >> 8); + if (ret < 0) + return ret; + } + + ret = stmpe_set_bits(stmpe, STMPE_KPC_CTRL_MSB, + STMPE_KPC_CTRL_MSB_SCAN_COUNT, + plat->scan_count << 4); + if (ret < 0) + return ret; + + return stmpe_set_bits(stmpe, STMPE_KPC_CTRL_LSB, + STMPE_KPC_CTRL_LSB_SCAN | + STMPE_KPC_CTRL_LSB_DEBOUNCE, + STMPE_KPC_CTRL_LSB_SCAN | + (plat->debounce_ms << 1)); +} + +static int __devinit stmpe_keypad_probe(struct platform_device *pdev) +{ + struct stmpe *stmpe = dev_get_drvdata(pdev->dev.parent); + struct stmpe_keypad_platform_data *plat; + struct stmpe_keypad *keypad; + struct input_dev *input; + int ret; + int irq; + int i; + + plat = stmpe->pdata->keypad; + if (!plat) + return -ENODEV; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + keypad = kzalloc(sizeof(struct stmpe_keypad), GFP_KERNEL); + if (!keypad) + return -ENOMEM; + + input = input_allocate_device(); + if (!input) { + ret = -ENOMEM; + goto out_freekeypad; + } + + input->name = "STMPE-keypad"; + input->id.bustype = BUS_I2C; + input->dev.parent = &pdev->dev; + + input_set_capability(input, EV_MSC, MSC_SCAN); + + __set_bit(EV_KEY, input->evbit); + if (!plat->no_autorepeat) + __set_bit(EV_REP, input->evbit); + + input->keycode = keypad->keymap; + input->keycodesize = sizeof(keypad->keymap[0]); + input->keycodemax = ARRAY_SIZE(keypad->keymap); + + matrix_keypad_build_keymap(plat->keymap_data, STMPE_KEYPAD_ROW_SHIFT, + input->keycode, input->keybit); + + for (i = 0; i < plat->keymap_data->keymap_size; i++) { + unsigned int key = plat->keymap_data->keymap[i]; + + keypad->cols |= 1 << KEY_COL(key); + keypad->rows |= 1 << KEY_ROW(key); + } + + keypad->stmpe = stmpe; + keypad->plat = plat; + keypad->input = input; + keypad->variant = &stmpe_keypad_variants[stmpe->partnum]; + + ret = stmpe_keypad_chip_init(keypad); + if (ret < 0) + goto out_freeinput; + + ret = input_register_device(input); + if (ret) { + dev_err(&pdev->dev, + "unable to register input device: %d\n", ret); + goto out_freeinput; + } + + ret = request_threaded_irq(irq, NULL, stmpe_keypad_irq, IRQF_ONESHOT, + "stmpe-keypad", keypad); + if (ret) { + dev_err(&pdev->dev, "unable to get irq: %d\n", ret); + goto out_unregisterinput; + } + + /* sysfs implementation for dynamic enable/disable the input event */ + ret = sysfs_create_group(&pdev->dev.kobj, &stmpe_attr_group); + if (ret) { + dev_err(&pdev->dev, "failed to create sysfs entries\n"); + goto out_free_irq; + } + + keypad->enable = true; + platform_set_drvdata(pdev, keypad); + + return 0; + +out_free_irq: + free_irq(irq, keypad); +out_unregisterinput: + input_unregister_device(input); + input = NULL; +out_freeinput: + input_free_device(input); +out_freekeypad: + kfree(keypad); + return ret; +} + +static int __devexit stmpe_keypad_remove(struct platform_device *pdev) +{ + struct stmpe_keypad *keypad = platform_get_drvdata(pdev); + struct stmpe *stmpe = keypad->stmpe; + int irq = platform_get_irq(pdev, 0); + + stmpe_disable(stmpe, STMPE_BLOCK_KEYPAD); + + sysfs_remove_group(&pdev->dev.kobj, &stmpe_attr_group); + free_irq(irq, keypad); + input_unregister_device(keypad->input); + platform_set_drvdata(pdev, NULL); + kfree(keypad); + + return 0; +} + +#ifdef CONFIG_PM +static int stmpe_keypad_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct stmpe_keypad *keypad = platform_get_drvdata(pdev); + struct stmpe *stmpe = keypad->stmpe; + + if (!device_may_wakeup(stmpe->dev)) + stmpe_disable(stmpe, STMPE_BLOCK_KEYPAD); + + return 0; +} + +static int stmpe_keypad_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct stmpe_keypad *keypad = platform_get_drvdata(pdev); + struct stmpe *stmpe = keypad->stmpe; + + if (!device_may_wakeup(stmpe->dev)) + stmpe_enable(stmpe, STMPE_BLOCK_KEYPAD); + + return 0; +} + +static const struct dev_pm_ops stmpe_keypad_dev_pm_ops = { + .suspend = stmpe_keypad_suspend, + .resume = stmpe_keypad_resume, +}; +#endif + +static struct platform_driver stmpe_keypad_driver = { + .driver.name = "stmpe-keypad", + .driver.owner = THIS_MODULE, +#if CONFIG_PM + .driver.pm = &stmpe_keypad_dev_pm_ops, +#endif + .probe = stmpe_keypad_probe, + .remove = __devexit_p(stmpe_keypad_remove), +}; + +static int __init stmpe_keypad_init(void) +{ + return platform_driver_register(&stmpe_keypad_driver); +} +module_init(stmpe_keypad_init); + +static void __exit stmpe_keypad_exit(void) +{ + platform_driver_unregister(&stmpe_keypad_driver); +} +module_exit(stmpe_keypad_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("STMPExxxx keypad driver"); +MODULE_AUTHOR("Rabin Vincent <rabin.vincent@stericsson.com>"); diff --git a/drivers/input/keyboard/tc35893-keypad.c b/drivers/input/keyboard/tc35893-keypad.c new file mode 100644 index 00000000000..5ced8c34151 --- /dev/null +++ b/drivers/input/keyboard/tc35893-keypad.c @@ -0,0 +1,621 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * Author: Jayeeta Banerjee <jayeeta.banerjee@stericsson.com> for ST-Ericsson + * Author: Sundar Iyer <sundar.iyer@stericsson.com> for ST-Ericsson + * + * License Terms: GNU General Public License, version 2 + * + * TC35893 MFD Keypad Controller driver + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/spinlock.h> +#include <linux/input.h> +#include <linux/input/matrix_keypad.h> +#include <linux/i2c.h> +#include <linux/slab.h> +#include <mach/tc35893-keypad.h> + +/* Maximum supported keypad matrix row/columns size */ +#define TC_MAX_KPROW 8 +#define TC_MAX_KPCOL 12 + +/* keypad related Constants */ +#define TC_MAX_DEBOUNCE_SETTLE 0xFF +#define DEDICATED_KEY_VAL 0xFF + +/* Keyboard Configuration Registers Index */ +#define TC_KBDSETTLE_REG 0x01 +#define TC_KBDBOUNCE 0x02 +#define TC_KBDSIZE 0x03 +#define TC_KBCFG_LSB 0x04 +#define TC_KBCFG_MSB 0x05 +#define TC_KBDRIS 0x06 +#define TC_KBDMIS 0x07 +#define TC_KBDIC 0x08 +#define TC_KBDMSK 0x09 +#define TC_KBDCODE0 0x0B +#define TC_KBDCODE1 0x0C +#define TC_KBDCODE2 0x0D +#define TC_KBDCODE3 0x0E +#define TC_EVTCODE_FIFO 0x10 + +/* System registers Index */ +#define TC_MANUFACTURE_CODE 0x80 +#define TC_VERSION_ID 0x81 +#define TC_I2CSA 0x82 +#define TC_IOCFG 0xA7 + +/* clock control registers */ +#define TC_CLKMODE 0x88 +#define TC_CLKCFG 0x89 +#define TC_CLKEN 0x8A + +/* Reset Control registers */ +#define TC_RSTCTRL 0x82 +#define TC_RSTINTCLR 0x84 + +/* special function register and drive config registers */ +#define TC_KBDMFS 0x8F +#define TC_IRQST 0x91 +#define TC_DRIVE0_LSB 0xA0 +#define TC_DRIVE0_MSB 0xA1 +#define TC_DRIVE1_LSB 0xA2 +#define TC_DRIVE1_MSB 0xA3 +#define TC_DRIVE2_LSB 0xA4 +#define TC_DRIVE2_MSB 0xA5 +#define TC_DRIVE3 0xA6 + +/* Pull up/down configuration registers */ +#define TC_IOCFG 0xA7 +#define TC_IOPULLCFG0_LSB 0xAA +#define TC_IOPULLCFG0_MSB 0xAB +#define TC_IOPULLCFG1_LSB 0xAC +#define TC_IOPULLCFG1_MSB 0xAD +#define TC_IOPULLCFG2_LSB 0xAE + +/* Pull up/down masks */ +#define TC_NO_PULL_MASK 0x0 +#define TC_PULL_DOWN_MASK 0x1 +#define TC_PULL_UP_MASK 0x2 +#define TC_PULLUP_ALL_MASK 0xAA +#define TC_IO_PULL_VAL(index, mask) ((mask)<<((index)%4)*2)) + +/* Bit masks for IOCFG register */ +#define IOCFG_BALLCFG 0x01 +#define IOCFG_IG 0x08 + +#define KP_EVCODE_COL_MASK 0x0F +#define KP_EVCODE_ROW_MASK 0x70 +#define KP_RELEASE_EVT_MASK 0x80 + +#define KP_ROW_SHIFT 4 + +#define KP_NO_VALID_KEY_MASK 0x7F + + +#define TC_MAN_CODE_VAL 0x03 +#define TC_SW_VERSION 0x80 + +/* bit masks for RESTCTRL register */ +#define TC_KBDRST 0x2 +#define TC_IRQRST 0x10 +#define TC_RESET_ALL 0x1B + +/* KBDMFS register bit mask */ +#define TC_KBDMFS_EN 0x1 + +/* CLKEN register bitmask */ +#define KPD_CLK_EN 0x1 + +/* RSTINTCLR register bit mask */ +#define IRQ_CLEAR 0x1 + +/* bit masks for keyboard interrupts*/ +#define TC_EVT_LOSS_INT 0x8 +#define TC_EVT_INT 0x4 +#define TC_KBD_LOSS_INT 0x2 +#define TC_KBD_INT 0x1 + +/* bit masks for keyboard interrupt clear*/ +#define TC_EVT_INT_CLR 0x2 +#define TC_KBD_INT_CLR 0x1 + +#define TC_KBD_KEYMAP_SIZE 64 + +/** + * struct tc_keypad - data structure used by keypad driver + * @input: pointer to input device object + * @board: keypad platform device + * @client: pointer to i2c client + * @lock: lock + * @krow: number of rows + * @kcol: number of coloumns + * @keymap: matrix scan code table for keycodes + * @enable: bool to enable/disable keypad operation + */ +struct tc_keypad { + struct input_dev *input; + const struct tc35893_platform_data *board; + struct i2c_client *client; + struct mutex lock; + unsigned int krow; + unsigned int kcol; + unsigned short keymap[TC_KBD_KEYMAP_SIZE]; + bool enable; +}; + + +static int tc35893_write_byte(struct i2c_client *client, + unsigned char reg, unsigned char data) +{ + int ret; + struct tc_keypad *keypad = i2c_get_clientdata(client); + + mutex_lock(&keypad->lock); + ret = i2c_smbus_write_byte_data(client, reg, data); + if (ret < 0) + dev_err(&client->dev, "Error in writing reg %x: error = %d\n", + reg, ret); + mutex_unlock(&keypad->lock); + + return ret; +} + +static int tc35893_read_byte(struct i2c_client *client, unsigned char reg, + unsigned char *val) +{ + int ret; + struct tc_keypad *keypad = i2c_get_clientdata(client); + + mutex_lock(&keypad->lock); + ret = i2c_smbus_read_byte_data(client, reg); + if (ret < 0) + dev_err(&client->dev, "Error in reading reg %x: error = %d\n", + reg, ret); + *val = ret; + mutex_unlock(&keypad->lock); + + return ret; +} + +static int tc35893_set_bits(struct i2c_client *client, u16 addr, + u8 mask, u8 data) +{ + int err; + u8 val; + + err = tc35893_read_byte(client, addr, &val); + if (err < 0) + return err; + + val &= ~mask; + val |= data; + + err = tc35893_write_byte(client, addr, val); + + return err; +} + +static int __devinit tc35893_keypad_init_key_hardware(struct tc_keypad *keypad) +{ + int ret; + struct i2c_client *client = keypad->client; + u8 settle_time = keypad->board->settle_time; + u8 dbounce_period = keypad->board->debounce_period; + u8 rows = keypad->board->krow & 0xf; /* mask out the nibble */ + u8 column = keypad->board->kcol & 0xf; /* mask out the nibble */ + + /* validate platform configurations */ + if ((keypad->board->kcol > TC_MAX_KPCOL) || + (keypad->board->krow > TC_MAX_KPROW) || + (keypad->board->debounce_period > TC_MAX_DEBOUNCE_SETTLE) || + (keypad->board->settle_time > TC_MAX_DEBOUNCE_SETTLE)) + return -EINVAL; + + /* configure KBDSIZE 4 LSbits for cols and 4 MSbits for rows */ + ret = tc35893_set_bits(client, TC_KBDSIZE, 0x0, + (rows << KP_ROW_SHIFT) | column); + if (ret < 0) + return ret; + + /* configure dedicated key config, no dedicated key selected */ + ret = tc35893_set_bits(client, TC_KBCFG_LSB, 0x0, DEDICATED_KEY_VAL); + if (ret < 0) + return ret; + + ret = tc35893_set_bits(client, TC_KBCFG_MSB, 0x0, DEDICATED_KEY_VAL); + if (ret < 0) + return ret; + + /* Configure settle time */ + ret = tc35893_set_bits(client, TC_KBDSETTLE_REG, 0x0, settle_time); + if (ret < 0) + return ret; + + /* Configure debounce time */ + ret = tc35893_set_bits(client, TC_KBDBOUNCE, 0x0, dbounce_period); + if (ret < 0) + return ret; + + /* Start of initialise keypad GPIOs */ + ret = tc35893_set_bits(client, TC_IOCFG, 0x0, IOCFG_IG); + if (ret < 0) + return ret; + + /* Configure pull-up resistors for all row GPIOs */ + ret = tc35893_set_bits(client, TC_IOPULLCFG0_LSB, 0x0, + TC_PULLUP_ALL_MASK); + if (ret < 0) + return ret; + + ret = tc35893_set_bits(client, TC_IOPULLCFG0_MSB, 0x0, + TC_PULLUP_ALL_MASK); + if (ret < 0) + return ret; + + /* Configure pull-up resistors for all column GPIOs */ + ret = tc35893_set_bits(client, TC_IOPULLCFG1_LSB, 0x0, + TC_PULLUP_ALL_MASK); + if (ret < 0) + return ret; + + ret = tc35893_set_bits(client, TC_IOPULLCFG1_MSB, 0x0, + TC_PULLUP_ALL_MASK); + if (ret < 0) + return ret; + + ret = tc35893_set_bits(client, TC_IOPULLCFG2_LSB, 0x0, + TC_PULLUP_ALL_MASK); + + return ret; +} + +#define TC35893_DATA_REGS 4 +#define TC35893_KEYCODE_FIFO_EMPTY 0x7f +#define TC35893_KEYCODE_FIFO_CLEAR 0xff + +static irqreturn_t tc35893_keypad_irq(int irq, void *dev) +{ + struct tc_keypad *keypad = (struct tc_keypad *)dev; + u8 i, row_index, col_index, kbd_code, up; + u8 code; + + for (i = 0; i < (TC35893_DATA_REGS * 2); i++) { + tc35893_read_byte(keypad->client, TC_EVTCODE_FIFO, &kbd_code); + + /* loop till fifo is empty and no more keys are pressed */ + if ((kbd_code == TC35893_KEYCODE_FIFO_EMPTY) || + (kbd_code == TC35893_KEYCODE_FIFO_CLEAR)) + continue; + + /* valid key is found */ + col_index = kbd_code & KP_EVCODE_COL_MASK; + row_index = (kbd_code & KP_EVCODE_ROW_MASK) >> KP_ROW_SHIFT; + code = MATRIX_SCAN_CODE(row_index, col_index, 0x3); + up = kbd_code & KP_RELEASE_EVT_MASK; + + input_event(keypad->input, EV_MSC, MSC_SCAN, code); + input_report_key(keypad->input, keypad->keymap[code], !up); + input_sync(keypad->input); + } + + /* clear IRQ */ + tc35893_set_bits(keypad->client, TC_KBDIC, + 0x0, TC_EVT_INT_CLR | TC_KBD_INT_CLR); + /* enable IRQ */ + tc35893_set_bits(keypad->client, TC_KBDMSK, + 0x0, TC_EVT_LOSS_INT | TC_EVT_INT); + + return IRQ_HANDLED; +} + +static int __devinit tc35893_keypad_enable(struct i2c_client *client) +{ + int ret; + + /* pull the keypad module out of reset */ + ret = tc35893_set_bits(client, TC_RSTCTRL, TC_KBDRST, 0x0); + if (ret < 0) + return ret; + + /* configure KBDMFS */ + ret = tc35893_set_bits(client, TC_KBDMFS, 0x0, TC_KBDMFS_EN); + if (ret < 0) + return ret; + + /* enable the keypad clock */ + ret = tc35893_set_bits(client, TC_CLKEN, 0x0, KPD_CLK_EN); + if (ret < 0) + return ret; + + /* clear pending IRQs */ + ret = tc35893_set_bits(client, TC_RSTINTCLR, 0x0, 0x1); + if (ret < 0) + return ret; + + /* enable the IRQs */ + ret = tc35893_set_bits(client, TC_KBDMSK, 0x0, + TC_EVT_LOSS_INT | TC_EVT_INT); + + return ret; +} + +static int __devexit tc35893_keypad_disable(struct i2c_client *client) +{ + int ret; + + /* clear IRQ */ + ret = tc35893_set_bits(client, TC_KBDIC, + 0x0, TC_EVT_INT_CLR | TC_KBD_INT_CLR); + if (ret < 0) + return ret; + + /* disable all interrupts */ + ret = tc35893_set_bits(client, TC_KBDMSK, + ~(TC_EVT_LOSS_INT | TC_EVT_INT), 0x0); + if (ret < 0) + return ret; + + /* disable the keypad module */ + ret = tc35893_set_bits(client, TC_CLKEN, 0x1, 0x0); + if (ret < 0) + return ret; + + /* put the keypad module into reset */ + ret = tc35893_set_bits(client, TC_RSTCTRL, TC_KBDRST, 0x1); + + return ret; +} + +static ssize_t keypad_show_attr_enable(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct tc_keypad *keypad = i2c_get_clientdata(client); + + return sprintf(buf, "%u\n", keypad->enable); +} + +static ssize_t keypad_store_attr_enable(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct tc_keypad *keypad = i2c_get_clientdata(client); + unsigned long state; + + if (sscanf(buf, "%lu", &state) != 1) + return -EINVAL; + + if ((state != 1) && (state != 0)) + return -EINVAL; + + if (state != keypad->enable) { + if (state) + tc35893_keypad_enable(keypad->client); + else + tc35893_keypad_disable(keypad->client); + keypad->enable = state; + } + + return strnlen(buf, count); +} + +static DEVICE_ATTR(enable, S_IWUSR | S_IRUGO, + keypad_show_attr_enable, keypad_store_attr_enable); + +static struct attribute *tc35893_keypad_attrs[] = { + &dev_attr_enable.attr, + NULL, +}; + +static struct attribute_group tc35893_attr_group = { + .attrs = tc35893_keypad_attrs, +}; + +static int __devinit tc35893_keypad_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + const struct tc35893_platform_data *plat = client->dev.platform_data; + struct tc_keypad *keypad; + struct input_dev *input; + int error; + + if (!plat) { + dev_err(&client->dev, "invalid keypad platform data\n"); + return -EINVAL; + } + + if (plat->irq < 0) { + dev_err(&client->dev, "failed to get keypad irq\n"); + return -EINVAL; + } + + keypad = kzalloc(sizeof(struct tc_keypad), GFP_KERNEL); + input = input_allocate_device(); + if (!keypad || !input) { + dev_err(&client->dev, "failed to allocate keypad memory\n"); + error = -ENOMEM; + goto err_free_mem; + } + + keypad->client = client; + keypad->board = plat; + keypad->input = input; + mutex_init(&keypad->lock); + + i2c_set_clientdata(client, keypad); + + /* issue a soft reset to all modules */ + error = tc35893_set_bits(client, TC_RSTCTRL, 0x0, TC_RESET_ALL); + if (error < 0) + return error; + + /* pull the irq module out of reset */ + error = tc35893_set_bits(client, TC_RSTCTRL, TC_IRQRST, 0x0); + if (error < 0) + return error; + + error = tc35893_keypad_enable(client); + if (error < 0) { + dev_err(&client->dev, "failed to enable keypad module\n"); + goto err_free_mem; + } + + error = tc35893_keypad_init_key_hardware(keypad); + if (error < 0) { + dev_err(&client->dev, "failed to configure keypad module\n"); + goto err_free_mem; + } + + input->id.bustype = BUS_HOST; + input->name = client->name; + input->dev.parent = &client->dev; + + input->keycode = keypad->keymap; + input->keycodesize = sizeof(keypad->keymap[0]); + input->keycodemax = ARRAY_SIZE(keypad->keymap); + + input_set_capability(input, EV_MSC, MSC_SCAN); + + __set_bit(EV_KEY, input->evbit); + if (!plat->no_autorepeat) + __set_bit(EV_REP, input->evbit); + + matrix_keypad_build_keymap(plat->keymap_data, 0x3, + input->keycode, input->keybit); + + error = request_threaded_irq(plat->irq, NULL, + tc35893_keypad_irq, plat->irqtype, + input->name, keypad); + if (error < 0) { + dev_err(&client->dev, + "Could not allocate irq %d,error %d\n", + plat->irq, error); + goto err_free_mem; + } + + error = input_register_device(input); + if (error) { + dev_err(&client->dev, "Could not register input device\n"); + goto err_free_irq; + } + + device_init_wakeup(&client->dev, plat->enable_wakeup); + device_set_wakeup_capable(&client->dev, plat->enable_wakeup); + + /* sysfs implementation for dynamic enable/disable the input event */ + error = sysfs_create_group(&client->dev.kobj, &tc35893_attr_group); + if (error) { + dev_err(&client->dev, "Could not register sysfs entries\n"); + goto err_free_irq; + } + + keypad->enable = true; + + return 0; + +err_free_irq: + free_irq(plat->irq, keypad); +err_free_mem: + input_free_device(input); + kfree(keypad); + return error; +} + +static int __devexit tc35893_keypad_remove(struct i2c_client *client) +{ + struct tc_keypad *keypad = i2c_get_clientdata(client); + + free_irq(keypad->board->irq, keypad); + + sysfs_remove_group(&client->dev.kobj, &tc35893_attr_group); + + input_unregister_device(keypad->input); + + tc35893_keypad_disable(keypad->client); + + kfree(keypad); + + return 0; +} + +#ifdef CONFIG_PM +static int tc35893_keypad_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct tc_keypad *keypad = i2c_get_clientdata(client); + int irq = keypad->board->irq; + + client = keypad->client; + + /* disable the IRQ */ + if (!device_may_wakeup(&client->dev)) + tc35893_keypad_disable(client); + else + enable_irq_wake(irq); + + return 0; +} + +static int tc35893_keypad_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct tc_keypad *keypad = i2c_get_clientdata(client); + int irq = keypad->board->irq; + + client = keypad->client; + + /* enable the IRQ */ + if (!device_may_wakeup(&client->dev)) + tc35893_keypad_enable(client); + else + disable_irq_wake(irq); + + return 0; +} + +static const struct dev_pm_ops tc35893_keypad_dev_pm_ops = { + .suspend = tc35893_keypad_suspend, + .resume = tc35893_keypad_resume, +}; +#endif + +static const struct i2c_device_id tc35893_id[] = { + { "tc35893-kp", 23 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tc35893_id); + +static struct i2c_driver tc35893kpd_driver = { + .driver = { + .name = "tc35893-kp", +#ifdef CONFIG_PM + .pm = &tc35893_keypad_dev_pm_ops, +#endif + }, + .probe = tc35893_keypad_probe, + .remove = __devexit_p(tc35893_keypad_remove), + .id_table = tc35893_id, +}; + +static int __init tc35893_keypad_init(void) +{ + return i2c_add_driver(&tc35893kpd_driver); +} + +static void __exit tc35893_keypad_exit(void) +{ + return i2c_del_driver(&tc35893kpd_driver); +} + +module_init(tc35893_keypad_init); +module_exit(tc35893_keypad_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Jayeeta Banerjee/Sundar Iyer"); +MODULE_DESCRIPTION("TC35893 Keypad Driver"); + diff --git a/drivers/input/keyreset.c b/drivers/input/keyreset.c new file mode 100644 index 00000000000..b51350602fc --- /dev/null +++ b/drivers/input/keyreset.c @@ -0,0 +1,230 @@ +/* drivers/input/keyreset.c + * + * Copyright (C) 2008 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + +#include <linux/input.h> +#include <linux/keyreset.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/reboot.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/syscalls.h> + + +struct keyreset_state { + struct input_handler input_handler; + unsigned long keybit[BITS_TO_LONGS(KEY_CNT)]; + unsigned long upbit[BITS_TO_LONGS(KEY_CNT)]; + unsigned long key[BITS_TO_LONGS(KEY_CNT)]; + spinlock_t lock; + int key_down_target; + int key_down; + int key_up; + int restart_disabled; +}; + +int restart_requested; +static void deferred_restart(struct work_struct *dummy) +{ + restart_requested = 2; + sys_sync(); + restart_requested = 3; + kernel_restart(NULL); +} +static DECLARE_WORK(restart_work, deferred_restart); + +static void keyreset_event(struct input_handle *handle, unsigned int type, + unsigned int code, int value) +{ + unsigned long flags; + struct keyreset_state *state = handle->private; + + if (type != EV_KEY) + return; + + if (code >= KEY_MAX) + return; + + if (!test_bit(code, state->keybit)) + return; + + spin_lock_irqsave(&state->lock, flags); + if (!test_bit(code, state->key) == !value) + goto done; + __change_bit(code, state->key); + if (test_bit(code, state->upbit)) { + if (value) { + state->restart_disabled = 1; + state->key_up++; + } else + state->key_up--; + } else { + if (value) + state->key_down++; + else + state->key_down--; + } + if (state->key_down == 0 && state->key_up == 0) + state->restart_disabled = 0; + + pr_debug("reset key changed %d %d new state %d-%d-%d\n", code, value, + state->key_down, state->key_up, state->restart_disabled); + + if (value && !state->restart_disabled && + state->key_down == state->key_down_target) { + state->restart_disabled = 1; + if (restart_requested) + panic("keyboard reset failed, %d", restart_requested); + pr_info("keyboard reset\n"); + schedule_work(&restart_work); + restart_requested = 1; + } +done: + spin_unlock_irqrestore(&state->lock, flags); +} + +static int keyreset_connect(struct input_handler *handler, + struct input_dev *dev, + const struct input_device_id *id) +{ + int i; + int ret; + struct input_handle *handle; + struct keyreset_state *state = + container_of(handler, struct keyreset_state, input_handler); + + for (i = 0; i < KEY_MAX; i++) { + if (test_bit(i, state->keybit) && test_bit(i, dev->keybit)) + break; + } + if (i == KEY_MAX) + return -ENODEV; + + handle = kzalloc(sizeof(*handle), GFP_KERNEL); + if (!handle) + return -ENOMEM; + + handle->dev = dev; + handle->handler = handler; + handle->name = "keyreset"; + handle->private = state; + + ret = input_register_handle(handle); + if (ret) + goto err_input_register_handle; + + ret = input_open_device(handle); + if (ret) + goto err_input_open_device; + + pr_info("using input dev %s for key reset\n", dev->name); + + return 0; + +err_input_open_device: + input_unregister_handle(handle); +err_input_register_handle: + kfree(handle); + return ret; +} + +static void keyreset_disconnect(struct input_handle *handle) +{ + input_close_device(handle); + input_unregister_handle(handle); + kfree(handle); +} + +static const struct input_device_id keyreset_ids[] = { + { + .flags = INPUT_DEVICE_ID_MATCH_EVBIT, + .evbit = { BIT_MASK(EV_KEY) }, + }, + { }, +}; +MODULE_DEVICE_TABLE(input, keyreset_ids); + +static int keyreset_probe(struct platform_device *pdev) +{ + int ret; + int key, *keyp; + struct keyreset_state *state; + struct keyreset_platform_data *pdata = pdev->dev.platform_data; + + if (!pdata) + return -EINVAL; + + state = kzalloc(sizeof(*state), GFP_KERNEL); + if (!state) + return -ENOMEM; + + spin_lock_init(&state->lock); + keyp = pdata->keys_down; + while ((key = *keyp++)) { + if (key >= KEY_MAX) + continue; + state->key_down_target++; + __set_bit(key, state->keybit); + } + if (pdata->keys_up) { + keyp = pdata->keys_up; + while ((key = *keyp++)) { + if (key >= KEY_MAX) + continue; + __set_bit(key, state->keybit); + __set_bit(key, state->upbit); + } + } + state->input_handler.event = keyreset_event; + state->input_handler.connect = keyreset_connect; + state->input_handler.disconnect = keyreset_disconnect; + state->input_handler.name = KEYRESET_NAME; + state->input_handler.id_table = keyreset_ids; + ret = input_register_handler(&state->input_handler); + if (ret) { + kfree(state); + return ret; + } + platform_set_drvdata(pdev, state); + return 0; +} + +int keyreset_remove(struct platform_device *pdev) +{ + struct keyreset_state *state = platform_get_drvdata(pdev); + input_unregister_handler(&state->input_handler); + kfree(state); + return 0; +} + + +struct platform_driver keyreset_driver = { + .driver.name = KEYRESET_NAME, + .probe = keyreset_probe, + .remove = keyreset_remove, +}; + +static int __init keyreset_init(void) +{ + return platform_driver_register(&keyreset_driver); +} + +static void __exit keyreset_exit(void) +{ + return platform_driver_unregister(&keyreset_driver); +} + +module_init(keyreset_init); +module_exit(keyreset_exit); diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig index c44b9eafc55..9151ea98c5f 100644 --- a/drivers/input/misc/Kconfig +++ b/drivers/input/misc/Kconfig @@ -22,6 +22,23 @@ config INPUT_88PM860X_ONKEY To compile this driver as a module, choose M here: the module will be called 88pm860x_onkey. +config INPUT_AB8500_ACCDET + bool "AB8500 AV Accessory detection" + depends on AB8500_CORE && AB8500_GPADC && AB8500_GPIO + help + Say Y here to enable AV accessory detection features for ST-Ericsson's + AB8500 Mix-Sig PMIC. + +config INPUT_AB8500_PONKEY + tristate "AB5500/AB8500 Pon (PowerOn) Key" + depends on AB5500_CORE || AB8500_CORE + help + Say Y here to use the PowerOn Key for ST-Ericsson's AB5500/AB8500 + Mix-Sig PMICs. + + To compile this driver as a module, choose M here: the module + will be called ab8500-ponkey. + config INPUT_AD714X tristate "Analog Devices AD714x Capacitance Touch Sensor" help @@ -183,6 +200,17 @@ config INPUT_ATI_REMOTE2 To compile this driver as a module, choose M here: the module will be called ati_remote2. +config INPUT_KEYCHORD + tristate "Key chord input driver support" + help + Say Y here if you want to enable the key chord driver + accessible at /dev/keychord. This driver can be used + for receiving notifications when client specified key + combinations are pressed. + + To compile this driver as a module, choose M here: the + module will be called keychord. + config INPUT_KEYSPAN_REMOTE tristate "Keyspan DMR USB remote control (EXPERIMENTAL)" depends on EXPERIMENTAL @@ -199,6 +227,12 @@ config INPUT_KEYSPAN_REMOTE To compile this driver as a module, choose M here: the module will be called keyspan_remote. +config INPUT_LPS001WP + tristate "LPS0001WP pressure sensor from ST Micro" + help + This is a pressure sensor connected to I2C, mounted on the + snowball and other ST-E boards + config INPUT_POWERMATE tristate "Griffin PowerMate and Contour Jog support" depends on USB_ARCH_HAS_HCD @@ -302,6 +336,11 @@ config INPUT_WINBOND_CIR To compile this driver as a module, choose M here: the module will be called winbond_cir. +config INPUT_GPIO + tristate "GPIO driver support" + help + Say Y here if you want to support gpio based keys, wheels etc... + config HP_SDC_RTC tristate "HP SDC Real Time Clock" depends on (GSC || HP300) && SERIO @@ -390,4 +429,14 @@ config INPUT_PCAP To compile this driver as a module, choose M here: the module will be called pcap_keys. +config INPUT_STE_FF_VIBRA + tristate "ST-Ericsson Force Feedback Vibrator" + depends on STE_AUDIO_IO_DEV + select INPUT_FF_MEMLESS + help + This option enables support for ST-Ericsson's Vibrator which + registers as an input force feedback driver. + + To compile this driver as a module, choose M here. The module will + be called ste_ff_vibra. endif diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile index 71fe57d8023..ee8d05c963a 100644 --- a/drivers/input/misc/Makefile +++ b/drivers/input/misc/Makefile @@ -5,6 +5,8 @@ # Each configuration option enables a list of files. obj-$(CONFIG_INPUT_88PM860X_ONKEY) += 88pm860x_onkey.o +obj-$(CONFIG_INPUT_AB8500_ACCDET) += ab8500-accdet.o +obj-$(CONFIG_INPUT_AB8500_PONKEY) += ab8500-ponkey.o obj-$(CONFIG_INPUT_AD714X) += ad714x.o obj-$(CONFIG_INPUT_AD714X_I2C) += ad714x-i2c.o obj-$(CONFIG_INPUT_AD714X_SPI) += ad714x-spi.o @@ -16,9 +18,12 @@ obj-$(CONFIG_INPUT_BFIN_ROTARY) += bfin_rotary.o obj-$(CONFIG_INPUT_CM109) += cm109.o obj-$(CONFIG_INPUT_COBALT_BTNS) += cobalt_btns.o obj-$(CONFIG_INPUT_DM355EVM) += dm355evm_keys.o +obj-$(CONFIG_INPUT_GPIO) += gpio_event.o gpio_matrix.o gpio_input.o gpio_output.o gpio_axis.o obj-$(CONFIG_HP_SDC_RTC) += hp_sdc_rtc.o obj-$(CONFIG_INPUT_IXP4XX_BEEPER) += ixp4xx-beeper.o +obj-$(CONFIG_INPUT_KEYCHORD) += keychord.o obj-$(CONFIG_INPUT_KEYSPAN_REMOTE) += keyspan_remote.o +obj-$(CONFIG_INPUT_LPS001WP) += lps001wp_prs.o obj-$(CONFIG_INPUT_M68K_BEEP) += m68kspkr.o obj-$(CONFIG_INPUT_MAX8925_ONKEY) += max8925_onkey.o obj-$(CONFIG_INPUT_PCAP) += pcap_keys.o @@ -37,4 +42,4 @@ obj-$(CONFIG_INPUT_WINBOND_CIR) += winbond-cir.o obj-$(CONFIG_INPUT_WISTRON_BTNS) += wistron_btns.o obj-$(CONFIG_INPUT_WM831X_ON) += wm831x-on.o obj-$(CONFIG_INPUT_YEALINK) += yealink.o - +obj-$(CONFIG_INPUT_STE_FF_VIBRA) += ste_ff_vibra.o diff --git a/drivers/input/misc/ab8500-accdet.c b/drivers/input/misc/ab8500-accdet.c new file mode 100644 index 00000000000..4544c0dd5b3 --- /dev/null +++ b/drivers/input/misc/ab8500-accdet.c @@ -0,0 +1,1428 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * Author: Jarmo K. Kuronen <jarmo.kuronen@symbio.com> + * for ST-Ericsson. + * + * License terms: GPL V2 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ + +#include <linux/module.h> /* Needed by all modules */ +#include <linux/kernel.h> /* Needed for KERN_INFO */ +#include <linux/init.h> /* Needed for the macros */ +#include <linux/workqueue.h> +#include <linux/delay.h> +#include <linux/jiffies.h> +#include <linux/slab.h> +#include <linux/input.h> +#include <sound/soc.h> +#include <sound/jack.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> +#include <linux/mfd/ab8500.h> +#include <linux/mfd/abx500.h> +#include <linux/mfd/ab8500/ab8500-gpadc.h> +#include <linux/mfd/ab8500/ab8500-gpio.h> +#include <linux/gpio.h> +#include <mach/ab8500-accdet.h> +#ifdef CONFIG_SND_SOC_UX500_AB8500 +#include <sound/ux500_ab8500.h> +#else +#define ux500_ab8500_jack_report(i) +#endif + +#define MAX_DET_COUNT 10 +#define MAX_VOLT_DIFF 30 +#define MIN_MIC_POWER -100 + +/* Unique value used to identify Headset button input device */ +#define BTN_INPUT_UNIQUE_VALUE "AB8500HsBtn" +#define BTN_INPUT_DEV_NAME "AB8500 Hs Button" + +#define DEBOUNCE_PLUG_EVENT_MS 100 +#define DEBOUNCE_PLUG_RETEST_MS 25 +#define DEBOUNCE_UNPLUG_EVENT_MS 0 + +/* + * Register definition for accessory detection. + */ +#define AB8500_REGU_CTRL1_SPARE_REG 0x84 +#define AB8500_ACC_DET_DB1_REG 0x80 +#define AB8500_ACC_DET_DB2_REG 0x81 +#define AB8500_ACC_DET_CTRL_REG 0x82 +#define AB8500_IT_SOURCE5_REG 0x04 + +/* REGISTER: AB8500_ACC_DET_CTRL_REG */ +#define BITS_ACCDETCTRL2_ENA (0x20 | 0x10 | 0x08) +#define BITS_ACCDETCTRL1_ENA (0x02 | 0x01) + +/* REGISTER: AB8500_REGU_CTRL1_SPARE_REG */ +#define BIT_REGUCTRL1SPARE_VAMIC1_GROUND 0x01 + +/* REGISTER: AB8500_IT_SOURCE5_REG */ +#define BIT_ITSOURCE5_ACCDET1 0x04 + +/* After being loaded, how fast the first check is to be made */ +#define INIT_DELAY_MS 3000 + +/* Voltage limits (mV) for various types of AV Accessories */ +#define ACCESSORY_DET_VOL_DONTCARE -1 +#define ACCESSORY_HEADPHONE_DET_VOL_MIN 0 +#define ACCESSORY_HEADPHONE_DET_VOL_MAX 40 +#define ACCESSORY_CVIDEO_DET_VOL_MIN 41 +#define ACCESSORY_CVIDEO_DET_VOL_MAX 105 +#define ACCESSORY_CARKIT_DET_VOL_MIN 1100 +#define ACCESSORY_CARKIT_DET_VOL_MAX 1300 +#define ACCESSORY_HEADSET_DET_VOL_MIN 0 +#define ACCESSORY_HEADSET_DET_VOL_MAX 200 +#define ACCESSORY_OPENCABLE_DET_VOL_MIN 1730 +#define ACCESSORY_OPENCABLE_DET_VOL_MAX 2150 + +/* Macros */ + +/* + * Conviniency macros to check jack characteristics. + */ +#define jack_supports_mic(type) \ + (type == JACK_TYPE_HEADSET || type == JACK_TYPE_CARKIT) +#define jack_supports_spkr(type) \ + ((type != JACK_TYPE_DISCONNECTED) && (type != JACK_TYPE_CONNECTED)) +#define jack_supports_buttons(type) \ + ((type == JACK_TYPE_HEADSET) ||\ + (type == JACK_TYPE_CARKIT) ||\ + (type == JACK_TYPE_OPENCABLE) ||\ + (type == JACK_TYPE_CONNECTED)) + + +/* Enumerations */ + +/** + * @JACK_TYPE_UNSPECIFIED Not known whether any accessories are connected. + * @JACK_TYPE_DISCONNECTED No accessories connected. + * @JACK_TYPE_CONNECTED Accessory is connected but functionality was unable to + * detect the actual type. In this mode, possible button events are reported. + * @JACK_TYPE_HEADPHONE Headphone type of accessory (spkrs only) connected + * @JACK_TYPE_HEADSET Headset type of accessory (mic+spkrs) connected + * @JACK_TYPE_CARKIT Carkit type of accessory connected + * @JACK_TYPE_OPENCABLE Open cable connected + * @JACK_TYPE_CVIDEO CVideo type of accessory connected. + */ +enum accessory_jack_type { + JACK_TYPE_UNSPECIFIED, + JACK_TYPE_DISCONNECTED, + JACK_TYPE_CONNECTED, + JACK_TYPE_HEADPHONE, + JACK_TYPE_HEADSET, + JACK_TYPE_CARKIT, + JACK_TYPE_OPENCABLE, + JACK_TYPE_CVIDEO +}; + +/** + * @BUTTON_UNK Button state not known + * @BUTTON_PRESSED Button "down" + * @BUTTON_RELEASED Button "up" + */ +enum accessory_button_state { + BUTTON_UNK, + BUTTON_PRESSED, + BUTTON_RELEASED +}; + +/** + * @PLUG_IRQ Interrupt gen. when accessory plugged in + * @UNPLUG_IRQ Interrupt gen. when accessory plugged out + * @BUTTON_PRESS_IRQ Interrupt gen. when accessory button pressed. + * @BUTTON_RELEASE_IRQ Interrupt gen. when accessory button released. + */ +enum accessory_irq { + PLUG_IRQ, + UNPLUG_IRQ, + BUTTON_PRESS_IRQ, + BUTTON_RELEASE_IRQ +}; + +/** + * Enumerates the op. modes of the avcontrol switch + * @AUDIO_IN Audio input is selected + * @VIDEO_OUT Video output is selected + * @NOT_SET The av-switch control signal is disconnected. + */ +enum accessory_avcontrol_dir { + AUDIO_IN, + VIDEO_OUT, + NOT_SET, +}; + +/** + * @REGULATOR_VAUDIO v-audio regulator + * @REGULATOR_VAMIC1 v-amic1 regulator + * @REGULATOR_AVSWITCH Audio/Video select switch regulator + * @REGULATOR_ALL All regulators combined + */ +enum accessory_regulator { + REGULATOR_NONE = 0x0, + REGULATOR_VAUDIO = 0x1, + REGULATOR_VAMIC1 = 0x2, + REGULATOR_AVSWITCH = 0x4, + REGULATOR_ALL = 0xFF +}; + +/* Structures */ + +/** + * Describes an interrupt + * @irq interrupt identifier + * @name name of the irq in platform data + * @isr interrupt service routine + * @register are we currently registered to receive interrupts from this source. + */ +struct accessory_irq_descriptor { + enum accessory_irq irq; + const char *name; + irq_handler_t isr; + int registered; +}; + +/** + * Encapsulates info of single regulator. + * @id regulator identifier + * @name name of the regulator + * @enabled flag indicating whether regu is currently enabled. + * @handle regulator handle + */ +struct accessory_regu_descriptor { + enum accessory_regulator id; + const char *name; + int enabled; + struct regulator *handle; +}; + +/** + * Defines attributes for accessory detection operation. + * @typename type as string + * @type Type of accessory this task tests + * @req_det_count How many times this particular type of accessory + * needs to be detected in sequence in order to accept. Multidetection + * implemented to avoid false detections during plug-in. + * @meas_mv Should ACCDETECT2 input voltage be measured just before + * making the decision or can cached voltage be used instead. + * @minvol minimum voltage (mV) for decision + * @maxvol maximum voltage (mV) for decision + */ +struct accessory_detect_task { + const char *typename; + enum accessory_jack_type type; + int req_det_count; + int meas_mv; + int minvol; + int maxvol; +}; + +/** + * Device data, capsulates all relevant device data structures. + * + * @pdev pointer to platform device + * @pdata Platform data + * @gpadc interface for ADC data + * @irq_work_queue Work queue for deferred interrupt processing + * + * @detect_work work item to perform detection work + * @unplug_irq_work work item to process unplug event + * @init_work work item to process initialization work. + * + * @btn_input_dev button input device used to report btn presses + * @btn_state Current state of accessory button + * + * @jack_type type of currently connected accessory + * @reported_jack_type previously reported jack type. + * @jack_type_temp temporary storage for currently connected accessory + * + * @jack_det_count counter how many times in sequence the accessory + * type detection has produced same result. + * @total_jack_det_count after plug-in irq, how many times detection + * has totally been made in order to detect the accessory type + * + * @detect_jiffies Used to save timestamp when detection was made. Timestamp + * used to filter out spurious button presses that might occur during the + * plug-in procedure. + * + * @accdet1_th_set flag to indicate whether accdet1 threshold and debounce + * times are configured + * @accdet2_th_set flag to indicate whether accdet2 thresholds are configured + * @gpio35_dir_set flag to indicate whether GPIO35 (VIDEOCTRL) direction + * has been configured. + */ +struct ab8500_ad { + struct platform_device *pdev; + struct ab8500_accdet_platform_data *pdata; + struct ab8500_gpadc *gpadc; + struct workqueue_struct *irq_work_queue; + + struct delayed_work detect_work; + struct delayed_work unplug_irq_work; + struct delayed_work init_work; + + struct input_dev *btn_input_dev; + enum accessory_button_state btn_state; + + enum accessory_jack_type jack_type; + enum accessory_jack_type reported_jack_type; + enum accessory_jack_type jack_type_temp; + + int jack_det_count; + int total_jack_det_count; + + unsigned long detect_jiffies; + + int accdet1_th_set; + int accdet2_th_set; + int gpio35_dir_set; +}; + +/* Forward declarations */ + +static void config_accdetect(struct ab8500_ad *dd); + +static void release_irq(struct ab8500_ad *dd, enum accessory_irq irq_id); +static void claim_irq(struct ab8500_ad *dd, enum accessory_irq irq_id); + +static irqreturn_t unplug_irq_handler(int irq, void *_userdata); +static irqreturn_t plug_irq_handler(int irq, void *_userdata); +static irqreturn_t button_press_irq_handler(int irq, void *_userdata); +static irqreturn_t button_release_irq_handler(int irq, void *_userdata); + +static void unplug_irq_handler_work(struct work_struct *work); +static void detect_work(struct work_struct *work); +static void init_work(struct work_struct *work); + +static enum accessory_jack_type detect(struct ab8500_ad *dd, int *required_det); +static void set_av_switch(struct ab8500_ad *dd, + enum accessory_avcontrol_dir dir); + +/* Static data initialization */ + +static struct accessory_detect_task detect_ops[] = { + { + .type = JACK_TYPE_DISCONNECTED, + .typename = "DISCONNECTED", + .meas_mv = 1, + .req_det_count = 1, + .minvol = ACCESSORY_DET_VOL_DONTCARE, + .maxvol = ACCESSORY_DET_VOL_DONTCARE + }, + { + .type = JACK_TYPE_HEADPHONE, + .typename = "HEADPHONE", + .meas_mv = 1, + .req_det_count = 1, + .minvol = ACCESSORY_HEADPHONE_DET_VOL_MIN, + .maxvol = ACCESSORY_HEADPHONE_DET_VOL_MAX + }, + { + .type = JACK_TYPE_CVIDEO, + .typename = "CVIDEO", + .meas_mv = 0, + .req_det_count = 4, + .minvol = ACCESSORY_CVIDEO_DET_VOL_MIN, + .maxvol = ACCESSORY_CVIDEO_DET_VOL_MAX + }, + { + .type = JACK_TYPE_OPENCABLE, + .typename = "OPENCABLE", + .meas_mv = 0, + .req_det_count = 4, + .minvol = ACCESSORY_OPENCABLE_DET_VOL_MIN, + .maxvol = ACCESSORY_OPENCABLE_DET_VOL_MAX + }, + { + .type = JACK_TYPE_CARKIT, + .typename = "CARKIT", + .meas_mv = 1, + .req_det_count = 1, + .minvol = ACCESSORY_CARKIT_DET_VOL_MIN, + .maxvol = ACCESSORY_CARKIT_DET_VOL_MAX + }, + { + .type = JACK_TYPE_HEADSET, + .typename = "HEADSET", + .meas_mv = 0, + .req_det_count = 2, + .minvol = ACCESSORY_HEADSET_DET_VOL_MIN, + .maxvol = ACCESSORY_HEADSET_DET_VOL_MAX + }, + { + .type = JACK_TYPE_CONNECTED, + .typename = "CONNECTED", + .meas_mv = 0, + .req_det_count = 4, + .minvol = ACCESSORY_DET_VOL_DONTCARE, + .maxvol = ACCESSORY_DET_VOL_DONTCARE + } +}; + +static struct accessory_regu_descriptor regu_desc[3] = { + { + .id = REGULATOR_VAUDIO, + .name = "v-audio", + }, + { + .id = REGULATOR_VAMIC1, + .name = "v-amic1", + }, + { + .id = REGULATOR_AVSWITCH, + .name = "vcc-avswitch", + }, +}; + +static struct accessory_irq_descriptor irq_desc_norm[] = { + { + .irq = PLUG_IRQ, + .name = "ACC_DETECT_1DB_F", + .isr = plug_irq_handler, + }, + { + .irq = UNPLUG_IRQ, + .name = "ACC_DETECT_1DB_R", + .isr = unplug_irq_handler, + }, + { + .irq = BUTTON_PRESS_IRQ, + .name = "ACC_DETECT_22DB_F", + .isr = button_press_irq_handler, + }, + { + .irq = BUTTON_RELEASE_IRQ, + .name = "ACC_DETECT_22DB_R", + .isr = button_release_irq_handler, + }, +}; + +static struct accessory_irq_descriptor irq_desc_inverted[] = { + { + .irq = PLUG_IRQ, + .name = "ACC_DETECT_1DB_R", + .isr = plug_irq_handler, + }, + { + .irq = UNPLUG_IRQ, + .name = "ACC_DETECT_1DB_F", + .isr = unplug_irq_handler, + }, + { + .irq = BUTTON_PRESS_IRQ, + .name = "ACC_DETECT_22DB_R", + .isr = button_press_irq_handler, + }, + { + .irq = BUTTON_RELEASE_IRQ, + .name = "ACC_DETECT_22DB_F", + .isr = button_release_irq_handler, + }, +}; + +static struct accessory_irq_descriptor *irq_desc; + +/* + * textual represenation of the accessory type + */ +static const char *accessory_str(enum accessory_jack_type type) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(detect_ops); i++) + if (type == detect_ops[i].type) + return detect_ops[i].typename; + + return "UNKNOWN?"; +} + +/* + * enables regulator but only if it has not been enabled earlier. + */ +static void accessory_regulator_enable(enum accessory_regulator reg) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(regu_desc); i++) { + if (reg & regu_desc[i].id) { + if (!regu_desc[i].enabled) { + if (!regulator_enable(regu_desc[i].handle)) + regu_desc[i].enabled = 1; + } + } + } +} + +/* + * disables regulator but only if it has been previously enabled. + */ +static void accessory_regulator_disable(enum accessory_regulator reg) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(regu_desc); i++) { + if (reg & regu_desc[i].id) { + if (regu_desc[i].enabled) { + if (!regulator_disable(regu_desc[i].handle)) + regu_desc[i].enabled = 0; + } + } + } +} + +/* + * frees previously retrieved regulators. + */ +static void free_regulators(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(regu_desc); i++) { + if (regu_desc[i].handle) { + regulator_put(regu_desc[i].handle); + regu_desc[i].handle = NULL; + } + } +} + +/* + * gets required regulators. + */ +static int create_regulators(struct ab8500_ad *dd) +{ + int i; + int status = 0; + + for (i = 0; i < ARRAY_SIZE(regu_desc); i++) { + struct regulator *regu = + regulator_get(&dd->pdev->dev, regu_desc[i].name); + if (IS_ERR(regu)) { + status = PTR_ERR(regu); + dev_err(&dd->pdev->dev, + "%s: Failed to get supply '%s' (%d).\n", + __func__, regu_desc[i].name, status); + free_regulators(); + goto out; + } else { + regu_desc[i].handle = regu; + } + } + +out: + return status; +} + +/* + * configures accdet2 input on/off + */ +static void config_accdetect2_hw(struct ab8500_ad *dd, int enable) +{ + int ret = 0; + + if (!dd->accdet2_th_set) { + /* Configure accdetect21+22 thresholds */ + ret = abx500_set_register_interruptible(&dd->pdev->dev, + AB8500_ECI_AV_ACC, + AB8500_ACC_DET_DB2_REG, + dd->pdata->accdet2122_th); + if (ret < 0) { + dev_err(&dd->pdev->dev, + "%s: Failed to write reg (%d).\n", __func__, + ret); + goto out; + } else { + dd->accdet2_th_set = 1; + } + } + + /* Enable/Disable accdetect21 comparators + pullup */ + ret = abx500_mask_and_set_register_interruptible( + &dd->pdev->dev, + AB8500_ECI_AV_ACC, + AB8500_ACC_DET_CTRL_REG, + BITS_ACCDETCTRL2_ENA, + enable ? BITS_ACCDETCTRL2_ENA : 0); + + if (ret < 0) + dev_err(&dd->pdev->dev, "%s: Failed to update reg (%d).\n", + __func__, ret); + +out: + return; +} + +/* + * configures accdet1 input on/off + */ +void config_accdetect1_hw(struct ab8500_ad *dd, int enable) +{ + int ret; + + if (!dd->accdet1_th_set) { + ret = abx500_set_register_interruptible(&dd->pdev->dev, + AB8500_ECI_AV_ACC, + AB8500_ACC_DET_DB1_REG, + dd->pdata->accdet1_dbth); + if (ret < 0) + dev_err(&dd->pdev->dev, + "%s: Failed to write reg (%d).\n", __func__, + ret); + else + dd->accdet1_th_set = 1; + } + + /* enable accdetect1 comparator */ + ret = abx500_mask_and_set_register_interruptible( + &dd->pdev->dev, + AB8500_ECI_AV_ACC, + AB8500_ACC_DET_CTRL_REG, + BITS_ACCDETCTRL1_ENA, + enable ? BITS_ACCDETCTRL1_ENA : 0); + + if (ret < 0) + dev_err(&dd->pdev->dev, + "%s: Failed to update reg (%d).\n", __func__, ret); +} + +/* + * create input device for button press reporting + */ +static int create_btn_input_dev(struct ab8500_ad *dd) +{ + int err; + + dd->btn_input_dev = input_allocate_device(); + if (!dd->btn_input_dev) { + dev_err(&dd->pdev->dev, "%s: Failed to allocate input dev.\n", + __func__); + err = -ENOMEM; + goto out; + } + + input_set_capability(dd->btn_input_dev, + EV_KEY, + dd->pdata->btn_keycode); + + dd->btn_input_dev->name = BTN_INPUT_DEV_NAME; + dd->btn_input_dev->uniq = BTN_INPUT_UNIQUE_VALUE; + dd->btn_input_dev->dev.parent = &dd->pdev->dev; + + err = input_register_device(dd->btn_input_dev); + if (err) { + dev_err(&dd->pdev->dev, + "%s: register_input_device failed (%d).\n", __func__, + err); + input_free_device(dd->btn_input_dev); + dd->btn_input_dev = NULL; + goto out; + } +out: + return err; +} + +/* + * reports jack status + */ +void report_jack_status(struct ab8500_ad *dd) +{ + int value = 0; + + /* Never report possible open cable */ + if (dd->jack_type == JACK_TYPE_OPENCABLE) + goto out; + + /* Never report same state twice in a row */ + if (dd->jack_type == dd->reported_jack_type) + goto out; + dd->reported_jack_type = dd->jack_type; + + dev_info(&dd->pdev->dev, "Accessory: %s\n", + accessory_str(dd->jack_type)); + + if (dd->jack_type != JACK_TYPE_DISCONNECTED && + dd->jack_type != JACK_TYPE_UNSPECIFIED) + value |= SND_JACK_MECHANICAL; + if (jack_supports_mic(dd->jack_type)) + value |= SND_JACK_MICROPHONE; + if (jack_supports_spkr(dd->jack_type)) + value |= (SND_JACK_HEADPHONE | SND_JACK_LINEOUT); + if (dd->jack_type == JACK_TYPE_CVIDEO) { + value |= SND_JACK_VIDEOOUT; + set_av_switch(dd, VIDEO_OUT); + } + + ux500_ab8500_jack_report(value); + +out: return; +} + +/* + * returns the high level status whether some accessory is connected (1|0). + */ +static int detect_plugged_in(struct ab8500_ad *dd) +{ + u8 value = 0; + + int status = abx500_get_register_interruptible( + &dd->pdev->dev, + AB8500_INTERRUPT, + AB8500_IT_SOURCE5_REG, + &value); + if (status < 0) { + dev_err(&dd->pdev->dev, "%s: reg read failed (%d).\n", + __func__, status); + return 0; + } + + if (dd->pdata->is_detection_inverted) + return value & BIT_ITSOURCE5_ACCDET1 ? 1 : 0; + else + return value & BIT_ITSOURCE5_ACCDET1 ? 0 : 1; +} + +/* + * mic_line_voltage_stable - measures a relative stable voltage from spec. input + */ +static int meas_voltage_stable(struct ab8500_ad *dd, u8 input) +{ + int iterations = 2; + int v1, v2, dv; + + v1 = ab8500_gpadc_convert(dd->gpadc, input); + do { + msleep(1); + --iterations; + v2 = ab8500_gpadc_convert(dd->gpadc, input); + dv = abs(v2 - v1); + v1 = v2; + } while (iterations > 0 && dv > MAX_VOLT_DIFF); + + return v1; +} + +/* + * worker routine to handle accessory unplug case + */ +static void unplug_irq_handler_work(struct work_struct *work) +{ + struct ab8500_ad *dd = container_of(work, + struct ab8500_ad, unplug_irq_work.work); + + dev_dbg(&dd->pdev->dev, "%s: Enter\n", __func__); + + dd->jack_type = dd->jack_type_temp = JACK_TYPE_DISCONNECTED; + dd->jack_det_count = dd->total_jack_det_count = 0; + dd->btn_state = BUTTON_UNK; + config_accdetect(dd); + + accessory_regulator_disable(REGULATOR_ALL); + + report_jack_status(dd); +} + +/* + * interrupt service routine for accessory unplug. + */ +static irqreturn_t unplug_irq_handler(int irq, void *_userdata) +{ + struct ab8500_ad *dd = _userdata; + + dev_dbg(&dd->pdev->dev, "%s: Enter (irq=%d)\n", __func__, irq); + + queue_delayed_work(dd->irq_work_queue, &dd->unplug_irq_work, + msecs_to_jiffies(DEBOUNCE_UNPLUG_EVENT_MS)); + + return IRQ_HANDLED; +} + +/* + * interrupt service routine for accessory plug. + */ +static irqreturn_t plug_irq_handler(int irq, void *_userdata) +{ + struct ab8500_ad *dd = _userdata; + + dev_dbg(&dd->pdev->dev, "%s: Enter (irq=%d)\n", + __func__, irq); + + switch (dd->jack_type) { + case JACK_TYPE_DISCONNECTED: + case JACK_TYPE_UNSPECIFIED: + queue_delayed_work(dd->irq_work_queue, &dd->detect_work, + msecs_to_jiffies(DEBOUNCE_PLUG_EVENT_MS)); + break; + + default: + dev_err(&dd->pdev->dev, "%s: Unexpected plug IRQ\n", __func__); + break; + } + + return IRQ_HANDLED; +} + +/* + * worker routine to perform detection. + */ +static void detect_work(struct work_struct *work) +{ + int req_det_count = 1; + enum accessory_jack_type new_type; + struct ab8500_ad *dd = container_of(work, + struct ab8500_ad, detect_work.work); + + dev_dbg(&dd->pdev->dev, "%s: Enter\n", __func__); + + set_av_switch(dd, AUDIO_IN); + + new_type = detect(dd, &req_det_count); + + dd->total_jack_det_count++; + if (dd->jack_type_temp == new_type) { + dd->jack_det_count++; + } else { + dd->jack_det_count = 1; + dd->jack_type_temp = new_type; + } + + if (dd->total_jack_det_count >= MAX_DET_COUNT) { + dev_err(&dd->pdev->dev, + "%s: MAX_DET_COUNT(=%d) reached. Bailing out.\n", + __func__, MAX_DET_COUNT); + queue_delayed_work(dd->irq_work_queue, &dd->unplug_irq_work, + msecs_to_jiffies(DEBOUNCE_UNPLUG_EVENT_MS)); + } else if (dd->jack_det_count >= req_det_count) { + dd->total_jack_det_count = dd->jack_det_count = 0; + dd->jack_type = new_type; + dd->detect_jiffies = jiffies; + report_jack_status(dd); + config_accdetect(dd); + } else { + queue_delayed_work(dd->irq_work_queue, + &dd->detect_work, + msecs_to_jiffies(DEBOUNCE_PLUG_RETEST_MS)); + } +} + +/* + * reports a button event (pressed, released). + */ +static void report_btn_event(struct ab8500_ad *dd, int down) +{ + input_report_key(dd->btn_input_dev, dd->pdata->btn_keycode, down); + input_sync(dd->btn_input_dev); + + dev_dbg(&dd->pdev->dev, "HS-BTN: %s\n", down ? "PRESSED" : "RELEASED"); +} + +/* + * interrupt service routine invoked when hs button is pressed down. + */ +static irqreturn_t button_press_irq_handler(int irq, void *_userdata) +{ + struct ab8500_ad *dd = _userdata; + + unsigned long accept_jiffies = dd->detect_jiffies + + msecs_to_jiffies(1000); + if (time_before(jiffies, accept_jiffies)) { + dev_dbg(&dd->pdev->dev, "%s: Skipped spurious btn press.\n", + __func__); + return IRQ_HANDLED; + } + + dev_dbg(&dd->pdev->dev, "%s: Enter (irq=%d)\n", __func__, irq); + + if (dd->jack_type == JACK_TYPE_OPENCABLE) { + /* Someting got connected to open cable -> detect.. */ + config_accdetect2_hw(dd, 0); + queue_delayed_work(dd->irq_work_queue, &dd->detect_work, + msecs_to_jiffies(DEBOUNCE_PLUG_EVENT_MS)); + return IRQ_HANDLED; + } + + if (dd->btn_state == BUTTON_PRESSED) + return IRQ_HANDLED; + + if (jack_supports_buttons(dd->jack_type)) { + dd->btn_state = BUTTON_PRESSED; + report_btn_event(dd, 1); + } else { + dd->btn_state = BUTTON_UNK; + } + + return IRQ_HANDLED; +} + +/* + * interrupts service routine invoked when hs button is released. + */ +static irqreturn_t button_release_irq_handler(int irq, void *_userdata) +{ + struct ab8500_ad *dd = _userdata; + + dev_dbg(&dd->pdev->dev, "%s: Enter (irq=%d)\n", __func__, irq); + + if (dd->jack_type == JACK_TYPE_OPENCABLE) + return IRQ_HANDLED; + + if (dd->btn_state != BUTTON_PRESSED) + return IRQ_HANDLED; + + if (jack_supports_buttons(dd->jack_type)) { + report_btn_event(dd, 0); + dd->btn_state = BUTTON_RELEASED; + } else { + dd->btn_state = BUTTON_UNK; + } + + return IRQ_HANDLED; +} + +/* + * configures HW so that it is possible to make decision whether + * accessory is connected or not. + */ +static void config_hw_test_plug_connected(struct ab8500_ad *dd, int enable) +{ + int ret; + + dev_dbg(&dd->pdev->dev, "%s:%d\n", __func__, enable); + + ret = ab8500_config_pull_up_or_down(&dd->pdev->dev, + dd->pdata->video_ctrl_gpio, !enable); + if (ret < 0) { + dev_err(&dd->pdev->dev, + "%s: Failed to update reg (%d).\n", __func__, ret); + return; + } + + if (enable) + accessory_regulator_enable(REGULATOR_VAMIC1); +} + +/* + * configures HW so that carkit/headset detection can be accomplished. + */ +static void config_hw_test_basic_carkit(struct ab8500_ad *dd, int enable) +{ + int ret; + + dev_dbg(&dd->pdev->dev, "%s:%d\n", __func__, enable); + + if (enable) + accessory_regulator_disable(REGULATOR_VAMIC1); + + /* Un-Ground the VAMic1 output when enabled */ + ret = abx500_mask_and_set_register_interruptible( + &dd->pdev->dev, + AB8500_REGU_CTRL1, + AB8500_REGU_CTRL1_SPARE_REG, + BIT_REGUCTRL1SPARE_VAMIC1_GROUND, + enable ? BIT_REGUCTRL1SPARE_VAMIC1_GROUND : 0); + if (ret < 0) + dev_err(&dd->pdev->dev, + "%s: Failed to update reg (%d).\n", __func__, ret); +} + +/* + * checks whether measured voltage is in given range. depending on arguments, + * voltage might be re-measured or previously measured voltage is reused. + */ +static int mic_vol_in_range(struct ab8500_ad *dd, + int lo, int hi, int force_read) +{ + static int mv = MIN_MIC_POWER; + + if (mv == -100 || force_read) + mv = meas_voltage_stable(dd, ACC_DETECT2); + + return (mv >= lo && mv <= hi) ? 1 : 0; +} + +/* + * checks whether the currently connected HW is of given type. + */ +static int detect_hw(struct ab8500_ad *dd, + struct accessory_detect_task *task) +{ + int status; + + switch (task->type) { + case JACK_TYPE_DISCONNECTED: + config_hw_test_plug_connected(dd, 1); + status = !detect_plugged_in(dd); + break; + case JACK_TYPE_CONNECTED: + config_hw_test_plug_connected(dd, 1); + status = detect_plugged_in(dd); + break; + case JACK_TYPE_CARKIT: + config_hw_test_basic_carkit(dd, 1); + /* flow through.. */ + case JACK_TYPE_HEADPHONE: + case JACK_TYPE_CVIDEO: + case JACK_TYPE_HEADSET: + case JACK_TYPE_OPENCABLE: + status = mic_vol_in_range(dd, + task->minvol, + task->maxvol, + task->meas_mv); + break; + default: + status = 0; + } + + return status; +} + +/* + * sets the av switch direction - audio-in vs video-out + */ +static void set_av_switch(struct ab8500_ad *dd, + enum accessory_avcontrol_dir dir) +{ + int ret; + + dev_dbg(&dd->pdev->dev, "%s: Enter (%d)\n", __func__, dir); + if (dir == NOT_SET) { + ret = gpio_direction_input(dd->pdata->video_ctrl_gpio); + dd->gpio35_dir_set = 0; + ret = gpio_direction_output(dd->pdata->video_ctrl_gpio, 0); + } else if (!dd->gpio35_dir_set) { + ret = gpio_direction_output(dd->pdata->video_ctrl_gpio, + dir == AUDIO_IN ? 1 : 0); + if (ret < 0) { + dev_err(&dd->pdev->dev, + "%s: Output video ctrl signal failed (%d).\n", + __func__, ret); + } else { + dd->gpio35_dir_set = 1; + dev_dbg(&dd->pdev->dev, "AV-SWITCH: %s\n", + dir == AUDIO_IN ? "AUDIO_IN" : "VIDEO_OUT"); + } + } else { + gpio_set_value(dd->pdata->video_ctrl_gpio, + dir == AUDIO_IN ? 1 : 0); + } +} + +/* + * Tries to detect the currently attached accessory + */ +static enum accessory_jack_type detect(struct ab8500_ad *dd, + int *req_det_count) +{ + enum accessory_jack_type type = JACK_TYPE_DISCONNECTED; + int i; + + accessory_regulator_enable(REGULATOR_VAUDIO | REGULATOR_AVSWITCH); + + for (i = 0; i < ARRAY_SIZE(detect_ops); ++i) { + if (detect_hw(dd, &detect_ops[i])) { + type = detect_ops[i].type; + *req_det_count = detect_ops[i].req_det_count; + break; + } + } + + config_hw_test_basic_carkit(dd, 0); + config_hw_test_plug_connected(dd, 0); + + if (jack_supports_buttons(type)) + accessory_regulator_enable(REGULATOR_VAMIC1); + else + accessory_regulator_disable(REGULATOR_VAMIC1 | + REGULATOR_AVSWITCH); + + accessory_regulator_disable(REGULATOR_VAUDIO); + + return type; +} + +/* + * registers to specific interrupt + */ +static void claim_irq(struct ab8500_ad *dd, enum accessory_irq irq_id) +{ + int ret; + int irq; + + if (dd->pdata->is_detection_inverted) + irq_desc = irq_desc_inverted; + else + irq_desc = irq_desc_norm; + + if (irq_desc[irq_id].registered) + return; + + irq = platform_get_irq_byname( + dd->pdev, + irq_desc[irq_id].name); + if (irq < 0) { + dev_err(&dd->pdev->dev, + "%s: Failed to get irq %s\n", __func__, + irq_desc[irq_id].name); + return; + } + + ret = request_threaded_irq(irq, + NULL, + irq_desc[irq_id].isr, + IRQF_NO_SUSPEND | IRQF_SHARED, + irq_desc[irq_id].name, + dd); + if (ret != 0) { + dev_err(&dd->pdev->dev, + "%s: Failed to claim irq %s (%d)\n", + __func__, + irq_desc[irq_id].name, + ret); + } else { + irq_desc[irq_id].registered = 1; + dev_dbg(&dd->pdev->dev, "%s: %s\n", + __func__, irq_desc[irq_id].name); + } +} + +/* + * releases specific interrupt + */ +static void release_irq(struct ab8500_ad *dd, enum accessory_irq irq_id) +{ + int irq; + + if (dd->pdata->is_detection_inverted) + irq_desc = irq_desc_inverted; + else + irq_desc = irq_desc_norm; + + if (!irq_desc[irq_id].registered) + return; + + irq = platform_get_irq_byname( + dd->pdev, + irq_desc[irq_id].name); + if (irq < 0) { + dev_err(&dd->pdev->dev, + "%s: Failed to get irq %s (%d)\n", + __func__, + irq_desc[irq_id].name, irq); + } else { + free_irq(irq, dd); + irq_desc[irq_id].registered = 0; + dev_dbg(&dd->pdev->dev, "%s: %s\n", + __func__, irq_desc[irq_id].name); + } +} + +/* + * configures interrupts + detection hardware to meet the requirements + * set by currently attached accessory type. + */ +static void config_accdetect(struct ab8500_ad *dd) +{ + switch (dd->jack_type) { + case JACK_TYPE_UNSPECIFIED: + config_accdetect1_hw(dd, 1); + config_accdetect2_hw(dd, 0); + + release_irq(dd, PLUG_IRQ); + release_irq(dd, UNPLUG_IRQ); + release_irq(dd, BUTTON_PRESS_IRQ); + release_irq(dd, BUTTON_RELEASE_IRQ); + set_av_switch(dd, NOT_SET); + break; + + case JACK_TYPE_DISCONNECTED: + set_av_switch(dd, NOT_SET); + case JACK_TYPE_HEADPHONE: + case JACK_TYPE_CVIDEO: + config_accdetect1_hw(dd, 1); + config_accdetect2_hw(dd, 0); + + claim_irq(dd, PLUG_IRQ); + claim_irq(dd, UNPLUG_IRQ); + release_irq(dd, BUTTON_PRESS_IRQ); + release_irq(dd, BUTTON_RELEASE_IRQ); + break; + + case JACK_TYPE_CONNECTED: + case JACK_TYPE_HEADSET: + case JACK_TYPE_CARKIT: + case JACK_TYPE_OPENCABLE: + config_accdetect1_hw(dd, 1); + config_accdetect2_hw(dd, 1); + + release_irq(dd, PLUG_IRQ); + claim_irq(dd, UNPLUG_IRQ); + claim_irq(dd, BUTTON_PRESS_IRQ); + claim_irq(dd, BUTTON_RELEASE_IRQ); + break; + + default: + dev_err(&dd->pdev->dev, "%s: Unknown type: %d\n", + __func__, dd->jack_type); + } +} + +/* + * Deferred initialization of the work. + */ +static void init_work(struct work_struct *work) +{ + struct ab8500_ad *dd = container_of(work, + struct ab8500_ad, init_work.work); + + dev_dbg(&dd->pdev->dev, "%s: Enter\n", __func__); + + dd->jack_type = dd->reported_jack_type = JACK_TYPE_UNSPECIFIED; + config_accdetect(dd); + queue_delayed_work(dd->irq_work_queue, + &dd->detect_work, + msecs_to_jiffies(0)); +} + +/* + * performs platform device initialization + */ +int ab8500_accessory_init(struct platform_device *pdev) +{ + struct ab8500_ad *dd; + struct ab8500_platform_data *plat; + + dev_dbg(&pdev->dev, "Enter: %s\n", __func__); + + dd = kzalloc(sizeof(struct ab8500_ad), GFP_KERNEL); + if (!dd) { + dev_err(&pdev->dev, "%s: Mem. alloc failed\n", __func__); + goto fail_no_mem_for_devdata; + } + + dd->pdev = pdev; + dd->pdata = pdev->dev.platform_data; + plat = dev_get_platdata(pdev->dev.parent); + + if (!plat || !plat->accdet) { + dev_err(&pdev->dev, "%s: Failed to get accdet plat data.\n", + __func__); + goto fail_no_ab8500_dev; + } + dd->pdata = plat->accdet; + + if (dd->pdata->video_ctrl_gpio) { + if (!gpio_is_valid(dd->pdata->video_ctrl_gpio)) { + dev_err(&pdev->dev, + "%s: Video ctrl GPIO invalid (%d).\n", __func__, + dd->pdata->video_ctrl_gpio); + goto fail_video_ctrl_gpio; + } + if (gpio_request(dd->pdata->video_ctrl_gpio, "Video Control")) { + dev_err(&pdev->dev, "%s: Get video ctrl GPIO failed.\n", + __func__); + goto fail_video_ctrl_gpio; + } + } + + if (create_btn_input_dev(dd) < 0) { + dev_err(&pdev->dev, "%s: create_button_input_dev failed.\n", + __func__); + goto fail_no_btn_input_dev; + } + + if (create_regulators(dd) < 0) { + dev_err(&pdev->dev, "%s: failed to create regulators\n", + __func__); + goto fail_no_regulators; + } + dd->btn_state = BUTTON_UNK; + + dd->irq_work_queue = create_singlethread_workqueue("ab8500_accdet_wq"); + if (!dd->irq_work_queue) { + dev_err(&pdev->dev, "%s: Failed to create wq\n", __func__); + goto fail_no_mem_for_wq; + } + dd->gpadc = ab8500_gpadc_get(); + + INIT_DELAYED_WORK(&dd->detect_work, detect_work); + INIT_DELAYED_WORK(&dd->unplug_irq_work, unplug_irq_handler_work); + INIT_DELAYED_WORK(&dd->init_work, init_work); + + /* Deferred init/detect since no use for the info early in boot */ + queue_delayed_work(dd->irq_work_queue, + &dd->init_work, + msecs_to_jiffies(INIT_DELAY_MS)); + + platform_set_drvdata(pdev, dd); + + return 0; + +fail_no_mem_for_wq: + free_regulators(); +fail_no_regulators: + input_unregister_device(dd->btn_input_dev); +fail_no_btn_input_dev: + gpio_free(dd->pdata->video_ctrl_gpio); +fail_video_ctrl_gpio: +fail_no_ab8500_dev: + kfree(dd); +fail_no_mem_for_devdata: + + return -ENOMEM; +} + +/* + * Performs platform device cleanup + */ +void ab8500_accessory_cleanup(struct ab8500_ad *dd) +{ + dev_dbg(&dd->pdev->dev, "Enter: %s\n", __func__); + + dd->jack_type = JACK_TYPE_UNSPECIFIED; + config_accdetect(dd); + + gpio_free(dd->pdata->video_ctrl_gpio); + input_unregister_device(dd->btn_input_dev); + free_regulators(); + + cancel_delayed_work(&dd->detect_work); + cancel_delayed_work(&dd->unplug_irq_work); + cancel_delayed_work(&dd->init_work); + flush_workqueue(dd->irq_work_queue); + destroy_workqueue(dd->irq_work_queue); + + kfree(dd); +} + +static int __devinit ab8500_acc_detect_probe(struct platform_device *pdev) +{ + return ab8500_accessory_init(pdev); +} + + +static int __devexit ab8500_acc_detect_remove(struct platform_device *pdev) +{ + ab8500_accessory_cleanup(platform_get_drvdata(pdev)); + platform_set_drvdata(pdev, NULL); + + return 0; +} + +#if defined(CONFIG_PM) +static u8 acc_det_ctrl_suspend_val; + +static int ab8500_acc_detect_suspend(struct platform_device *pdev, + pm_message_t state) +{ + struct ab8500_ad *dd = platform_get_drvdata(pdev); + int irq_id, irq; + + dev_dbg(&dd->pdev->dev, "%s: Enter\n", __func__); + + cancel_delayed_work_sync(&dd->unplug_irq_work); + cancel_delayed_work_sync(&dd->detect_work); + cancel_delayed_work_sync(&dd->init_work); + + if (dd->pdata->is_detection_inverted) + irq_desc = irq_desc_inverted; + else + irq_desc = irq_desc_norm; + + for (irq_id = 0; irq_id < ARRAY_SIZE(irq_desc_norm); irq_id++) { + if (irq_desc[irq_id].registered == 1) { + irq = platform_get_irq_byname( + dd->pdev, + irq_desc[irq_id].name); + + disable_irq(irq); + } + } + + /* Turn off AccDetect comparators and pull-up */ + (void) abx500_get_register_interruptible( + &dd->pdev->dev, + AB8500_ECI_AV_ACC, + AB8500_ACC_DET_CTRL_REG, + &acc_det_ctrl_suspend_val); + /* 0x10 = Keep AccDetect21Ena to avoid headset button irq */ + (void) abx500_set_register_interruptible( + &dd->pdev->dev, + AB8500_ECI_AV_ACC, + AB8500_ACC_DET_CTRL_REG, + 0x10); + return 0; +} + +static int ab8500_acc_detect_resume(struct platform_device *pdev) +{ + struct ab8500_ad *dd = platform_get_drvdata(pdev); + int irq_id, irq; + + dev_dbg(&dd->pdev->dev, "%s: Enter\n", __func__); + + /* Turn on AccDetect comparators and pull-up */ + (void) abx500_set_register_interruptible( + &dd->pdev->dev, + AB8500_ECI_AV_ACC, + AB8500_ACC_DET_CTRL_REG, + acc_det_ctrl_suspend_val); + + if (dd->pdata->is_detection_inverted) + irq_desc = irq_desc_inverted; + else + irq_desc = irq_desc_norm; + + for (irq_id = 0; irq_id < ARRAY_SIZE(irq_desc_norm); irq_id++) { + if (irq_desc[irq_id].registered == 1) { + irq = platform_get_irq_byname( + dd->pdev, + irq_desc[irq_id].name); + + enable_irq(irq); + + } + } + + /* After resume, reinitialize */ + dd->gpio35_dir_set = dd->accdet1_th_set = dd->accdet2_th_set = 0; + queue_delayed_work(dd->irq_work_queue, &dd->init_work, 0); + + return 0; +} +#else +#define ab8500_acc_detect_suspend NULL +#define ab8500_acc_detect_resume NULL +#endif + +static struct platform_driver ab8500_acc_detect_platform_driver = { + .driver = { + .name = "ab8500-acc-det", + .owner = THIS_MODULE, + }, + .probe = ab8500_acc_detect_probe, + .remove = __devexit_p(ab8500_acc_detect_remove), + .suspend = ab8500_acc_detect_suspend, + .resume = ab8500_acc_detect_resume, +}; + +static int __init ab8500_acc_detect_init(void) +{ + return platform_driver_register(&ab8500_acc_detect_platform_driver); +} + +static void __exit ab8500_acc_detect_exit(void) +{ + platform_driver_unregister(&ab8500_acc_detect_platform_driver); +} + +module_init(ab8500_acc_detect_init); +module_exit(ab8500_acc_detect_exit); + +MODULE_DESCRIPTION("AB8500 AV Accessory detection driver"); +MODULE_ALIAS("platform:ab8500-acc-det"); +MODULE_AUTHOR("ST-Ericsson"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/input/misc/ab8500-ponkey.c b/drivers/input/misc/ab8500-ponkey.c new file mode 100644 index 00000000000..39964956de2 --- /dev/null +++ b/drivers/input/misc/ab8500-ponkey.c @@ -0,0 +1,182 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * License Terms: GNU General Public License v2 + * Author: Sundar Iyer <sundar.iyer@stericsson.com> for ST-Ericsson + * + * AB8500 Power-On Key handler + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/slab.h> + +struct ab8500_ponkey_variant { + const char *irq_falling; + const char *irq_rising; +}; + +static const struct ab8500_ponkey_variant ab5500_onswa = { + .irq_falling = "ONSWAn_falling", + .irq_rising = "ONSWAn_rising", +}; + +static const struct ab8500_ponkey_variant ab8500_ponkey = { + .irq_falling = "ONKEY_DBF", + .irq_rising = "ONKEY_DBR", +}; + +/** + * struct ab8500_ponkey_info - ab8500 ponkey information + * @input_dev: pointer to input device + * @irq_dbf: irq number for falling transition + * @irq_dbr: irq number for rising transition + */ +struct ab8500_ponkey_info { + struct input_dev *idev; + int irq_dbf; + int irq_dbr; +}; + +/* AB8500 gives us an interrupt when ONKEY is held */ +static irqreturn_t ab8500_ponkey_handler(int irq, void *data) +{ + struct ab8500_ponkey_info *info = data; + + if (irq == info->irq_dbf) + input_report_key(info->idev, KEY_POWER, true); + else if (irq == info->irq_dbr) + input_report_key(info->idev, KEY_POWER, false); + + input_sync(info->idev); + + return IRQ_HANDLED; +} + +static int __devinit ab8500_ponkey_probe(struct platform_device *pdev) +{ + const struct ab8500_ponkey_variant *variant; + struct ab8500_ponkey_info *info; + int irq_dbf, irq_dbr, ret; + + variant = (const struct ab8500_ponkey_variant *) + pdev->id_entry->driver_data; + + irq_dbf = platform_get_irq_byname(pdev, variant->irq_falling); + if (irq_dbf < 0) { + dev_err(&pdev->dev, "No IRQ for %s: %d\n", + variant->irq_falling, irq_dbf); + return irq_dbf; + } + + irq_dbr = platform_get_irq_byname(pdev, variant->irq_rising); + if (irq_dbr < 0) { + dev_err(&pdev->dev, "No IRQ for %s: %d\n", + variant->irq_rising, irq_dbr); + return irq_dbr; + } + + info = kzalloc(sizeof(struct ab8500_ponkey_info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + info->irq_dbf = irq_dbf; + info->irq_dbr = irq_dbr; + + info->idev = input_allocate_device(); + if (!info->idev) { + dev_err(&pdev->dev, "Failed to allocate input dev\n"); + ret = -ENOMEM; + goto out; + } + + info->idev->name = "AB8500 POn(PowerOn) Key"; + info->idev->dev.parent = &pdev->dev; + info->idev->evbit[0] = BIT_MASK(EV_KEY); + info->idev->keybit[BIT_WORD(KEY_POWER)] = BIT_MASK(KEY_POWER); + + ret = input_register_device(info->idev); + if (ret) { + dev_err(&pdev->dev, "Can't register input device: %d\n", ret); + goto out_unfreedevice; + } + + ret = request_threaded_irq(info->irq_dbf, NULL, ab8500_ponkey_handler, + IRQF_NO_SUSPEND, "ab8500-ponkey-dbf", + info); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to request dbf IRQ#%d: %d\n", + info->irq_dbf, ret); + goto out_unregisterdevice; + } + + ret = request_threaded_irq(info->irq_dbr, NULL, ab8500_ponkey_handler, + IRQF_NO_SUSPEND, "ab8500-ponkey-dbr", + info); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to request dbr IRQ#%d: %d\n", + info->irq_dbr, ret); + goto out_irq_dbf; + } + + platform_set_drvdata(pdev, info); + + return 0; + +out_irq_dbf: + free_irq(info->irq_dbf, info); +out_unregisterdevice: + input_unregister_device(info->idev); + info->idev = NULL; +out_unfreedevice: + input_free_device(info->idev); +out: + kfree(info); + return ret; +} + +static int __devexit ab8500_ponkey_remove(struct platform_device *pdev) +{ + struct ab8500_ponkey_info *info = platform_get_drvdata(pdev); + + free_irq(info->irq_dbf, info); + free_irq(info->irq_dbr, info); + input_unregister_device(info->idev); + kfree(info); + return 0; +} + +static struct platform_device_id ab8500_ponkey_id_table[] = { + { "ab5500-onswa", (kernel_ulong_t)&ab5500_onswa, }, + { "ab8500-poweron-key", (kernel_ulong_t)&ab8500_ponkey, }, + { }, +}; +MODULE_DEVICE_TABLE(platform, ab8500_ponkey_id_table); + +static struct platform_driver ab8500_ponkey_driver = { + .driver = { + .name = "ab8500-poweron-key", + .owner = THIS_MODULE, + }, + .id_table = ab8500_ponkey_id_table, + .probe = ab8500_ponkey_probe, + .remove = __devexit_p(ab8500_ponkey_remove), +}; + +static int __init ab8500_ponkey_init(void) +{ + return platform_driver_register(&ab8500_ponkey_driver); +} +module_init(ab8500_ponkey_init); + +static void __exit ab8500_ponkey_exit(void) +{ + platform_driver_unregister(&ab8500_ponkey_driver); +} +module_exit(ab8500_ponkey_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Sundar Iyer <sundar.iyer@stericsson.com>"); +MODULE_DESCRIPTION("ST-Ericsson AB8500 Power-ON(Pon) Key driver"); diff --git a/drivers/input/misc/gpio_axis.c b/drivers/input/misc/gpio_axis.c new file mode 100644 index 00000000000..0acf4a576f5 --- /dev/null +++ b/drivers/input/misc/gpio_axis.c @@ -0,0 +1,192 @@ +/* drivers/input/misc/gpio_axis.c + * + * Copyright (C) 2007 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + +#include <linux/kernel.h> +#include <linux/gpio.h> +#include <linux/gpio_event.h> +#include <linux/interrupt.h> +#include <linux/slab.h> + +struct gpio_axis_state { + struct gpio_event_input_devs *input_devs; + struct gpio_event_axis_info *info; + uint32_t pos; +}; + +uint16_t gpio_axis_4bit_gray_map_table[] = { + [0x0] = 0x0, [0x1] = 0x1, /* 0000 0001 */ + [0x3] = 0x2, [0x2] = 0x3, /* 0011 0010 */ + [0x6] = 0x4, [0x7] = 0x5, /* 0110 0111 */ + [0x5] = 0x6, [0x4] = 0x7, /* 0101 0100 */ + [0xc] = 0x8, [0xd] = 0x9, /* 1100 1101 */ + [0xf] = 0xa, [0xe] = 0xb, /* 1111 1110 */ + [0xa] = 0xc, [0xb] = 0xd, /* 1010 1011 */ + [0x9] = 0xe, [0x8] = 0xf, /* 1001 1000 */ +}; +uint16_t gpio_axis_4bit_gray_map(struct gpio_event_axis_info *info, uint16_t in) +{ + return gpio_axis_4bit_gray_map_table[in]; +} + +uint16_t gpio_axis_5bit_singletrack_map_table[] = { + [0x10] = 0x00, [0x14] = 0x01, [0x1c] = 0x02, /* 10000 10100 11100 */ + [0x1e] = 0x03, [0x1a] = 0x04, [0x18] = 0x05, /* 11110 11010 11000 */ + [0x08] = 0x06, [0x0a] = 0x07, [0x0e] = 0x08, /* 01000 01010 01110 */ + [0x0f] = 0x09, [0x0d] = 0x0a, [0x0c] = 0x0b, /* 01111 01101 01100 */ + [0x04] = 0x0c, [0x05] = 0x0d, [0x07] = 0x0e, /* 00100 00101 00111 */ + [0x17] = 0x0f, [0x16] = 0x10, [0x06] = 0x11, /* 10111 10110 00110 */ + [0x02] = 0x12, [0x12] = 0x13, [0x13] = 0x14, /* 00010 10010 10011 */ + [0x1b] = 0x15, [0x0b] = 0x16, [0x03] = 0x17, /* 11011 01011 00011 */ + [0x01] = 0x18, [0x09] = 0x19, [0x19] = 0x1a, /* 00001 01001 11001 */ + [0x1d] = 0x1b, [0x15] = 0x1c, [0x11] = 0x1d, /* 11101 10101 10001 */ +}; +uint16_t gpio_axis_5bit_singletrack_map( + struct gpio_event_axis_info *info, uint16_t in) +{ + return gpio_axis_5bit_singletrack_map_table[in]; +} + +static void gpio_event_update_axis(struct gpio_axis_state *as, int report) +{ + struct gpio_event_axis_info *ai = as->info; + int i; + int change; + uint16_t state = 0; + uint16_t pos; + uint16_t old_pos = as->pos; + for (i = ai->count - 1; i >= 0; i--) + state = (state << 1) | gpio_get_value(ai->gpio[i]); + pos = ai->map(ai, state); + if (ai->flags & GPIOEAF_PRINT_RAW) + pr_info("axis %d-%d raw %x, pos %d -> %d\n", + ai->type, ai->code, state, old_pos, pos); + if (report && pos != old_pos) { + if (ai->type == EV_REL) { + change = (ai->decoded_size + pos - old_pos) % + ai->decoded_size; + if (change > ai->decoded_size / 2) + change -= ai->decoded_size; + if (change == ai->decoded_size / 2) { + if (ai->flags & GPIOEAF_PRINT_EVENT) + pr_info("axis %d-%d unknown direction, " + "pos %d -> %d\n", ai->type, + ai->code, old_pos, pos); + change = 0; /* no closest direction */ + } + if (ai->flags & GPIOEAF_PRINT_EVENT) + pr_info("axis %d-%d change %d\n", + ai->type, ai->code, change); + input_report_rel(as->input_devs->dev[ai->dev], + ai->code, change); + } else { + if (ai->flags & GPIOEAF_PRINT_EVENT) + pr_info("axis %d-%d now %d\n", + ai->type, ai->code, pos); + input_event(as->input_devs->dev[ai->dev], + ai->type, ai->code, pos); + } + input_sync(as->input_devs->dev[ai->dev]); + } + as->pos = pos; +} + +static irqreturn_t gpio_axis_irq_handler(int irq, void *dev_id) +{ + struct gpio_axis_state *as = dev_id; + gpio_event_update_axis(as, 1); + return IRQ_HANDLED; +} + +int gpio_event_axis_func(struct gpio_event_input_devs *input_devs, + struct gpio_event_info *info, void **data, int func) +{ + int ret; + int i; + int irq; + struct gpio_event_axis_info *ai; + struct gpio_axis_state *as; + + ai = container_of(info, struct gpio_event_axis_info, info); + if (func == GPIO_EVENT_FUNC_SUSPEND) { + for (i = 0; i < ai->count; i++) + disable_irq(gpio_to_irq(ai->gpio[i])); + return 0; + } + if (func == GPIO_EVENT_FUNC_RESUME) { + for (i = 0; i < ai->count; i++) + enable_irq(gpio_to_irq(ai->gpio[i])); + return 0; + } + + if (func == GPIO_EVENT_FUNC_INIT) { + *data = as = kmalloc(sizeof(*as), GFP_KERNEL); + if (as == NULL) { + ret = -ENOMEM; + goto err_alloc_axis_state_failed; + } + as->input_devs = input_devs; + as->info = ai; + if (ai->dev >= input_devs->count) { + pr_err("gpio_event_axis: bad device index %d >= %d " + "for %d:%d\n", ai->dev, input_devs->count, + ai->type, ai->code); + ret = -EINVAL; + goto err_bad_device_index; + } + + input_set_capability(input_devs->dev[ai->dev], + ai->type, ai->code); + if (ai->type == EV_ABS) { + input_set_abs_params(input_devs->dev[ai->dev], ai->code, + 0, ai->decoded_size - 1, 0, 0); + } + for (i = 0; i < ai->count; i++) { + ret = gpio_request(ai->gpio[i], "gpio_event_axis"); + if (ret < 0) + goto err_request_gpio_failed; + ret = gpio_direction_input(ai->gpio[i]); + if (ret < 0) + goto err_gpio_direction_input_failed; + ret = irq = gpio_to_irq(ai->gpio[i]); + if (ret < 0) + goto err_get_irq_num_failed; + ret = request_irq(irq, gpio_axis_irq_handler, + IRQF_TRIGGER_RISING | + IRQF_TRIGGER_FALLING, + "gpio_event_axis", as); + if (ret < 0) + goto err_request_irq_failed; + } + gpio_event_update_axis(as, 0); + return 0; + } + + ret = 0; + as = *data; + for (i = ai->count - 1; i >= 0; i--) { + free_irq(gpio_to_irq(ai->gpio[i]), as); +err_request_irq_failed: +err_get_irq_num_failed: +err_gpio_direction_input_failed: + gpio_free(ai->gpio[i]); +err_request_gpio_failed: + ; + } +err_bad_device_index: + kfree(as); + *data = NULL; +err_alloc_axis_state_failed: + return ret; +} diff --git a/drivers/input/misc/gpio_event.c b/drivers/input/misc/gpio_event.c new file mode 100644 index 00000000000..a98be67d1ab --- /dev/null +++ b/drivers/input/misc/gpio_event.c @@ -0,0 +1,260 @@ +/* drivers/input/misc/gpio_event.c + * + * Copyright (C) 2007 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + +#include <linux/earlysuspend.h> +#include <linux/module.h> +#include <linux/input.h> +#include <linux/gpio_event.h> +#include <linux/hrtimer.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +struct gpio_event { + struct gpio_event_input_devs *input_devs; + const struct gpio_event_platform_data *info; + struct early_suspend early_suspend; + void *state[0]; +}; + +static int gpio_input_event( + struct input_dev *dev, unsigned int type, unsigned int code, int value) +{ + int i; + int devnr; + int ret = 0; + int tmp_ret; + struct gpio_event_info **ii; + struct gpio_event *ip = input_get_drvdata(dev); + + for (devnr = 0; devnr < ip->input_devs->count; devnr++) + if (ip->input_devs->dev[devnr] == dev) + break; + if (devnr == ip->input_devs->count) { + pr_err("gpio_input_event: unknown device %p\n", dev); + return -EIO; + } + + for (i = 0, ii = ip->info->info; i < ip->info->info_count; i++, ii++) { + if ((*ii)->event) { + tmp_ret = (*ii)->event(ip->input_devs, *ii, + &ip->state[i], + devnr, type, code, value); + if (tmp_ret) + ret = tmp_ret; + } + } + return ret; +} + +static int gpio_event_call_all_func(struct gpio_event *ip, int func) +{ + int i; + int ret; + struct gpio_event_info **ii; + + if (func == GPIO_EVENT_FUNC_INIT || func == GPIO_EVENT_FUNC_RESUME) { + ii = ip->info->info; + for (i = 0; i < ip->info->info_count; i++, ii++) { + if ((*ii)->func == NULL) { + ret = -ENODEV; + pr_err("gpio_event_probe: Incomplete pdata, " + "no function\n"); + goto err_no_func; + } + if (func == GPIO_EVENT_FUNC_RESUME && (*ii)->no_suspend) + continue; + ret = (*ii)->func(ip->input_devs, *ii, &ip->state[i], + func); + if (ret) { + pr_err("gpio_event_probe: function failed\n"); + goto err_func_failed; + } + } + return 0; + } + + ret = 0; + i = ip->info->info_count; + ii = ip->info->info + i; + while (i > 0) { + i--; + ii--; + if ((func & ~1) == GPIO_EVENT_FUNC_SUSPEND && (*ii)->no_suspend) + continue; + (*ii)->func(ip->input_devs, *ii, &ip->state[i], func & ~1); +err_func_failed: +err_no_func: + ; + } + return ret; +} + +#ifdef CONFIG_HAS_EARLYSUSPEND +void gpio_event_suspend(struct early_suspend *h) +{ + struct gpio_event *ip; + ip = container_of(h, struct gpio_event, early_suspend); + gpio_event_call_all_func(ip, GPIO_EVENT_FUNC_SUSPEND); + ip->info->power(ip->info, 0); +} + +void gpio_event_resume(struct early_suspend *h) +{ + struct gpio_event *ip; + ip = container_of(h, struct gpio_event, early_suspend); + ip->info->power(ip->info, 1); + gpio_event_call_all_func(ip, GPIO_EVENT_FUNC_RESUME); +} +#endif + +static int gpio_event_probe(struct platform_device *pdev) +{ + int err; + struct gpio_event *ip; + struct gpio_event_platform_data *event_info; + int dev_count = 1; + int i; + int registered = 0; + + event_info = pdev->dev.platform_data; + if (event_info == NULL) { + pr_err("gpio_event_probe: No pdata\n"); + return -ENODEV; + } + if ((!event_info->name && !event_info->names[0]) || + !event_info->info || !event_info->info_count) { + pr_err("gpio_event_probe: Incomplete pdata\n"); + return -ENODEV; + } + if (!event_info->name) + while (event_info->names[dev_count]) + dev_count++; + ip = kzalloc(sizeof(*ip) + + sizeof(ip->state[0]) * event_info->info_count + + sizeof(*ip->input_devs) + + sizeof(ip->input_devs->dev[0]) * dev_count, GFP_KERNEL); + if (ip == NULL) { + err = -ENOMEM; + pr_err("gpio_event_probe: Failed to allocate private data\n"); + goto err_kp_alloc_failed; + } + ip->input_devs = (void*)&ip->state[event_info->info_count]; + platform_set_drvdata(pdev, ip); + + for (i = 0; i < dev_count; i++) { + struct input_dev *input_dev = input_allocate_device(); + if (input_dev == NULL) { + err = -ENOMEM; + pr_err("gpio_event_probe: " + "Failed to allocate input device\n"); + goto err_input_dev_alloc_failed; + } + input_set_drvdata(input_dev, ip); + input_dev->name = event_info->name ? + event_info->name : event_info->names[i]; + input_dev->event = gpio_input_event; + ip->input_devs->dev[i] = input_dev; + } + ip->input_devs->count = dev_count; + ip->info = event_info; + if (event_info->power) { +#ifdef CONFIG_HAS_EARLYSUSPEND + ip->early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN + 1; + ip->early_suspend.suspend = gpio_event_suspend; + ip->early_suspend.resume = gpio_event_resume; + register_early_suspend(&ip->early_suspend); +#endif + ip->info->power(ip->info, 1); + } + + err = gpio_event_call_all_func(ip, GPIO_EVENT_FUNC_INIT); + if (err) + goto err_call_all_func_failed; + + for (i = 0; i < dev_count; i++) { + err = input_register_device(ip->input_devs->dev[i]); + if (err) { + pr_err("gpio_event_probe: Unable to register %s " + "input device\n", ip->input_devs->dev[i]->name); + goto err_input_register_device_failed; + } + registered++; + } + + return 0; + +err_input_register_device_failed: + gpio_event_call_all_func(ip, GPIO_EVENT_FUNC_UNINIT); +err_call_all_func_failed: + if (event_info->power) { +#ifdef CONFIG_HAS_EARLYSUSPEND + unregister_early_suspend(&ip->early_suspend); +#endif + ip->info->power(ip->info, 0); + } + for (i = 0; i < registered; i++) + input_unregister_device(ip->input_devs->dev[i]); + for (i = dev_count - 1; i >= registered; i--) { + input_free_device(ip->input_devs->dev[i]); +err_input_dev_alloc_failed: + ; + } + kfree(ip); +err_kp_alloc_failed: + return err; +} + +static int gpio_event_remove(struct platform_device *pdev) +{ + struct gpio_event *ip = platform_get_drvdata(pdev); + int i; + + gpio_event_call_all_func(ip, GPIO_EVENT_FUNC_UNINIT); + if (ip->info->power) { +#ifdef CONFIG_HAS_EARLYSUSPEND + unregister_early_suspend(&ip->early_suspend); +#endif + ip->info->power(ip->info, 0); + } + for (i = 0; i < ip->input_devs->count; i++) + input_unregister_device(ip->input_devs->dev[i]); + kfree(ip); + return 0; +} + +static struct platform_driver gpio_event_driver = { + .probe = gpio_event_probe, + .remove = gpio_event_remove, + .driver = { + .name = GPIO_EVENT_DEV_NAME, + }, +}; + +static int __devinit gpio_event_init(void) +{ + return platform_driver_register(&gpio_event_driver); +} + +static void __exit gpio_event_exit(void) +{ + platform_driver_unregister(&gpio_event_driver); +} + +module_init(gpio_event_init); +module_exit(gpio_event_exit); + +MODULE_DESCRIPTION("GPIO Event Driver"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/input/misc/gpio_input.c b/drivers/input/misc/gpio_input.c new file mode 100644 index 00000000000..758df480600 --- /dev/null +++ b/drivers/input/misc/gpio_input.c @@ -0,0 +1,353 @@ +/* drivers/input/misc/gpio_input.c + * + * Copyright (C) 2007 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + +#include <linux/kernel.h> +#include <linux/gpio.h> +#include <linux/gpio_event.h> +#include <linux/hrtimer.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/wakelock.h> + +enum { + DEBOUNCE_UNSTABLE = BIT(0), /* Got irq, while debouncing */ + DEBOUNCE_PRESSED = BIT(1), + DEBOUNCE_NOTPRESSED = BIT(2), + DEBOUNCE_WAIT_IRQ = BIT(3), /* Stable irq state */ + DEBOUNCE_POLL = BIT(4), /* Stable polling state */ + + DEBOUNCE_UNKNOWN = + DEBOUNCE_PRESSED | DEBOUNCE_NOTPRESSED, +}; + +struct gpio_key_state { + struct gpio_input_state *ds; + uint8_t debounce; +}; + +struct gpio_input_state { + struct gpio_event_input_devs *input_devs; + const struct gpio_event_input_info *info; + struct hrtimer timer; + int use_irq; + int debounce_count; + spinlock_t irq_lock; + struct wake_lock wake_lock; + struct gpio_key_state key_state[0]; +}; + +static enum hrtimer_restart gpio_event_input_timer_func(struct hrtimer *timer) +{ + int i; + int pressed; + struct gpio_input_state *ds = + container_of(timer, struct gpio_input_state, timer); + unsigned gpio_flags = ds->info->flags; + unsigned npolarity; + int nkeys = ds->info->keymap_size; + const struct gpio_event_direct_entry *key_entry; + struct gpio_key_state *key_state; + unsigned long irqflags; + uint8_t debounce; + +#if 0 + key_entry = kp->keys_info->keymap; + key_state = kp->key_state; + for (i = 0; i < nkeys; i++, key_entry++, key_state++) + pr_info("gpio_read_detect_status %d %d\n", key_entry->gpio, + gpio_read_detect_status(key_entry->gpio)); +#endif + key_entry = ds->info->keymap; + key_state = ds->key_state; + spin_lock_irqsave(&ds->irq_lock, irqflags); + for (i = 0; i < nkeys; i++, key_entry++, key_state++) { + debounce = key_state->debounce; + if (debounce & DEBOUNCE_WAIT_IRQ) + continue; + if (key_state->debounce & DEBOUNCE_UNSTABLE) { + debounce = key_state->debounce = DEBOUNCE_UNKNOWN; + enable_irq(gpio_to_irq(key_entry->gpio)); + pr_info("gpio_keys_scan_keys: key %x-%x, %d " + "(%d) continue debounce\n", + ds->info->type, key_entry->code, + i, key_entry->gpio); + } + npolarity = !(gpio_flags & GPIOEDF_ACTIVE_HIGH); + pressed = gpio_get_value(key_entry->gpio) ^ npolarity; + if (debounce & DEBOUNCE_POLL) { + if (pressed == !(debounce & DEBOUNCE_PRESSED)) { + ds->debounce_count++; + key_state->debounce = DEBOUNCE_UNKNOWN; + if (gpio_flags & GPIOEDF_PRINT_KEY_DEBOUNCE) + pr_info("gpio_keys_scan_keys: key %x-" + "%x, %d (%d) start debounce\n", + ds->info->type, key_entry->code, + i, key_entry->gpio); + } + continue; + } + if (pressed && (debounce & DEBOUNCE_NOTPRESSED)) { + if (gpio_flags & GPIOEDF_PRINT_KEY_DEBOUNCE) + pr_info("gpio_keys_scan_keys: key %x-%x, %d " + "(%d) debounce pressed 1\n", + ds->info->type, key_entry->code, + i, key_entry->gpio); + key_state->debounce = DEBOUNCE_PRESSED; + continue; + } + if (!pressed && (debounce & DEBOUNCE_PRESSED)) { + if (gpio_flags & GPIOEDF_PRINT_KEY_DEBOUNCE) + pr_info("gpio_keys_scan_keys: key %x-%x, %d " + "(%d) debounce pressed 0\n", + ds->info->type, key_entry->code, + i, key_entry->gpio); + key_state->debounce = DEBOUNCE_NOTPRESSED; + continue; + } + /* key is stable */ + ds->debounce_count--; + if (ds->use_irq) + key_state->debounce |= DEBOUNCE_WAIT_IRQ; + else + key_state->debounce |= DEBOUNCE_POLL; + if (gpio_flags & GPIOEDF_PRINT_KEYS) + pr_info("gpio_keys_scan_keys: key %x-%x, %d (%d) " + "changed to %d\n", ds->info->type, + key_entry->code, i, key_entry->gpio, pressed); + input_event(ds->input_devs->dev[key_entry->dev], ds->info->type, + key_entry->code, pressed); + } + +#if 0 + key_entry = kp->keys_info->keymap; + key_state = kp->key_state; + for (i = 0; i < nkeys; i++, key_entry++, key_state++) { + pr_info("gpio_read_detect_status %d %d\n", key_entry->gpio, + gpio_read_detect_status(key_entry->gpio)); + } +#endif + + if (ds->debounce_count) + hrtimer_start(timer, ds->info->debounce_time, HRTIMER_MODE_REL); + else if (!ds->use_irq) + hrtimer_start(timer, ds->info->poll_time, HRTIMER_MODE_REL); + else + wake_unlock(&ds->wake_lock); + + spin_unlock_irqrestore(&ds->irq_lock, irqflags); + + return HRTIMER_NORESTART; +} + +static irqreturn_t gpio_event_input_irq_handler(int irq, void *dev_id) +{ + struct gpio_key_state *ks = dev_id; + struct gpio_input_state *ds = ks->ds; + int keymap_index = ks - ds->key_state; + const struct gpio_event_direct_entry *key_entry; + unsigned long irqflags; + int pressed; + + if (!ds->use_irq) + return IRQ_HANDLED; + + key_entry = &ds->info->keymap[keymap_index]; + + if (ds->info->debounce_time.tv64) { + spin_lock_irqsave(&ds->irq_lock, irqflags); + if (ks->debounce & DEBOUNCE_WAIT_IRQ) { + ks->debounce = DEBOUNCE_UNKNOWN; + if (ds->debounce_count++ == 0) { + wake_lock(&ds->wake_lock); + hrtimer_start( + &ds->timer, ds->info->debounce_time, + HRTIMER_MODE_REL); + } + if (ds->info->flags & GPIOEDF_PRINT_KEY_DEBOUNCE) + pr_info("gpio_event_input_irq_handler: " + "key %x-%x, %d (%d) start debounce\n", + ds->info->type, key_entry->code, + keymap_index, key_entry->gpio); + } else { + disable_irq_nosync(irq); + ks->debounce = DEBOUNCE_UNSTABLE; + } + spin_unlock_irqrestore(&ds->irq_lock, irqflags); + } else { + pressed = gpio_get_value(key_entry->gpio) ^ + !(ds->info->flags & GPIOEDF_ACTIVE_HIGH); + if (ds->info->flags & GPIOEDF_PRINT_KEYS) + pr_info("gpio_event_input_irq_handler: key %x-%x, %d " + "(%d) changed to %d\n", + ds->info->type, key_entry->code, keymap_index, + key_entry->gpio, pressed); + input_event(ds->input_devs->dev[key_entry->dev], ds->info->type, + key_entry->code, pressed); + } + return IRQ_HANDLED; +} + +static int gpio_event_input_request_irqs(struct gpio_input_state *ds) +{ + int i; + int err; + unsigned int irq; + unsigned long req_flags = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING; + + for (i = 0; i < ds->info->keymap_size; i++) { + err = irq = gpio_to_irq(ds->info->keymap[i].gpio); + if (err < 0) + goto err_gpio_get_irq_num_failed; + err = request_irq(irq, gpio_event_input_irq_handler, + req_flags, "gpio_keys", &ds->key_state[i]); + if (err) { + pr_err("gpio_event_input_request_irqs: request_irq " + "failed for input %d, irq %d\n", + ds->info->keymap[i].gpio, irq); + goto err_request_irq_failed; + } + enable_irq_wake(irq); + } + return 0; + + for (i = ds->info->keymap_size - 1; i >= 0; i--) { + free_irq(gpio_to_irq(ds->info->keymap[i].gpio), + &ds->key_state[i]); +err_request_irq_failed: +err_gpio_get_irq_num_failed: + ; + } + return err; +} + +int gpio_event_input_func(struct gpio_event_input_devs *input_devs, + struct gpio_event_info *info, void **data, int func) +{ + int ret; + int i; + unsigned long irqflags; + struct gpio_event_input_info *di; + struct gpio_input_state *ds = *data; + + di = container_of(info, struct gpio_event_input_info, info); + + if (func == GPIO_EVENT_FUNC_SUSPEND) { + if (ds->use_irq) + for (i = 0; i < di->keymap_size; i++) + disable_irq(gpio_to_irq(di->keymap[i].gpio)); + hrtimer_cancel(&ds->timer); + return 0; + } + if (func == GPIO_EVENT_FUNC_RESUME) { + spin_lock_irqsave(&ds->irq_lock, irqflags); + if (ds->use_irq) + for (i = 0; i < di->keymap_size; i++) + enable_irq(gpio_to_irq(di->keymap[i].gpio)); + hrtimer_start(&ds->timer, ktime_set(0, 0), HRTIMER_MODE_REL); + spin_unlock_irqrestore(&ds->irq_lock, irqflags); + return 0; + } + + if (func == GPIO_EVENT_FUNC_INIT) { + if (ktime_to_ns(di->poll_time) <= 0) + di->poll_time = ktime_set(0, 20 * NSEC_PER_MSEC); + + *data = ds = kzalloc(sizeof(*ds) + sizeof(ds->key_state[0]) * + di->keymap_size, GFP_KERNEL); + if (ds == NULL) { + ret = -ENOMEM; + pr_err("gpio_event_input_func: " + "Failed to allocate private data\n"); + goto err_ds_alloc_failed; + } + ds->debounce_count = di->keymap_size; + ds->input_devs = input_devs; + ds->info = di; + wake_lock_init(&ds->wake_lock, WAKE_LOCK_SUSPEND, "gpio_input"); + spin_lock_init(&ds->irq_lock); + + for (i = 0; i < di->keymap_size; i++) { + int dev = di->keymap[i].dev; + if (dev >= input_devs->count) { + pr_err("gpio_event_input_func: bad device " + "index %d >= %d for key code %d\n", + dev, input_devs->count, + di->keymap[i].code); + ret = -EINVAL; + goto err_bad_keymap; + } + input_set_capability(input_devs->dev[dev], di->type, + di->keymap[i].code); + ds->key_state[i].ds = ds; + ds->key_state[i].debounce = DEBOUNCE_UNKNOWN; + } + + for (i = 0; i < di->keymap_size; i++) { + ret = gpio_request(di->keymap[i].gpio, "gpio_kp_in"); + if (ret) { + pr_err("gpio_event_input_func: gpio_request " + "failed for %d\n", di->keymap[i].gpio); + goto err_gpio_request_failed; + } + ret = gpio_direction_input(di->keymap[i].gpio); + if (ret) { + pr_err("gpio_event_input_func: " + "gpio_direction_input failed for %d\n", + di->keymap[i].gpio); + goto err_gpio_configure_failed; + } + } + + ret = gpio_event_input_request_irqs(ds); + + spin_lock_irqsave(&ds->irq_lock, irqflags); + ds->use_irq = ret == 0; + + pr_info("GPIO Input Driver: Start gpio inputs for %s%s in %s " + "mode\n", input_devs->dev[0]->name, + (input_devs->count > 1) ? "..." : "", + ret == 0 ? "interrupt" : "polling"); + + hrtimer_init(&ds->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + ds->timer.function = gpio_event_input_timer_func; + hrtimer_start(&ds->timer, ktime_set(0, 0), HRTIMER_MODE_REL); + spin_unlock_irqrestore(&ds->irq_lock, irqflags); + return 0; + } + + ret = 0; + spin_lock_irqsave(&ds->irq_lock, irqflags); + hrtimer_cancel(&ds->timer); + if (ds->use_irq) { + for (i = di->keymap_size - 1; i >= 0; i--) { + free_irq(gpio_to_irq(di->keymap[i].gpio), + &ds->key_state[i]); + } + } + spin_unlock_irqrestore(&ds->irq_lock, irqflags); + + for (i = di->keymap_size - 1; i >= 0; i--) { +err_gpio_configure_failed: + gpio_free(di->keymap[i].gpio); +err_gpio_request_failed: + ; + } +err_bad_keymap: + wake_lock_destroy(&ds->wake_lock); + kfree(ds); +err_ds_alloc_failed: + return ret; +} diff --git a/drivers/input/misc/gpio_matrix.c b/drivers/input/misc/gpio_matrix.c new file mode 100644 index 00000000000..227eb8fe3c0 --- /dev/null +++ b/drivers/input/misc/gpio_matrix.c @@ -0,0 +1,432 @@ +/* drivers/input/misc/gpio_matrix.c + * + * Copyright (C) 2007 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + +#include <linux/kernel.h> +#include <linux/gpio.h> +#include <linux/gpio_event.h> +#include <linux/hrtimer.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/wakelock.h> + +struct gpio_kp { + struct gpio_event_input_devs *input_devs; + struct gpio_event_matrix_info *keypad_info; + struct hrtimer timer; + struct wake_lock wake_lock; + int current_output; + unsigned int use_irq:1; + unsigned int key_state_changed:1; + unsigned int last_key_state_changed:1; + unsigned int some_keys_pressed:2; + unsigned int disabled_irq:1; + unsigned long keys_pressed[0]; +}; + +static void clear_phantom_key(struct gpio_kp *kp, int out, int in) +{ + struct gpio_event_matrix_info *mi = kp->keypad_info; + int key_index = out * mi->ninputs + in; + unsigned short keyentry = mi->keymap[key_index]; + unsigned short keycode = keyentry & MATRIX_KEY_MASK; + unsigned short dev = keyentry >> MATRIX_CODE_BITS; + + if (!test_bit(keycode, kp->input_devs->dev[dev]->key)) { + if (mi->flags & GPIOKPF_PRINT_PHANTOM_KEYS) + pr_info("gpiomatrix: phantom key %x, %d-%d (%d-%d) " + "cleared\n", keycode, out, in, + mi->output_gpios[out], mi->input_gpios[in]); + __clear_bit(key_index, kp->keys_pressed); + } else { + if (mi->flags & GPIOKPF_PRINT_PHANTOM_KEYS) + pr_info("gpiomatrix: phantom key %x, %d-%d (%d-%d) " + "not cleared\n", keycode, out, in, + mi->output_gpios[out], mi->input_gpios[in]); + } +} + +static int restore_keys_for_input(struct gpio_kp *kp, int out, int in) +{ + int rv = 0; + int key_index; + + key_index = out * kp->keypad_info->ninputs + in; + while (out < kp->keypad_info->noutputs) { + if (test_bit(key_index, kp->keys_pressed)) { + rv = 1; + clear_phantom_key(kp, out, in); + } + key_index += kp->keypad_info->ninputs; + out++; + } + return rv; +} + +static void remove_phantom_keys(struct gpio_kp *kp) +{ + int out, in, inp; + int key_index; + + if (kp->some_keys_pressed < 3) + return; + + for (out = 0; out < kp->keypad_info->noutputs; out++) { + inp = -1; + key_index = out * kp->keypad_info->ninputs; + for (in = 0; in < kp->keypad_info->ninputs; in++, key_index++) { + if (test_bit(key_index, kp->keys_pressed)) { + if (inp == -1) { + inp = in; + continue; + } + if (inp >= 0) { + if (!restore_keys_for_input(kp, out + 1, + inp)) + break; + clear_phantom_key(kp, out, inp); + inp = -2; + } + restore_keys_for_input(kp, out, in); + } + } + } +} + +static void report_key(struct gpio_kp *kp, int key_index, int out, int in) +{ + struct gpio_event_matrix_info *mi = kp->keypad_info; + int pressed = test_bit(key_index, kp->keys_pressed); + unsigned short keyentry = mi->keymap[key_index]; + unsigned short keycode = keyentry & MATRIX_KEY_MASK; + unsigned short dev = keyentry >> MATRIX_CODE_BITS; + + if (pressed != test_bit(keycode, kp->input_devs->dev[dev]->key)) { + if (keycode == KEY_RESERVED) { + if (mi->flags & GPIOKPF_PRINT_UNMAPPED_KEYS) + pr_info("gpiomatrix: unmapped key, %d-%d " + "(%d-%d) changed to %d\n", + out, in, mi->output_gpios[out], + mi->input_gpios[in], pressed); + } else { + if (mi->flags & GPIOKPF_PRINT_MAPPED_KEYS) + pr_info("gpiomatrix: key %x, %d-%d (%d-%d) " + "changed to %d\n", keycode, + out, in, mi->output_gpios[out], + mi->input_gpios[in], pressed); + input_report_key(kp->input_devs->dev[dev], keycode, pressed); + } + } +} + +static enum hrtimer_restart gpio_keypad_timer_func(struct hrtimer *timer) +{ + int out, in; + int key_index; + int gpio; + struct gpio_kp *kp = container_of(timer, struct gpio_kp, timer); + struct gpio_event_matrix_info *mi = kp->keypad_info; + unsigned gpio_keypad_flags = mi->flags; + unsigned polarity = !!(gpio_keypad_flags & GPIOKPF_ACTIVE_HIGH); + + out = kp->current_output; + if (out == mi->noutputs) { + out = 0; + kp->last_key_state_changed = kp->key_state_changed; + kp->key_state_changed = 0; + kp->some_keys_pressed = 0; + } else { + key_index = out * mi->ninputs; + for (in = 0; in < mi->ninputs; in++, key_index++) { + gpio = mi->input_gpios[in]; + if (gpio_get_value(gpio) ^ !polarity) { + if (kp->some_keys_pressed < 3) + kp->some_keys_pressed++; + kp->key_state_changed |= !__test_and_set_bit( + key_index, kp->keys_pressed); + } else + kp->key_state_changed |= __test_and_clear_bit( + key_index, kp->keys_pressed); + } + gpio = mi->output_gpios[out]; + if (gpio_keypad_flags & GPIOKPF_DRIVE_INACTIVE) + gpio_set_value(gpio, !polarity); + else + gpio_direction_input(gpio); + out++; + } + kp->current_output = out; + if (out < mi->noutputs) { + gpio = mi->output_gpios[out]; + if (gpio_keypad_flags & GPIOKPF_DRIVE_INACTIVE) + gpio_set_value(gpio, polarity); + else + gpio_direction_output(gpio, polarity); + hrtimer_start(timer, mi->settle_time, HRTIMER_MODE_REL); + return HRTIMER_NORESTART; + } + if (gpio_keypad_flags & GPIOKPF_DEBOUNCE) { + if (kp->key_state_changed) { + hrtimer_start(&kp->timer, mi->debounce_delay, + HRTIMER_MODE_REL); + return HRTIMER_NORESTART; + } + kp->key_state_changed = kp->last_key_state_changed; + } + if (kp->key_state_changed) { + if (gpio_keypad_flags & GPIOKPF_REMOVE_SOME_PHANTOM_KEYS) + remove_phantom_keys(kp); + key_index = 0; + for (out = 0; out < mi->noutputs; out++) + for (in = 0; in < mi->ninputs; in++, key_index++) + report_key(kp, key_index, out, in); + } + if (!kp->use_irq || kp->some_keys_pressed) { + hrtimer_start(timer, mi->poll_time, HRTIMER_MODE_REL); + return HRTIMER_NORESTART; + } + + /* No keys are pressed, reenable interrupt */ + for (out = 0; out < mi->noutputs; out++) { + if (gpio_keypad_flags & GPIOKPF_DRIVE_INACTIVE) + gpio_set_value(mi->output_gpios[out], polarity); + else + gpio_direction_output(mi->output_gpios[out], polarity); + } + for (in = 0; in < mi->ninputs; in++) + enable_irq(gpio_to_irq(mi->input_gpios[in])); + wake_unlock(&kp->wake_lock); + return HRTIMER_NORESTART; +} + +static irqreturn_t gpio_keypad_irq_handler(int irq_in, void *dev_id) +{ + int i; + struct gpio_kp *kp = dev_id; + struct gpio_event_matrix_info *mi = kp->keypad_info; + unsigned gpio_keypad_flags = mi->flags; + + if (!kp->use_irq) { + /* ignore interrupt while registering the handler */ + kp->disabled_irq = 1; + disable_irq_nosync(irq_in); + return IRQ_HANDLED; + } + + for (i = 0; i < mi->ninputs; i++) + disable_irq_nosync(gpio_to_irq(mi->input_gpios[i])); + for (i = 0; i < mi->noutputs; i++) { + if (gpio_keypad_flags & GPIOKPF_DRIVE_INACTIVE) + gpio_set_value(mi->output_gpios[i], + !(gpio_keypad_flags & GPIOKPF_ACTIVE_HIGH)); + else + gpio_direction_input(mi->output_gpios[i]); + } + wake_lock(&kp->wake_lock); + hrtimer_start(&kp->timer, ktime_set(0, 0), HRTIMER_MODE_REL); + return IRQ_HANDLED; +} + +static int gpio_keypad_request_irqs(struct gpio_kp *kp) +{ + int i; + int err; + unsigned int irq; + unsigned long request_flags; + struct gpio_event_matrix_info *mi = kp->keypad_info; + + switch (mi->flags & (GPIOKPF_ACTIVE_HIGH|GPIOKPF_LEVEL_TRIGGERED_IRQ)) { + default: + request_flags = IRQF_TRIGGER_FALLING; + break; + case GPIOKPF_ACTIVE_HIGH: + request_flags = IRQF_TRIGGER_RISING; + break; + case GPIOKPF_LEVEL_TRIGGERED_IRQ: + request_flags = IRQF_TRIGGER_LOW; + break; + case GPIOKPF_LEVEL_TRIGGERED_IRQ | GPIOKPF_ACTIVE_HIGH: + request_flags = IRQF_TRIGGER_HIGH; + break; + } + + for (i = 0; i < mi->ninputs; i++) { + err = irq = gpio_to_irq(mi->input_gpios[i]); + if (err < 0) + goto err_gpio_get_irq_num_failed; + err = request_irq(irq, gpio_keypad_irq_handler, request_flags, + "gpio_kp", kp); + if (err) { + pr_err("gpiomatrix: request_irq failed for input %d, " + "irq %d\n", mi->input_gpios[i], irq); + goto err_request_irq_failed; + } + err = set_irq_wake(irq, 1); + if (err) { + pr_err("gpiomatrix: set_irq_wake failed for input %d, " + "irq %d\n", mi->input_gpios[i], irq); + } + disable_irq(irq); + if (kp->disabled_irq) { + kp->disabled_irq = 0; + enable_irq(irq); + } + } + return 0; + + for (i = mi->noutputs - 1; i >= 0; i--) { + free_irq(gpio_to_irq(mi->input_gpios[i]), kp); +err_request_irq_failed: +err_gpio_get_irq_num_failed: + ; + } + return err; +} + +int gpio_event_matrix_func(struct gpio_event_input_devs *input_devs, + struct gpio_event_info *info, void **data, int func) +{ + int i; + int err; + int key_count; + struct gpio_kp *kp; + struct gpio_event_matrix_info *mi; + + mi = container_of(info, struct gpio_event_matrix_info, info); + if (func == GPIO_EVENT_FUNC_SUSPEND || func == GPIO_EVENT_FUNC_RESUME) { + /* TODO: disable scanning */ + return 0; + } + + if (func == GPIO_EVENT_FUNC_INIT) { + if (mi->keymap == NULL || + mi->input_gpios == NULL || + mi->output_gpios == NULL) { + err = -ENODEV; + pr_err("gpiomatrix: Incomplete pdata\n"); + goto err_invalid_platform_data; + } + key_count = mi->ninputs * mi->noutputs; + + *data = kp = kzalloc(sizeof(*kp) + sizeof(kp->keys_pressed[0]) * + BITS_TO_LONGS(key_count), GFP_KERNEL); + if (kp == NULL) { + err = -ENOMEM; + pr_err("gpiomatrix: Failed to allocate private data\n"); + goto err_kp_alloc_failed; + } + kp->input_devs = input_devs; + kp->keypad_info = mi; + for (i = 0; i < key_count; i++) { + unsigned short keyentry = mi->keymap[i]; + unsigned short keycode = keyentry & MATRIX_KEY_MASK; + unsigned short dev = keyentry >> MATRIX_CODE_BITS; + if (dev >= input_devs->count) { + pr_err("gpiomatrix: bad device index %d >= " + "%d for key code %d\n", + dev, input_devs->count, keycode); + err = -EINVAL; + goto err_bad_keymap; + } + if (keycode && keycode <= KEY_MAX) + input_set_capability(input_devs->dev[dev], + EV_KEY, keycode); + } + + for (i = 0; i < mi->noutputs; i++) { + err = gpio_request(mi->output_gpios[i], "gpio_kp_out"); + if (err) { + pr_err("gpiomatrix: gpio_request failed for " + "output %d\n", mi->output_gpios[i]); + goto err_request_output_gpio_failed; + } + if (gpio_cansleep(mi->output_gpios[i])) { + pr_err("gpiomatrix: unsupported output gpio %d," + " can sleep\n", mi->output_gpios[i]); + err = -EINVAL; + goto err_output_gpio_configure_failed; + } + if (mi->flags & GPIOKPF_DRIVE_INACTIVE) + err = gpio_direction_output(mi->output_gpios[i], + !(mi->flags & GPIOKPF_ACTIVE_HIGH)); + else + err = gpio_direction_input(mi->output_gpios[i]); + if (err) { + pr_err("gpiomatrix: gpio_configure failed for " + "output %d\n", mi->output_gpios[i]); + goto err_output_gpio_configure_failed; + } + } + for (i = 0; i < mi->ninputs; i++) { + err = gpio_request(mi->input_gpios[i], "gpio_kp_in"); + if (err) { + pr_err("gpiomatrix: gpio_request failed for " + "input %d\n", mi->input_gpios[i]); + goto err_request_input_gpio_failed; + } + err = gpio_direction_input(mi->input_gpios[i]); + if (err) { + pr_err("gpiomatrix: gpio_direction_input failed" + " for input %d\n", mi->input_gpios[i]); + goto err_gpio_direction_input_failed; + } + } + kp->current_output = mi->noutputs; + kp->key_state_changed = 1; + + hrtimer_init(&kp->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + kp->timer.function = gpio_keypad_timer_func; + wake_lock_init(&kp->wake_lock, WAKE_LOCK_SUSPEND, "gpio_kp"); + err = gpio_keypad_request_irqs(kp); + kp->use_irq = err == 0; + + pr_info("GPIO Matrix Keypad Driver: Start keypad matrix for " + "%s%s in %s mode\n", input_devs->dev[0]->name, + (input_devs->count > 1) ? "..." : "", + kp->use_irq ? "interrupt" : "polling"); + + if (kp->use_irq) + wake_lock(&kp->wake_lock); + hrtimer_start(&kp->timer, ktime_set(0, 0), HRTIMER_MODE_REL); + + return 0; + } + + err = 0; + kp = *data; + + if (kp->use_irq) + for (i = mi->noutputs - 1; i >= 0; i--) + free_irq(gpio_to_irq(mi->input_gpios[i]), kp); + + hrtimer_cancel(&kp->timer); + wake_lock_destroy(&kp->wake_lock); + for (i = mi->noutputs - 1; i >= 0; i--) { +err_gpio_direction_input_failed: + gpio_free(mi->input_gpios[i]); +err_request_input_gpio_failed: + ; + } + for (i = mi->noutputs - 1; i >= 0; i--) { +err_output_gpio_configure_failed: + gpio_free(mi->output_gpios[i]); +err_request_output_gpio_failed: + ; + } +err_bad_keymap: + kfree(kp); +err_kp_alloc_failed: +err_invalid_platform_data: + return err; +} diff --git a/drivers/input/misc/gpio_output.c b/drivers/input/misc/gpio_output.c new file mode 100644 index 00000000000..2aac2fad0a1 --- /dev/null +++ b/drivers/input/misc/gpio_output.c @@ -0,0 +1,97 @@ +/* drivers/input/misc/gpio_output.c + * + * Copyright (C) 2007 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + +#include <linux/kernel.h> +#include <linux/gpio.h> +#include <linux/gpio_event.h> + +int gpio_event_output_event( + struct gpio_event_input_devs *input_devs, struct gpio_event_info *info, + void **data, unsigned int dev, unsigned int type, + unsigned int code, int value) +{ + int i; + struct gpio_event_output_info *oi; + oi = container_of(info, struct gpio_event_output_info, info); + if (type != oi->type) + return 0; + if (!(oi->flags & GPIOEDF_ACTIVE_HIGH)) + value = !value; + for (i = 0; i < oi->keymap_size; i++) + if (dev == oi->keymap[i].dev && code == oi->keymap[i].code) + gpio_set_value(oi->keymap[i].gpio, value); + return 0; +} + +int gpio_event_output_func( + struct gpio_event_input_devs *input_devs, struct gpio_event_info *info, + void **data, int func) +{ + int ret; + int i; + struct gpio_event_output_info *oi; + oi = container_of(info, struct gpio_event_output_info, info); + + if (func == GPIO_EVENT_FUNC_SUSPEND || func == GPIO_EVENT_FUNC_RESUME) + return 0; + + if (func == GPIO_EVENT_FUNC_INIT) { + int output_level = !(oi->flags & GPIOEDF_ACTIVE_HIGH); + + for (i = 0; i < oi->keymap_size; i++) { + int dev = oi->keymap[i].dev; + if (dev >= input_devs->count) { + pr_err("gpio_event_output_func: bad device " + "index %d >= %d for key code %d\n", + dev, input_devs->count, + oi->keymap[i].code); + ret = -EINVAL; + goto err_bad_keymap; + } + input_set_capability(input_devs->dev[dev], oi->type, + oi->keymap[i].code); + } + + for (i = 0; i < oi->keymap_size; i++) { + ret = gpio_request(oi->keymap[i].gpio, + "gpio_event_output"); + if (ret) { + pr_err("gpio_event_output_func: gpio_request " + "failed for %d\n", oi->keymap[i].gpio); + goto err_gpio_request_failed; + } + ret = gpio_direction_output(oi->keymap[i].gpio, + output_level); + if (ret) { + pr_err("gpio_event_output_func: " + "gpio_direction_output failed for %d\n", + oi->keymap[i].gpio); + goto err_gpio_direction_output_failed; + } + } + return 0; + } + + ret = 0; + for (i = oi->keymap_size - 1; i >= 0; i--) { +err_gpio_direction_output_failed: + gpio_free(oi->keymap[i].gpio); +err_gpio_request_failed: + ; + } +err_bad_keymap: + return ret; +} + diff --git a/drivers/input/misc/keychord.c b/drivers/input/misc/keychord.c new file mode 100644 index 00000000000..ca23905f304 --- /dev/null +++ b/drivers/input/misc/keychord.c @@ -0,0 +1,387 @@ +/* + * drivers/input/misc/keychord.c + * + * Copyright (C) 2008 Google, Inc. + * Author: Mike Lockwood <lockwood@android.com> + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * +*/ + +#include <linux/poll.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/smp_lock.h> +#include <linux/fs.h> +#include <linux/miscdevice.h> +#include <linux/keychord.h> +#include <linux/sched.h> + +#define KEYCHORD_NAME "keychord" +#define BUFFER_SIZE 16 + +MODULE_AUTHOR("Mike Lockwood <lockwood@android.com>"); +MODULE_DESCRIPTION("Key chord input driver"); +MODULE_SUPPORTED_DEVICE("keychord"); +MODULE_LICENSE("GPL"); + +#define NEXT_KEYCHORD(kc) ((struct input_keychord *) \ + ((char *)kc + sizeof(struct input_keychord) + \ + kc->count * sizeof(kc->keycodes[0]))) + +struct keychord_device { + struct input_handler input_handler; + int registered; + + /* list of keychords to monitor */ + struct input_keychord *keychords; + int keychord_count; + + /* bitmask of keys contained in our keychords */ + unsigned long keybit[BITS_TO_LONGS(KEY_CNT)]; + /* current state of the keys */ + unsigned long keystate[BITS_TO_LONGS(KEY_CNT)]; + /* number of keys that are currently pressed */ + int key_down; + + /* second input_device_id is needed for null termination */ + struct input_device_id device_ids[2]; + + spinlock_t lock; + wait_queue_head_t waitq; + unsigned char head; + unsigned char tail; + __u16 buff[BUFFER_SIZE]; +}; + +static int check_keychord(struct keychord_device *kdev, + struct input_keychord *keychord) +{ + int i; + + if (keychord->count != kdev->key_down) + return 0; + + for (i = 0; i < keychord->count; i++) { + if (!test_bit(keychord->keycodes[i], kdev->keystate)) + return 0; + } + + /* we have a match */ + return 1; +} + +static void keychord_event(struct input_handle *handle, unsigned int type, + unsigned int code, int value) +{ + struct keychord_device *kdev = handle->private; + struct input_keychord *keychord; + unsigned long flags; + int i, got_chord = 0; + + if (type != EV_KEY || code >= KEY_MAX) + return; + + spin_lock_irqsave(&kdev->lock, flags); + /* do nothing if key state did not change */ + if (!test_bit(code, kdev->keystate) == !value) + goto done; + __change_bit(code, kdev->keystate); + if (value) + kdev->key_down++; + else + kdev->key_down--; + + /* don't notify on key up */ + if (!value) + goto done; + /* ignore this event if it is not one of the keys we are monitoring */ + if (!test_bit(code, kdev->keybit)) + goto done; + + keychord = kdev->keychords; + if (!keychord) + goto done; + + /* check to see if the keyboard state matches any keychords */ + for (i = 0; i < kdev->keychord_count; i++) { + if (check_keychord(kdev, keychord)) { + kdev->buff[kdev->head] = keychord->id; + kdev->head = (kdev->head + 1) % BUFFER_SIZE; + got_chord = 1; + break; + } + /* skip to next keychord */ + keychord = NEXT_KEYCHORD(keychord); + } + +done: + spin_unlock_irqrestore(&kdev->lock, flags); + + if (got_chord) + wake_up_interruptible(&kdev->waitq); +} + +static int keychord_connect(struct input_handler *handler, + struct input_dev *dev, + const struct input_device_id *id) +{ + int i, ret; + struct input_handle *handle; + struct keychord_device *kdev = + container_of(handler, struct keychord_device, input_handler); + + /* + * ignore this input device if it does not contain any keycodes + * that we are monitoring + */ + for (i = 0; i < KEY_MAX; i++) { + if (test_bit(i, kdev->keybit) && test_bit(i, dev->keybit)) + break; + } + if (i == KEY_MAX) + return -ENODEV; + + handle = kzalloc(sizeof(*handle), GFP_KERNEL); + if (!handle) + return -ENOMEM; + + handle->dev = dev; + handle->handler = handler; + handle->name = KEYCHORD_NAME; + handle->private = kdev; + + ret = input_register_handle(handle); + if (ret) + goto err_input_register_handle; + + ret = input_open_device(handle); + if (ret) + goto err_input_open_device; + + pr_info("keychord: using input dev %s for fevent\n", dev->name); + + return 0; + +err_input_open_device: + input_unregister_handle(handle); +err_input_register_handle: + kfree(handle); + return ret; +} + +static void keychord_disconnect(struct input_handle *handle) +{ + input_close_device(handle); + input_unregister_handle(handle); + kfree(handle); +} + +/* + * keychord_read is used to read keychord events from the driver + */ +static ssize_t keychord_read(struct file *file, char __user *buffer, + size_t count, loff_t *ppos) +{ + struct keychord_device *kdev = file->private_data; + __u16 id; + int retval; + unsigned long flags; + + if (count < sizeof(id)) + return -EINVAL; + count = sizeof(id); + + if (kdev->head == kdev->tail && (file->f_flags & O_NONBLOCK)) + return -EAGAIN; + + retval = wait_event_interruptible(kdev->waitq, + kdev->head != kdev->tail); + if (retval) + return retval; + + spin_lock_irqsave(&kdev->lock, flags); + /* pop a keychord ID off the queue */ + id = kdev->buff[kdev->tail]; + kdev->tail = (kdev->tail + 1) % BUFFER_SIZE; + spin_unlock_irqrestore(&kdev->lock, flags); + + if (copy_to_user(buffer, &id, count)) + return -EFAULT; + + return count; +} + +/* + * keychord_write is used to configure the driver + */ +static ssize_t keychord_write(struct file *file, const char __user *buffer, + size_t count, loff_t *ppos) +{ + struct keychord_device *kdev = file->private_data; + struct input_keychord *keychords = 0; + struct input_keychord *keychord, *next, *end; + int ret, i, key; + unsigned long flags; + + if (count < sizeof(struct input_keychord)) + return -EINVAL; + keychords = kzalloc(count, GFP_KERNEL); + if (!keychords) + return -ENOMEM; + + /* read list of keychords from userspace */ + if (copy_from_user(keychords, buffer, count)) { + kfree(keychords); + return -EFAULT; + } + + /* unregister handler before changing configuration */ + if (kdev->registered) { + input_unregister_handler(&kdev->input_handler); + kdev->registered = 0; + } + + spin_lock_irqsave(&kdev->lock, flags); + /* clear any existing configuration */ + kfree(kdev->keychords); + kdev->keychords = 0; + kdev->keychord_count = 0; + kdev->key_down = 0; + memset(kdev->keybit, 0, sizeof(kdev->keybit)); + memset(kdev->keystate, 0, sizeof(kdev->keystate)); + kdev->head = kdev->tail = 0; + + keychord = keychords; + end = (struct input_keychord *)((char *)keychord + count); + + while (keychord < end) { + next = NEXT_KEYCHORD(keychord); + if (keychord->count <= 0 || next > end) { + pr_err("keychord: invalid keycode count %d\n", + keychord->count); + goto err_unlock_return; + } + if (keychord->version != KEYCHORD_VERSION) { + pr_err("keychord: unsupported version %d\n", + keychord->version); + goto err_unlock_return; + } + + /* keep track of the keys we are monitoring in keybit */ + for (i = 0; i < keychord->count; i++) { + key = keychord->keycodes[i]; + if (key < 0 || key >= KEY_CNT) { + pr_err("keychord: keycode %d out of range\n", + key); + goto err_unlock_return; + } + __set_bit(key, kdev->keybit); + } + + kdev->keychord_count++; + keychord = next; + } + + kdev->keychords = keychords; + spin_unlock_irqrestore(&kdev->lock, flags); + + ret = input_register_handler(&kdev->input_handler); + if (ret) { + kfree(keychords); + kdev->keychords = 0; + return ret; + } + kdev->registered = 1; + + return count; + +err_unlock_return: + spin_unlock_irqrestore(&kdev->lock, flags); + kfree(keychords); + return -EINVAL; +} + +static unsigned int keychord_poll(struct file *file, poll_table *wait) +{ + struct keychord_device *kdev = file->private_data; + + poll_wait(file, &kdev->waitq, wait); + + if (kdev->head != kdev->tail) + return POLLIN | POLLRDNORM; + + return 0; +} + +static int keychord_open(struct inode *inode, struct file *file) +{ + struct keychord_device *kdev; + + kdev = kzalloc(sizeof(struct keychord_device), GFP_KERNEL); + if (!kdev) + return -ENOMEM; + + spin_lock_init(&kdev->lock); + init_waitqueue_head(&kdev->waitq); + + kdev->input_handler.event = keychord_event; + kdev->input_handler.connect = keychord_connect; + kdev->input_handler.disconnect = keychord_disconnect; + kdev->input_handler.name = KEYCHORD_NAME; + kdev->input_handler.id_table = kdev->device_ids; + + kdev->device_ids[0].flags = INPUT_DEVICE_ID_MATCH_EVBIT; + __set_bit(EV_KEY, kdev->device_ids[0].evbit); + + file->private_data = kdev; + + return 0; +} + +static int keychord_release(struct inode *inode, struct file *file) +{ + struct keychord_device *kdev = file->private_data; + + if (kdev->registered) + input_unregister_handler(&kdev->input_handler); + kfree(kdev); + + return 0; +} + +static const struct file_operations keychord_fops = { + .owner = THIS_MODULE, + .open = keychord_open, + .release = keychord_release, + .read = keychord_read, + .write = keychord_write, + .poll = keychord_poll, +}; + +static struct miscdevice keychord_misc = { + .fops = &keychord_fops, + .name = KEYCHORD_NAME, + .minor = MISC_DYNAMIC_MINOR, +}; + +static int __init keychord_init(void) +{ + return misc_register(&keychord_misc); +} + +static void __exit keychord_exit(void) +{ + misc_deregister(&keychord_misc); +} + +module_init(keychord_init); +module_exit(keychord_exit); diff --git a/drivers/input/misc/lps001wp_prs.c b/drivers/input/misc/lps001wp_prs.c new file mode 100644 index 00000000000..9ec96ba3863 --- /dev/null +++ b/drivers/input/misc/lps001wp_prs.c @@ -0,0 +1,1276 @@ + +/******************** (C) COPYRIGHT 2010 STMicroelectronics ******************** +* +* File Name : lps001wp_prs.c +* Authors : MSH - Motion Mems BU - Application Team +* : Matteo Dameno (matteo.dameno@st.com) +* : Carmine Iascone (carmine.iascone@st.com) +* : Both authors are willing to be considered the contact +* : and update points for the driver. +* Version : V 1.1.1 +* Date : 2010/11/22 +* Description : LPS001WP pressure temperature sensor driver +* +******************************************************************************** +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License version 2 as +* published by the Free Software Foundation. +* +* THE PRESENT SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES +* OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, FOR THE SOLE +* PURPOSE TO SUPPORT YOUR APPLICATION DEVELOPMENT. +* AS A RESULT, STMICROELECTRONICS SHALL NOT BE HELD LIABLE FOR ANY DIRECT, +* INDIRECT OR CONSEQUENTIAL DAMAGES WITH RESPECT TO ANY CLAIMS ARISING FROM THE +* CONTENT OF SUCH SOFTWARE AND/OR THE USE MADE BY CUSTOMERS OF THE CODING +* INFORMATION CONTAINED HEREIN IN CONNECTION WITH THEIR PRODUCTS. +* +****************************************************************************** + + Revision 0.9.0 01/10/2010: + first beta release + Revision 1.1.0 05/11/2010: + add sysfs management + Revision 1.1.1 22/11/2010: + moved to input/misc +******************************************************************************/ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/err.h> +#include <linux/errno.h> +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/input.h> +#include <linux/workqueue.h> +#include <linux/device.h> + +#include <linux/input/lps001wp.h> + + + +#define DEBUG 1 + + +#define PR_ABS_MAX 0xffff +#define PR_ABS_MIN 0x0000 +#define PR_DLT_MAX 0x7ffff +#define PR_DLT_MIN -0x80000 /* 16-bit signed value */ +#define TEMP_MAX 0x7fff +#define TEMP_MIN -0x80000 /* 16-bit signed value */ + + +#define SENSITIVITY_T_SHIFT 6 /** = 64 LSB/degrC */ +#define SENSITIVITY_P_SHIFT 4 /** = 16 LSB/mbar */ + + +#define OUTDATA_REG 0x28 +#define INDATA_REG 0X30 + +#define WHOAMI_LPS001WP_PRS 0xBA /* Expctd content for WAI */ + +/* CONTROL REGISTERS */ +#define WHO_AM_I 0x0F /* WhoAmI register */ +#define CTRL_REG1 0x20 /* power / ODR control reg */ +#define CTRL_REG2 0x21 /* boot reg */ +#define CTRL_REG3 0x22 /* interrupt control reg */ + +#define STATUS_REG 0X27 /* status reg */ + +#define PRESS_OUT_L OUTDATA_REG + + +#define REF_P_L INDATA_REG /* pressure reference */ +#define REF_P_H 0x31 /* pressure reference */ +#define THS_P_L 0x32 /* pressure threshold */ +#define THS_P_H 0x33 /* pressure threshold */ + +#define INT_CFG 0x34 /* interrupt config */ +#define INT_SRC 0x35 /* interrupt source */ +#define INT_ACK 0x36 /* interrupt acknoledge */ +/* end CONTROL REGISTRES */ + + +/* Barometer and Termometer output data rate ODR */ +#define LPS001WP_PRS_ODR_MASK 0x30 /* Mask to access odr bits only */ +#define LPS001WP_PRS_ODR_7_1 0x00 /* 7Hz baro and 1Hz term ODR */ +#define LPS001WP_PRS_ODR_7_7 0x01 /* 7Hz baro and 7Hz term ODR */ +#define LPS001WP_PRS_ODR_12_12 0x11 /* 12.5Hz baro and 12.5Hz term ODR */ + + +#define LPS001WP_PRS_ENABLE_MASK 0x40 /* */ +#define LPS001WP_PRS_DIFF_MASK 0x08 +#define LPS001WP_PRS_LPOW_MASK 0x80 + +#define LPS001WP_PRS_DIFF_ON 0x08 +#define LPS001WP_PRS_DIFF_OFF 0x00 + +#define LPS001WP_PRS_LPOW_ON 0x80 +#define LPS001WP_PRS_LPOW_OFF 0x00 + +#define FUZZ 0 +#define FLAT 0 +#define I2C_RETRY_DELAY 5 +#define I2C_RETRIES 5 +#define I2C_AUTO_INCREMENT 0x80 + +/* RESUME STATE INDICES */ +#define RES_CTRL_REG1 0 +#define RES_CTRL_REG2 1 +#define RES_CTRL_REG3 2 +#define RES_REF_P_L 3 +#define RES_REF_P_H 4 +#define RES_THS_P_L 5 +#define RES_THS_P_H 6 +#define RES_INT_CFG 7 + +#define RESUME_ENTRIES 8 +/* end RESUME STATE INDICES */ + +/* Pressure Sensor Operating Mode */ +#define LPS001WP_PRS_DIFF_ENABLE 1 +#define LPS001WP_PRS_DIFF_DISABLE 0 +#define LPS001WP_PRS_LPOWER_EN 1 +#define LPS001WP_PRS_LPOWER_DIS 0 + +static const struct { + unsigned int cutoff_ms; + unsigned int mask; +} lps001wp_prs_odr_table[] = { + {80, LPS001WP_PRS_ODR_12_12 }, + {143, LPS001WP_PRS_ODR_7_7 }, + {1000, LPS001WP_PRS_ODR_7_1 }, +}; + +struct lps001wp_prs_data { + struct i2c_client *client; + struct lps001wp_prs_platform_data *pdata; + + struct mutex lock; + struct delayed_work input_work; + + struct input_dev *input_dev; + + int hw_initialized; + /* hw_working=-1 means not tested yet */ + int hw_working; + u8 diff_enabled; + u8 lpowmode_enabled ; + + atomic_t enabled; + int on_before_suspend; + + u8 resume_state[RESUME_ENTRIES]; + +#ifdef DEBUG + u8 reg_addr; +#endif +}; + +struct outputdata { + u16 abspress; + s16 temperature; + s16 deltapress; +}; + + +static int lps001wp_prs_i2c_read(struct lps001wp_prs_data *prs, + u8 *buf, int len) +{ + int err; + int tries = 0; + + struct i2c_msg msgs[] = { + { + .addr = prs->client->addr, + .flags = prs->client->flags & I2C_M_TEN, + .len = 1, + .buf = buf, + }, + { + .addr = prs->client->addr, + .flags = (prs->client->flags & I2C_M_TEN) | I2C_M_RD, + .len = len, + .buf = buf, + }, + }; + + do { + err = i2c_transfer(prs->client->adapter, msgs, 2); + if (err != 2) + msleep_interruptible(I2C_RETRY_DELAY); + } while ((err != 2) && (++tries < I2C_RETRIES)); + + if (err != 2) { + dev_err(&prs->client->dev, "read transfer error\n"); + err = -EIO; + } else { + err = 0; + } + + return err; +} + +static int lps001wp_prs_i2c_write(struct lps001wp_prs_data *prs, + u8 *buf, int len) +{ + int err; + int tries = 0; + struct i2c_msg msgs[] = { + { + .addr = prs->client->addr, + .flags = prs->client->flags & I2C_M_TEN, + .len = len + 1, + .buf = buf, + }, + }; + + do { + err = i2c_transfer(prs->client->adapter, msgs, 1); + if (err != 1) + msleep_interruptible(I2C_RETRY_DELAY); + } while ((err != 1) && (++tries < I2C_RETRIES)); + + if (err != 1) { + dev_err(&prs->client->dev, "write transfer error\n"); + err = -EIO; + } else { + err = 0; + } + + return err; +} + +static int lps001wp_prs_i2c_update(struct lps001wp_prs_data *prs, + u8 reg_address, u8 mask, u8 new_bit_values) +{ + int err = -1; + u8 rdbuf[1] = { reg_address }; + u8 wrbuf[2] = { reg_address , 0x00 }; + + u8 init_val; + u8 updated_val; + err = lps001wp_prs_i2c_read(prs, rdbuf, 1); + if (!(err < 0)) { + init_val = rdbuf[0]; + updated_val = ((mask & new_bit_values) | ((~mask) & init_val)); + wrbuf[1] = updated_val; + err = lps001wp_prs_i2c_write(prs, wrbuf, 2); + } + return err; +} +/* */ + +static int lps001wp_prs_register_write(struct lps001wp_prs_data *prs, u8 *buf, + u8 reg_address, u8 new_value) +{ + int err = -1; + + /* Sets configuration register at reg_address + * NOTE: this is a straight overwrite */ + buf[0] = reg_address; + buf[1] = new_value; + err = lps001wp_prs_i2c_write(prs, buf, 1); + if (err < 0) + return err; + return err; +} + +static int lps001wp_prs_register_read(struct lps001wp_prs_data *prs, u8 *buf, + u8 reg_address) +{ + + int err = -1; + buf[0] = (reg_address); + err = lps001wp_prs_i2c_read(prs, buf, 1); + + return err; +} + +static int lps001wp_prs_register_update(struct lps001wp_prs_data *prs, u8 *buf, + u8 reg_address, u8 mask, u8 new_bit_values) +{ + int err = -1; + u8 init_val; + u8 updated_val; + err = lps001wp_prs_register_read(prs, buf, reg_address); + if (!(err < 0)) { + init_val = buf[0]; + updated_val = ((mask & new_bit_values) | ((~mask) & init_val)); + err = lps001wp_prs_register_write(prs, buf, reg_address, + updated_val); + } + return err; +} + +/* */ + + +static int lps001wp_prs_hw_init(struct lps001wp_prs_data *prs) +{ + int err = -1; + u8 buf[6]; + + printk(KERN_DEBUG "%s: hw init start\n", LPS001WP_PRS_DEV_NAME); + + buf[0] = WHO_AM_I; + err = lps001wp_prs_i2c_read(prs, buf, 1); + if (err < 0) + goto error_firstread; + else + prs->hw_working = 1; + if (buf[0] != WHOAMI_LPS001WP_PRS) { + err = -1; /* TODO:choose the right coded error */ + goto error_unknown_device; + } + + + buf[0] = (I2C_AUTO_INCREMENT | INDATA_REG); + buf[1] = prs->resume_state[RES_REF_P_L]; + buf[2] = prs->resume_state[RES_REF_P_H]; + buf[3] = prs->resume_state[RES_THS_P_L]; + buf[4] = prs->resume_state[RES_THS_P_H]; + err = lps001wp_prs_i2c_write(prs, buf, 4); + if (err < 0) + goto error1; + + buf[0] = (I2C_AUTO_INCREMENT | CTRL_REG1); + buf[1] = prs->resume_state[RES_CTRL_REG1]; + buf[2] = prs->resume_state[RES_CTRL_REG2]; + buf[3] = prs->resume_state[RES_CTRL_REG3]; + err = lps001wp_prs_i2c_write(prs, buf, 3); + if (err < 0) + goto error1; + + buf[0] = INT_CFG; + buf[1] = prs->resume_state[RES_INT_CFG]; + err = lps001wp_prs_i2c_write(prs, buf, 1); + if (err < 0) + goto error1; + + + prs->hw_initialized = 1; + printk(KERN_DEBUG "%s: hw init done\n", LPS001WP_PRS_DEV_NAME); + return 0; + +error_firstread: + prs->hw_working = 0; + dev_warn(&prs->client->dev, "Error reading WHO_AM_I: is device " + "available/working?\n"); + goto error1; +error_unknown_device: + dev_err(&prs->client->dev, + "device unknown. Expected: 0x%x," + " Replies: 0x%x\n", WHOAMI_LPS001WP_PRS, buf[0]); +error1: + prs->hw_initialized = 0; + dev_err(&prs->client->dev, "hw init error 0x%x,0x%x: %d\n", buf[0], + buf[1], err); + return err; +} + +static void lps001wp_prs_device_power_off(struct lps001wp_prs_data *prs) +{ + int err; + u8 buf[2] = { CTRL_REG1, LPS001WP_PRS_PM_OFF }; + + err = lps001wp_prs_i2c_write(prs, buf, 1); + if (err < 0) + dev_err(&prs->client->dev, "soft power off failed: %d\n", err); + + if (prs->pdata->power_off) { + prs->pdata->power_off(); + prs->hw_initialized = 0; + } + if (prs->hw_initialized) { + prs->hw_initialized = 0; + } + +} + +static int lps001wp_prs_device_power_on(struct lps001wp_prs_data *prs) +{ + int err = -1; + + if (prs->pdata->power_on) { + err = prs->pdata->power_on(); + if (err < 0) { + dev_err(&prs->client->dev, + "power_on failed: %d\n", err); + return err; + } + } + + if (!prs->hw_initialized) { + err = lps001wp_prs_hw_init(prs); + if (prs->hw_working == 1 && err < 0) { + lps001wp_prs_device_power_off(prs); + return err; + } + } + + return 0; +} + + + +int lps001wp_prs_update_odr(struct lps001wp_prs_data *prs, int poll_interval_ms) +{ + int err = -1; + int i; + + u8 buf[2]; + u8 updated_val; + u8 init_val; + u8 new_val; + u8 mask = LPS001WP_PRS_ODR_MASK; + + /* Following, looks for the longest possible odr interval scrolling the + * odr_table vector from the end (shortest interval) backward (longest + * interval), to support the poll_interval requested by the system. + * It must be the longest interval lower then the poll interval.*/ + for (i = ARRAY_SIZE(lps001wp_prs_odr_table) - 1; i >= 0; i--) { + if (lps001wp_prs_odr_table[i].cutoff_ms <= poll_interval_ms) + break; + } + + new_val = lps001wp_prs_odr_table[i].mask; + + /* If device is currently enabled, we need to write new + * configuration out to it */ + if (atomic_read(&prs->enabled)) { + buf[0] = CTRL_REG1; + err = lps001wp_prs_i2c_read(prs, buf, 1); + if (err < 0) + goto error; + init_val = buf[0]; + prs->resume_state[RES_CTRL_REG1] = init_val; + + buf[0] = CTRL_REG1; + updated_val = ((mask & new_val) | ((~mask) & init_val)); + buf[1] = updated_val; + buf[0] = CTRL_REG1; + err = lps001wp_prs_i2c_write(prs, buf, 1); + if (err < 0) + goto error; + prs->resume_state[RES_CTRL_REG1] = updated_val; + } + return err; + +error: + dev_err(&prs->client->dev, "update odr failed 0x%x,0x%x: %d\n", + buf[0], buf[1], err); + + return err; +} + +static int lps001wp_prs_set_press_reference(struct lps001wp_prs_data *prs, + u16 new_reference) +{ + int err = -1; + u8 const reg_addressL = REF_P_L; + u8 const reg_addressH = REF_P_H; + u8 bit_valuesL, bit_valuesH; + u8 buf[2]; + + bit_valuesL = (u8) (new_reference & 0x00FF); + bit_valuesH = (u8)((new_reference & 0xFF00) >> 8); + + err = lps001wp_prs_register_write(prs, buf, reg_addressL, + bit_valuesL); + if (err < 0) + return err; + err = lps001wp_prs_register_write(prs, buf, reg_addressH, + bit_valuesH); + if (err < 0) { + lps001wp_prs_register_write(prs, buf, reg_addressL, + prs->resume_state[RES_REF_P_L]); + return err; + } + prs->resume_state[RES_REF_P_L] = bit_valuesL; + prs->resume_state[RES_REF_P_H] = bit_valuesH; + return err; +} + +static int lps001wp_prs_get_press_reference(struct lps001wp_prs_data *prs, + u16 *buf16) +{ + int err = -1; + + u8 bit_valuesL, bit_valuesH; + u8 buf[2] = {0}; + u16 temp = 0; + + err = lps001wp_prs_register_read(prs, buf, REF_P_L); + if (err < 0) + return err; + bit_valuesL = buf[0]; + err = lps001wp_prs_register_read(prs, buf, REF_P_H); + if (err < 0) + return err; + bit_valuesH = buf[0]; + + temp = (((u16) bit_valuesH) << 8); + *buf16 = (temp | ((u16) bit_valuesL)); + + return err; +} + +static int lps001wp_prs_lpow_manage(struct lps001wp_prs_data *prs, u8 control) +{ + int err = -1; + u8 buf[2] = {0x00, 0x00}; + u8 const mask = LPS001WP_PRS_LPOW_MASK; + u8 bit_values = LPS001WP_PRS_LPOW_OFF; + + if (control >= LPS001WP_PRS_LPOWER_EN) { + bit_values = LPS001WP_PRS_LPOW_ON; + } + + err = lps001wp_prs_register_update(prs, buf, CTRL_REG1, + mask, bit_values); + + if (err < 0) + return err; + prs->resume_state[RES_CTRL_REG1] = ((mask & bit_values) | + (~mask & prs->resume_state[RES_CTRL_REG1])); + if (bit_values == LPS001WP_PRS_LPOW_ON) + prs->lpowmode_enabled = 1; + else + prs->lpowmode_enabled = 0; + return err; +} + +static int lps001wp_prs_diffen_manage(struct lps001wp_prs_data *prs, u8 control) +{ + int err = -1; + u8 buf[2] = {0x00, 0x00}; + u8 const mask = LPS001WP_PRS_DIFF_MASK; + u8 bit_values = LPS001WP_PRS_DIFF_OFF; + + if (control >= LPS001WP_PRS_DIFF_ENABLE) { + bit_values = LPS001WP_PRS_DIFF_ON; + } + + err = lps001wp_prs_register_update(prs, buf, CTRL_REG1, + mask, bit_values); + + if (err < 0) + return err; + prs->resume_state[RES_CTRL_REG1] = ((mask & bit_values) | + (~mask & prs->resume_state[RES_CTRL_REG1])); + if (bit_values == LPS001WP_PRS_DIFF_ON) + prs->diff_enabled = 1; + else + prs->diff_enabled = 0; + return err; +} + + +static int lps001wp_prs_get_presstemp_data(struct lps001wp_prs_data *prs, + struct outputdata *out) +{ + int err = -1; + /* Data bytes from hardware PRESS_OUT_L, PRESS_OUT_H, + * TEMP_OUT_L, TEMP_OUT_H, + * DELTA_L, DELTA_H */ + u8 prs_data[6]; + + u16 abspr; + s16 temperature, deltapr; + int regToRead = 4; + prs_data[4] = 0; + prs_data[5] = 0; + + if (prs->diff_enabled) + regToRead = 6; + + prs_data[0] = (I2C_AUTO_INCREMENT | OUTDATA_REG); + err = lps001wp_prs_i2c_read(prs, prs_data, regToRead); + if (err < 0) + return err; + + abspr = ((((u16) prs_data[1] << 8) | ((u16) prs_data[0]))); + temperature = ((s16) (((u16) prs_data[3] << 8) | ((u16)prs_data[2]))); + + out->abspress = abspr; + out->temperature = temperature; + + deltapr = ((s16) (((u16) prs_data[5] << 8) | ((u16)prs_data[4]))); + out->deltapress = deltapr; + + return err; +} + +static void lps001wp_prs_report_values(struct lps001wp_prs_data *prs, + struct outputdata *out) +{ + input_report_abs(prs->input_dev, ABS_PR, out->abspress); + input_report_abs(prs->input_dev, ABS_TEMP, out->temperature); + input_report_abs(prs->input_dev, ABS_DLTPR, out->deltapress); + input_sync(prs->input_dev); +} + +static int lps001wp_prs_enable(struct lps001wp_prs_data *prs) +{ + int err; + + if (!atomic_cmpxchg(&prs->enabled, 0, 1)) { + err = lps001wp_prs_device_power_on(prs); + if (err < 0) { + atomic_set(&prs->enabled, 0); + return err; + } + schedule_delayed_work(&prs->input_work, + msecs_to_jiffies(prs->pdata->poll_interval)); + } + + return 0; +} + +static int lps001wp_prs_disable(struct lps001wp_prs_data *prs) +{ + if (atomic_cmpxchg(&prs->enabled, 1, 0)) { + cancel_delayed_work_sync(&prs->input_work); + lps001wp_prs_device_power_off(prs); + } + + return 0; +} + +static ssize_t read_single_reg(struct device *dev, char *buf, u8 reg) +{ + ssize_t ret; + struct lps001wp_prs_data *prs = dev_get_drvdata(dev); + int rc = 0; + + u8 data = reg; + rc = lps001wp_prs_i2c_read(prs, &data, 1); + /*TODO: error need to be managed */ + ret = sprintf(buf, "0x%02x\n", data); + return ret; + +} + +static int write_reg(struct device *dev, const char *buf, u8 reg) +{ + int rc = 0; + struct lps001wp_prs_data *prs = dev_get_drvdata(dev); + u8 x[2]; + unsigned long val; + + if (strict_strtoul(buf, 16, &val)) + return -EINVAL; + + x[0] = reg; + x[1] = val; + rc = lps001wp_prs_i2c_write(prs, x, 1); + /*TODO: error need to be managed */ + return rc; +} + +static ssize_t attr_get_polling_rate(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int val; + struct lps001wp_prs_data *prs = dev_get_drvdata(dev); + mutex_lock(&prs->lock); + val = prs->pdata->poll_interval; + mutex_unlock(&prs->lock); + return sprintf(buf, "%d\n", val); +} + +static ssize_t attr_set_polling_rate(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct lps001wp_prs_data *prs = dev_get_drvdata(dev); + unsigned long interval_ms; + + if (strict_strtoul(buf, 10, &interval_ms)) + return -EINVAL; + if (!interval_ms) + return -EINVAL; + mutex_lock(&prs->lock); + prs->pdata->poll_interval = interval_ms; + lps001wp_prs_update_odr(prs, interval_ms); + mutex_unlock(&prs->lock); + return size; +} + +static ssize_t attr_get_diff_enable(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + u8 val; + struct lps001wp_prs_data *prs = dev_get_drvdata(dev); + mutex_lock(&prs->lock); + val = prs->diff_enabled; + mutex_unlock(&prs->lock); + return sprintf(buf, "%d\n", val); +} + +static ssize_t attr_set_diff_enable(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct lps001wp_prs_data *prs = dev_get_drvdata(dev); + unsigned long val; + + if (strict_strtoul(buf, 10, &val)) + return -EINVAL; + + mutex_lock(&prs->lock); + lps001wp_prs_diffen_manage(prs, (u8) val); + mutex_unlock(&prs->lock); + return size; +} + +static ssize_t attr_get_enable(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct lps001wp_prs_data *prs = dev_get_drvdata(dev); + int val = atomic_read(&prs->enabled); + return sprintf(buf, "%d\n", val); +} + +static ssize_t attr_set_enable(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct lps001wp_prs_data *prs = dev_get_drvdata(dev); + unsigned long val; + + if (strict_strtoul(buf, 10, &val)) + return -EINVAL; + + if (val) + lps001wp_prs_enable(prs); + else + lps001wp_prs_disable(prs); + + return size; +} + +static ssize_t attr_get_press_ref(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int err = -1; + struct lps001wp_prs_data *prs = dev_get_drvdata(dev); + u16 val = 0; + + mutex_lock(&prs->lock); + err = lps001wp_prs_get_press_reference(prs, &val); + mutex_unlock(&prs->lock); + if (err < 0) + return err; + + return sprintf(buf, "%d\n", val); +} + +static ssize_t attr_set_press_ref(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + int err = -1; + struct lps001wp_prs_data *prs = dev_get_drvdata(dev); + unsigned long val = 0; + + if (strict_strtoul(buf, 10, &val)) + return -EINVAL; + + if (val < PR_ABS_MIN || val > PR_ABS_MAX) + return -EINVAL; + + mutex_lock(&prs->lock); + err = lps001wp_prs_set_press_reference(prs, val); + mutex_unlock(&prs->lock); + if (err < 0) + return err; + return size; +} + + +static ssize_t attr_get_lowpowmode(struct device *dev, + struct device_attribute *attr, char *buf) +{ + u8 val; + struct lps001wp_prs_data *prs = dev_get_drvdata(dev); + mutex_lock(&prs->lock); + val = prs->lpowmode_enabled; + mutex_unlock(&prs->lock); + return sprintf(buf, "%d\n", val); +} + +static ssize_t attr_set_lowpowmode(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + int err = -1; + struct lps001wp_prs_data *prs = dev_get_drvdata(dev); + unsigned long val; + + if (strict_strtoul(buf, 10, &val)) + return -EINVAL; + + mutex_lock(&prs->lock); + err = lps001wp_prs_lpow_manage(prs, (u8) val); + mutex_unlock(&prs->lock); + if (err < 0) + return err; + return size; +} + + +#ifdef DEBUG +static ssize_t attr_reg_set(struct device *dev, struct device_attribute *attr, + const char *buf, size_t size) +{ + int rc; + struct lps001wp_prs_data *prs = dev_get_drvdata(dev); + u8 x[2]; + unsigned long val; + + if (strict_strtoul(buf, 16, &val)) + return -EINVAL; + mutex_lock(&prs->lock); + x[0] = prs->reg_addr; + mutex_unlock(&prs->lock); + x[1] = val; + rc = lps001wp_prs_i2c_write(prs, x, 1); + /*TODO: error need to be managed */ + return size; +} + +static ssize_t attr_reg_get(struct device *dev, struct device_attribute *attr, + char *buf) +{ + ssize_t ret; + struct lps001wp_prs_data *prs = dev_get_drvdata(dev); + int rc; + u8 data; + + mutex_lock(&prs->lock); + data = prs->reg_addr; + mutex_unlock(&prs->lock); + rc = lps001wp_prs_i2c_read(prs, &data, 1); + /*TODO: error need to be managed */ + ret = sprintf(buf, "0x%02x\n", data); + return ret; +} + +static ssize_t attr_addr_set(struct device *dev, struct device_attribute *attr, + const char *buf, size_t size) +{ + struct lps001wp_prs_data *prs = dev_get_drvdata(dev); + unsigned long val; + if (strict_strtoul(buf, 16, &val)) + return -EINVAL; + mutex_lock(&prs->lock); + prs->reg_addr = val; + mutex_unlock(&prs->lock); + return size; +} +#endif + + + +static struct device_attribute attributes[] = { + __ATTR(pollrate_ms, 0664, attr_get_polling_rate, attr_set_polling_rate), + __ATTR(enable, 0664, attr_get_enable, attr_set_enable), + __ATTR(diff_enable, 0664, attr_get_diff_enable, attr_set_diff_enable), + __ATTR(press_reference, 0664, attr_get_press_ref, attr_set_press_ref), + __ATTR(lowpow_enable, 0664, attr_get_lowpowmode, attr_set_lowpowmode), +#ifdef DEBUG + __ATTR(reg_value, 0664, attr_reg_get, attr_reg_set), + __ATTR(reg_addr, 0220, NULL, attr_addr_set), +#endif +}; + +static int create_sysfs_interfaces(struct device *dev) +{ + int i; + for (i = 0; i < ARRAY_SIZE(attributes); i++) + if (device_create_file(dev, attributes + i)) + goto error; + return 0; + +error: + for ( ; i >= 0; i--) + device_remove_file(dev, attributes + i); + dev_err(dev, "%s:Unable to create interface\n", __func__); + return -1; +} + +static int remove_sysfs_interfaces(struct device *dev) +{ + int i; + for (i = 0; i < ARRAY_SIZE(attributes); i++) + device_remove_file(dev, attributes + i); + return 0; +} + + +static void lps001wp_prs_input_work_func(struct work_struct *work) +{ + struct lps001wp_prs_data *prs; + + struct outputdata output; + struct outputdata *out = &output; + int err; + + prs = container_of((struct delayed_work *)work, + struct lps001wp_prs_data, input_work); + + mutex_lock(&prs->lock); + err = lps001wp_prs_get_presstemp_data(prs, out); + if (err < 0) + dev_err(&prs->client->dev, "get_pressure_data failed\n"); + else + lps001wp_prs_report_values(prs, out); + + schedule_delayed_work(&prs->input_work, + msecs_to_jiffies(prs->pdata->poll_interval)); + mutex_unlock(&prs->lock); +} + +int lps001wp_prs_input_open(struct input_dev *input) +{ + struct lps001wp_prs_data *prs = input_get_drvdata(input); + + return lps001wp_prs_enable(prs); +} + +void lps001wp_prs_input_close(struct input_dev *dev) +{ + struct lps001wp_prs_data *prs = input_get_drvdata(dev); + + lps001wp_prs_disable(prs); +} + + +static int lps001wp_prs_validate_pdata(struct lps001wp_prs_data *prs) +{ + prs->pdata->poll_interval = max(prs->pdata->poll_interval, + prs->pdata->min_interval); + + /* Enforce minimum polling interval */ + if (prs->pdata->poll_interval < prs->pdata->min_interval) { + dev_err(&prs->client->dev, "minimum poll interval violated\n"); + return -EINVAL; + } + + return 0; +} + +static int lps001wp_prs_input_init(struct lps001wp_prs_data *prs) +{ + int err; + + INIT_DELAYED_WORK(&prs->input_work, lps001wp_prs_input_work_func); + prs->input_dev = input_allocate_device(); + if (!prs->input_dev) { + err = -ENOMEM; + dev_err(&prs->client->dev, "input device allocate failed\n"); + goto err0; + } + + prs->input_dev->open = lps001wp_prs_input_open; + prs->input_dev->close = lps001wp_prs_input_close; + prs->input_dev->name = LPS001WP_PRS_DEV_NAME; + prs->input_dev->id.bustype = BUS_I2C; + prs->input_dev->dev.parent = &prs->client->dev; + + input_set_drvdata(prs->input_dev, prs); + + set_bit(EV_ABS, prs->input_dev->evbit); + + input_set_abs_params(prs->input_dev, ABS_PR, + PR_ABS_MIN, PR_ABS_MAX, FUZZ, FLAT); + input_set_abs_params(prs->input_dev, ABS_TEMP, + PR_DLT_MIN, PR_DLT_MAX, FUZZ, FLAT); + input_set_abs_params(prs->input_dev, ABS_DLTPR, + TEMP_MIN, TEMP_MAX, FUZZ, FLAT); + + + prs->input_dev->name = "LPS001WP barometer"; + + err = input_register_device(prs->input_dev); + if (err) { + dev_err(&prs->client->dev, + "unable to register input polled device %s\n", + prs->input_dev->name); + goto err1; + } + + return 0; + +err1: + input_free_device(prs->input_dev); +err0: + return err; +} + +static void lps001wp_prs_input_cleanup(struct lps001wp_prs_data *prs) +{ + input_unregister_device(prs->input_dev); + input_free_device(prs->input_dev); +} + +static int lps001wp_prs_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct lps001wp_prs_data *prs; + int err = -1; + int tempvalue; + + pr_info("%s: probe start.\n", LPS001WP_PRS_DEV_NAME); + + if (client->dev.platform_data == NULL) { + dev_err(&client->dev, "platform data is NULL. exiting.\n"); + err = -ENODEV; + goto exit_check_functionality_failed; + } + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + dev_err(&client->dev, "client not i2c capable\n"); + err = -ENODEV; + goto exit_check_functionality_failed; + } + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_BYTE | + I2C_FUNC_SMBUS_BYTE_DATA | + I2C_FUNC_SMBUS_WORD_DATA)) { + dev_err(&client->dev, "client not smb-i2c capable:2\n"); + err = -EIO; + goto exit_check_functionality_failed; + } + + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_I2C_BLOCK)){ + dev_err(&client->dev, "client not smb-i2c capable:3\n"); + err = -EIO; + goto exit_check_functionality_failed; + } + + + prs = kzalloc(sizeof(struct lps001wp_prs_data), GFP_KERNEL); + if (prs == NULL) { + err = -ENOMEM; + dev_err(&client->dev, + "failed to allocate memory for module data: " + "%d\n", err); + goto exit_alloc_data_failed; + } + + mutex_init(&prs->lock); + mutex_lock(&prs->lock); + + prs->client = client; + i2c_set_clientdata(client, prs); + + + if (i2c_smbus_read_byte(client) < 0) { + printk(KERN_ERR "i2c_smbus_read_byte error!!\n"); + goto err_mutexunlockfreedata; + } else { + printk(KERN_DEBUG "%s Device detected!\n", + LPS001WP_PRS_DEV_NAME); + } + + /* read chip id */ + tempvalue = i2c_smbus_read_word_data(client, WHO_AM_I); + if ((tempvalue & 0x00FF) == WHOAMI_LPS001WP_PRS) { + printk(KERN_DEBUG "%s I2C driver registered!\n", + LPS001WP_PRS_DEV_NAME); + } else { + prs->client = NULL; + printk(KERN_DEBUG "I2C driver not registered!" + " Device unknown\n"); + goto err_mutexunlockfreedata; + } + + prs->pdata = kmalloc(sizeof(*prs->pdata), GFP_KERNEL); + if (prs->pdata == NULL) { + err = -ENOMEM; + dev_err(&client->dev, + "failed to allocate memory for pdata: %d\n", + err); + goto err_mutexunlockfreedata; + } + + memcpy(prs->pdata, client->dev.platform_data, sizeof(*prs->pdata)); + + err = lps001wp_prs_validate_pdata(prs); + if (err < 0) { + dev_err(&client->dev, "failed to validate platform data\n"); + goto exit_kfree_pdata; + } + + i2c_set_clientdata(client, prs); + + + if (prs->pdata->init) { + err = prs->pdata->init(); + if (err < 0) { + dev_err(&client->dev, "init failed: %d\n", err); + goto err2; + } + } + + memset(prs->resume_state, 0, ARRAY_SIZE(prs->resume_state)); + + prs->resume_state[RES_CTRL_REG1] = LPS001WP_PRS_PM_NORMAL; + prs->resume_state[RES_CTRL_REG2] = 0x00; + prs->resume_state[RES_CTRL_REG3] = 0x00; + prs->resume_state[RES_REF_P_L] = 0x00; + prs->resume_state[RES_REF_P_H] = 0x00; + prs->resume_state[RES_THS_P_L] = 0x00; + prs->resume_state[RES_THS_P_H] = 0x00; + prs->resume_state[RES_INT_CFG] = 0x00; + + err = lps001wp_prs_device_power_on(prs); + if (err < 0) { + dev_err(&client->dev, "power on failed: %d\n", err); + goto err2; + } + + prs->diff_enabled = 0; + prs->lpowmode_enabled = 0; + atomic_set(&prs->enabled, 1); + + err = lps001wp_prs_update_odr(prs, prs->pdata->poll_interval); + if (err < 0) { + dev_err(&client->dev, "update_odr failed\n"); + goto err_power_off; + } + + err = lps001wp_prs_input_init(prs); + if (err < 0) { + dev_err(&client->dev, "input init failed\n"); + goto err_power_off; + } + + + err = create_sysfs_interfaces(&client->dev); + if (err < 0) { + dev_err(&client->dev, + "device LPS001WP_PRS_DEV_NAME sysfs register failed\n"); + goto err_input_cleanup; + } + + + lps001wp_prs_device_power_off(prs); + + /* As default, do not report information */ + atomic_set(&prs->enabled, 0); + + + mutex_unlock(&prs->lock); + + dev_info(&client->dev, "%s: probed\n", LPS001WP_PRS_DEV_NAME); + + return 0; + +/* +remove_sysfs_int: + remove_sysfs_interfaces(&client->dev); +*/ +err_input_cleanup: + lps001wp_prs_input_cleanup(prs); +err_power_off: + lps001wp_prs_device_power_off(prs); +err2: + if (prs->pdata->exit) + prs->pdata->exit(); +exit_kfree_pdata: + kfree(prs->pdata); + +err_mutexunlockfreedata: + mutex_unlock(&prs->lock); + kfree(prs); +exit_alloc_data_failed: +exit_check_functionality_failed: + printk(KERN_ERR "%s: Driver Init failed\n", LPS001WP_PRS_DEV_NAME); + return err; +} + +static int __devexit lps001wp_prs_remove(struct i2c_client *client) +{ + struct lps001wp_prs_data *prs = i2c_get_clientdata(client); + + lps001wp_prs_input_cleanup(prs); + lps001wp_prs_device_power_off(prs); + remove_sysfs_interfaces(&client->dev); + + if (prs->pdata->exit) + prs->pdata->exit(); + kfree(prs->pdata); + kfree(prs); + + return 0; +} + +static int lps001wp_prs_resume(struct i2c_client *client) +{ + struct lps001wp_prs_data *prs = i2c_get_clientdata(client); + + if (prs->on_before_suspend) + return lps001wp_prs_enable(prs); + return 0; +} + +static int lps001wp_prs_suspend(struct i2c_client *client, pm_message_t mesg) +{ + struct lps001wp_prs_data *prs = i2c_get_clientdata(client); + + prs->on_before_suspend = atomic_read(&prs->enabled); + return lps001wp_prs_disable(prs); +} + +static const struct i2c_device_id lps001wp_prs_id[] + = { { LPS001WP_PRS_DEV_NAME, 0}, { },}; + +MODULE_DEVICE_TABLE(i2c, lps001wp_prs_id); + +static struct i2c_driver lps001wp_prs_driver = { + .driver = { + .name = LPS001WP_PRS_DEV_NAME, + .owner = THIS_MODULE, + }, + .probe = lps001wp_prs_probe, + .remove = __devexit_p(lps001wp_prs_remove), + .id_table = lps001wp_prs_id, + .resume = lps001wp_prs_resume, + .suspend = lps001wp_prs_suspend, +}; + +static int __init lps001wp_prs_init(void) +{ + printk(KERN_DEBUG "%s barometer driver: init\n", + LPS001WP_PRS_DEV_NAME); + return i2c_add_driver(&lps001wp_prs_driver); +} + +static void __exit lps001wp_prs_exit(void) +{ + #if DEBUG + printk(KERN_DEBUG "%s barometer driver exit\n", + LPS001WP_PRS_DEV_NAME); + #endif + i2c_del_driver(&lps001wp_prs_driver); + return; +} + +module_init(lps001wp_prs_init); +module_exit(lps001wp_prs_exit); + +MODULE_DESCRIPTION("STMicrolelectronics lps001wp pressure sensor sysfs driver"); +MODULE_AUTHOR("Matteo Dameno, Carmine Iascone, STMicroelectronics"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/input/misc/ste_ff_vibra.c b/drivers/input/misc/ste_ff_vibra.c new file mode 100644 index 00000000000..9038e6be046 --- /dev/null +++ b/drivers/input/misc/ste_ff_vibra.c @@ -0,0 +1,234 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Marcin Mielczarczyk <marcin.mielczarczyk@tieto.com> + * for ST-Ericsson + * License Terms: GNU General Public License v2 + */ + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/input.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/workqueue.h> +#include <mach/ste_audio_io_vibrator.h> + +#define FF_VIBRA_DOWN 0x0000 /* 0 degrees */ +#define FF_VIBRA_LEFT 0x4000 /* 90 degrees */ +#define FF_VIBRA_UP 0x8000 /* 180 degrees */ +#define FF_VIBRA_RIGHT 0xC000 /* 270 degrees */ + +/** + * struct vibra_info - Vibrator information structure + * @idev: Pointer to input device structure + * @vibra_workqueue: Pointer to vibrator workqueue structure + * @vibra_work: Vibrator work + * @direction: Vibration direction + * @speed: Vibration speed + * + * Structure vibra_info holds vibrator informations + **/ +struct vibra_info { + struct input_dev *idev; + struct workqueue_struct *vibra_workqueue; + struct work_struct vibra_work; + int direction; + unsigned char speed; +}; + +/** + * vibra_play_work() - Vibrator work, sets speed and direction + * @work: Pointer to work structure + * + * This function is called from workqueue, turns on/off vibrator + **/ +static void vibra_play_work(struct work_struct *work) +{ + struct vibra_info *vinfo = container_of(work, + struct vibra_info, vibra_work); + struct ste_vibra_speed left_speed = { + .positive = 0, + .negative = 0, + }; + struct ste_vibra_speed right_speed = { + .positive = 0, + .negative = 0, + }; + + /* Divide by 2 because supported range by PWM is 0-100 */ + vinfo->speed /= 2; + + if ((vinfo->direction > FF_VIBRA_DOWN) && + (vinfo->direction < FF_VIBRA_UP)) { + /* 1 - 179 degrees, turn on left vibrator */ + left_speed.positive = vinfo->speed; + } else if (vinfo->direction > FF_VIBRA_UP) { + /* more than 180 degrees, turn on right vibrator */ + right_speed.positive = vinfo->speed; + } else { + /* 0 (down) or 180 (up) degrees, turn on 2 vibrators */ + left_speed.positive = vinfo->speed; + right_speed.positive = vinfo->speed; + } + + ste_audioio_vibrator_pwm_control(STE_AUDIOIO_CLIENT_FF_VIBRA, + left_speed, right_speed); +} + +/** + * vibra_play() - Memless device control function + * @idev: Pointer to input device structure + * @data: Pointer to private data (not used) + * @effect: Pointer to force feedback effect structure + * + * This function controls memless device + * + * Returns: + * 0 - success + **/ +static int vibra_play(struct input_dev *idev, void *data, + struct ff_effect *effect) +{ + struct vibra_info *vinfo = input_get_drvdata(idev); + + vinfo->direction = effect->direction; + vinfo->speed = effect->u.rumble.strong_magnitude >> 8; + if (!vinfo->speed) + /* Shift weak magnitude to make it feelable on vibrator */ + vinfo->speed = effect->u.rumble.weak_magnitude >> 9; + + queue_work(vinfo->vibra_workqueue, &vinfo->vibra_work); + + return 0; +} + +/** + * ste_ff_vibra_open() - Input device open function + * @idev: Pointer to input device structure + * + * This function is called on opening input device + * + * Returns: + * -ENOMEM - no memory left + * 0 - success + **/ +static int ste_ff_vibra_open(struct input_dev *idev) +{ + struct vibra_info *vinfo = input_get_drvdata(idev); + + vinfo->vibra_workqueue = + create_singlethread_workqueue("ste_ff-ff-vibra"); + if (!vinfo->vibra_workqueue) { + dev_err(&idev->dev, "couldn't create vibra workqueue\n"); + return -ENOMEM; + } + return 0; +} + +/** + * ste_ff_vibra_close() - Input device close function + * @idev: Pointer to input device structure + * + * This function is called on closing input device + **/ +static void ste_ff_vibra_close(struct input_dev *idev) +{ + struct vibra_info *vinfo = input_get_drvdata(idev); + + cancel_work_sync(&vinfo->vibra_work); + INIT_WORK(&vinfo->vibra_work, vibra_play_work); + destroy_workqueue(vinfo->vibra_workqueue); + vinfo->vibra_workqueue = NULL; +} + +static int __devinit ste_ff_vibra_probe(struct platform_device *pdev) +{ + struct vibra_info *vinfo; + int ret; + + vinfo = kmalloc(sizeof *vinfo, GFP_KERNEL); + if (!vinfo) { + dev_err(&pdev->dev, "failed to allocate memory\n"); + return -ENOMEM; + } + + vinfo->idev = input_allocate_device(); + if (!vinfo->idev) { + dev_err(&pdev->dev, "failed to allocate input device\n"); + ret = -ENOMEM; + goto exit_vinfo_free; + } + + vinfo->idev->name = "ste-ff-vibra"; + vinfo->idev->dev.parent = pdev->dev.parent; + vinfo->idev->open = ste_ff_vibra_open; + vinfo->idev->close = ste_ff_vibra_close; + INIT_WORK(&vinfo->vibra_work, vibra_play_work); + __set_bit(FF_RUMBLE, vinfo->idev->ffbit); + + ret = input_ff_create_memless(vinfo->idev, NULL, vibra_play); + if (ret) { + dev_err(&pdev->dev, "failed to create memless device\n"); + goto exit_idev_free; + } + + ret = input_register_device(vinfo->idev); + if (ret) { + dev_err(&pdev->dev, "failed to register input device\n"); + goto exit_destroy_memless; + } + + input_set_drvdata(vinfo->idev, vinfo); + platform_set_drvdata(pdev, vinfo); + return 0; + +exit_destroy_memless: + input_ff_destroy(vinfo->idev); +exit_idev_free: + input_free_device(vinfo->idev); +exit_vinfo_free: + kfree(vinfo); + return ret; +} + +static int __devexit ste_ff_vibra_remove(struct platform_device *pdev) +{ + struct vibra_info *vinfo = platform_get_drvdata(pdev); + + /* + * Function device_release() will call input_dev_release() + * which will free ff and input device. No need to call + * input_ff_destroy() and input_free_device() explicitly. + */ + input_unregister_device(vinfo->idev); + kfree(vinfo); + platform_set_drvdata(pdev, NULL); + + return 0; +} + +static struct platform_driver ste_ff_vibra_driver = { + .driver = { + .name = "ste_ff_vibra", + .owner = THIS_MODULE, + }, + .probe = ste_ff_vibra_probe, + .remove = __devexit_p(ste_ff_vibra_remove) +}; + +static int __init ste_ff_vibra_init(void) +{ + return platform_driver_register(&ste_ff_vibra_driver); +} +module_init(ste_ff_vibra_init); + +static void __exit ste_ff_vibra_exit(void) +{ + platform_driver_unregister(&ste_ff_vibra_driver); +} +module_exit(ste_ff_vibra_exit); + +MODULE_AUTHOR("Marcin Mielczarczyk <marcin.mielczarczyk@tieto.com>"); +MODULE_DESCRIPTION("STE Force Feedback Vibrator Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig index 3b9d5e2105d..5a54525b335 100644 --- a/drivers/input/touchscreen/Kconfig +++ b/drivers/input/touchscreen/Kconfig @@ -99,6 +99,30 @@ config TOUCHSCREEN_BITSY To compile this driver as a module, choose M here: the module will be called h3600_ts_input. +config TOUCHSCREEN_BU21013 + tristate "BU21013 based touch panel controllers" + depends on I2C + default y + help + Say Y here if you have a ROHM BU21013 and + want to enable support for the built-in touchscreen. + + To compile this driver as a module, choose M here: the + module will be called bu21013_ts. + +config TOUCHSCREEN_CORGI + tristate "SharpSL (Corgi and Spitz series) touchscreen driver (DEPRECATED)" + depends on PXA_SHARPSL + select CORGI_SSP_DEPRECATED + help + Say Y here if you have a bu21013 touchscreen connected to + your system. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called bu21013_ts. + config TOUCHSCREEN_DA9034 tristate "Touchscreen support for Dialog Semiconductor DA9034" depends on PMIC_DA903X @@ -303,6 +327,12 @@ config TOUCHSCREEN_MIGOR To compile this driver as a module, choose M here: the module will be called migor_ts. +config TOUCHSCREEN_SYNAPTICS_I2C_RMI + tristate "Synaptics i2c touchscreen" + depends on I2C + help + This enables support for Synaptics RMI over I2C based touchscreens. + config TOUCHSCREEN_TOUCHRIGHT tristate "Touchright serial touchscreen" select SERIO diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile index 497964a7a21..b582cec3007 100644 --- a/drivers/input/touchscreen/Makefile +++ b/drivers/input/touchscreen/Makefile @@ -12,6 +12,8 @@ obj-$(CONFIG_TOUCHSCREEN_AD7879) += ad7879.o obj-$(CONFIG_TOUCHSCREEN_ADS7846) += ads7846.o obj-$(CONFIG_TOUCHSCREEN_ATMEL_TSADCC) += atmel_tsadcc.o obj-$(CONFIG_TOUCHSCREEN_BITSY) += h3600_ts_input.o +obj-$(CONFIG_TOUCHSCREEN_BU21013) += bu21013_ts.o +obj-$(CONFIG_TOUCHSCREEN_CORGI) += corgi_ts.o obj-$(CONFIG_TOUCHSCREEN_DYNAPRO) += dynapro.o obj-$(CONFIG_TOUCHSCREEN_HAMPSHIRE) += hampshire.o obj-$(CONFIG_TOUCHSCREEN_GUNZE) += gunze.o @@ -31,6 +33,7 @@ obj-$(CONFIG_TOUCHSCREEN_USB_COMPOSITE) += usbtouchscreen.o obj-$(CONFIG_TOUCHSCREEN_PCAP) += pcap_ts.o obj-$(CONFIG_TOUCHSCREEN_PENMOUNT) += penmount.o obj-$(CONFIG_TOUCHSCREEN_S3C2410) += s3c2410_ts.o +obj-$(CONFIG_TOUCHSCREEN_SYNAPTICS_I2C_RMI) += synaptics_i2c_rmi.o obj-$(CONFIG_TOUCHSCREEN_TOUCHIT213) += touchit213.o obj-$(CONFIG_TOUCHSCREEN_TOUCHRIGHT) += touchright.o obj-$(CONFIG_TOUCHSCREEN_TOUCHWIN) += touchwin.o diff --git a/drivers/input/touchscreen/bu21013_ts.c b/drivers/input/touchscreen/bu21013_ts.c new file mode 100644 index 00000000000..de460a41d03 --- /dev/null +++ b/drivers/input/touchscreen/bu21013_ts.c @@ -0,0 +1,931 @@ +/* + * 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 + */ + +#include <linux/kernel.h> +#include <linux/earlysuspend.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/i2c.h> +#include <linux/workqueue.h> +#include <linux/input.h> +#include <linux/input/bu21013.h> +#include <linux/slab.h> +#include <linux/clk.h> +#include <linux/regulator/consumer.h> + +#define PEN_DOWN_INTR 0 +#define RESET_DELAY 30 +#define PENUP_TIMEOUT 2 /* 2msecs */ +#define SCALE_FACTOR 1000 +#define DELTA_MIN 16 +#define MASK_BITS 0x03 +#define SHIFT_8 8 +#define SHIFT_2 2 +#define LENGTH_OF_BUFFER 11 +#define I2C_RETRY_COUNT 5 + +#define BU21013_SENSORS_BTN_0_7_REG 0x70 +#define BU21013_SENSORS_BTN_8_15_REG 0x71 +#define BU21013_SENSORS_BTN_16_23_REG 0x72 +#define BU21013_X1_POS_MSB_REG 0x73 +#define BU21013_X1_POS_LSB_REG 0x74 +#define BU21013_Y1_POS_MSB_REG 0x75 +#define BU21013_Y1_POS_LSB_REG 0x76 +#define BU21013_X2_POS_MSB_REG 0x77 +#define BU21013_X2_POS_LSB_REG 0x78 +#define BU21013_Y2_POS_MSB_REG 0x79 +#define BU21013_Y2_POS_LSB_REG 0x7A +#define BU21013_INT_CLR_REG 0xE8 +#define BU21013_INT_MODE_REG 0xE9 +#define BU21013_GAIN_REG 0xEA +#define BU21013_OFFSET_MODE_REG 0xEB +#define BU21013_XY_EDGE_REG 0xEC +#define BU21013_RESET_REG 0xED +#define BU21013_CALIB_REG 0xEE +#define BU21013_DONE_REG 0xEF +#define BU21013_SENSOR_0_7_REG 0xF0 +#define BU21013_SENSOR_8_15_REG 0xF1 +#define BU21013_SENSOR_16_23_REG 0xF2 +#define BU21013_POS_MODE1_REG 0xF3 +#define BU21013_POS_MODE2_REG 0xF4 +#define BU21013_CLK_MODE_REG 0xF5 +#define BU21013_IDLE_REG 0xFA +#define BU21013_FILTER_REG 0xFB +#define BU21013_TH_ON_REG 0xFC +#define BU21013_TH_OFF_REG 0xFD + + +#define BU21013_RESET_ENABLE 0x01 + +#define BU21013_SENSORS_EN_0_7 0x3F +#define BU21013_SENSORS_EN_8_15 0xFC +#define BU21013_SENSORS_EN_16_23 0x1F + +#define BU21013_POS_MODE1_0 0x02 +#define BU21013_POS_MODE1_1 0x04 +#define BU21013_POS_MODE1_2 0x08 + +#define BU21013_POS_MODE2_ZERO 0x01 +#define BU21013_POS_MODE2_AVG1 0x02 +#define BU21013_POS_MODE2_AVG2 0x04 +#define BU21013_POS_MODE2_EN_XY 0x08 +#define BU21013_POS_MODE2_EN_RAW 0x10 +#define BU21013_POS_MODE2_MULTI 0x80 + +#define BU21013_CLK_MODE_DIV 0x01 +#define BU21013_CLK_MODE_EXT 0x02 +#define BU21013_CLK_MODE_CALIB 0x80 + +#define BU21013_IDLET_0 0x01 +#define BU21013_IDLET_1 0x02 +#define BU21013_IDLET_2 0x04 +#define BU21013_IDLET_3 0x08 +#define BU21013_IDLE_INTERMIT_EN 0x10 + +#define BU21013_DELTA_0_6 0x7F +#define BU21013_FILTER_EN 0x80 + +#define BU21013_INT_MODE_LEVEL 0x00 +#define BU21013_INT_MODE_EDGE 0x01 + +#define BU21013_GAIN_0 0x01 +#define BU21013_GAIN_1 0x02 +#define BU21013_GAIN_2 0x04 + +#define BU21013_OFFSET_MODE_DEFAULT 0x00 +#define BU21013_OFFSET_MODE_MOVE 0x01 +#define BU21013_OFFSET_MODE_DISABLE 0x02 + +#define BU21013_TH_ON_0 0x01 +#define BU21013_TH_ON_1 0x02 +#define BU21013_TH_ON_2 0x04 +#define BU21013_TH_ON_3 0x08 +#define BU21013_TH_ON_4 0x10 +#define BU21013_TH_ON_5 0x20 +#define BU21013_TH_ON_6 0x40 +#define BU21013_TH_ON_7 0x80 +#define BU21013_TH_ON_MAX 0xFF + +#define BU21013_TH_OFF_0 0x01 +#define BU21013_TH_OFF_1 0x02 +#define BU21013_TH_OFF_2 0x04 +#define BU21013_TH_OFF_3 0x08 +#define BU21013_TH_OFF_4 0x10 +#define BU21013_TH_OFF_5 0x20 +#define BU21013_TH_OFF_6 0x40 +#define BU21013_TH_OFF_7 0x80 +#define BU21013_TH_OFF_MAX 0xFF + +#define BU21013_X_EDGE_0 0x01 +#define BU21013_X_EDGE_1 0x02 +#define BU21013_X_EDGE_2 0x04 +#define BU21013_X_EDGE_3 0x08 +#define BU21013_Y_EDGE_0 0x10 +#define BU21013_Y_EDGE_1 0x20 +#define BU21013_Y_EDGE_2 0x40 +#define BU21013_Y_EDGE_3 0x80 + +#define BU21013_DONE 0x01 +#define BU21013_NUMBER_OF_X_SENSORS (6) +#define BU21013_NUMBER_OF_Y_SENSORS (11) + +#define DRIVER_TP "bu21013_ts" + +/** + * struct bu21013_ts_data - touch panel data structure + * @client: pointer to the i2c client + * @wait: variable to wait_queue_head_t structure + * @touch_stopped: touch stop flag + * @chip: pointer to the touch panel controller + * @in_dev: pointer to the input device structure + * @intr_pin: interrupt pin value + * @regulator: pointer to the Regulator used for touch screen + * @enable: variable to indicate the enable/disable of touch screen + * @ext_clk_enable: true if running on ext clk + * @ext_clk_state: Saved state for suspend/resume of ext clk + * @factor_x: x scale factor + * @factor_y: y scale factor + * @tpclk: pointer to clock structure + * @early_suspend: early_suspend structure variable + * + * Touch panel device data structure + */ +struct bu21013_ts_data { + struct i2c_client *client; + wait_queue_head_t wait; + bool touch_stopped; + struct bu21013_platform_device *chip; + struct input_dev *in_dev; + unsigned int intr_pin; + struct regulator *regulator; + bool enable; + bool ext_clk_enable; + bool ext_clk_state; + unsigned int factor_x; + unsigned int factor_y; + struct clk *tpclk; + struct early_suspend early_suspend; +}; + +static int bu21013_init_chip(struct bu21013_ts_data *data, bool on_ext_clk); + +#ifdef CONFIG_HAS_EARLYSUSPEND +static void bu21013_ts_early_suspend(struct early_suspend *data); +static void bu21013_ts_late_resume(struct early_suspend *data); +#endif + +/** + * bu21013_ext_clk() - enable/disable the external clock + * @pdata: touch screen data + * @enable: enable external clock + * @reconfig: reconfigure chip upon external clock off. + * + * This function used to enable or disable the external clock and possible + * reconfigure hw. + */ +static int bu21013_ext_clk(struct bu21013_ts_data *pdata, bool enable, + bool reconfig) +{ + int retval = 0; + + if (!pdata->tpclk || pdata->ext_clk_enable == enable) + return retval; + + if (enable) { + pdata->ext_clk_enable = true; + clk_enable(pdata->tpclk); + retval = bu21013_init_chip(pdata, true); + } else { + pdata->ext_clk_enable = false; + if (reconfig) + retval = bu21013_init_chip(pdata, false); + clk_disable(pdata->tpclk); + } + return retval; +} + +/** + * bu21013_enable() - enable the touch driver event + * @pdata: touch screen data + * + * This function used to enable the driver and returns integer + */ +static int bu21013_enable(struct bu21013_ts_data *pdata) +{ + int retval; + + if (pdata->regulator) + regulator_enable(pdata->regulator); + + if (pdata->chip->cs_en) { + retval = pdata->chip->cs_en(pdata->chip->cs_pin); + if (retval < 0) { + dev_err(&pdata->client->dev, "enable hw failed\n"); + return retval; + } + } + + if (pdata->ext_clk_state) + retval = bu21013_ext_clk(pdata, true, true); + else + retval = bu21013_init_chip(pdata, false); + + if (retval < 0) { + dev_err(&pdata->client->dev, "enable hw failed\n"); + return retval; + } + pdata->touch_stopped = false; + enable_irq(pdata->chip->irq); + + return 0; +} + +/** + * bu21013_disable() - disable the touch driver event + * @pdata: touch screen data + * + * This function used to disable the driver and returns integer + */ +static void bu21013_disable(struct bu21013_ts_data *pdata) +{ + pdata->touch_stopped = true; + + pdata->ext_clk_state = pdata->ext_clk_enable; + (void) bu21013_ext_clk(pdata, false, false); + + disable_irq(pdata->chip->irq); + if (pdata->chip->cs_dis) + pdata->chip->cs_dis(pdata->chip->cs_pin); + if (pdata->regulator) + regulator_disable(pdata->regulator); +} + +/** + * bu21013_show_attr_enable() - show the touch screen controller status + * @dev: pointer to device structure + * @attr: pointer to device attribute + * @buf: parameter buffer + * + * This funtion is used to show whether the touch screen is enabled or + * disabled + */ +static ssize_t bu21013_show_attr_enable(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct bu21013_ts_data *pdata = dev_get_drvdata(dev); + return sprintf(buf, "%d\n", pdata->enable); +} + +/** + * bu21013_store_attr_enable() - Enable/Disable the touchscreen. + * @dev: pointer to device structure + * @attr: pointer to device attribute + * @buf: parameter buffer + * @count: number of parameters + * + * This funtion is used to enable or disable the touch screen controller. + */ +static ssize_t bu21013_store_attr_enable(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int ret = 0; + unsigned long val; + + struct bu21013_ts_data *pdata = dev_get_drvdata(dev); + + if (strict_strtoul(buf, 0, &val)) + return -EINVAL; + + if ((val != 0) && (val != 1)) + return -EINVAL; + + if (pdata->enable != val) { + pdata->enable = val ? true : false; + if (pdata->enable) { + ret = bu21013_enable(pdata); + if (ret < 0) + return ret; + } else + bu21013_disable(pdata); + } + return count; +} + +/** + * bu21013_show_attr_extclk() - shows the external clock status + * @dev: pointer to device structure + * @attr: pointer to device attribute + * @buf: parameter buffer + * + * This funtion is used to show whether the external clock for the touch + * screen is enabled or disabled. + */ +static ssize_t bu21013_show_attr_extclk(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct bu21013_ts_data *pdata = dev_get_drvdata(dev); + return sprintf(buf, "%d\n", pdata->ext_clk_enable); +} + +/** + * bu21013_store_attr_extclk() - Enable/Disable the external clock + * for the tocuh screen controller. + * @dev: pointer to device structure + * @attr: pointer to device attribute + * @buf: parameter buffer + * @count: number of parameters + * + * This funtion is used enabled or disable the external clock for the touch + * screen controller. + */ +static ssize_t bu21013_store_attr_extclk(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int retval = 0; + struct bu21013_ts_data *pdata = dev_get_drvdata(dev); + unsigned long val; + + if (strict_strtoul(buf, 0, &val)) + return -EINVAL; + + if ((val != 0) && (val != 1)) + return -EINVAL; + + if (pdata->chip->has_ext_clk) { + if (pdata->enable) + retval = bu21013_ext_clk(pdata, val, true); + else + pdata->ext_clk_state = val; + if (retval < 0) + return retval; + } + return count; +} + +static DEVICE_ATTR(enable, S_IWUSR | S_IRUGO, + bu21013_show_attr_enable, bu21013_store_attr_enable); + +static DEVICE_ATTR(ext_clk, S_IWUSR | S_IRUGO, + bu21013_show_attr_extclk, bu21013_store_attr_extclk); + + +static struct attribute *bu21013_attribute[] = { + &dev_attr_enable.attr, + &dev_attr_ext_clk.attr, + NULL, +}; + +static struct attribute_group bu21013_attr_group = { + .attrs = bu21013_attribute, +}; + + +/** + * bu21013_read_block_data(): read the touch co-ordinates + * @data: bu21013_ts_data structure pointer + * @buf: byte pointer + * + * Read the touch co-ordinates using i2c read block into buffer + * and returns integer. + */ +static int bu21013_read_block_data(struct bu21013_ts_data *data, u8 *buf) +{ + int ret, i; + + for (i = 0; i < I2C_RETRY_COUNT; i++) { + ret = i2c_smbus_read_i2c_block_data + (data->client, BU21013_SENSORS_BTN_0_7_REG, + LENGTH_OF_BUFFER, buf); + if (ret == LENGTH_OF_BUFFER) + return 0; + } + return -EINVAL; +} + +/** + * bu21013_do_touch_report(): Get the touch co-ordinates + * @data: bu21013_ts_data structure pointer + * + * Get the touch co-ordinates from touch sensor registers and writes + * into device structure and returns integer. + */ +static int bu21013_do_touch_report(struct bu21013_ts_data *data) +{ + u8 buf[LENGTH_OF_BUFFER]; + unsigned int pos_x[2], pos_y[2]; + bool has_x_sensors, has_y_sensors; + int finger_down_count = 0; + int i; + + if (data == NULL) + return -EINVAL; + + if (bu21013_read_block_data(data, buf) < 0) + return -EINVAL; + + has_x_sensors = hweight32(buf[0] & BU21013_SENSORS_EN_0_7); + has_y_sensors = hweight32(((buf[1] & BU21013_SENSORS_EN_8_15) | + ((buf[2] & BU21013_SENSORS_EN_16_23) << SHIFT_8)) >> SHIFT_2); + if (!has_x_sensors || !has_y_sensors) + return 0; + + for (i = 0; i < 2; i++) { + const u8 *p = &buf[4 * i + 3]; + unsigned int x = p[0] << SHIFT_2 | (p[1] & MASK_BITS); + unsigned int y = p[2] << SHIFT_2 | (p[3] & MASK_BITS); + if (x == 0 || y == 0) + continue; + x = x * data->factor_x / SCALE_FACTOR; + y = y * data->factor_y / SCALE_FACTOR; + pos_x[finger_down_count] = x; + pos_y[finger_down_count] = y; + finger_down_count++; + } + + if (finger_down_count) { + if (finger_down_count == 2 && + (abs(pos_x[0] - pos_x[1]) < DELTA_MIN || + abs(pos_y[0] - pos_y[1]) < DELTA_MIN)) + return 0; + + for (i = 0; i < finger_down_count; i++) { + if (data->chip->portrait && data->chip->x_flip) + pos_x[i] = data->chip->x_max_res - pos_x[i]; + if (data->chip->portrait && data->chip->y_flip) + pos_y[i] = data->chip->y_max_res - pos_y[i]; + input_report_abs(data->in_dev, ABS_MT_TOUCH_MAJOR, + max(pos_x[i], pos_y[i])); + input_report_abs(data->in_dev, ABS_MT_POSITION_X, + pos_x[i]); + input_report_abs(data->in_dev, ABS_MT_POSITION_Y, + pos_y[i]); + input_mt_sync(data->in_dev); + } + } else + input_mt_sync(data->in_dev); + + input_sync(data->in_dev); + + return 0; +} +/** + * bu21013_gpio_irq() - gpio thread function for touch interrupt + * @irq: irq value + * @device_data: void pointer + * + * This gpio thread function for touch interrupt + * and returns irqreturn_t. + */ +static irqreturn_t bu21013_gpio_irq(int irq, void *device_data) +{ + struct bu21013_ts_data *data = device_data; + struct i2c_client *i2c = data->client; + int retval; + + do { + retval = bu21013_do_touch_report(data); + if (retval < 0) { + dev_err(&i2c->dev, "bu21013_do_touch_report failed\n"); + return IRQ_NONE; + } + data->intr_pin = data->chip->irq_read_val(); + if (data->intr_pin == PEN_DOWN_INTR) + wait_event_timeout(data->wait, data->touch_stopped, + msecs_to_jiffies(PENUP_TIMEOUT)); + } while (!data->intr_pin && !data->touch_stopped); + return IRQ_HANDLED; +} + +/** + * bu21013_init_chip() - power on sequence for the bu21013 controller + * @data: device structure pointer + * @on_ext_clk: Run on external clock + * + * This function is used to power on + * the bu21013 controller and returns integer. + */ +static int bu21013_init_chip(struct bu21013_ts_data *data, bool on_ext_clk) +{ + int retval; + struct i2c_client *i2c = data->client; + + retval = i2c_smbus_write_byte_data(i2c, BU21013_RESET_REG, + BU21013_RESET_ENABLE); + if (retval < 0) { + dev_err(&i2c->dev, "BU21013_RESET reg write failed\n"); + return retval; + } + msleep(RESET_DELAY); + + retval = i2c_smbus_write_byte_data(i2c, BU21013_SENSOR_0_7_REG, + BU21013_SENSORS_EN_0_7); + if (retval < 0) { + dev_err(&i2c->dev, "BU21013_SENSOR_0_7 reg write failed\n"); + return retval; + } + retval = i2c_smbus_write_byte_data(i2c, BU21013_SENSOR_8_15_REG, + BU21013_SENSORS_EN_8_15); + if (retval < 0) { + dev_err(&i2c->dev, "BU21013_SENSOR_8_15 reg write failed\n"); + return retval; + } + retval = i2c_smbus_write_byte_data(i2c, BU21013_SENSOR_16_23_REG, + BU21013_SENSORS_EN_16_23); + if (retval < 0) { + dev_err(&i2c->dev, "BU21013_SENSOR_16_23 reg write failed\n"); + return retval; + } + retval = i2c_smbus_write_byte_data(i2c, BU21013_POS_MODE1_REG, + (BU21013_POS_MODE1_0 | BU21013_POS_MODE1_1)); + if (retval < 0) { + dev_err(&i2c->dev, "BU21013_POS_MODE1 reg write failed\n"); + return retval; + } + retval = i2c_smbus_write_byte_data(i2c, BU21013_POS_MODE2_REG, + (BU21013_POS_MODE2_ZERO | BU21013_POS_MODE2_AVG1 | + BU21013_POS_MODE2_AVG2 | BU21013_POS_MODE2_EN_RAW | + BU21013_POS_MODE2_MULTI)); + if (retval < 0) { + dev_err(&i2c->dev, "BU21013_POS_MODE2 reg write failed\n"); + return retval; + } + if (on_ext_clk) + retval = i2c_smbus_write_byte_data(i2c, BU21013_CLK_MODE_REG, + (BU21013_CLK_MODE_EXT | BU21013_CLK_MODE_CALIB)); + else + retval = i2c_smbus_write_byte_data(i2c, BU21013_CLK_MODE_REG, + (BU21013_CLK_MODE_DIV | BU21013_CLK_MODE_CALIB)); + if (retval < 0) { + dev_err(&i2c->dev, "BU21013_CLK_MODE reg write failed\n"); + return retval; + } + retval = i2c_smbus_write_byte_data(i2c, BU21013_IDLE_REG, + (BU21013_IDLET_0 | BU21013_IDLE_INTERMIT_EN)); + if (retval < 0) { + dev_err(&i2c->dev, "BU21013_IDLE reg write failed\n"); + return retval; + } + retval = i2c_smbus_write_byte_data(i2c, BU21013_INT_MODE_REG, + BU21013_INT_MODE_LEVEL); + if (retval < 0) { + dev_err(&i2c->dev, "BU21013_INT_MODE reg write failed\n"); + return retval; + } + retval = i2c_smbus_write_byte_data(i2c, BU21013_FILTER_REG, + (BU21013_DELTA_0_6 | + BU21013_FILTER_EN)); + if (retval < 0) { + dev_err(&i2c->dev, "BU21013_FILTER reg write failed\n"); + return retval; + } + + retval = i2c_smbus_write_byte_data(i2c, BU21013_TH_ON_REG, + BU21013_TH_ON_5); + if (retval < 0) { + dev_err(&i2c->dev, "BU21013_TH_ON reg write failed\n"); + return retval; + } + retval = i2c_smbus_write_byte_data(i2c, BU21013_TH_OFF_REG, + BU21013_TH_OFF_4 | BU21013_TH_OFF_3); + if (retval < 0) { + dev_err(&i2c->dev, "BU21013_TH_OFF reg write failed\n"); + return retval; + } + retval = i2c_smbus_write_byte_data(i2c, BU21013_GAIN_REG, + (BU21013_GAIN_0 | BU21013_GAIN_1)); + if (retval < 0) { + dev_err(&i2c->dev, "BU21013_GAIN reg write failed\n"); + return retval; + } + + retval = i2c_smbus_write_byte_data(i2c, BU21013_OFFSET_MODE_REG, + BU21013_OFFSET_MODE_DEFAULT); + if (retval < 0) { + dev_err(&i2c->dev, "BU21013_OFFSET_MODE reg write failed\n"); + return retval; + } + retval = i2c_smbus_write_byte_data(i2c, BU21013_XY_EDGE_REG, + (BU21013_X_EDGE_0 | BU21013_X_EDGE_2 | + BU21013_Y_EDGE_1 | BU21013_Y_EDGE_3)); + if (retval < 0) { + dev_err(&i2c->dev, "BU21013_XY_EDGE reg write failed\n"); + return retval; + } + retval = i2c_smbus_write_byte_data(i2c, BU21013_DONE_REG, + BU21013_DONE); + if (retval < 0) { + dev_err(&i2c->dev, "BU21013_REG_DONE reg write failed\n"); + return retval; + } + + data->factor_x = (data->chip->x_max_res * SCALE_FACTOR / + data->chip->touch_x_max); + data->factor_y = (data->chip->y_max_res * SCALE_FACTOR / + data->chip->touch_y_max); + return retval; +} + +/** + * bu21013_probe() - initialzes the i2c-client touchscreen driver + * @client: i2c client structure pointer + * @id: i2c device id pointer + * + * This function used to initializes the i2c-client touchscreen + * driver and returns integer. + */ +static int __devinit bu21013_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int retval; + struct bu21013_ts_data *bu21013_data; + struct input_dev *in_dev; + struct bu21013_platform_device *pdata = + client->dev.platform_data; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_BYTE_DATA)) { + dev_err(&client->dev, "i2c smbus byte data not supported\n"); + return -EIO; + } + + if (!pdata) { + dev_err(&client->dev, "platform data not defined\n"); + retval = -EINVAL; + return retval; + } + + bu21013_data = kzalloc(sizeof(struct bu21013_ts_data), GFP_KERNEL); + if (!bu21013_data) { + dev_err(&client->dev, "device memory alloc failed\n"); + retval = -ENOMEM; + return retval; + } + /* allocate input device */ + in_dev = input_allocate_device(); + if (!in_dev) { + dev_err(&client->dev, "input device memory alloc failed\n"); + retval = -ENOMEM; + goto err_alloc; + } + + bu21013_data->in_dev = in_dev; + bu21013_data->chip = pdata; + bu21013_data->client = client; + + bu21013_data->regulator = regulator_get(&client->dev, "avdd"); + if (IS_ERR(bu21013_data->regulator)) { + dev_warn(&client->dev, "regulator_get failed\n"); + bu21013_data->regulator = NULL; + } + if (bu21013_data->regulator) + regulator_enable(bu21013_data->regulator); + + /* configure the gpio pins */ + if (pdata->cs_en) { + retval = pdata->cs_en(pdata->cs_pin); + if (retval < 0) { + dev_err(&client->dev, "chip init failed\n"); + goto err_init_cs; + } + } + + if (pdata->has_ext_clk) { + bu21013_data->tpclk = clk_get(&client->dev, NULL); + if (IS_ERR(bu21013_data->tpclk)) { + dev_warn(&client->dev, "get extern clock failed\n"); + bu21013_data->tpclk = NULL; + } + } + + if (pdata->enable_ext_clk && bu21013_data->tpclk) { + retval = clk_enable(bu21013_data->tpclk); + if (retval < 0) { + dev_err(&client->dev, "clock enable failed\n"); + goto err_ext_clk; + } + bu21013_data->ext_clk_enable = true; + } + + /* configure the touch panel controller */ + retval = bu21013_init_chip(bu21013_data, bu21013_data->ext_clk_enable); + if (retval < 0) { + dev_err(&client->dev, "error in bu21013 config\n"); + goto err_init_config; + } + + init_waitqueue_head(&bu21013_data->wait); + bu21013_data->touch_stopped = false; + + /* register the device to input subsystem */ + in_dev->name = DRIVER_TP; + in_dev->id.bustype = BUS_I2C; + in_dev->dev.parent = &client->dev; + + __set_bit(EV_SYN, in_dev->evbit); + __set_bit(EV_KEY, in_dev->evbit); + __set_bit(EV_ABS, in_dev->evbit); + + input_set_abs_params(in_dev, ABS_MT_POSITION_X, 0, + pdata->x_max_res, 0, 0); + input_set_abs_params(in_dev, ABS_MT_POSITION_Y, 0, + pdata->y_max_res, 0, 0); + input_set_abs_params(in_dev, ABS_MT_TOUCH_MAJOR, 0, + max(pdata->x_max_res , pdata->y_max_res), 0, 0); + input_set_drvdata(in_dev, bu21013_data); + retval = input_register_device(in_dev); + if (retval) + goto err_input_register; + + retval = request_threaded_irq(pdata->irq, NULL, bu21013_gpio_irq, + (IRQF_TRIGGER_FALLING | IRQF_SHARED), + DRIVER_TP, bu21013_data); + if (retval) { + dev_err(&client->dev, "request irq %d failed\n", pdata->irq); + goto err_init_irq; + } + bu21013_data->enable = true; + i2c_set_clientdata(client, bu21013_data); + + /* sysfs implementation for dynamic enable/disable the input event */ + retval = sysfs_create_group(&client->dev.kobj, &bu21013_attr_group); + if (retval) { + dev_err(&client->dev, "failed to create sysfs entries\n"); + goto err_sysfs_create; + } + +#ifdef CONFIG_HAS_EARLYSUSPEND + bu21013_data->early_suspend.level = + EARLY_SUSPEND_LEVEL_BLANK_SCREEN + 1; + bu21013_data->early_suspend.suspend = bu21013_ts_early_suspend; + bu21013_data->early_suspend.resume = bu21013_ts_late_resume; + register_early_suspend(&bu21013_data->early_suspend); +#endif + return retval; + +err_sysfs_create: + free_irq(pdata->irq, bu21013_data); + i2c_set_clientdata(client, NULL); +err_init_irq: + input_unregister_device(bu21013_data->in_dev); +err_input_register: + wake_up(&bu21013_data->wait); +err_init_config: + if (bu21013_data->tpclk) { + if (bu21013_data->ext_clk_enable) + clk_disable(bu21013_data->tpclk); + clk_put(bu21013_data->tpclk); + } +err_ext_clk: + if (pdata->cs_dis) + pdata->cs_dis(pdata->cs_pin); +err_init_cs: + if (bu21013_data->regulator) { + regulator_disable(bu21013_data->regulator); + regulator_put(bu21013_data->regulator); + } + input_free_device(bu21013_data->in_dev); +err_alloc: + kfree(bu21013_data); + + return retval; +} + +/** + * bu21013_remove() - removes the i2c-client touchscreen driver + * @client: i2c client structure pointer + * + * This function uses to remove the i2c-client + * touchscreen driver and returns integer. + */ +static int __devexit bu21013_remove(struct i2c_client *client) +{ + struct bu21013_ts_data *bu21013_data = i2c_get_clientdata(client); + + bu21013_data->touch_stopped = true; + sysfs_remove_group(&client->dev.kobj, &bu21013_attr_group); + wake_up(&bu21013_data->wait); + free_irq(bu21013_data->chip->irq, bu21013_data); + bu21013_data->chip->cs_dis(bu21013_data->chip->cs_pin); + input_unregister_device(bu21013_data->in_dev); + + if (bu21013_data->tpclk) { + if (bu21013_data->ext_clk_enable) + clk_disable(bu21013_data->tpclk); + clk_put(bu21013_data->tpclk); + } + if (bu21013_data->regulator) { + regulator_disable(bu21013_data->regulator); + regulator_put(bu21013_data->regulator); + } + kfree(bu21013_data); + + return 0; +} + +#if !defined(CONFIG_HAS_EARLYSUSPEND) && defined(CONFIG_PM) +/** + * bu21013_suspend() - suspend the touch screen controller + * @dev: pointer to device structure + * + * This function is used to suspend the + * touch panel controller and returns integer + */ +static int bu21013_suspend(struct device *dev) +{ + struct bu21013_ts_data *bu21013_data = dev_get_drvdata(dev); + + bu21013_disable(bu21013_data); + + return 0; +} + +/** + * bu21013_resume() - resume the touch screen controller + * @dev: pointer to device structure + * + * This function is used to resume the touch panel + * controller and returns integer. + */ +static int bu21013_resume(struct device *dev) +{ + struct bu21013_ts_data *bu21013_data = dev_get_drvdata(dev); + + return bu21013_enable(bu21013_data); +} + +static const struct dev_pm_ops bu21013_dev_pm_ops = { + .suspend = bu21013_suspend, + .resume = bu21013_resume, +}; + +#else +static void bu21013_ts_early_suspend(struct early_suspend *data) +{ + struct bu21013_ts_data *bu21013_data = + container_of(data, struct bu21013_ts_data, early_suspend); + bu21013_disable(bu21013_data); +} + +static void bu21013_ts_late_resume(struct early_suspend *data) +{ + struct bu21013_ts_data *bu21013_data = + container_of(data, struct bu21013_ts_data, early_suspend); + struct i2c_client *client = bu21013_data->client; + int retval; + + retval = bu21013_enable(bu21013_data); + if (retval < 0) + dev_err(&client->dev, "bu21013 enable failed\n"); +} +#endif + +static const struct i2c_device_id bu21013_id[] = { + { DRIVER_TP, 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, bu21013_id); + +static struct i2c_driver bu21013_driver = { + .driver = { + .name = DRIVER_TP, + .owner = THIS_MODULE, +#if !defined(CONFIG_HAS_EARLYSUSPEND) && defined(CONFIG_PM) + .pm = &bu21013_dev_pm_ops, +#endif + }, + .probe = bu21013_probe, + .remove = __devexit_p(bu21013_remove), + .id_table = bu21013_id, +}; + +/** + * bu21013_init() - initializes the bu21013 touchscreen driver + * + * This function used to initializes the bu21013 + * touchscreen driver and returns integer. + */ +static int __init bu21013_init(void) +{ + return i2c_add_driver(&bu21013_driver); +} + +/** + * bu21013_exit() - de-initializes the bu21013 touchscreen driver + * + * This function uses to de-initializes the bu21013 + * touchscreen driver and returns none. + */ +static void __exit bu21013_exit(void) +{ + i2c_del_driver(&bu21013_driver); +} + +module_init(bu21013_init); +module_exit(bu21013_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Naveen Kumar G <naveen.gaddipati@stericsson.com>"); +MODULE_DESCRIPTION("bu21013 touch screen controller driver"); diff --git a/drivers/input/touchscreen/synaptics_i2c_rmi.c b/drivers/input/touchscreen/synaptics_i2c_rmi.c new file mode 100644 index 00000000000..5729602cbb6 --- /dev/null +++ b/drivers/input/touchscreen/synaptics_i2c_rmi.c @@ -0,0 +1,675 @@ +/* drivers/input/keyboard/synaptics_i2c_rmi.c + * + * Copyright (C) 2007 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/earlysuspend.h> +#include <linux/hrtimer.h> +#include <linux/i2c.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/synaptics_i2c_rmi.h> + +static struct workqueue_struct *synaptics_wq; + +struct synaptics_ts_data { + uint16_t addr; + struct i2c_client *client; + struct input_dev *input_dev; + int use_irq; + bool has_relative_report; + struct hrtimer timer; + struct work_struct work; + uint16_t max[2]; + int snap_state[2][2]; + int snap_down_on[2]; + int snap_down_off[2]; + int snap_up_on[2]; + int snap_up_off[2]; + int snap_down[2]; + int snap_up[2]; + uint32_t flags; + int reported_finger_count; + int8_t sensitivity_adjust; + int (*power)(int on); + struct early_suspend early_suspend; +}; + +#ifdef CONFIG_HAS_EARLYSUSPEND +static void synaptics_ts_early_suspend(struct early_suspend *h); +static void synaptics_ts_late_resume(struct early_suspend *h); +#endif + +static int synaptics_init_panel(struct synaptics_ts_data *ts) +{ + int ret; + + ret = i2c_smbus_write_byte_data(ts->client, 0xff, 0x10); /* page select = 0x10 */ + if (ret < 0) { + printk(KERN_ERR "i2c_smbus_write_byte_data failed for page select\n"); + goto err_page_select_failed; + } + ret = i2c_smbus_write_byte_data(ts->client, 0x41, 0x04); /* Set "No Clip Z" */ + if (ret < 0) + printk(KERN_ERR "i2c_smbus_write_byte_data failed for No Clip Z\n"); + + ret = i2c_smbus_write_byte_data(ts->client, 0x44, + ts->sensitivity_adjust); + if (ret < 0) + pr_err("synaptics_ts: failed to set Sensitivity Adjust\n"); + +err_page_select_failed: + ret = i2c_smbus_write_byte_data(ts->client, 0xff, 0x04); /* page select = 0x04 */ + if (ret < 0) + printk(KERN_ERR "i2c_smbus_write_byte_data failed for page select\n"); + ret = i2c_smbus_write_byte_data(ts->client, 0xf0, 0x81); /* normal operation, 80 reports per second */ + if (ret < 0) + printk(KERN_ERR "synaptics_ts_resume: i2c_smbus_write_byte_data failed\n"); + return ret; +} + +static void synaptics_ts_work_func(struct work_struct *work) +{ + int i; + int ret; + int bad_data = 0; + struct i2c_msg msg[2]; + uint8_t start_reg; + uint8_t buf[15]; + struct synaptics_ts_data *ts = container_of(work, struct synaptics_ts_data, work); + int buf_len = ts->has_relative_report ? 15 : 13; + + msg[0].addr = ts->client->addr; + msg[0].flags = 0; + msg[0].len = 1; + msg[0].buf = &start_reg; + start_reg = 0x00; + msg[1].addr = ts->client->addr; + msg[1].flags = I2C_M_RD; + msg[1].len = buf_len; + msg[1].buf = buf; + + /* printk("synaptics_ts_work_func\n"); */ + for (i = 0; i < ((ts->use_irq && !bad_data) ? 1 : 10); i++) { + ret = i2c_transfer(ts->client->adapter, msg, 2); + if (ret < 0) { + printk(KERN_ERR "synaptics_ts_work_func: i2c_transfer failed\n"); + bad_data = 1; + } else { + /* printk("synaptics_ts_work_func: %x %x %x %x %x %x" */ + /* " %x %x %x %x %x %x %x %x %x, ret %d\n", */ + /* buf[0], buf[1], buf[2], buf[3], */ + /* buf[4], buf[5], buf[6], buf[7], */ + /* buf[8], buf[9], buf[10], buf[11], */ + /* buf[12], buf[13], buf[14], ret); */ + if ((buf[buf_len - 1] & 0xc0) != 0x40) { + printk(KERN_WARNING "synaptics_ts_work_func:" + " bad read %x %x %x %x %x %x %x %x %x" + " %x %x %x %x %x %x, ret %d\n", + buf[0], buf[1], buf[2], buf[3], + buf[4], buf[5], buf[6], buf[7], + buf[8], buf[9], buf[10], buf[11], + buf[12], buf[13], buf[14], ret); + if (bad_data) + synaptics_init_panel(ts); + bad_data = 1; + continue; + } + bad_data = 0; + if ((buf[buf_len - 1] & 1) == 0) { + /* printk("read %d coordinates\n", i); */ + break; + } else { + int pos[2][2]; + int f, a; + int base; + /* int x = buf[3] | (uint16_t)(buf[2] & 0x1f) << 8; */ + /* int y = buf[5] | (uint16_t)(buf[4] & 0x1f) << 8; */ + int z = buf[1]; + int w = buf[0] >> 4; + int finger = buf[0] & 7; + + /* int x2 = buf[3+6] | (uint16_t)(buf[2+6] & 0x1f) << 8; */ + /* int y2 = buf[5+6] | (uint16_t)(buf[4+6] & 0x1f) << 8; */ + /* int z2 = buf[1+6]; */ + /* int w2 = buf[0+6] >> 4; */ + /* int finger2 = buf[0+6] & 7; */ + + /* int dx = (int8_t)buf[12]; */ + /* int dy = (int8_t)buf[13]; */ + int finger2_pressed; + + /* printk("x %4d, y %4d, z %3d, w %2d, F %d, 2nd: x %4d, y %4d, z %3d, w %2d, F %d, dx %4d, dy %4d\n", */ + /* x, y, z, w, finger, */ + /* x2, y2, z2, w2, finger2, */ + /* dx, dy); */ + + base = 2; + for (f = 0; f < 2; f++) { + uint32_t flip_flag = SYNAPTICS_FLIP_X; + for (a = 0; a < 2; a++) { + int p = buf[base + 1]; + p |= (uint16_t)(buf[base] & 0x1f) << 8; + if (ts->flags & flip_flag) + p = ts->max[a] - p; + if (ts->flags & SYNAPTICS_SNAP_TO_INACTIVE_EDGE) { + if (ts->snap_state[f][a]) { + if (p <= ts->snap_down_off[a]) + p = ts->snap_down[a]; + else if (p >= ts->snap_up_off[a]) + p = ts->snap_up[a]; + else + ts->snap_state[f][a] = 0; + } else { + if (p <= ts->snap_down_on[a]) { + p = ts->snap_down[a]; + ts->snap_state[f][a] = 1; + } else if (p >= ts->snap_up_on[a]) { + p = ts->snap_up[a]; + ts->snap_state[f][a] = 1; + } + } + } + pos[f][a] = p; + base += 2; + flip_flag <<= 1; + } + base += 2; + if (ts->flags & SYNAPTICS_SWAP_XY) + swap(pos[f][0], pos[f][1]); + } + if (z) { + input_report_abs(ts->input_dev, ABS_X, pos[0][0]); + input_report_abs(ts->input_dev, ABS_Y, pos[0][1]); + } + input_report_abs(ts->input_dev, ABS_PRESSURE, z); + input_report_abs(ts->input_dev, ABS_TOOL_WIDTH, w); + input_report_key(ts->input_dev, BTN_TOUCH, finger); + finger2_pressed = finger > 1 && finger != 7; + input_report_key(ts->input_dev, BTN_2, finger2_pressed); + if (finger2_pressed) { + input_report_abs(ts->input_dev, ABS_HAT0X, pos[1][0]); + input_report_abs(ts->input_dev, ABS_HAT0Y, pos[1][1]); + } + + if (!finger) + z = 0; + input_report_abs(ts->input_dev, ABS_MT_TOUCH_MAJOR, z); + input_report_abs(ts->input_dev, ABS_MT_WIDTH_MAJOR, w); + input_report_abs(ts->input_dev, ABS_MT_POSITION_X, pos[0][0]); + input_report_abs(ts->input_dev, ABS_MT_POSITION_Y, pos[0][1]); + input_mt_sync(ts->input_dev); + if (finger2_pressed) { + input_report_abs(ts->input_dev, ABS_MT_TOUCH_MAJOR, z); + input_report_abs(ts->input_dev, ABS_MT_WIDTH_MAJOR, w); + input_report_abs(ts->input_dev, ABS_MT_POSITION_X, pos[1][0]); + input_report_abs(ts->input_dev, ABS_MT_POSITION_Y, pos[1][1]); + input_mt_sync(ts->input_dev); + } else if (ts->reported_finger_count > 1) { + input_report_abs(ts->input_dev, ABS_MT_TOUCH_MAJOR, 0); + input_report_abs(ts->input_dev, ABS_MT_WIDTH_MAJOR, 0); + input_mt_sync(ts->input_dev); + } + ts->reported_finger_count = finger; + input_sync(ts->input_dev); + } + } + } + if (ts->use_irq) + enable_irq(ts->client->irq); +} + +static enum hrtimer_restart synaptics_ts_timer_func(struct hrtimer *timer) +{ + struct synaptics_ts_data *ts = container_of(timer, struct synaptics_ts_data, timer); + /* printk("synaptics_ts_timer_func\n"); */ + + queue_work(synaptics_wq, &ts->work); + + hrtimer_start(&ts->timer, ktime_set(0, 12500000), HRTIMER_MODE_REL); + return HRTIMER_NORESTART; +} + +static irqreturn_t synaptics_ts_irq_handler(int irq, void *dev_id) +{ + struct synaptics_ts_data *ts = dev_id; + + /* printk("synaptics_ts_irq_handler\n"); */ + disable_irq_nosync(ts->client->irq); + queue_work(synaptics_wq, &ts->work); + return IRQ_HANDLED; +} + +static int synaptics_ts_probe( + struct i2c_client *client, const struct i2c_device_id *id) +{ + struct synaptics_ts_data *ts; + uint8_t buf0[4]; + uint8_t buf1[8]; + struct i2c_msg msg[2]; + int ret = 0; + uint16_t max_x, max_y; + int fuzz_x, fuzz_y, fuzz_p, fuzz_w; + struct synaptics_i2c_rmi_platform_data *pdata; + unsigned long irqflags; + int inactive_area_left; + int inactive_area_right; + int inactive_area_top; + int inactive_area_bottom; + int snap_left_on; + int snap_left_off; + int snap_right_on; + int snap_right_off; + int snap_top_on; + int snap_top_off; + int snap_bottom_on; + int snap_bottom_off; + uint32_t panel_version; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + printk(KERN_ERR "synaptics_ts_probe: need I2C_FUNC_I2C\n"); + ret = -ENODEV; + goto err_check_functionality_failed; + } + + ts = kzalloc(sizeof(*ts), GFP_KERNEL); + if (ts == NULL) { + ret = -ENOMEM; + goto err_alloc_data_failed; + } + INIT_WORK(&ts->work, synaptics_ts_work_func); + ts->client = client; + i2c_set_clientdata(client, ts); + pdata = client->dev.platform_data; + if (pdata) + ts->power = pdata->power; + if (ts->power) { + ret = ts->power(1); + if (ret < 0) { + printk(KERN_ERR "synaptics_ts_probe power on failed\n"); + goto err_power_failed; + } + } + + ret = i2c_smbus_write_byte_data(ts->client, 0xf4, 0x01); /* device command = reset */ + if (ret < 0) { + printk(KERN_ERR "i2c_smbus_write_byte_data failed\n"); + /* fail? */ + } + { + int retry = 10; + while (retry-- > 0) { + ret = i2c_smbus_read_byte_data(ts->client, 0xe4); + if (ret >= 0) + break; + msleep(100); + } + } + if (ret < 0) { + printk(KERN_ERR "i2c_smbus_read_byte_data failed\n"); + goto err_detect_failed; + } + printk(KERN_INFO "synaptics_ts_probe: Product Major Version %x\n", ret); + panel_version = ret << 8; + ret = i2c_smbus_read_byte_data(ts->client, 0xe5); + if (ret < 0) { + printk(KERN_ERR "i2c_smbus_read_byte_data failed\n"); + goto err_detect_failed; + } + printk(KERN_INFO "synaptics_ts_probe: Product Minor Version %x\n", ret); + panel_version |= ret; + + ret = i2c_smbus_read_byte_data(ts->client, 0xe3); + if (ret < 0) { + printk(KERN_ERR "i2c_smbus_read_byte_data failed\n"); + goto err_detect_failed; + } + printk(KERN_INFO "synaptics_ts_probe: product property %x\n", ret); + + if (pdata) { + while (pdata->version > panel_version) + pdata++; + ts->flags = pdata->flags; + ts->sensitivity_adjust = pdata->sensitivity_adjust; + irqflags = pdata->irqflags; + inactive_area_left = pdata->inactive_left; + inactive_area_right = pdata->inactive_right; + inactive_area_top = pdata->inactive_top; + inactive_area_bottom = pdata->inactive_bottom; + snap_left_on = pdata->snap_left_on; + snap_left_off = pdata->snap_left_off; + snap_right_on = pdata->snap_right_on; + snap_right_off = pdata->snap_right_off; + snap_top_on = pdata->snap_top_on; + snap_top_off = pdata->snap_top_off; + snap_bottom_on = pdata->snap_bottom_on; + snap_bottom_off = pdata->snap_bottom_off; + fuzz_x = pdata->fuzz_x; + fuzz_y = pdata->fuzz_y; + fuzz_p = pdata->fuzz_p; + fuzz_w = pdata->fuzz_w; + } else { + irqflags = 0; + inactive_area_left = 0; + inactive_area_right = 0; + inactive_area_top = 0; + inactive_area_bottom = 0; + snap_left_on = 0; + snap_left_off = 0; + snap_right_on = 0; + snap_right_off = 0; + snap_top_on = 0; + snap_top_off = 0; + snap_bottom_on = 0; + snap_bottom_off = 0; + fuzz_x = 0; + fuzz_y = 0; + fuzz_p = 0; + fuzz_w = 0; + } + + ret = i2c_smbus_read_byte_data(ts->client, 0xf0); + if (ret < 0) { + printk(KERN_ERR "i2c_smbus_read_byte_data failed\n"); + goto err_detect_failed; + } + printk(KERN_INFO "synaptics_ts_probe: device control %x\n", ret); + + ret = i2c_smbus_read_byte_data(ts->client, 0xf1); + if (ret < 0) { + printk(KERN_ERR "i2c_smbus_read_byte_data failed\n"); + goto err_detect_failed; + } + printk(KERN_INFO "synaptics_ts_probe: interrupt enable %x\n", ret); + + ret = i2c_smbus_write_byte_data(ts->client, 0xf1, 0); /* disable interrupt */ + if (ret < 0) { + printk(KERN_ERR "i2c_smbus_write_byte_data failed\n"); + goto err_detect_failed; + } + + msg[0].addr = ts->client->addr; + msg[0].flags = 0; + msg[0].len = 1; + msg[0].buf = buf0; + buf0[0] = 0xe0; + msg[1].addr = ts->client->addr; + msg[1].flags = I2C_M_RD; + msg[1].len = 8; + msg[1].buf = buf1; + ret = i2c_transfer(ts->client->adapter, msg, 2); + if (ret < 0) { + printk(KERN_ERR "i2c_transfer failed\n"); + goto err_detect_failed; + } + printk(KERN_INFO "synaptics_ts_probe: 0xe0: %x %x %x %x %x %x %x %x\n", + buf1[0], buf1[1], buf1[2], buf1[3], + buf1[4], buf1[5], buf1[6], buf1[7]); + + ret = i2c_smbus_write_byte_data(ts->client, 0xff, 0x10); /* page select = 0x10 */ + if (ret < 0) { + printk(KERN_ERR "i2c_smbus_write_byte_data failed for page select\n"); + goto err_detect_failed; + } + ret = i2c_smbus_read_word_data(ts->client, 0x02); + if (ret < 0) { + printk(KERN_ERR "i2c_smbus_read_word_data failed\n"); + goto err_detect_failed; + } + ts->has_relative_report = !(ret & 0x100); + printk(KERN_INFO "synaptics_ts_probe: Sensor properties %x\n", ret); + ret = i2c_smbus_read_word_data(ts->client, 0x04); + if (ret < 0) { + printk(KERN_ERR "i2c_smbus_read_word_data failed\n"); + goto err_detect_failed; + } + ts->max[0] = max_x = (ret >> 8 & 0xff) | ((ret & 0x1f) << 8); + ret = i2c_smbus_read_word_data(ts->client, 0x06); + if (ret < 0) { + printk(KERN_ERR "i2c_smbus_read_word_data failed\n"); + goto err_detect_failed; + } + ts->max[1] = max_y = (ret >> 8 & 0xff) | ((ret & 0x1f) << 8); + if (ts->flags & SYNAPTICS_SWAP_XY) + swap(max_x, max_y); + + ret = synaptics_init_panel(ts); /* will also switch back to page 0x04 */ + if (ret < 0) { + printk(KERN_ERR "synaptics_init_panel failed\n"); + goto err_detect_failed; + } + + ts->input_dev = input_allocate_device(); + if (ts->input_dev == NULL) { + ret = -ENOMEM; + printk(KERN_ERR "synaptics_ts_probe: Failed to allocate input device\n"); + goto err_input_dev_alloc_failed; + } + ts->input_dev->name = "synaptics-rmi-touchscreen"; + set_bit(EV_SYN, ts->input_dev->evbit); + set_bit(EV_KEY, ts->input_dev->evbit); + set_bit(BTN_TOUCH, ts->input_dev->keybit); + set_bit(BTN_2, ts->input_dev->keybit); + set_bit(EV_ABS, ts->input_dev->evbit); + inactive_area_left = inactive_area_left * max_x / 0x10000; + inactive_area_right = inactive_area_right * max_x / 0x10000; + inactive_area_top = inactive_area_top * max_y / 0x10000; + inactive_area_bottom = inactive_area_bottom * max_y / 0x10000; + snap_left_on = snap_left_on * max_x / 0x10000; + snap_left_off = snap_left_off * max_x / 0x10000; + snap_right_on = snap_right_on * max_x / 0x10000; + snap_right_off = snap_right_off * max_x / 0x10000; + snap_top_on = snap_top_on * max_y / 0x10000; + snap_top_off = snap_top_off * max_y / 0x10000; + snap_bottom_on = snap_bottom_on * max_y / 0x10000; + snap_bottom_off = snap_bottom_off * max_y / 0x10000; + fuzz_x = fuzz_x * max_x / 0x10000; + fuzz_y = fuzz_y * max_y / 0x10000; + ts->snap_down[!!(ts->flags & SYNAPTICS_SWAP_XY)] = -inactive_area_left; + ts->snap_up[!!(ts->flags & SYNAPTICS_SWAP_XY)] = max_x + inactive_area_right; + ts->snap_down[!(ts->flags & SYNAPTICS_SWAP_XY)] = -inactive_area_top; + ts->snap_up[!(ts->flags & SYNAPTICS_SWAP_XY)] = max_y + inactive_area_bottom; + ts->snap_down_on[!!(ts->flags & SYNAPTICS_SWAP_XY)] = snap_left_on; + ts->snap_down_off[!!(ts->flags & SYNAPTICS_SWAP_XY)] = snap_left_off; + ts->snap_up_on[!!(ts->flags & SYNAPTICS_SWAP_XY)] = max_x - snap_right_on; + ts->snap_up_off[!!(ts->flags & SYNAPTICS_SWAP_XY)] = max_x - snap_right_off; + ts->snap_down_on[!(ts->flags & SYNAPTICS_SWAP_XY)] = snap_top_on; + ts->snap_down_off[!(ts->flags & SYNAPTICS_SWAP_XY)] = snap_top_off; + ts->snap_up_on[!(ts->flags & SYNAPTICS_SWAP_XY)] = max_y - snap_bottom_on; + ts->snap_up_off[!(ts->flags & SYNAPTICS_SWAP_XY)] = max_y - snap_bottom_off; + printk(KERN_INFO "synaptics_ts_probe: max_x %d, max_y %d\n", max_x, max_y); + printk(KERN_INFO "synaptics_ts_probe: inactive_x %d %d, inactive_y %d %d\n", + inactive_area_left, inactive_area_right, + inactive_area_top, inactive_area_bottom); + printk(KERN_INFO "synaptics_ts_probe: snap_x %d-%d %d-%d, snap_y %d-%d %d-%d\n", + snap_left_on, snap_left_off, snap_right_on, snap_right_off, + snap_top_on, snap_top_off, snap_bottom_on, snap_bottom_off); + input_set_abs_params(ts->input_dev, ABS_X, -inactive_area_left, max_x + inactive_area_right, fuzz_x, 0); + input_set_abs_params(ts->input_dev, ABS_Y, -inactive_area_top, max_y + inactive_area_bottom, fuzz_y, 0); + input_set_abs_params(ts->input_dev, ABS_PRESSURE, 0, 255, fuzz_p, 0); + input_set_abs_params(ts->input_dev, ABS_TOOL_WIDTH, 0, 15, fuzz_w, 0); + input_set_abs_params(ts->input_dev, ABS_HAT0X, -inactive_area_left, max_x + inactive_area_right, fuzz_x, 0); + input_set_abs_params(ts->input_dev, ABS_HAT0Y, -inactive_area_top, max_y + inactive_area_bottom, fuzz_y, 0); + input_set_abs_params(ts->input_dev, ABS_MT_POSITION_X, -inactive_area_left, max_x + inactive_area_right, fuzz_x, 0); + input_set_abs_params(ts->input_dev, ABS_MT_POSITION_Y, -inactive_area_top, max_y + inactive_area_bottom, fuzz_y, 0); + input_set_abs_params(ts->input_dev, ABS_MT_TOUCH_MAJOR, 0, 255, fuzz_p, 0); + input_set_abs_params(ts->input_dev, ABS_MT_WIDTH_MAJOR, 0, 15, fuzz_w, 0); + /* ts->input_dev->name = ts->keypad_info->name; */ + ret = input_register_device(ts->input_dev); + if (ret) { + printk(KERN_ERR "synaptics_ts_probe: Unable to register %s input device\n", ts->input_dev->name); + goto err_input_register_device_failed; + } + if (client->irq) { + ret = request_irq(client->irq, synaptics_ts_irq_handler, irqflags, client->name, ts); + if (ret == 0) { + ret = i2c_smbus_write_byte_data(ts->client, 0xf1, 0x01); /* enable abs int */ + if (ret) + free_irq(client->irq, ts); + } + if (ret == 0) + ts->use_irq = 1; + else + dev_err(&client->dev, "request_irq failed\n"); + } + if (!ts->use_irq) { + hrtimer_init(&ts->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + ts->timer.function = synaptics_ts_timer_func; + hrtimer_start(&ts->timer, ktime_set(1, 0), HRTIMER_MODE_REL); + } +#ifdef CONFIG_HAS_EARLYSUSPEND + ts->early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN + 1; + ts->early_suspend.suspend = synaptics_ts_early_suspend; + ts->early_suspend.resume = synaptics_ts_late_resume; + register_early_suspend(&ts->early_suspend); +#endif + + printk(KERN_INFO "synaptics_ts_probe: Start touchscreen %s in %s mode\n", ts->input_dev->name, ts->use_irq ? "interrupt" : "polling"); + + return 0; + +err_input_register_device_failed: + input_free_device(ts->input_dev); + +err_input_dev_alloc_failed: +err_detect_failed: +err_power_failed: + kfree(ts); +err_alloc_data_failed: +err_check_functionality_failed: + return ret; +} + +static int synaptics_ts_remove(struct i2c_client *client) +{ + struct synaptics_ts_data *ts = i2c_get_clientdata(client); + unregister_early_suspend(&ts->early_suspend); + if (ts->use_irq) + free_irq(client->irq, ts); + else + hrtimer_cancel(&ts->timer); + input_unregister_device(ts->input_dev); + kfree(ts); + return 0; +} + +static int synaptics_ts_suspend(struct i2c_client *client, pm_message_t mesg) +{ + int ret; + struct synaptics_ts_data *ts = i2c_get_clientdata(client); + + if (ts->use_irq) + disable_irq(client->irq); + else + hrtimer_cancel(&ts->timer); + ret = cancel_work_sync(&ts->work); + if (ret && ts->use_irq) /* if work was pending disable-count is now 2 */ + enable_irq(client->irq); + ret = i2c_smbus_write_byte_data(ts->client, 0xf1, 0); /* disable interrupt */ + if (ret < 0) + printk(KERN_ERR "synaptics_ts_suspend: i2c_smbus_write_byte_data failed\n"); + + ret = i2c_smbus_write_byte_data(client, 0xf0, 0x86); /* deep sleep */ + if (ret < 0) + printk(KERN_ERR "synaptics_ts_suspend: i2c_smbus_write_byte_data failed\n"); + if (ts->power) { + ret = ts->power(0); + if (ret < 0) + printk(KERN_ERR "synaptics_ts_resume power off failed\n"); + } + return 0; +} + +static int synaptics_ts_resume(struct i2c_client *client) +{ + int ret; + struct synaptics_ts_data *ts = i2c_get_clientdata(client); + + if (ts->power) { + ret = ts->power(1); + if (ret < 0) + printk(KERN_ERR "synaptics_ts_resume power on failed\n"); + } + + synaptics_init_panel(ts); + + if (ts->use_irq) + enable_irq(client->irq); + + if (!ts->use_irq) + hrtimer_start(&ts->timer, ktime_set(1, 0), HRTIMER_MODE_REL); + else + i2c_smbus_write_byte_data(ts->client, 0xf1, 0x01); /* enable abs int */ + + return 0; +} + +#ifdef CONFIG_HAS_EARLYSUSPEND +static void synaptics_ts_early_suspend(struct early_suspend *h) +{ + struct synaptics_ts_data *ts; + ts = container_of(h, struct synaptics_ts_data, early_suspend); + synaptics_ts_suspend(ts->client, PMSG_SUSPEND); +} + +static void synaptics_ts_late_resume(struct early_suspend *h) +{ + struct synaptics_ts_data *ts; + ts = container_of(h, struct synaptics_ts_data, early_suspend); + synaptics_ts_resume(ts->client); +} +#endif + +static const struct i2c_device_id synaptics_ts_id[] = { + { SYNAPTICS_I2C_RMI_NAME, 0 }, + { } +}; + +static struct i2c_driver synaptics_ts_driver = { + .probe = synaptics_ts_probe, + .remove = synaptics_ts_remove, +#ifndef CONFIG_HAS_EARLYSUSPEND + .suspend = synaptics_ts_suspend, + .resume = synaptics_ts_resume, +#endif + .id_table = synaptics_ts_id, + .driver = { + .name = SYNAPTICS_I2C_RMI_NAME, + }, +}; + +static int __devinit synaptics_ts_init(void) +{ + synaptics_wq = create_singlethread_workqueue("synaptics_wq"); + if (!synaptics_wq) + return -ENOMEM; + return i2c_add_driver(&synaptics_ts_driver); +} + +static void __exit synaptics_ts_exit(void) +{ + i2c_del_driver(&synaptics_ts_driver); + if (synaptics_wq) + destroy_workqueue(synaptics_wq); +} + +module_init(synaptics_ts_init); +module_exit(synaptics_ts_exit); + +MODULE_DESCRIPTION("Synaptics Touchscreen Driver"); +MODULE_LICENSE("GPL"); |