diff options
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/input/keyboard/Kconfig | 12 | ||||
-rw-r--r-- | drivers/input/keyboard/Makefile | 3 | ||||
-rw-r--r-- | drivers/input/keyboard/db5500_keypad.c | 799 | ||||
-rw-r--r-- | drivers/input/keyboard/gpio_keys.c | 21 | ||||
-rw-r--r-- | drivers/input/keyboard/nomadik-ske-keypad.c | 770 | ||||
-rw-r--r-- | drivers/input/keyboard/stmpe-keypad.c | 89 | ||||
-rw-r--r-- | drivers/input/misc/Kconfig | 32 | ||||
-rw-r--r-- | drivers/input/misc/Makefile | 3 | ||||
-rw-r--r-- | drivers/input/misc/ab5500-accdet.c | 284 | ||||
-rw-r--r-- | drivers/input/misc/ab8500-accdet.c | 464 | ||||
-rw-r--r-- | drivers/input/misc/ab8500-ponkey.c | 213 | ||||
-rw-r--r-- | drivers/input/misc/abx500-accdet.c | 1019 | ||||
-rw-r--r-- | drivers/input/misc/ste_ff_vibra.c | 234 | ||||
-rw-r--r-- | drivers/input/touchscreen/bu21013_ts.c | 499 | ||||
-rw-r--r-- | drivers/input/touchscreen/synaptics_i2c_rmi.c | 675 | ||||
-rw-r--r-- | drivers/staging/ste_rmi4/board-mop500-u8500uib-rmi4.c | 5 | ||||
-rw-r--r-- | drivers/staging/ste_rmi4/synaptics_i2c_rmi4.c | 316 | ||||
-rw-r--r-- | drivers/staging/ste_rmi4/synaptics_i2c_rmi4.h | 1 |
18 files changed, 5033 insertions, 406 deletions
diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig index f354813a13e..8aba6cc2bac 100644 --- a/drivers/input/keyboard/Kconfig +++ b/drivers/input/keyboard/Kconfig @@ -151,6 +151,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 @@ -381,7 +391,7 @@ config KEYBOARD_NEWTON To compile this driver as a module, choose M here: the module will be called newtonkbd. -config KEYBOARD_NOMADIK +config KEYBOARD_NOMADIK_SKE tristate "ST-Ericsson Nomadik SKE keyboard" depends on PLAT_NOMADIK help diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile index df7061f1291..90a01405e51 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_GPIO_POLLED) += gpio_keys_polled.o @@ -31,7 +32,7 @@ obj-$(CONFIG_KEYBOARD_MAX7359) += max7359_keypad.o obj-$(CONFIG_KEYBOARD_MCS) += mcs_touchkey.o obj-$(CONFIG_KEYBOARD_MPR121) += mpr121_touchkey.o obj-$(CONFIG_KEYBOARD_NEWTON) += newtonkbd.o -obj-$(CONFIG_KEYBOARD_NOMADIK) += nomadik-ske-keypad.o +obj-$(CONFIG_KEYBOARD_NOMADIK_SKE) += nomadik-ske-keypad.o obj-$(CONFIG_KEYBOARD_OMAP) += omap-keypad.o obj-$(CONFIG_KEYBOARD_OMAP4) += omap4-keypad.o obj-$(CONFIG_KEYBOARD_OPENCORES) += opencores-kbd.o diff --git a/drivers/input/keyboard/db5500_keypad.c b/drivers/input/keyboard/db5500_keypad.c new file mode 100644 index 00000000000..729775d99e8 --- /dev/null +++ b/drivers/input/keyboard/db5500_keypad.c @@ -0,0 +1,799 @@ +/* + * 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> +#include <linux/regulator/consumer.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_ROW_SHIFT 3 +#define KEYPAD_KEYMAP_SIZE \ + (KEYPAD_MAX_ROWS * KEYPAD_MAX_COLS) + +#define KEY_PRESSED_DELAY 10 +/** + * 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 + * @regulator : regulator used by keypad + * @switch_work : delayed work variable for switching to gpio + * @gpio_work : delayed work variable for reporting key event in gpio mode + * @previous_set: previous set of registers + * @enable : flag to enable the driver event + * @enable_on_resume: set if keypad should be enabled on resume + * @valid_key : hold the state of valid key press + * @db5500_rows : rows gpio array for db5500 keypad + * @db5500_cols : cols gpio array for db5500 keypad + * @gpio_input_irq : array for gpio irqs + * @gpio_row : gpio row + * @gpio_col : gpio_col + */ +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; + struct regulator *regulator; + struct delayed_work switch_work; + struct delayed_work gpio_work; + u8 previous_set[KEYPAD_MAX_ROWS]; + bool enable; + bool enable_on_resume; + bool valid_key; + int db5500_rows[KEYPAD_MAX_ROWS]; + int db5500_cols[KEYPAD_MAX_COLS]; + int gpio_input_irq[KEYPAD_MAX_ROWS]; + int gpio_row; + int gpio_col; +}; + +/** + * db5500_keypad_report() - reports the keypad event + * @keypad: pointer to device structure + * @row: row value of keypad + * @curr: current event + * @previous: previous event + * + * This function uses to reports the event of the keypad + * and returns NONE. + * + * 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 void db5500_keypad_scan(struct db5500_keypad *keypad) +{ + 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; + } + + 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; + + 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; +} + +/** + * db5500_keypad_writel() - write into keypad registers + * @keypad: pointer to device structure + * @val: value to write into register + * @reg: register offset + * + * This function uses to write into the keypad registers + * and returns NONE. + */ +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); +} + +/** + * db5500_keypad_chip_init() - initialize the keypad chip + * @keypad: pointer to device structure + * + * This function uses to initializes the keypad controller + * and returns integer. + */ +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_mode_enable(struct db5500_keypad *keypad, bool enable) +{ + int i; + + if (!enable) { + db5500_keypad_writel(keypad, 0, KEYPAD_CTR); + db5500_keypad_writel(keypad, 0, KEYPAD_INT_ENABLE); + 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(); + db5500_keypad_chip_init(keypad); + } +} + +static void db5500_gpio_switch_work(struct work_struct *work) +{ + struct db5500_keypad *keypad = container_of(work, + struct db5500_keypad, switch_work.work); + + db5500_mode_enable(keypad, false); + keypad->enable = false; +} + +static void db5500_gpio_release_work(struct work_struct *work) +{ + int code; + struct db5500_keypad *keypad = container_of(work, + struct db5500_keypad, gpio_work.work); + struct input_dev *input = keypad->input; + + code = MATRIX_SCAN_CODE(keypad->gpio_col, keypad->gpio_row, + 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 db5500_read_get_gpio_row(struct db5500_keypad *keypad) +{ + int row; + int value = 0; + int ret; + + /* read all rows GPIO data register values */ + for (row = 0; row < keypad->board->krow; row++) { + ret = gpio_get_value(keypad->db5500_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 db5500_set_cols(struct db5500_keypad *keypad, int col) +{ + int i, ret; + int value; + + /* + * Set all columns except the requested column + * output pin as high + */ + for (i = 0; i < keypad->board->kcol; i++) { + if (i == col) + value = 0; + else + value = 1; + ret = gpio_request(keypad->db5500_cols[i], "db5500-kpd"); + + if (ret < 0) { + pr_err("db5500_set_cols: gpio request failed\n"); + continue; + } + + gpio_direction_output(keypad->db5500_cols[i], value); + gpio_free(keypad->db5500_cols[i]); + } +} + +static void db5500_free_cols(struct db5500_keypad *keypad) +{ + int i, ret; + + for (i = 0; i < keypad->board->kcol; i++) { + ret = gpio_request(keypad->db5500_cols[i], "db5500-kpd"); + + if (ret < 0) { + pr_err("db5500_free_cols: gpio request failed\n"); + continue; + } + + gpio_direction_output(keypad->db5500_cols[i], 0); + gpio_free(keypad->db5500_cols[i]); + } +} + +static void db5500_manual_scan(struct db5500_keypad *keypad) +{ + int row; + int col; + + keypad->valid_key = false; + + for (col = 0; col < keypad->board->kcol; col++) { + db5500_set_cols(keypad, col); + row = db5500_read_get_gpio_row(keypad); + if (row >= 0) { + keypad->valid_key = true; + keypad->gpio_row = row; + keypad->gpio_col = col; + break; + } + } + db5500_free_cols(keypad); +} + +static irqreturn_t db5500_keypad_gpio_irq(int irq, void *dev_id) +{ + struct db5500_keypad *keypad = dev_id; + + if (!gpio_get_value(IRQ_TO_GPIO(irq))) { + db5500_manual_scan(keypad); + if (!keypad->enable) { + keypad->enable = true; + db5500_mode_enable(keypad, true); + } + + /* + * Schedule the work queue to change it to + * report the key pressed, if it is not detected in keypad mode. + */ + if (keypad->valid_key) { + schedule_delayed_work(&keypad->gpio_work, + KEY_PRESSED_DELAY); + } + } + + return IRQ_HANDLED; +} + +static irqreturn_t db5500_keypad_irq(int irq, void *dev_id) +{ + struct db5500_keypad *keypad = dev_id; + + cancel_delayed_work_sync(&keypad->gpio_work); + cancel_delayed_work_sync(&keypad->switch_work); + db5500_keypad_scan(keypad); + + /* + * Schedule the work queue to change it to + * GPIO mode, if there is no activity in keypad mode + */ + if (keypad->enable) + schedule_delayed_work(&keypad->switch_work, + keypad->board->switch_delay); + + return IRQ_HANDLED; +} + +/** + * db5500_keypad_probe() - Initialze the the keypad driver + * @pdev: pointer to platform device structure + * + * This function will allocate and initialize the instance + * data and request the irq and register to input subsystem driver. + */ +static int __devinit db5500_keypad_probe(struct platform_device *pdev) +{ + 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; + int i; + + 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; + } + + keypad->regulator = regulator_get(&pdev->dev, "v-ape"); + if (IS_ERR(keypad->regulator)) { + dev_err(&pdev->dev, "regulator_get failed\n"); + keypad->regulator = NULL; + ret = -EINVAL; + 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 = "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; + + INIT_DELAYED_WORK(&keypad->switch_work, db5500_gpio_switch_work); + INIT_DELAYED_WORK(&keypad->gpio_work, db5500_gpio_release_work); + + 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 < keypad->board->krow; i++) { + keypad->db5500_rows[i] = *plat->gpio_input_pins; + keypad->gpio_input_irq[i] = + GPIO_TO_IRQ(keypad->db5500_rows[i]); + plat->gpio_input_pins++; + } + + for (i = 0; i < keypad->board->kcol; i++) { + keypad->db5500_cols[i] = *plat->gpio_output_pins; + plat->gpio_output_pins++; + } + + for (i = 0; i < keypad->board->krow; i++) { + ret = request_threaded_irq(keypad->gpio_input_irq[i], + NULL, db5500_keypad_gpio_irq, + IRQF_TRIGGER_FALLING | IRQF_NO_SUSPEND, + "db5500-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, 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); + + clk_disable(keypad->clk); + regulator_disable(keypad->regulator); + return 0; + +out_unregisterinput: + input_unregister_device(input); + input = NULL; + clk_disable(keypad->clk); +out_freeinput: + input_free_device(input); +out_regulator_enable: + regulator_put(keypad->regulator); +out_regulator_get: + 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; +} + +/** + * db5500_keypad_remove() - Removes the keypad driver + * @pdev: pointer to platform device structure + * + * This function uses to remove the keypad + * driver and returns integer. + */ +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); + + cancel_delayed_work_sync(&keypad->gpio_work); + cancel_delayed_work_sync(&keypad->switch_work); + free_irq(keypad->irq, keypad); + input_unregister_device(keypad->input); + + clk_disable(keypad->clk); + clk_put(keypad->clk); + + if (keypad->board->exit) + keypad->board->exit(); + + regulator_put(keypad->regulator); + + iounmap(keypad->base); + + if (res) + release_mem_region(res->start, resource_size(res)); + + kfree(keypad); + + return 0; +} + +#ifdef CONFIG_PM +/** + * db5500_keypad_suspend() - suspend the keypad controller + * @dev: pointer to device structure + * + * This function is used to suspend the + * keypad controller and returns integer + */ +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 { + cancel_delayed_work_sync(&keypad->gpio_work); + cancel_delayed_work_sync(&keypad->switch_work); + disable_irq(irq); + keypad->enable_on_resume = keypad->enable; + if (keypad->enable) { + db5500_mode_enable(keypad, false); + keypad->enable = false; + } + } + + return 0; +} + +/** + * db5500_keypad_resume() - resume the keypad controller + * @dev: pointer to device structure + * + * This function is used to resume the keypad + * controller and returns integer. + */ +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 { + if (keypad->enable_on_resume && !keypad->enable) { + keypad->enable = true; + db5500_mode_enable(keypad, true); + /* + * Schedule the work queue to change it to GPIO mode + * if there is no activity keypad mode + */ + schedule_delayed_work(&keypad->switch_work, + keypad->board->switch_delay); + } + 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), +}; + +/** + * db5500_keypad_init() - Initialize the keypad driver + * + * This function uses to initializes the db5500 + * keypad driver and returns integer. + */ +static int __init db5500_keypad_init(void) +{ + return platform_driver_register(&db5500_keypad_driver); +} +module_init(db5500_keypad_init); + +/** + * db5500_keypad_exit() - De-initialize the keypad driver + * + * This function uses to de-initialize the db5500 + * keypad driver and returns none. + */ +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/gpio_keys.c b/drivers/input/keyboard/gpio_keys.c index 62bfce468f9..1fdf54bb4f6 100644 --- a/drivers/input/keyboard/gpio_keys.c +++ b/drivers/input/keyboard/gpio_keys.c @@ -29,6 +29,7 @@ #include <linux/of_platform.h> #include <linux/of_gpio.h> #include <linux/spinlock.h> +#include <linux/pm_runtime.h> struct gpio_button_data { const struct gpio_keys_button *button; @@ -46,6 +47,8 @@ struct gpio_keys_drvdata { struct input_dev *input; struct mutex disable_lock; unsigned int n_buttons; + bool enabled; + bool enable_after_suspend; int (*enable)(struct device *dev); void (*disable)(struct device *dev); struct gpio_button_data data[0]; @@ -524,6 +527,8 @@ static int gpio_keys_open(struct input_dev *input) { struct gpio_keys_drvdata *ddata = input_get_drvdata(input); + pm_runtime_get_sync(input->dev.parent); + ddata->enabled = true; return ddata->enable ? ddata->enable(input->dev.parent) : 0; } @@ -533,6 +538,8 @@ static void gpio_keys_close(struct input_dev *input) if (ddata->disable) ddata->disable(input->dev.parent); + ddata->enabled = false; + pm_runtime_put(input->dev.parent); } /* @@ -675,6 +682,7 @@ static int __devinit gpio_keys_probe(struct platform_device *pdev) ddata->n_buttons = pdata->nbuttons; ddata->enable = pdata->enable; ddata->disable = pdata->disable; + ddata->enabled = false; mutex_init(&ddata->disable_lock); platform_set_drvdata(pdev, ddata); @@ -691,6 +699,8 @@ static int __devinit gpio_keys_probe(struct platform_device *pdev) input->id.product = 0x0001; input->id.version = 0x0100; + pm_runtime_enable(&pdev->dev); + /* Enable auto repeat feature of Linux input subsystem */ if (pdata->rep) __set_bit(EV_REP, input->evbit); @@ -756,6 +766,8 @@ static int __devexit gpio_keys_remove(struct platform_device *pdev) struct input_dev *input = ddata->input; int i; + pm_runtime_disable(&pdev->dev); + sysfs_remove_group(&pdev->dev.kobj, &gpio_keys_attr_group); device_init_wakeup(&pdev->dev, 0); @@ -790,6 +802,10 @@ static int gpio_keys_suspend(struct device *dev) if (bdata->button->wakeup) enable_irq_wake(bdata->irq); } + } else { + ddata->enable_after_suspend = ddata->enabled; + if (ddata->enabled && ddata->disable) + ddata->disable(dev); } return 0; @@ -808,6 +824,11 @@ static int gpio_keys_resume(struct device *dev) if (gpio_is_valid(bdata->button->gpio)) gpio_keys_gpio_report_event(bdata); } + + if (!device_may_wakeup(dev) && ddata->enable_after_suspend + && ddata->enable) + ddata->enable(dev); + input_sync(ddata->input); return 0; diff --git a/drivers/input/keyboard/nomadik-ske-keypad.c b/drivers/input/keyboard/nomadik-ske-keypad.c index 101e245944e..6a0707f7f76 100644 --- a/drivers/input/keyboard/nomadik-ske-keypad.c +++ b/drivers/input/keyboard/nomadik-ske-keypad.c @@ -2,7 +2,7 @@ * 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 + * co-Author: Sundar Iyer <sundar.iyer@stericsson.com> for ST-Ericsson * * License terms:GNU General Public License (GPL) version 2 * @@ -12,6 +12,7 @@ #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> @@ -19,8 +20,10 @@ #include <linux/slab.h> #include <linux/clk.h> #include <linux/module.h> +#include <linux/regulator/consumer.h> #include <plat/ske.h> +#include <linux/gpio/nomadik.h> /* SKE_CR bits */ #define SKE_KPMLT (0x1 << 6) @@ -48,17 +51,38 @@ #define SKE_ASR3 0x2C #define SKE_NUM_ASRX_REGISTERS (4) +#define KEY_PRESSED_DELAY 10 + + +#define KEY_REPORTED 1 +#define KEY_PRESSED 2 /** * 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 + * @dev: Pointer to the structure device + * @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 + * @ske_keypad_lock: lock used while writting into registers + * @enable: flag to enable the driver event + * @enable_on_resume: set if keypad should be enabled on resume + * @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 + * @keys: matrix holding key status + * @scan_work: delayed work for scaning new key actions */ struct ske_keypad { + struct device *dev; int irq; void __iomem *reg_base; struct input_dev *input; @@ -66,6 +90,19 @@ struct ske_keypad { unsigned short keymap[SKE_KPD_KEYMAP_SIZE]; struct clk *clk; spinlock_t ske_keypad_lock; + bool enable; + bool enable_on_resume; + struct regulator *regulator; + int *gpio_input_irq; + int key_pressed; + struct delayed_work work; + int *ske_rows; + int *ske_cols; + int gpio_row; + int gpio_col; + struct delayed_work gpio_work; + u8 **keys; + struct delayed_work scan_work; }; static void ske_keypad_set_bits(struct ske_keypad *keypad, u16 addr, @@ -83,15 +120,15 @@ static void ske_keypad_set_bits(struct ske_keypad *keypad, u16 addr, spin_unlock(&keypad->ske_keypad_lock); } -/* +/** * ske_keypad_chip_init: init keypad controller configuration - * + * @keypad: pointer to device structure * Enable Multi key press detection, auto scan mode */ static int __init ske_keypad_chip_init(struct ske_keypad *keypad) { u32 value; - int timeout = 50; + int timeout = keypad->board->debounce_ms; /* check SKE_RIS to be 0 */ while ((readl(keypad->reg_base + SKE_RIS) != 0x00000000) && timeout--) @@ -100,7 +137,7 @@ static int __init ske_keypad_chip_init(struct ske_keypad *keypad) if (!timeout) return -EINVAL; - /* + /** * set debounce value * keypad dbounce is configured in DBCR[15:8] * dbounce value in steps of 32/32.768 ms @@ -115,7 +152,7 @@ static int __init ske_keypad_chip_init(struct ske_keypad *keypad) /* 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 */ @@ -134,14 +171,125 @@ static int __init ske_keypad_chip_init(struct ske_keypad *keypad) return 0; } +static void ske_mode_enable(struct ske_keypad *keypad, bool enable) +{ + int i; + + if (!enable) { + dev_dbg(keypad->dev, "%s disable keypad\n", __func__); + writel(0, keypad->reg_base + SKE_CR); + if (keypad->board->exit) + keypad->board->exit(); + for (i = 0; i < keypad->board->kconnected_rows; 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 { + dev_dbg(keypad->dev, "%s enable keypad\n", __func__); + regulator_enable(keypad->regulator); + clk_enable(keypad->clk); + for (i = 0; i < keypad->board->kconnected_rows; 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; + 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); + + if (row >= keypad->board->krow) + /* no more rows supported by this keypad */ + break; + + 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; + + dev_dbg(keypad->dev, + "%s key_pressed:%d code:%d row:%d col:%d\n", + __func__, keypad->key_pressed, code, row, col); + + if (keypad->key_pressed) + keypad->keys[row][col] |= KEY_PRESSED; + + num_of_rows--; + } while (num_of_rows); +} + static void ske_keypad_read_data(struct ske_keypad *keypad) { - struct input_dev *input = keypad->input; - u16 status; - int col = 0, row = 0, code; - int ske_asr, ske_ris, key_pressed, i; + 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. @@ -153,59 +301,274 @@ static void ske_keypad_read_data(struct ske_keypad *keypad) if (!ske_asr) continue; - /* now that ASRx is zero, find out the column x and row y*/ - if (ske_asr & 0xff) { + /* now that ASRx is zero, find out the coloumn x and row y */ + status = ske_asr & 0xff; + if (status) { col = i * 2; - status = ske_asr & 0xff; - } else { + if (col >= keypad->board->kcol) + /* no more columns supported by this keypad */ + break; + ske_keypad_report(keypad, status, col); + } + status = (ske_asr & 0xff00) >> 8; + if (status) { col = (i * 2) + 1; - status = (ske_asr & 0xff00) >> 8; + if (col >= keypad->board->kcol) + /* no more columns supported by this keypad */ + break; + ske_keypad_report(keypad, status, col); } + } +} - /* find out the row */ - row = __ffs(status); +static void ske_keypad_scan_work(struct work_struct *work) +{ + int timeout = 10; + int i, j, code; + struct ske_keypad *keypad = container_of(work, + struct ske_keypad, scan_work.work); + struct input_dev *input = keypad->input; - code = MATRIX_SCAN_CODE(row, col, SKE_KEYPAD_ROW_SHIFT); - ske_ris = readl(keypad->reg_base + SKE_RIS); - key_pressed = ske_ris & SKE_KPRISA; + /* Wait for autoscan to complete */ + while (readl(keypad->reg_base + SKE_CR) & SKE_KPASON) + cpu_relax(); - input_event(input, EV_MSC, MSC_SCAN, code); - input_report_key(input, keypad->keymap[code], key_pressed); - input_sync(input); + /* SKEx registers are stable and can be read */ + ske_keypad_read_data(keypad); + + /* Check for key actions */ + for (i = 0; i < keypad->board->krow; i++) { + for (j = 0; j < keypad->board->kcol; j++) { + switch (keypad->keys[i][j]) { + case KEY_REPORTED: + /** + * Key was reported but is no longer pressed, + * report it as released. + */ + code = MATRIX_SCAN_CODE(i, j, + SKE_KEYPAD_ROW_SHIFT); + input_event(input, EV_MSC, MSC_SCAN, code); + input_report_key(input, keypad->keymap[code], + 0); + input_sync(input); + keypad->keys[i][j] = 0; + dev_dbg(keypad->dev, + "%s Key release reported, code:%d " + "(key %d)\n", + __func__, code, keypad->keymap[code]); + break; + case KEY_PRESSED: + /* Key pressed but not yet reported, report */ + code = MATRIX_SCAN_CODE(i, j, + SKE_KEYPAD_ROW_SHIFT); + input_event(input, EV_MSC, MSC_SCAN, code); + input_report_key(input, keypad->keymap[code], + 1); + input_sync(input); + dev_dbg(keypad->dev, + "%s Key press reported, code:%d " + "(key %d)\n", + __func__, code, keypad->keymap[code]); + /* Intentional fall though */ + case (KEY_REPORTED | KEY_PRESSED): + /** + * Key pressed and reported, just reset + * KEY_PRESSED for next scan + */ + keypad->keys[i][j] = KEY_REPORTED; + break; + } + } + } + + if (keypad->key_pressed) { + /* + * Key still pressed, schedule work to poll changes in 100 ms + * After increasing the delay from 50 to 100 it is taking + * 2% to 3% load on average. + */ + schedule_delayed_work(&keypad->scan_work, + msecs_to_jiffies(100)); + } else { + /* For safty measure, clear interrupt once more */ + ske_keypad_set_bits(keypad, SKE_ICR, 0x0, SKE_KPICA); + + /* Wait for raw interrupt to clear */ + while ((readl(keypad->reg_base + SKE_RIS) & SKE_KPRISA) && + --timeout) { + udelay(10); + } + + if (!timeout) + dev_err(keypad->dev, + "%s Timeed out waiting on irq to clear\n", + __func__); + + /* enable auto scan interrupts */ + ske_keypad_set_bits(keypad, SKE_IMSC, 0x0, SKE_KPIMA); + + /** + * 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); } } +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); + + dev_dbg(keypad->dev, "%s Key press reported, code:%d (key %d)\n", + __func__, code, keypad->keymap[code]); + + 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 < keypad->board->kconnected_rows ; row++) { + ret = gpio_get_value(keypad->ske_rows[row]); + value += (1 << row) * ret; + } + + /* get the exact row */ + for (row = 0; row < keypad->board->kconnected_rows; 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 < keypad->board->kconnected_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 < keypad->board->kconnected_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->kconnected_cols; 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(NOMADIK_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 back to GPIO + * mode if there is no activity in SKE mode + */ + schedule_delayed_work(&keypad->work, + keypad->board->switch_delay); + } + /** + * Schedule delayed work to report key press 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; - int retries = 20; + cancel_delayed_work_sync(&keypad->gpio_work); + cancel_delayed_work_sync(&keypad->work); /* disable auto scan interrupt; mask the interrupt generated */ - ske_keypad_set_bits(keypad, SKE_IMSC, ~SKE_KPIMA, 0x0); + 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) && --retries) - msleep(5); - - if (retries) { - /* SKEx registers are stable and can be read */ - ske_keypad_read_data(keypad); - } - - /* enable auto scan interrupts */ - ske_keypad_set_bits(keypad, SKE_IMSC, 0x0, SKE_KPIMA); + schedule_delayed_work(&keypad->scan_work, 0); return IRQ_HANDLED; } static int __init ske_keypad_probe(struct platform_device *pdev) { - const struct ske_keypad_platform_data *plat = pdev->dev.platform_data; struct ske_keypad *keypad; + struct resource *res = NULL; struct input_dev *input; - struct resource *res; + struct clk *clk; + void __iomem *reg_base; + int ret = 0; int irq; - int error; + int i; + struct ske_keypad_platform_data *plat = pdev->dev.platform_data; if (!plat) { dev_err(&pdev->dev, "invalid keypad platform data\n"); @@ -219,42 +582,56 @@ static int __init ske_keypad_probe(struct platform_device *pdev) } res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!res) { + if (res == NULL) { dev_err(&pdev->dev, "missing platform resources\n"); - return -EINVAL; + return -ENXIO; } - keypad = kzalloc(sizeof(struct ske_keypad), GFP_KERNEL); - input = input_allocate_device(); - if (!keypad || !input) { - dev_err(&pdev->dev, "failed to allocate keypad memory\n"); - error = -ENOMEM; - goto err_free_mem; + 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; } - keypad->irq = irq; - keypad->board = plat; - keypad->input = input; - spin_lock_init(&keypad->ske_keypad_lock); + 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; + } - if (!request_mem_region(res->start, resource_size(res), pdev->name)) { - dev_err(&pdev->dev, "failed to request I/O memory\n"); - error = -EBUSY; - goto err_free_mem; + 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; } - keypad->reg_base = ioremap(res->start, resource_size(res)); - if (!keypad->reg_base) { - dev_err(&pdev->dev, "failed to remap I/O memory\n"); - error = -ENXIO; - goto err_free_mem_region; + /* 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; } + keypad->dev = &pdev->dev; - keypad->clk = clk_get(&pdev->dev, NULL); - if (IS_ERR(keypad->clk)) { - dev_err(&pdev->dev, "failed to get clk\n"); - error = PTR_ERR(keypad->clk); - goto err_iounmap; + 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; @@ -266,38 +643,153 @@ static int __init ske_keypad_probe(struct platform_device *pdev) 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); + 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); + INIT_DELAYED_WORK(&keypad->scan_work, ske_keypad_scan_work); + /* allocations are sane, we begin HW initialization */ clk_enable(keypad->clk); - /* go through board initialization helpers */ - if (keypad->board->init) - keypad->board->init(); + if (!keypad->board->init) { + dev_err(&pdev->dev, "init funtion not defined\n"); + ret = -EINVAL; + goto out_unregisterinput; + } - error = ske_keypad_chip_init(keypad); - if (error) { - dev_err(&pdev->dev, "unable to init keypad hardware\n"); - goto err_clk_disable; + if (keypad->board->init() < 0) { + dev_err(&pdev->dev, "keyboard init config failed\n"); + ret = -EINVAL; + goto out_unregisterinput; } - error = request_threaded_irq(keypad->irq, NULL, ske_keypad_irq, - IRQF_ONESHOT, "ske-keypad", keypad); - if (error) { - dev_err(&pdev->dev, "allocate irq %d failed\n", keypad->irq); - goto err_clk_disable; + 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; + } + + if (plat->kconnected_rows == 0) { + /* + * Board config data does not specify the number of connected + * rows and columns; assume that it matches the specified max + * values. + */ + plat->kconnected_rows = plat->krow; + plat->kconnected_cols = plat->kcol; + } + + /* this code doesn't currently support non-square keypad */ + if (plat->kconnected_rows != plat->kconnected_cols) { + dev_err(&pdev->dev, + "invalid keypad configuration (not square)\n"), + ret = -EINVAL; + goto out_unregisterinput; } - error = input_register_device(input); - if (error) { + if (plat->kconnected_rows > SKE_KPD_MAX_ROWS || + plat->kconnected_cols > SKE_KPD_MAX_COLS) { dev_err(&pdev->dev, - "unable to register input device: %d\n", error); - goto err_free_irq; + "invalid keypad configuration (too many rows/cols)\n"), + ret = -EINVAL; + goto out_unregisterinput; + } + + keypad->gpio_input_irq = kmalloc(sizeof(*keypad->gpio_input_irq) * + plat->kconnected_rows, + GFP_KERNEL); + if (!keypad->gpio_input_irq) { + dev_err(&pdev->dev, "failed to allocate input_irq memory\n"); + goto out_unregisterinput; + } + + keypad->ske_rows = kmalloc(sizeof(*keypad->ske_rows) * + plat->kconnected_rows, GFP_KERNEL); + if (!keypad->ske_rows) { + dev_err(&pdev->dev, "failed to allocate ske_rows memory\n"); + goto out_freemem_input_irq; + } + + keypad->ske_cols = kmalloc(sizeof(*keypad->ske_cols) * + plat->kconnected_cols, GFP_KERNEL); + if (!keypad->ske_cols) { + dev_err(&pdev->dev, "failed to allocate ske_cols memory\n"); + goto out_freemem_rows; + } + + keypad->keys = kzalloc(sizeof(*keypad->keys) * plat->krow, GFP_KERNEL); + if (!keypad->keys) { + dev_err(&pdev->dev, "failed to allocate keys:rows memory\n"); + goto out_freemem_cols; + } + for (i = 0; i < plat->krow; i++) { + keypad->keys[i] = kzalloc(sizeof(*keypad->keys[i]) * + plat->kcol, GFP_KERNEL); + if (!keypad->keys[i]) { + dev_err(&pdev->dev, + "failed to allocate keys:cols memory\n"); + goto out_freemem_keys; + } + } + + for (i = 0; i < plat->kconnected_rows; i++) { + keypad->ske_rows[i] = *plat->gpio_input_pins; + keypad->ske_cols[i] = *plat->gpio_output_pins; + keypad->gpio_input_irq[i] = + NOMADIK_GPIO_TO_IRQ(keypad->ske_rows[i]); + } + + for (i = 0; i < keypad->board->kconnected_rows; 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_freemem_keys; + } + 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_freemem_keys; + } + + /* 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) @@ -305,37 +797,80 @@ static int __init ske_keypad_probe(struct platform_device *pdev) platform_set_drvdata(pdev, keypad); + clk_disable(keypad->clk); + regulator_disable(keypad->regulator); + return 0; -err_free_irq: +out_free_irq: free_irq(keypad->irq, keypad); -err_clk_disable: +out_freemem_keys: + for (i = 0; i < plat->krow; i++) + kfree(keypad->keys[i]); + kfree(keypad->keys); +out_freemem_cols: + kfree(keypad->ske_cols); +out_freemem_rows: + kfree(keypad->ske_rows); +out_freemem_input_irq: + kfree(keypad->gpio_input_irq); +out_unregisterinput: + input_unregister_device(input); + input = NULL; clk_disable(keypad->clk); - clk_put(keypad->clk); -err_iounmap: - iounmap(keypad->reg_base); -err_free_mem_region: - release_mem_region(res->start, resource_size(res)); -err_free_mem: +out_freeinput: + regulator_disable(keypad->regulator); +out_regulator_enable: + regulator_put(keypad->regulator); +out_regulator_get: input_free_device(input); +out_freekeypad: kfree(keypad); - return error; +out_freeclk: + clk_put(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); + int i; - free_irq(keypad->irq, keypad); + cancel_delayed_work_sync(&keypad->gpio_work); + cancel_delayed_work_sync(&keypad->work); + cancel_delayed_work_sync(&keypad->scan_work); - input_unregister_device(keypad->input); + for (i = 0; i < keypad->board->krow; i++) + kfree(keypad->keys[i]); + kfree(keypad->keys); + kfree(keypad->ske_cols); + kfree(keypad->ske_rows); - clk_disable(keypad->clk); + input_unregister_device(keypad->input); + sysfs_remove_group(&pdev->dev.kobj, &ske_attr_group); + if (keypad->enable) + clk_disable(keypad->clk); clk_put(keypad->clk); - if (keypad->board->exit) + if (keypad->enable && keypad->board->exit) keypad->board->exit(); + else { + for (i = 0; i < keypad->board->krow; i++) { + disable_irq_nosync(keypad->gpio_input_irq[i]); + disable_irq_wake(keypad->gpio_input_irq[i]); + } + } + for (i = 0; i < keypad->board->krow; i++) + free_irq(keypad->gpio_input_irq[i], keypad); + + kfree(keypad->gpio_input_irq); + free_irq(keypad->irq, keypad); + regulator_put(keypad->regulator); iounmap(keypad->reg_base); release_mem_region(res->start, resource_size(res)); @@ -353,8 +888,19 @@ static int ske_keypad_suspend(struct device *dev) if (device_may_wakeup(dev)) enable_irq_wake(irq); - else - ske_keypad_set_bits(keypad, SKE_IMSC, ~SKE_KPIMA, 0x0); + else { + cancel_delayed_work_sync(&keypad->gpio_work); + cancel_delayed_work_sync(&keypad->work); + cancel_delayed_work_sync(&keypad->scan_work); + disable_irq(irq); + + keypad->enable_on_resume = keypad->enable; + + if (keypad->enable) { + ske_mode_enable(keypad, false); + keypad->enable = false; + } + } return 0; } @@ -367,8 +913,20 @@ static int ske_keypad_resume(struct device *dev) if (device_may_wakeup(dev)) disable_irq_wake(irq); - else - ske_keypad_set_bits(keypad, SKE_IMSC, 0x0, SKE_KPIMA); + else { + if (keypad->enable_on_resume && !keypad->enable) { + keypad->enable = true; + ske_mode_enable(keypad, true); + /* + * Schedule the work queue to change it to GPIO mode + * if there is no activity in SKE mode + */ + if (!keypad->key_pressed) + schedule_delayed_work(&keypad->work, + keypad->board->switch_delay); + } + enable_irq(irq); + } return 0; } @@ -399,6 +957,6 @@ static void __exit ske_keypad_exit(void) module_exit(ske_keypad_exit); MODULE_LICENSE("GPL v2"); -MODULE_AUTHOR("Naveen Kumar <naveen.gaddipati@stericsson.com> / Sundar Iyer <sundar.iyer@stericsson.com>"); +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 index 9397cf9c625..892335275dd 100644 --- a/drivers/input/keyboard/stmpe-keypad.c +++ b/drivers/input/keyboard/stmpe-keypad.c @@ -108,10 +108,52 @@ struct stmpe_keypad { 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; @@ -285,7 +327,7 @@ static int __devinit stmpe_keypad_probe(struct platform_device *pdev) goto out_freekeypad; } - input->name = "STMPE keypad"; + input->name = "STMPE-keypad"; input->id.bustype = BUS_I2C; input->dev.parent = &pdev->dev; @@ -332,10 +374,20 @@ static int __devinit stmpe_keypad_probe(struct platform_device *pdev) 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; @@ -354,6 +406,7 @@ static int __devexit stmpe_keypad_remove(struct platform_device *pdev) 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); @@ -362,9 +415,43 @@ static int __devexit stmpe_keypad_remove(struct platform_device *pdev) 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, +#ifdef CONFIG_PM + .driver.pm = &stmpe_keypad_dev_pm_ops, +#endif .probe = stmpe_keypad_probe, .remove = __devexit_p(stmpe_keypad_remove), }; diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig index 7faf4a7fcaa..dedd5d6cf7a 100644 --- a/drivers/input/misc/Kconfig +++ b/drivers/input/misc/Kconfig @@ -22,12 +22,26 @@ 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 && GPIO_AB8500 + help + Say Y here to enable AV accessory detection features for ST-Ericsson's + AB8500 Mix-Sig PMIC. + +config INPUT_AB5500_ACCDET + bool "AB5500 AV Accessory detection" + depends on AB5500_CORE && AB5500_GPADC + help + Say Y here to enable AV accessory detection features for ST-Ericsson's + AB5500 Mix-Sig PMIC. + config INPUT_AB8500_PONKEY - tristate "AB8500 Pon (PowerOn) Key" - depends on AB8500_CORE + 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 AB8500 - Mix-Sig PMIC. + 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. @@ -590,4 +604,14 @@ config INPUT_XEN_KBDDEV_FRONTEND To compile this driver as a module, choose M here: the module will be called xen-kbdfront. +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 f55cdf4916f..e9aa2d854bc 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) += abx500-accdet.o ab8500-accdet.o +obj-$(CONFIG_INPUT_AB5500_ACCDET) += abx500-accdet.o ab5500-accdet.o obj-$(CONFIG_INPUT_AB8500_PONKEY) += ab8500-ponkey.o obj-$(CONFIG_INPUT_AD714X) += ad714x.o obj-$(CONFIG_INPUT_AD714X_I2C) += ad714x-i2c.o @@ -55,3 +57,4 @@ obj-$(CONFIG_INPUT_WISTRON_BTNS) += wistron_btns.o obj-$(CONFIG_INPUT_WM831X_ON) += wm831x-on.o obj-$(CONFIG_INPUT_XEN_KBDDEV_FRONTEND) += xen-kbdfront.o obj-$(CONFIG_INPUT_YEALINK) += yealink.o +obj-$(CONFIG_INPUT_STE_FF_VIBRA) += ste_ff_vibra.o diff --git a/drivers/input/misc/ab5500-accdet.c b/drivers/input/misc/ab5500-accdet.c new file mode 100644 index 00000000000..fa8e2523126 --- /dev/null +++ b/drivers/input/misc/ab5500-accdet.c @@ -0,0 +1,284 @@ +/* + * 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> +#include <linux/delay.h> +#include <linux/mfd/abx500/ab5500.h> +#include <linux/mfd/abx500/ab5500-gpadc.h> +#include <linux/mfd/abx500.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/input/abx500-accdet.h> + +/* + * Register definition for accessory detection. + */ +#define AB5500_REGU_CTRL1_SPARE_REG 0x84 +#define AB5500_ACC_DET_DB1_REG 0x20 +#define AB5500_ACC_DET_DB2_REG 0x21 +#define AB5500_ACC_DET_CTRL_REG 0x23 +#define AB5500_VDENC_CTRL0 0x80 + +/* 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 0x02 + +static struct accessory_irq_descriptor ab5500_irq_desc[] = { + { + .irq = PLUG_IRQ, + .name = "acc_detedt1db_falling", + .isr = plug_irq_handler, + }, + { + .irq = UNPLUG_IRQ, + .name = "acc_detedt1db_rising", + .isr = unplug_irq_handler, + }, + { + .irq = BUTTON_PRESS_IRQ, + .name = "acc_detedt21db_falling", + .isr = button_press_irq_handler, + }, + { + .irq = BUTTON_RELEASE_IRQ, + .name = "acc_detedt21db_rising", + .isr = button_release_irq_handler, + }, +}; + +static struct accessory_regu_descriptor ab5500_regu_desc[] = { + { + .id = REGULATOR_VAMIC1, + .name = "v-amic", + }, +}; + + +/* + * configures accdet2 input on/off + */ +static void ab5500_config_accdetect2_hw(struct abx500_ad *dd, int enable) +{ + int ret = 0; + + if (!dd->accdet2_th_set) { + /* Configure accdetect21+22 thresholds */ + ret = abx500_set_register_interruptible(&dd->pdev->dev, + AB5500_BANK_FG_BATTCOM_ACC, + AB5500_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, + AB5500_BANK_FG_BATTCOM_ACC, + AB5500_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 + */ +static void ab5500_config_accdetect1_hw(struct abx500_ad *dd, int enable) +{ + int ret; + + if (!dd->accdet1_th_set) { + ret = abx500_set_register_interruptible(&dd->pdev->dev, + AB5500_BANK_FG_BATTCOM_ACC, + AB5500_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, + AB5500_BANK_FG_BATTCOM_ACC, + AB5500_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); +} + +/* + * returns the high level status whether some accessory is connected (1|0). + */ +static int ab5500_detect_plugged_in(struct abx500_ad *dd) +{ + u8 value = 0; + + int status = abx500_get_register_interruptible( + &dd->pdev->dev, + AB5500_BANK_IT, + AB5500_IT_SOURCE3_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 ab5500_meas_voltage_stable(struct abx500_ad *dd) +{ + int iterations = 2; + int v1, v2, dv; + + v1 = ab5500_gpadc_convert((struct ab5500_gpadc *)dd->gpadc, + ACC_DETECT2); + do { + msleep(1); + --iterations; + v2 = ab5500_gpadc_convert((struct ab5500_gpadc *)dd->gpadc, + ACC_DETECT2); + dv = abs(v2 - v1); + v1 = v2; + } while (iterations > 0 && dv > MAX_VOLT_DIFF); + + return v1; +} + +/* + * not implemented + */ +static int ab5500_meas_alt_voltage_stable(struct abx500_ad *dd) +{ + return -1; +} + +/* + * configures HW so that it is possible to make decision whether + * accessory is connected or not. + */ +static void ab5500_config_hw_test_plug_connected(struct abx500_ad *dd, + int enable) +{ + dev_dbg(&dd->pdev->dev, "%s:%d\n", __func__, enable); + + /* enable mic BIAS2 */ + if (enable) + accessory_regulator_enable(dd, REGULATOR_VAMIC1); +} + +/* + * configures HW so that carkit/headset detection can be accomplished. + */ +static void ab5500_config_hw_test_basic_carkit(struct abx500_ad *dd, int enable) +{ + /* enable mic BIAS2 */ + if (enable) + accessory_regulator_disable(dd, REGULATOR_VAMIC1); +} + +static u8 acc_det_ctrl_suspend_val; + +static void ab5500_turn_off_accdet_comparator(struct platform_device *pdev) +{ + struct abx500_ad *dd = platform_get_drvdata(pdev); + + /* Turn off AccDetect comparators and pull-up */ + (void) abx500_get_register_interruptible( + &dd->pdev->dev, + AB5500_BANK_FG_BATTCOM_ACC, + AB5500_ACC_DET_CTRL_REG, + &acc_det_ctrl_suspend_val); + (void) abx500_set_register_interruptible( + &dd->pdev->dev, + AB5500_BANK_FG_BATTCOM_ACC, + AB5500_ACC_DET_CTRL_REG, + 0); +} + +static void ab5500_turn_on_accdet_comparator(struct platform_device *pdev) +{ + struct abx500_ad *dd = platform_get_drvdata(pdev); + + /* Turn on AccDetect comparators and pull-up */ + (void) abx500_set_register_interruptible( + &dd->pdev->dev, + AB5500_BANK_FG_BATTCOM_ACC, + AB5500_ACC_DET_CTRL_REG, + acc_det_ctrl_suspend_val); +} + +static void *ab5500_accdet_abx500_gpadc_get(void) +{ + return ab5500_gpadc_get("ab5500-adc.0"); +} + +struct abx500_accdet_platform_data * + ab5500_get_platform_data(struct platform_device *pdev) +{ + return pdev->dev.platform_data; +} + +struct abx500_ad ab5500_accessory_det_callbacks = { + .irq_desc_norm = ab5500_irq_desc, + .irq_desc_inverted = NULL, + .no_irqs = ARRAY_SIZE(ab5500_irq_desc), + .regu_desc = ab5500_regu_desc, + .no_of_regu_desc = ARRAY_SIZE(ab5500_regu_desc), + .config_accdetect2_hw = ab5500_config_accdetect2_hw, + .config_accdetect1_hw = ab5500_config_accdetect1_hw, + .detect_plugged_in = ab5500_detect_plugged_in, + .meas_voltage_stable = ab5500_meas_voltage_stable, + .meas_alt_voltage_stable = ab5500_meas_alt_voltage_stable, + .config_hw_test_basic_carkit = ab5500_config_hw_test_basic_carkit, + .turn_off_accdet_comparator = ab5500_turn_off_accdet_comparator, + .turn_on_accdet_comparator = ab5500_turn_on_accdet_comparator, + .accdet_abx500_gpadc_get = ab5500_accdet_abx500_gpadc_get, + .config_hw_test_plug_connected = ab5500_config_hw_test_plug_connected, + .set_av_switch = NULL, + .get_platform_data = ab5500_get_platform_data, +}; + +MODULE_LICENSE("GPL v2"); diff --git a/drivers/input/misc/ab8500-accdet.c b/drivers/input/misc/ab8500-accdet.c new file mode 100644 index 00000000000..1b96b6b3fef --- /dev/null +++ b/drivers/input/misc/ab8500-accdet.c @@ -0,0 +1,464 @@ +/* + * 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/delay.h> +#include <linux/interrupt.h> +#include <linux/mfd/abx500.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/mfd/abx500/ab8500-gpadc.h> +#include <linux/mfd/abx500/ab8500-gpio.h> +#include <linux/gpio.h> +#include <linux/err.h> +#include <linux/input/abx500-accdet.h> +#ifdef CONFIG_SND_SOC_UX500_AB8500 +#include <sound/ux500_ab8500_ext.h> +#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_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 + +/* Static data initialization */ + +static struct accessory_regu_descriptor ab8500_regu_desc[3] = { + { + .id = REGULATOR_VAUDIO, + .name = "v-audio", + }, + { + .id = REGULATOR_VAMIC1, + .name = "v-amic1", + }, + { + .id = REGULATOR_AVSWITCH, + .name = "vcc-N2158", + }, +}; + +static struct accessory_irq_descriptor ab8500_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 ab8500_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, + }, +}; + +/* + * configures accdet2 input on/off + */ +static void ab8500_config_accdetect2_hw(struct abx500_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 + */ +static void ab8500_config_accdetect1_hw(struct abx500_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); +} + +/* + * returns the high level status whether some accessory is connected (1|0). + */ +static int ab8500_detect_plugged_in(struct abx500_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; +} + +#ifdef CONFIG_SND_SOC_UX500_AB8500 + +/* + * meas_voltage_stable - measures relative stable voltage from spec. input + */ +static int ab8500_meas_voltage_stable(struct abx500_ad *dd) +{ + int ret, mv; + + ret = ux500_ab8500_audio_gpadc_measure((struct ab8500_gpadc *)dd->gpadc, + ACC_DETECT2, false, &mv); + + return (ret < 0) ? ret : mv; +} + +/* + * meas_alt_voltage_stable - measures relative stable voltage from spec. input + */ +static int ab8500_meas_alt_voltage_stable(struct abx500_ad *dd) +{ + int ret, mv; + + ret = ux500_ab8500_audio_gpadc_measure((struct ab8500_gpadc *)dd->gpadc, + ACC_DETECT2, true, &mv); + + return (ret < 0) ? ret : mv; +} + +#else + +/* + * meas_voltage_stable - measures relative stable voltage from spec. input + */ +static int ab8500_meas_voltage_stable(struct abx500_ad *dd) +{ + int iterations = 2; + int v1, v2, dv; + + v1 = ab8500_gpadc_convert((struct ab8500_gpadc *)dd->gpadc, + ACC_DETECT2); + do { + msleep(1); + --iterations; + v2 = ab8500_gpadc_convert((struct ab8500_gpadc *)dd->gpadc, + ACC_DETECT2); + dv = abs(v2 - v1); + v1 = v2; + } while (iterations > 0 && dv > MAX_VOLT_DIFF); + + return v1; +} + +/* + * not implemented for non soc setups + */ +static int ab8500_meas_alt_voltage_stable(struct abx500_ad *dd) +{ + return -1; +} + +#endif + +/* + * configures HW so that it is possible to make decision whether + * accessory is connected or not. + */ +static void ab8500_config_hw_test_plug_connected(struct abx500_ad *dd, + int enable) +{ + int ret; + + dev_dbg(&dd->pdev->dev, "%s:%d\n", __func__, enable); + + ret = ab8500_config_pulldown(&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(dd, REGULATOR_VAMIC1); +} + +/* + * configures HW so that carkit/headset detection can be accomplished. + */ +static void ab8500_config_hw_test_basic_carkit(struct abx500_ad *dd, int enable) +{ + int ret; + + dev_dbg(&dd->pdev->dev, "%s:%d\n", __func__, enable); + + if (enable) + accessory_regulator_disable(dd, 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); +} + +/* + * sets the av switch direction - audio-in vs video-out + */ +static void ab8500_set_av_switch(struct abx500_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); + if (dd->pdata->mic_ctrl) + gpio_direction_output(dd->pdata->mic_ctrl, 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: video_ctrl pin output config failed (%d).\n", + __func__, ret); + return; + } + + if (dd->pdata->mic_ctrl) { + ret = gpio_direction_output(dd->pdata->mic_ctrl, + dir == AUDIO_IN ? 1 : 0); + if (ret < 0) { + dev_err(&dd->pdev->dev, + "%s: mic_ctrl pin output" + "config failed (%d).\n", + __func__, ret); + return; + } + } + + 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); + } +} + +static u8 acc_det_ctrl_suspend_val; + +static void ab8500_turn_off_accdet_comparator(struct platform_device *pdev) +{ + struct abx500_ad *dd = platform_get_drvdata(pdev); + + /* 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); + (void) abx500_set_register_interruptible( + &dd->pdev->dev, + AB8500_ECI_AV_ACC, + AB8500_ACC_DET_CTRL_REG, + 0); + +} + +static void ab8500_turn_on_accdet_comparator(struct platform_device *pdev) +{ + struct abx500_ad *dd = platform_get_drvdata(pdev); + + /* 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); + +} + +static void *ab8500_accdet_abx500_gpadc_get(void) +{ + return ab8500_gpadc_get("ab8500-gpadc.0"); +} + +struct abx500_accdet_platform_data * + ab8500_get_platform_data(struct platform_device *pdev) +{ + struct ab8500_platform_data *plat; + + plat = dev_get_platdata(pdev->dev.parent); + + if (!plat || !plat->accdet) { + dev_err(&pdev->dev, "%s: Failed to get accdet plat data.\n", + __func__); + return ERR_PTR(-ENODEV); + } + + return plat->accdet; +} + +struct abx500_ad ab8500_accessory_det_callbacks = { + .irq_desc_norm = ab8500_irq_desc_norm, + .irq_desc_inverted = ab8500_irq_desc_inverted, + .no_irqs = ARRAY_SIZE(ab8500_irq_desc_norm), + .regu_desc = ab8500_regu_desc, + .no_of_regu_desc = ARRAY_SIZE(ab8500_regu_desc), + .config_accdetect2_hw = ab8500_config_accdetect2_hw, + .config_accdetect1_hw = ab8500_config_accdetect1_hw, + .detect_plugged_in = ab8500_detect_plugged_in, + .meas_voltage_stable = ab8500_meas_voltage_stable, + .meas_alt_voltage_stable = ab8500_meas_alt_voltage_stable, + .config_hw_test_basic_carkit = ab8500_config_hw_test_basic_carkit, + .turn_off_accdet_comparator = ab8500_turn_off_accdet_comparator, + .turn_on_accdet_comparator = ab8500_turn_on_accdet_comparator, + .accdet_abx500_gpadc_get = ab8500_accdet_abx500_gpadc_get, + .config_hw_test_plug_connected = ab8500_config_hw_test_plug_connected, + .set_av_switch = ab8500_set_av_switch, + .get_platform_data = ab8500_get_platform_data, +}; + +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 index 350fd0c385d..c3c3c51d302 100644 --- a/drivers/input/misc/ab8500-ponkey.c +++ b/drivers/input/misc/ab8500-ponkey.c @@ -6,7 +6,6 @@ * * AB8500 Power-On Key handler */ - #include <linux/kernel.h> #include <linux/module.h> #include <linux/platform_device.h> @@ -14,128 +13,208 @@ #include <linux/interrupt.h> #include <linux/mfd/abx500/ab8500.h> #include <linux/slab.h> +#include <linux/mfd/abx500.h> +#include <linux/mfd/abx500/ab5500.h> + +/* Ponkey time control bits */ +#define AB5500_MCB 0x2F +#define AB5500_PONKEY_10SEC 0x0 +#define AB5500_PONKEY_5SEC 0x1 +#define AB5500_PONKEY_DISABLE 0x2 +#define AB5500_PONKEY_TMR_MASK 0x1 +#define AB5500_PONKEY_TR_MASK 0x2 + +static int ab5500_ponkey_hw_init(struct platform_device *); + +struct ab8500_ponkey_variant { + const char *irq_falling; + const char *irq_rising; + int (*hw_init)(struct platform_device *); +}; + +static const struct ab8500_ponkey_variant ab5500_onswa = { + .irq_falling = "ONSWAn_falling", + .irq_rising = "ONSWAn_rising", + .hw_init = ab5500_ponkey_hw_init, +}; + +static const struct ab8500_ponkey_variant ab8500_ponkey = { + .irq_falling = "ONKEY_DBF", + .irq_rising = "ONKEY_DBR", +}; /** - * struct ab8500_ponkey - ab8500 ponkey information + * struct ab8500_ponkey_info - ab8500 ponkey information * @input_dev: pointer to input device - * @ab8500: ab8500 parent * @irq_dbf: irq number for falling transition * @irq_dbr: irq number for rising transition */ -struct ab8500_ponkey { +struct ab8500_ponkey_info { struct input_dev *idev; - struct ab8500 *ab8500; int irq_dbf; int irq_dbr; }; +static int ab5500_ponkey_hw_init(struct platform_device *pdev) +{ + u8 val; + struct ab5500_ponkey_platform_data *pdata; + + pdata = pdev->dev.platform_data; + if (pdata) { + switch (pdata->shutdown_secs) { + case 0: + val = AB5500_PONKEY_DISABLE; + break; + case 5: + val = AB5500_PONKEY_5SEC; + break; + case 10: + val = AB5500_PONKEY_10SEC; + break; + default: + val = AB5500_PONKEY_10SEC; + } + } else { + val = AB5500_PONKEY_10SEC; + } + return abx500_mask_and_set( + &pdev->dev, + AB5500_BANK_STARTUP, + AB5500_MCB, + AB5500_PONKEY_TMR_MASK | AB5500_PONKEY_TR_MASK, + val); +} + /* AB8500 gives us an interrupt when ONKEY is held */ static irqreturn_t ab8500_ponkey_handler(int irq, void *data) { - struct ab8500_ponkey *ponkey = data; + struct ab8500_ponkey_info *info = data; - if (irq == ponkey->irq_dbf) - input_report_key(ponkey->idev, KEY_POWER, true); - else if (irq == ponkey->irq_dbr) - input_report_key(ponkey->idev, KEY_POWER, false); + 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(ponkey->idev); + input_sync(info->idev); return IRQ_HANDLED; } static int __devinit ab8500_ponkey_probe(struct platform_device *pdev) { - struct ab8500 *ab8500 = dev_get_drvdata(pdev->dev.parent); - struct ab8500_ponkey *ponkey; - struct input_dev *input; - int irq_dbf, irq_dbr; - int error; + 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; + + if (variant->hw_init) { + ret = variant->hw_init(pdev); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to init hw"); + return ret; + } + } - irq_dbf = platform_get_irq_byname(pdev, "ONKEY_DBF"); + irq_dbf = platform_get_irq_byname(pdev, variant->irq_falling); if (irq_dbf < 0) { - dev_err(&pdev->dev, "No IRQ for ONKEY_DBF, error=%d\n", irq_dbf); + 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, "ONKEY_DBR"); + irq_dbr = platform_get_irq_byname(pdev, variant->irq_rising); if (irq_dbr < 0) { - dev_err(&pdev->dev, "No IRQ for ONKEY_DBR, error=%d\n", irq_dbr); + dev_err(&pdev->dev, "No IRQ for %s: %d\n", + variant->irq_rising, irq_dbr); return irq_dbr; } - ponkey = kzalloc(sizeof(struct ab8500_ponkey), GFP_KERNEL); - input = input_allocate_device(); - if (!ponkey || !input) { - error = -ENOMEM; - goto err_free_mem; - } + info = kzalloc(sizeof(struct ab8500_ponkey_info), GFP_KERNEL); + if (!info) + return -ENOMEM; - ponkey->idev = input; - ponkey->ab8500 = ab8500; - ponkey->irq_dbf = irq_dbf; - ponkey->irq_dbr = irq_dbr; + info->irq_dbf = irq_dbf; + info->irq_dbr = irq_dbr; - input->name = "AB8500 POn(PowerOn) Key"; - input->dev.parent = &pdev->dev; + info->idev = input_allocate_device(); + if (!info->idev) { + dev_err(&pdev->dev, "Failed to allocate input dev\n"); + ret = -ENOMEM; + goto out; + } - input_set_capability(input, EV_KEY, KEY_POWER); + 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); - error = request_any_context_irq(ponkey->irq_dbf, ab8500_ponkey_handler, - 0, "ab8500-ponkey-dbf", ponkey); - if (error < 0) { - dev_err(ab8500->dev, "Failed to request dbf IRQ#%d: %d\n", - ponkey->irq_dbf, error); - goto err_free_mem; + ret = input_register_device(info->idev); + if (ret) { + dev_err(&pdev->dev, "Can't register input device: %d\n", ret); + goto out_unfreedevice; } - error = request_any_context_irq(ponkey->irq_dbr, ab8500_ponkey_handler, - 0, "ab8500-ponkey-dbr", ponkey); - if (error < 0) { - dev_err(ab8500->dev, "Failed to request dbr IRQ#%d: %d\n", - ponkey->irq_dbr, error); - goto err_free_dbf_irq; + 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; } - error = input_register_device(ponkey->idev); - if (error) { - dev_err(ab8500->dev, "Can't register input device: %d\n", error); - goto err_free_dbr_irq; + 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, ponkey); - return 0; + platform_set_drvdata(pdev, info); -err_free_dbr_irq: - free_irq(ponkey->irq_dbr, ponkey); -err_free_dbf_irq: - free_irq(ponkey->irq_dbf, ponkey); -err_free_mem: - input_free_device(input); - kfree(ponkey); + return 0; - return error; +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 *ponkey = platform_get_drvdata(pdev); - - free_irq(ponkey->irq_dbf, ponkey); - free_irq(ponkey->irq_dbr, ponkey); - input_unregister_device(ponkey->idev); - kfree(ponkey); - - platform_set_drvdata(pdev, NULL); + 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), }; diff --git a/drivers/input/misc/abx500-accdet.c b/drivers/input/misc/abx500-accdet.c new file mode 100644 index 00000000000..4b5017a6e60 --- /dev/null +++ b/drivers/input/misc/abx500-accdet.c @@ -0,0 +1,1019 @@ +/* + * 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> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/workqueue.h> +#include <linux/irq.h> +#include <linux/jiffies.h> +#include <linux/slab.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/input/abx500-accdet.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> +#include <linux/gpio.h> +#include <linux/mfd/abx500.h> + +#include <sound/jack.h> +#include <sound/soc.h> +#include <sound/jack.h> + +#ifdef CONFIG_SND_SOC_UX500_AB8500 +#include <sound/ux500_ab8500.h> +#else +#define ux500_ab8500_jack_report(i) +#endif + +/* 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 + +/* 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_U_HEADSET_DET_VOL_MIN 47 +#define ACCESSORY_U_HEADSET_DET_VOL_MAX 732 +#define ACCESSORY_U_HEADSET_ALT_DET_VOL_MIN 25 +#define ACCESSORY_U_HEADSET_ALT_DET_VOL_MAX 50 +#define ACCESSORY_CARKIT_DET_VOL_MIN 1100 +#define ACCESSORY_CARKIT_DET_VOL_MAX 1300 +#define ACCESSORY_HEADSET_DET_VOL_MIN 1301 +#define ACCESSORY_HEADSET_DET_VOL_MAX 2000 +#define ACCESSORY_OPENCABLE_DET_VOL_MIN 2001 +#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)) + + +/* Forward declarations */ +static void config_accdetect(struct abx500_ad *dd); +static enum accessory_jack_type detect(struct abx500_ad *dd, int *required_det); + +/* 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, + .alt_minvol = ACCESSORY_DET_VOL_DONTCARE, + .alt_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, + .alt_minvol = ACCESSORY_DET_VOL_DONTCARE, + .alt_maxvol = ACCESSORY_DET_VOL_DONTCARE + }, + { + .type = JACK_TYPE_UNSUPPORTED_HEADSET, + .typename = "UNSUPPORTED HEADSET", + .meas_mv = 1, + .req_det_count = 2, + .minvol = ACCESSORY_U_HEADSET_DET_VOL_MIN, + .maxvol = ACCESSORY_U_HEADSET_DET_VOL_MAX, + .alt_minvol = ACCESSORY_U_HEADSET_ALT_DET_VOL_MIN, + .alt_maxvol = ACCESSORY_U_HEADSET_ALT_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, + .alt_minvol = ACCESSORY_DET_VOL_DONTCARE, + .alt_maxvol = ACCESSORY_DET_VOL_DONTCARE + }, + { + .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, + .alt_minvol = ACCESSORY_DET_VOL_DONTCARE, + .alt_maxvol = ACCESSORY_DET_VOL_DONTCARE + }, + { + .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, + .alt_minvol = ACCESSORY_DET_VOL_DONTCARE, + .alt_maxvol = ACCESSORY_DET_VOL_DONTCARE + }, + { + .type = JACK_TYPE_CONNECTED, + .typename = "CONNECTED", + .meas_mv = 0, + .req_det_count = 4, + .minvol = ACCESSORY_DET_VOL_DONTCARE, + .maxvol = ACCESSORY_DET_VOL_DONTCARE, + .alt_minvol = ACCESSORY_DET_VOL_DONTCARE, + .alt_maxvol = ACCESSORY_DET_VOL_DONTCARE + } +}; + +static struct accessory_irq_descriptor *abx500_accdet_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. + */ +void accessory_regulator_enable(struct abx500_ad *dd, + enum accessory_regulator reg) +{ + int i; + + for (i = 0; i < dd->no_of_regu_desc; i++) { + if (reg & dd->regu_desc[i].id) { + if (!dd->regu_desc[i].enabled) { + if (!regulator_enable(dd->regu_desc[i].handle)) + dd->regu_desc[i].enabled = 1; + } + } + } +} + +/* + * disables regulator but only if it has been previously enabled. + */ +void accessory_regulator_disable(struct abx500_ad *dd, + enum accessory_regulator reg) +{ + int i; + + for (i = 0; i < dd->no_of_regu_desc; i++) { + if (reg & dd->regu_desc[i].id) { + if (dd->regu_desc[i].enabled) { + if (!regulator_disable(dd->regu_desc[i].handle)) + dd->regu_desc[i].enabled = 0; + } + } + } +} + +/* + * frees previously retrieved regulators. + */ +static void free_regulators(struct abx500_ad *dd) +{ + int i; + + for (i = 0; i < dd->no_of_regu_desc; i++) { + if (dd->regu_desc[i].handle) { + regulator_put(dd->regu_desc[i].handle); + dd->regu_desc[i].handle = NULL; + } + } +} + +/* + * gets required regulators. + */ +static int create_regulators(struct abx500_ad *dd) +{ + int i; + int status = 0; + + for (i = 0; i < dd->no_of_regu_desc; i++) { + struct regulator *regu = + regulator_get(&dd->pdev->dev, dd->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__, dd->regu_desc[i].name, status); + free_regulators(dd); + goto out; + } else { + dd->regu_desc[i].handle = regu; + } + } + +out: + return status; +} + +/* + * create input device for button press reporting + */ +static int create_btn_input_dev(struct abx500_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 abx500_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_dbg(&dd->pdev->dev, "Accessory: %s\n", + accessory_str(dd->jack_type)); + + /* Never report unsupported headset */ + if (dd->jack_type == JACK_TYPE_UNSUPPORTED_HEADSET) + goto out; + + 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); + ux500_ab8500_jack_report(value); + +out: return; +} + +/* + * worker routine to handle accessory unplug case + */ +void unplug_irq_handler_work(struct work_struct *work) +{ + struct abx500_ad *dd = container_of(work, + struct abx500_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(dd, REGULATOR_ALL); + + report_jack_status(dd); +} + +/* + * interrupt service routine for accessory unplug. + */ +irqreturn_t unplug_irq_handler(int irq, void *_userdata) +{ + struct abx500_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. + */ +irqreturn_t plug_irq_handler(int irq, void *_userdata) +{ + struct abx500_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 abx500_ad *dd = container_of(work, + struct abx500_ad, detect_work.work); + + dev_dbg(&dd->pdev->dev, "%s: Enter\n", __func__); + + if (dd->set_av_switch) + dd->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 abx500_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. + */ +irqreturn_t button_press_irq_handler(int irq, void *_userdata) +{ + struct abx500_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.. */ + dd->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. + */ +irqreturn_t button_release_irq_handler(int irq, void *_userdata) +{ + struct abx500_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; +} + +/* + * 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 abx500_ad *dd, + int lo, int hi, int alt_lo, int alt_hi, int force_read) +{ + static int mv = MIN_MIC_POWER; + static int alt_mv = MIN_MIC_POWER; + + if (mv == MIN_MIC_POWER || force_read) + mv = dd->meas_voltage_stable(dd); + + if (mv < lo || mv > hi) + return 0; + + if (ACCESSORY_DET_VOL_DONTCARE == alt_lo && + ACCESSORY_DET_VOL_DONTCARE == alt_hi) + return 1; + + if (alt_mv == MIN_MIC_POWER || force_read) + alt_mv = dd->meas_alt_voltage_stable(dd); + + if (alt_mv < alt_lo || alt_mv > alt_hi) + return 0; + + return 1; +} + +/* + * checks whether the currently connected HW is of given type. + */ +static int detect_hw(struct abx500_ad *dd, + struct accessory_detect_task *task) +{ + int status; + + switch (task->type) { + case JACK_TYPE_DISCONNECTED: + dd->config_hw_test_plug_connected(dd, 1); + status = !dd->detect_plugged_in(dd); + break; + case JACK_TYPE_CONNECTED: + dd->config_hw_test_plug_connected(dd, 1); + status = dd->detect_plugged_in(dd); + break; + case JACK_TYPE_CARKIT: + case JACK_TYPE_HEADPHONE: + case JACK_TYPE_HEADSET: + case JACK_TYPE_UNSUPPORTED_HEADSET: + case JACK_TYPE_OPENCABLE: + status = mic_vol_in_range(dd, + task->minvol, + task->maxvol, + task->alt_minvol, + task->alt_maxvol, + task->meas_mv); + break; + default: + status = 0; + } + + return status; +} + +/* + * Tries to detect the currently attached accessory + */ +static enum accessory_jack_type detect(struct abx500_ad *dd, + int *req_det_count) +{ + enum accessory_jack_type type = JACK_TYPE_DISCONNECTED; + int i; + + accessory_regulator_enable(dd, REGULATOR_VAUDIO | REGULATOR_AVSWITCH); + /* enable the VAMIC1 regulator */ + dd->config_hw_test_basic_carkit(dd, 0); + + 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; + } + } + + dd->config_hw_test_plug_connected(dd, 0); + + if (jack_supports_buttons(type)) + accessory_regulator_enable(dd, REGULATOR_VAMIC1); + else + accessory_regulator_disable(dd, REGULATOR_VAMIC1 | + REGULATOR_AVSWITCH); + + accessory_regulator_disable(dd, REGULATOR_VAUDIO); + + return type; +} + +/* + * registers to specific interrupt + */ +static void claim_irq(struct abx500_ad *dd, enum accessory_irq irq_id) +{ + int ret; + int irq; + + if (dd->pdata->is_detection_inverted) + abx500_accdet_irq_desc = dd->irq_desc_inverted; + else + abx500_accdet_irq_desc = dd->irq_desc_norm; + + if (abx500_accdet_irq_desc[irq_id].registered) + return; + + irq = platform_get_irq_byname( + dd->pdev, + abx500_accdet_irq_desc[irq_id].name); + if (irq < 0) { + dev_err(&dd->pdev->dev, + "%s: Failed to get irq %s\n", __func__, + abx500_accdet_irq_desc[irq_id].name); + return; + } + + ret = request_threaded_irq(irq, + NULL, + abx500_accdet_irq_desc[irq_id].isr, + IRQF_NO_SUSPEND | IRQF_SHARED, + abx500_accdet_irq_desc[irq_id].name, + dd); + if (ret != 0) { + dev_err(&dd->pdev->dev, + "%s: Failed to claim irq %s (%d)\n", + __func__, + abx500_accdet_irq_desc[irq_id].name, + ret); + } else { + abx500_accdet_irq_desc[irq_id].registered = 1; + dev_dbg(&dd->pdev->dev, "%s: %s\n", + __func__, abx500_accdet_irq_desc[irq_id].name); + } +} + +/* + * releases specific interrupt + */ +static void release_irq(struct abx500_ad *dd, enum accessory_irq irq_id) +{ + int irq; + + if (dd->pdata->is_detection_inverted) + abx500_accdet_irq_desc = dd->irq_desc_inverted; + else + abx500_accdet_irq_desc = dd->irq_desc_norm; + + if (!abx500_accdet_irq_desc[irq_id].registered) + return; + + irq = platform_get_irq_byname( + dd->pdev, + abx500_accdet_irq_desc[irq_id].name); + if (irq < 0) { + dev_err(&dd->pdev->dev, + "%s: Failed to get irq %s (%d)\n", + __func__, + abx500_accdet_irq_desc[irq_id].name, irq); + } else { + free_irq(irq, dd); + abx500_accdet_irq_desc[irq_id].registered = 0; + dev_dbg(&dd->pdev->dev, "%s: %s\n", + __func__, abx500_accdet_irq_desc[irq_id].name); + } +} + +/* + * configures interrupts + detection hardware to meet the requirements + * set by currently attached accessory type. + */ +static void config_accdetect(struct abx500_ad *dd) +{ + switch (dd->jack_type) { + case JACK_TYPE_UNSPECIFIED: + dd->config_accdetect1_hw(dd, 1); + dd->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); + if (dd->set_av_switch) + dd->set_av_switch(dd, NOT_SET); + break; + + case JACK_TYPE_DISCONNECTED: + if (dd->set_av_switch) + dd->set_av_switch(dd, NOT_SET); + case JACK_TYPE_HEADPHONE: + dd->config_accdetect1_hw(dd, 1); + dd->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_UNSUPPORTED_HEADSET: + dd->config_accdetect1_hw(dd, 1); + dd->config_accdetect2_hw(dd, 1); + + release_irq(dd, PLUG_IRQ); + claim_irq(dd, UNPLUG_IRQ); + release_irq(dd, BUTTON_PRESS_IRQ); + release_irq(dd, BUTTON_RELEASE_IRQ); + if (dd->set_av_switch) + dd->set_av_switch(dd, NOT_SET); + break; + + case JACK_TYPE_CONNECTED: + case JACK_TYPE_HEADSET: + case JACK_TYPE_CARKIT: + case JACK_TYPE_OPENCABLE: + dd->config_accdetect1_hw(dd, 1); + dd->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 abx500_ad *dd = container_of(work, + struct abx500_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 + */ +static int abx500_accessory_init(struct platform_device *pdev) +{ + int ret; + struct abx500_ad *dd = (struct abx500_ad *)pdev->id_entry->driver_data; + + dev_dbg(&pdev->dev, "Enter: %s\n", __func__); + + dd->pdev = pdev; + dd->pdata = dd->get_platform_data(pdev); + if (IS_ERR(dd->pdata)) + return PTR_ERR(dd->pdata); + + if (dd->pdata->video_ctrl_gpio) { + ret = gpio_is_valid(dd->pdata->video_ctrl_gpio); + if (!ret) { + dev_err(&pdev->dev, + "%s: Video ctrl GPIO invalid (%d).\n", __func__, + dd->pdata->video_ctrl_gpio); + + return ret; + } + ret = gpio_request(dd->pdata->video_ctrl_gpio, + "Video Control"); + if (ret) { + dev_err(&pdev->dev, "%s: Get video ctrl GPIO" + "failed.\n", __func__); + return ret; + } + } + + if (dd->pdata->mic_ctrl) { + ret = gpio_is_valid(dd->pdata->mic_ctrl); + if (!ret) { + dev_err(&pdev->dev, + "%s: Mic ctrl GPIO invalid (%d).\n", __func__, + dd->pdata->mic_ctrl); + + goto mic_ctrl_fail; + } + ret = gpio_request(dd->pdata->mic_ctrl, + "Mic Control"); + if (ret) { + dev_err(&pdev->dev, "%s: Get mic ctrl GPIO" + "failed.\n", __func__); + goto mic_ctrl_fail; + } + } + + ret = create_btn_input_dev(dd); + if (ret < 0) { + dev_err(&pdev->dev, "%s: create_button_input_dev failed.\n", + __func__); + goto fail_no_btn_input_dev; + } + + ret = create_regulators(dd); + if (ret < 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("abx500_accdet_wq"); + if (!dd->irq_work_queue) { + dev_err(&pdev->dev, "%s: Failed to create wq\n", __func__); + ret = -ENOMEM; + goto fail_no_mem_for_wq; + } + + dd->gpadc = dd->accdet_abx500_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(dd); +fail_no_regulators: + input_unregister_device(dd->btn_input_dev); +fail_no_btn_input_dev: + if (dd->pdata->mic_ctrl) + gpio_free(dd->pdata->mic_ctrl); +mic_ctrl_fail: + if (dd->pdata->video_ctrl_gpio) + gpio_free(dd->pdata->video_ctrl_gpio); + return ret; +} + +/* + * Performs platform device cleanup + */ +static void abx500_accessory_cleanup(struct abx500_ad *dd) +{ + dev_dbg(&dd->pdev->dev, "Enter: %s\n", __func__); + + dd->jack_type = JACK_TYPE_UNSPECIFIED; + config_accdetect(dd); + + if (dd->pdata->mic_ctrl) + gpio_free(dd->pdata->mic_ctrl); + + if (dd->pdata->video_ctrl_gpio) + gpio_free(dd->pdata->video_ctrl_gpio); + + input_unregister_device(dd->btn_input_dev); + free_regulators(dd); + + 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); + +} + +static int __devinit abx500_acc_detect_probe(struct platform_device *pdev) +{ + + return abx500_accessory_init(pdev); +} + +static int __devexit abx500_acc_detect_remove(struct platform_device *pdev) +{ + abx500_accessory_cleanup(platform_get_drvdata(pdev)); + platform_set_drvdata(pdev, NULL); + + return 0; +} + +#if defined(CONFIG_PM) +static int abx500_acc_detect_suspend(struct device *dev) +{ + struct platform_device *pdev = container_of(dev, + struct platform_device, dev); + struct abx500_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) + abx500_accdet_irq_desc = dd->irq_desc_inverted; + else + abx500_accdet_irq_desc = dd->irq_desc_norm; + + for (irq_id = 0; irq_id < dd->no_irqs; irq_id++) { + if (abx500_accdet_irq_desc[irq_id].registered == 1) { + irq = platform_get_irq_byname( + dd->pdev, + abx500_accdet_irq_desc[irq_id].name); + + disable_irq(irq); + } + } + + dd->turn_off_accdet_comparator(pdev); + + if (dd->jack_type == JACK_TYPE_HEADSET) + accessory_regulator_disable(dd, REGULATOR_VAMIC1); + + return 0; +} + +static int abx500_acc_detect_resume(struct device *dev) +{ + struct platform_device *pdev = container_of(dev, + struct platform_device, dev); + struct abx500_ad *dd = platform_get_drvdata(pdev); + int irq_id, irq; + + dev_dbg(&dd->pdev->dev, "%s: Enter\n", __func__); + + if (dd->jack_type == JACK_TYPE_HEADSET) + accessory_regulator_enable(dd, REGULATOR_VAMIC1); + + dd->turn_on_accdet_comparator(pdev); + + if (dd->pdata->is_detection_inverted) + abx500_accdet_irq_desc = dd->irq_desc_inverted; + else + abx500_accdet_irq_desc = dd->irq_desc_norm; + + for (irq_id = 0; irq_id < dd->no_irqs; irq_id++) { + if (abx500_accdet_irq_desc[irq_id].registered == 1) { + irq = platform_get_irq_byname( + dd->pdev, + abx500_accdet_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 abx500_acc_detect_suspend NULL +#define abx500_acc_detect_resume NULL +#endif + +static struct platform_device_id abx500_accdet_ids[] = { +#ifdef CONFIG_INPUT_AB5500_ACCDET + { "ab5500-acc-det", (kernel_ulong_t)&ab5500_accessory_det_callbacks, }, +#endif +#ifdef CONFIG_INPUT_AB8500_ACCDET + { "ab8500-acc-det", (kernel_ulong_t)&ab8500_accessory_det_callbacks, }, +#endif + { }, +}; + +static const struct dev_pm_ops abx_ops = { + .suspend = abx500_acc_detect_suspend, + .resume = abx500_acc_detect_resume, +}; + +static struct platform_driver abx500_acc_detect_platform_driver = { + .driver = { + .name = "abx500-acc-det", + .owner = THIS_MODULE, + .pm = &abx_ops, + }, + .probe = abx500_acc_detect_probe, + .id_table = abx500_accdet_ids, + .remove = __devexit_p(abx500_acc_detect_remove), +}; + +static int __init abx500_acc_detect_init(void) +{ + return platform_driver_register(&abx500_acc_detect_platform_driver); +} + +static void __exit abx500_acc_detect_exit(void) +{ + platform_driver_unregister(&abx500_acc_detect_platform_driver); +} + +module_init(abx500_acc_detect_init); +module_exit(abx500_acc_detect_exit); + +MODULE_DESCRIPTION("ABx500 AV Accessory detection driver"); +MODULE_ALIAS("platform:abx500-acc-det"); +MODULE_AUTHOR("ST-Ericsson"); +MODULE_LICENSE("GPL v2"); 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/bu21013_ts.c b/drivers/input/touchscreen/bu21013_ts.c index f2d03c06c2d..d6ae75865e4 100644 --- a/drivers/input/touchscreen/bu21013_ts.c +++ b/drivers/input/touchscreen/bu21013_ts.c @@ -1,5 +1,5 @@ /* - * Copyright (C) ST-Ericsson SA 2010 + * 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 */ @@ -12,13 +12,14 @@ #include <linux/input.h> #include <linux/input/bu21013.h> #include <linux/slab.h> +#include <linux/clk.h> #include <linux/regulator/consumer.h> #include <linux/module.h> #define PEN_DOWN_INTR 0 -#define MAX_FINGERS 2 #define RESET_DELAY 30 -#define PENUP_TIMEOUT (10) +#define PENUP_TIMEOUT 2 /* 2msecs */ +#define SCALE_FACTOR 1000 #define DELTA_MIN 16 #define MASK_BITS 0x03 #define SHIFT_8 8 @@ -131,7 +132,7 @@ #define BU21013_NUMBER_OF_X_SENSORS (6) #define BU21013_NUMBER_OF_Y_SENSORS (11) -#define DRIVER_TP "bu21013_tp" +#define DRIVER_TP "bu21013_ts" /** * struct bu21013_ts_data - touch panel data structure @@ -142,6 +143,12 @@ * @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 * * Touch panel device data structure */ @@ -149,12 +156,226 @@ struct bu21013_ts_data { struct i2c_client *client; wait_queue_head_t wait; bool touch_stopped; - const struct bu21013_platform_device *chip; + 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; }; +static int bu21013_init_chip(struct bu21013_ts_data *data, bool on_ext_clk); + +/** + * 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 @@ -204,12 +425,14 @@ static int bu21013_do_touch_report(struct bu21013_ts_data *data) if (!has_x_sensors || !has_y_sensors) return 0; - for (i = 0; i < MAX_FINGERS; i++) { + 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++; @@ -217,21 +440,21 @@ static int bu21013_do_touch_report(struct bu21013_ts_data *data) 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)) { + (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->x_flip) - pos_x[i] = data->chip->touch_x_max - pos_x[i]; - if (data->chip->y_flip) - pos_y[i] = data->chip->touch_y_max - 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]); + 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 @@ -261,24 +484,23 @@ static irqreturn_t bu21013_gpio_irq(int irq, void *device_data) 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(2)); + 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) +static int bu21013_init_chip(struct bu21013_ts_data *data, bool on_ext_clk) { int retval; struct i2c_client *i2c = data->client; @@ -297,28 +519,24 @@ static int bu21013_init_chip(struct bu21013_ts_data *data) 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 | @@ -327,8 +545,7 @@ static int bu21013_init_chip(struct bu21013_ts_data *data) dev_err(&i2c->dev, "BU21013_POS_MODE2 reg write failed\n"); return retval; } - - if (data->chip->ext_clk) + if (on_ext_clk) retval = i2c_smbus_write_byte_data(i2c, BU21013_CLK_MODE_REG, (BU21013_CLK_MODE_EXT | BU21013_CLK_MODE_CALIB)); else @@ -338,21 +555,18 @@ static int bu21013_init_chip(struct bu21013_ts_data *data) 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)); @@ -367,14 +581,12 @@ static int bu21013_init_chip(struct bu21013_ts_data *data) 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) { @@ -388,7 +600,6 @@ static int bu21013_init_chip(struct bu21013_ts_data *data) 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)); @@ -396,7 +607,6 @@ static int bu21013_init_chip(struct bu21013_ts_data *data) 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) { @@ -404,25 +614,15 @@ static int bu21013_init_chip(struct bu21013_ts_data *data) return retval; } - return 0; + 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_free_irq() - frees IRQ registered for touchscreen - * @bu21013_data: device structure pointer - * - * This function signals interrupt thread to stop processing and - * frees interrupt. - */ -static void bu21013_free_irq(struct bu21013_ts_data *bu21013_data) -{ - bu21013_data->touch_stopped = true; - wake_up(&bu21013_data->wait); - free_irq(bu21013_data->chip->irq, bu21013_data); -} - -/** - * bu21013_probe() - initializes the i2c-client touchscreen driver + * bu21013_probe() - initialzes the i2c-client touchscreen driver * @client: i2c client structure pointer * @id: i2c device id pointer * @@ -432,11 +632,11 @@ static void bu21013_free_irq(struct bu21013_ts_data *bu21013_data) 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; - const struct bu21013_platform_device *pdata = + struct bu21013_platform_device *pdata = client->dev.platform_data; - int error; if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) { @@ -446,53 +646,72 @@ static int __devinit bu21013_probe(struct i2c_client *client, if (!pdata) { dev_err(&client->dev, "platform data not defined\n"); - return -EINVAL; + retval = -EINVAL; + return retval; } bu21013_data = kzalloc(sizeof(struct bu21013_ts_data), GFP_KERNEL); - in_dev = input_allocate_device(); - if (!bu21013_data || !in_dev) { + if (!bu21013_data) { dev_err(&client->dev, "device memory alloc failed\n"); - error = -ENOMEM; - goto err_free_mem; + 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, "V-TOUCH"); + bu21013_data->regulator = regulator_get(&client->dev, "avdd"); if (IS_ERR(bu21013_data->regulator)) { - dev_err(&client->dev, "regulator_get failed\n"); - error = PTR_ERR(bu21013_data->regulator); - goto err_free_mem; + dev_warn(&client->dev, "regulator_get failed\n"); + bu21013_data->regulator = NULL; } - - error = regulator_enable(bu21013_data->regulator); - if (error < 0) { - dev_err(&client->dev, "regulator enable failed\n"); - goto err_put_regulator; - } - - bu21013_data->touch_stopped = false; - init_waitqueue_head(&bu21013_data->wait); + if (bu21013_data->regulator) + regulator_enable(bu21013_data->regulator); /* configure the gpio pins */ if (pdata->cs_en) { - error = pdata->cs_en(pdata->cs_pin); - if (error < 0) { + retval = pdata->cs_en(pdata->cs_pin); + if (retval < 0) { dev_err(&client->dev, "chip init failed\n"); - goto err_disable_regulator; + 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 */ - error = bu21013_init_chip(bu21013_data); - if (error) { + 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_cs_disable; + 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; @@ -503,44 +722,63 @@ static int __devinit bu21013_probe(struct i2c_client *client, __set_bit(EV_ABS, in_dev->evbit); input_set_abs_params(in_dev, ABS_MT_POSITION_X, 0, - pdata->touch_x_max, 0, 0); + pdata->x_max_res, 0, 0); input_set_abs_params(in_dev, ABS_MT_POSITION_Y, 0, - pdata->touch_y_max, 0, 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); - - error = request_threaded_irq(pdata->irq, NULL, bu21013_gpio_irq, - IRQF_TRIGGER_FALLING | IRQF_SHARED, - DRIVER_TP, bu21013_data); - if (error) { + 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_cs_disable; + goto err_init_irq; } + bu21013_data->enable = true; + i2c_set_clientdata(client, bu21013_data); - error = input_register_device(in_dev); - if (error) { - dev_err(&client->dev, "failed to register input device\n"); - goto err_free_irq; + /* 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; } - device_init_wakeup(&client->dev, pdata->wakeup); - i2c_set_clientdata(client, bu21013_data); - - return 0; + return retval; -err_free_irq: - bu21013_free_irq(bu21013_data); -err_cs_disable: - pdata->cs_dis(pdata->cs_pin); -err_disable_regulator: - regulator_disable(bu21013_data->regulator); -err_put_regulator: - regulator_put(bu21013_data->regulator); -err_free_mem: - input_free_device(in_dev); +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 error; + return retval; } + /** * bu21013_remove() - removes the i2c-client touchscreen driver * @client: i2c client structure pointer @@ -552,19 +790,24 @@ static int __devexit bu21013_remove(struct i2c_client *client) { struct bu21013_ts_data *bu21013_data = i2c_get_clientdata(client); - bu21013_free_irq(bu21013_data); - + 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); - regulator_disable(bu21013_data->regulator); - regulator_put(bu21013_data->regulator); - + 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); - device_init_wakeup(&client->dev, false); - return 0; } @@ -579,15 +822,8 @@ static int __devexit bu21013_remove(struct i2c_client *client) static int bu21013_suspend(struct device *dev) { struct bu21013_ts_data *bu21013_data = dev_get_drvdata(dev); - struct i2c_client *client = bu21013_data->client; - bu21013_data->touch_stopped = true; - if (device_may_wakeup(&client->dev)) - enable_irq_wake(bu21013_data->chip->irq); - else - disable_irq(bu21013_data->chip->irq); - - regulator_disable(bu21013_data->regulator); + bu21013_disable(bu21013_data); return 0; } @@ -602,29 +838,8 @@ static int bu21013_suspend(struct device *dev) static int bu21013_resume(struct device *dev) { struct bu21013_ts_data *bu21013_data = dev_get_drvdata(dev); - struct i2c_client *client = bu21013_data->client; - int retval; - retval = regulator_enable(bu21013_data->regulator); - if (retval < 0) { - dev_err(&client->dev, "bu21013 regulator enable failed\n"); - return retval; - } - - retval = bu21013_init_chip(bu21013_data); - if (retval < 0) { - dev_err(&client->dev, "bu21013 controller config failed\n"); - return retval; - } - - bu21013_data->touch_stopped = false; - - if (device_may_wakeup(&client->dev)) - disable_irq_wake(bu21013_data->chip->irq); - else - enable_irq(bu21013_data->chip->irq); - - return 0; + return bu21013_enable(bu21013_data); } static const struct dev_pm_ops bu21013_dev_pm_ops = { 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"); diff --git a/drivers/staging/ste_rmi4/board-mop500-u8500uib-rmi4.c b/drivers/staging/ste_rmi4/board-mop500-u8500uib-rmi4.c index a272e488e5b..545e03d31fc 100644 --- a/drivers/staging/ste_rmi4/board-mop500-u8500uib-rmi4.c +++ b/drivers/staging/ste_rmi4/board-mop500-u8500uib-rmi4.c @@ -20,8 +20,9 @@ static struct synaptics_rmi4_platform_data rmi4_i2c_dev_platformdata = { .irq_number = NOMADIK_GPIO_TO_IRQ(84), .irq_type = (IRQF_TRIGGER_FALLING | IRQF_SHARED), - .x_flip = false, - .y_flip = true, + .x_flip = true, + .y_flip = false, + .regulator_en = true, }; struct i2c_board_info __initdata mop500_i2c3_devices_u8500[] = { diff --git a/drivers/staging/ste_rmi4/synaptics_i2c_rmi4.c b/drivers/staging/ste_rmi4/synaptics_i2c_rmi4.c index 11728a03f8a..fd7fed743f7 100644 --- a/drivers/staging/ste_rmi4/synaptics_i2c_rmi4.c +++ b/drivers/staging/ste_rmi4/synaptics_i2c_rmi4.c @@ -1,11 +1,10 @@ -/** - * +/* * Synaptics Register Mapped Interface (RMI4) I2C Physical Layer Driver. * Copyright (c) 2007-2010, Synaptics Incorporated * * Author: Js HA <js.ha@stericsson.com> for ST-Ericsson * Author: Naveen Kumar G <naveen.gaddipati@stericsson.com> for ST-Ericsson - * Copyright 2010 (c) ST-Ericsson AB + * Copyright 2010 (c) ST-Ericsson SA */ /* * This file is licensed under the GPL2 license. @@ -27,6 +26,7 @@ #include <linux/input.h> #include <linux/slab.h> +#include <linux/delay.h> #include <linux/i2c.h> #include <linux/interrupt.h> #include <linux/regulator/consumer.h> @@ -36,8 +36,10 @@ /* TODO: for multiple device support will need a per-device mutex */ #define DRIVER_NAME "synaptics_rmi4_i2c" +#define DELTA 8 #define MAX_ERROR_REPORT 6 -#define MAX_TOUCH_MAJOR 15 +#define TIMEOUT_PERIOD 1 +#define MAX_WIDTH_MAJOR 255 #define MAX_RETRY_COUNT 5 #define STD_QUERY_LEN 21 #define PAGE_LEN 2 @@ -45,6 +47,7 @@ #define BUF_LEN 37 #define QUERY_LEN 9 #define DATA_LEN 12 +#define RESUME_DELAY 100 /* msecs */ #define HAS_TAP 0x01 #define HAS_PALMDETECT 0x01 #define HAS_ROTATE 0x02 @@ -164,6 +167,8 @@ struct synaptics_rmi4_device_info { * @regulator: pointer to the regulator structure * @wait: wait queue structure variable * @touch_stopped: flag to stop the thread function + * @enable: flag to enable/disable the driver event. + * @resume_wq_handler: work queue for resume the device * * This structure gives the device data information. */ @@ -184,6 +189,8 @@ struct synaptics_rmi4_data { struct regulator *regulator; wait_queue_head_t wait; bool touch_stopped; + bool enable; + struct work_struct resume_wq_handler; }; /** @@ -291,6 +298,133 @@ exit: } /** + * synaptics_rmi4_enable() - enable the touchpad driver event + * @pdata: pointer to synaptics_rmi4_data structure + * + * This function is to enable the touchpad driver event and returns integer. + */ +static int synaptics_rmi4_enable(struct synaptics_rmi4_data *pdata) +{ + int retval; + unsigned char intr_status; + + if (pdata->board->regulator_en) + regulator_enable(pdata->regulator); + enable_irq(pdata->board->irq_number); + pdata->touch_stopped = false; + + msleep(RESUME_DELAY); + retval = synaptics_rmi4_i2c_block_read(pdata, + pdata->fn01_data_base_addr + 1, + &intr_status, + pdata->number_of_interrupt_register); + if (retval < 0) + return retval; + + retval = synaptics_rmi4_i2c_byte_write(pdata, + pdata->fn01_ctrl_base_addr + 1, + (intr_status | TOUCHPAD_CTRL_INTR)); + if (retval < 0) + return retval; + + return 0; +} + +/** + * synaptics_rmi4_disable() - disable the touchpad driver event + * @pdata: pointer to synaptics_rmi4_data structure + * + * This function is to disable the driver event and returns integer. + */ + +static int synaptics_rmi4_disable(struct synaptics_rmi4_data *pdata) +{ + int retval; + unsigned char intr_status; + + pdata->touch_stopped = true; + disable_irq(pdata->board->irq_number); + + retval = synaptics_rmi4_i2c_block_read(pdata, + pdata->fn01_data_base_addr + 1, + &intr_status, + pdata->number_of_interrupt_register); + if (retval < 0) + return retval; + + retval = synaptics_rmi4_i2c_byte_write(pdata, + pdata->fn01_ctrl_base_addr + 1, + (intr_status & ~TOUCHPAD_CTRL_INTR)); + if (retval < 0) + return retval; + if (pdata->board->regulator_en) + regulator_disable(pdata->regulator); + + return 0; +} + +/** + * synaptics_rmi4_show_attr_enable() - show the touchpad enable value + * @dev: pointer to device data structure + * @attr: pointer to attribute structure + * @buf: pointer to character buffer + * + * This function is to show the touchpad enable value and returns ssize_t. + */ +static ssize_t synaptics_rmi4_show_attr_enable(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct synaptics_rmi4_data *pdata = dev_get_drvdata(dev); + + return sprintf(buf, "%d\n", pdata->enable); +} + +/** + * synaptics_rmi4_store_attr_enable() - store the touchpad enable value + * @dev: pointer to device data structure + * @attr: pointer to attribute structure + * @buf: pointer to character buffer + * @count: number fo arguments + * + * This function is to store the touchpad enable value and returns ssize_t. + */ +static ssize_t synaptics_rmi4_store_attr_enable(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct synaptics_rmi4_data *pdata = dev_get_drvdata(dev); + unsigned long val; + int retval = 0; + + 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) + retval = synaptics_rmi4_enable(pdata); + else + retval = synaptics_rmi4_disable(pdata); + + } + return ((retval < 0) ? retval : count); +} + +static DEVICE_ATTR(enable, S_IWUSR | S_IRUGO, + synaptics_rmi4_show_attr_enable, synaptics_rmi4_store_attr_enable); + +static struct attribute *synaptics_rmi4_attrs[] = { + &dev_attr_enable.attr, + NULL, +}; + +static struct attribute_group synaptics_rmi4_attr_group = { + .attrs = synaptics_rmi4_attrs, +}; + +/** * synpatics_rmi4_touchpad_report() - reports for the rmi4 touchpad device * @pdata: pointer to synaptics_rmi4_data structure * @rfi: pointer to synaptics_rmi4_fn structure @@ -316,8 +450,9 @@ static int synpatics_rmi4_touchpad_report(struct synaptics_rmi4_data *pdata, unsigned char data[DATA_LEN]; int x[RMI4_NUMBER_OF_MAX_FINGERS]; int y[RMI4_NUMBER_OF_MAX_FINGERS]; - int wx[RMI4_NUMBER_OF_MAX_FINGERS]; - int wy[RMI4_NUMBER_OF_MAX_FINGERS]; + int w[RMI4_NUMBER_OF_MAX_FINGERS]; + static int prv_x[RMI4_NUMBER_OF_MAX_FINGERS]; + static int prv_y[RMI4_NUMBER_OF_MAX_FINGERS]; struct i2c_client *client = pdata->i2c_client; /* get 2D sensor finger data */ @@ -376,11 +511,7 @@ static int synpatics_rmi4_touchpad_report(struct synaptics_rmi4_data *pdata, y[touch_count] = (data[1] << 4) | ((data[2] >> 4) & MASK_4BIT); - wy[touch_count] = - (data[3] >> 4) & MASK_4BIT; - wx[touch_count] = - (data[3] & MASK_4BIT); - + w[touch_count] = data[3]; if (pdata->board->x_flip) x[touch_count] = pdata->sensor_max_x - @@ -389,6 +520,25 @@ static int synpatics_rmi4_touchpad_report(struct synaptics_rmi4_data *pdata, y[touch_count] = pdata->sensor_max_y - y[touch_count]; + if (x[touch_count] < 0) + x[touch_count] = 0; + else if (x[touch_count] >= pdata->sensor_max_x) + x[touch_count] = + pdata->sensor_max_x - 1; + + if (y[touch_count] < 0) + y[touch_count] = 0; + else if (y[touch_count] >= pdata->sensor_max_y) + y[touch_count] = + pdata->sensor_max_y - 1; + } + if ((abs(x[finger] - prv_x[finger]) < DELTA) && + (abs(y[finger] - prv_y[finger]) < DELTA)) { + x[finger] = prv_x[finger]; + y[finger] = prv_y[finger]; + } else { + prv_x[finger] = x[finger]; + prv_y[finger] = y[finger]; } /* number of active touch points */ touch_count++; @@ -399,7 +549,9 @@ static int synpatics_rmi4_touchpad_report(struct synaptics_rmi4_data *pdata, if (touch_count) { for (finger = 0; finger < touch_count; finger++) { input_report_abs(pdata->input_dev, ABS_MT_TOUCH_MAJOR, - max(wx[finger] , wy[finger])); + max(x[finger] , y[finger])); + input_report_abs(pdata->input_dev, ABS_MT_WIDTH_MAJOR, + w[finger]); input_report_abs(pdata->input_dev, ABS_MT_POSITION_X, x[finger]); input_report_abs(pdata->input_dev, ABS_MT_POSITION_Y, @@ -502,7 +654,7 @@ static irqreturn_t synaptics_rmi4_irq(int irq, void *data) touch_count = synaptics_rmi4_sensor_report(pdata); if (touch_count) wait_event_timeout(pdata->wait, pdata->touch_stopped, - msecs_to_jiffies(1)); + msecs_to_jiffies(TIMEOUT_PERIOD)); else break; } while (!pdata->touch_stopped); @@ -881,9 +1033,27 @@ static int synaptics_rmi4_i2c_query_device(struct synaptics_rmi4_data *pdata) } /** + * synaptics_rmi4_resume_handler() - work queue for resume handler + * @work:work_struct structure pointer + * + * This work queue handler used to resume the device and returns none + */ +static void synaptics_rmi4_resume_handler(struct work_struct *work) +{ + struct synaptics_rmi4_data *prmi4_data = container_of(work, + struct synaptics_rmi4_data, resume_wq_handler); + struct i2c_client *client = prmi4_data->i2c_client; + int retval; + + retval = synaptics_rmi4_enable(prmi4_data); + if (retval < 0) + dev_err(&client->dev, "%s: resume failed\n", __func__); +} + +/** * synaptics_rmi4_probe() - Initialze the i2c-client touchscreen driver - * @i2c: i2c client structure pointer - * @id:i2c device id pointer + * @client: i2c client structure pointer + * @dev_id:i2c device id pointer * * This function will allocate and initialize the instance * data and request the irq and set the instance data as the clients @@ -927,19 +1097,17 @@ static int __devinit synaptics_rmi4_probe goto err_input; } - rmi4_data->regulator = regulator_get(&client->dev, "vdd"); - if (IS_ERR(rmi4_data->regulator)) { - dev_err(&client->dev, "%s:get regulator failed\n", - __func__); - retval = PTR_ERR(rmi4_data->regulator); - goto err_get_regulator; - } - retval = regulator_enable(rmi4_data->regulator); - if (retval < 0) { - dev_err(&client->dev, "%s:regulator enable failed\n", - __func__); - goto err_regulator_enable; + if (platformdata->regulator_en) { + rmi4_data->regulator = regulator_get(&client->dev, "vdd"); + if (IS_ERR(rmi4_data->regulator)) { + dev_err(&client->dev, "%s:get regulator failed\n", + __func__); + retval = PTR_ERR(rmi4_data->regulator); + goto err_regulator; + } + regulator_enable(rmi4_data->regulator); } + init_waitqueue_head(&rmi4_data->wait); /* * Copy i2c_client pointer into RTID's i2c_client pointer for @@ -987,7 +1155,16 @@ static int __devinit synaptics_rmi4_probe input_set_abs_params(rmi4_data->input_dev, ABS_MT_POSITION_Y, 0, rmi4_data->sensor_max_y, 0, 0); input_set_abs_params(rmi4_data->input_dev, ABS_MT_TOUCH_MAJOR, 0, - MAX_TOUCH_MAJOR, 0, 0); + max(rmi4_data->sensor_max_y, rmi4_data->sensor_max_y), + 0, 0); + input_set_abs_params(rmi4_data->input_dev, ABS_MT_WIDTH_MAJOR, 0, + MAX_WIDTH_MAJOR, 0, 0); + + retval = input_register_device(rmi4_data->input_dev); + if (retval) { + dev_err(&client->dev, "%s:input register failed\n", __func__); + goto err_input_register; + } /* Clear interrupts */ synaptics_rmi4_i2c_block_read(rmi4_data, @@ -1000,24 +1177,34 @@ static int __devinit synaptics_rmi4_probe if (retval) { dev_err(&client->dev, "%s:Unable to get attn irq %d\n", __func__, platformdata->irq_number); - goto err_query_dev; + goto err_request_irq; } - retval = input_register_device(rmi4_data->input_dev); + INIT_WORK(&rmi4_data->resume_wq_handler, synaptics_rmi4_resume_handler); + + /* sysfs implementation for dynamic enable/disable the input event */ + retval = sysfs_create_group(&client->dev.kobj, + &synaptics_rmi4_attr_group); if (retval) { - dev_err(&client->dev, "%s:input register failed\n", __func__); - goto err_free_irq; + dev_err(&client->dev, "failed to create sysfs entries\n"); + goto err_sysfs; } - + rmi4_data->enable = true; return retval; -err_free_irq: +err_sysfs: + cancel_work_sync(&rmi4_data->resume_wq_handler); +err_request_irq: free_irq(platformdata->irq_number, rmi4_data); + input_unregister_device(rmi4_data->input_dev); +err_input_register: + i2c_set_clientdata(client, NULL); err_query_dev: - regulator_disable(rmi4_data->regulator); -err_regulator_enable: - regulator_put(rmi4_data->regulator); -err_get_regulator: + if (platformdata->regulator_en) { + regulator_disable(rmi4_data->regulator); + regulator_put(rmi4_data->regulator); + } +err_regulator: input_free_device(rmi4_data->input_dev); rmi4_data->input_dev = NULL; err_input: @@ -1037,12 +1224,16 @@ static int __devexit synaptics_rmi4_remove(struct i2c_client *client) struct synaptics_rmi4_data *rmi4_data = i2c_get_clientdata(client); const struct synaptics_rmi4_platform_data *pdata = rmi4_data->board; + sysfs_remove_group(&client->dev.kobj, &synaptics_rmi4_attr_group); rmi4_data->touch_stopped = true; wake_up(&rmi4_data->wait); + cancel_work_sync(&rmi4_data->resume_wq_handler); free_irq(pdata->irq_number, rmi4_data); input_unregister_device(rmi4_data->input_dev); - regulator_disable(rmi4_data->regulator); - regulator_put(rmi4_data->regulator); + if (pdata->regulator_en) { + regulator_disable(rmi4_data->regulator); + regulator_put(rmi4_data->regulator); + } kfree(rmi4_data); return 0; @@ -1059,31 +1250,11 @@ static int __devexit synaptics_rmi4_remove(struct i2c_client *client) static int synaptics_rmi4_suspend(struct device *dev) { /* Touch sleep mode */ - int retval; - unsigned char intr_status; struct synaptics_rmi4_data *rmi4_data = dev_get_drvdata(dev); - const struct synaptics_rmi4_platform_data *pdata = rmi4_data->board; - rmi4_data->touch_stopped = true; - disable_irq(pdata->irq_number); - - retval = synaptics_rmi4_i2c_block_read(rmi4_data, - rmi4_data->fn01_data_base_addr + 1, - &intr_status, - rmi4_data->number_of_interrupt_register); - if (retval < 0) - return retval; - - retval = synaptics_rmi4_i2c_byte_write(rmi4_data, - rmi4_data->fn01_ctrl_base_addr + 1, - (intr_status & ~TOUCHPAD_CTRL_INTR)); - if (retval < 0) - return retval; - - regulator_disable(rmi4_data->regulator); - - return 0; + return synaptics_rmi4_disable(rmi4_data); } + /** * synaptics_rmi4_resume() - resume the touch screen controller * @dev: pointer to device structure @@ -1093,28 +1264,9 @@ static int synaptics_rmi4_suspend(struct device *dev) */ static int synaptics_rmi4_resume(struct device *dev) { - int retval; - unsigned char intr_status; struct synaptics_rmi4_data *rmi4_data = dev_get_drvdata(dev); - const struct synaptics_rmi4_platform_data *pdata = rmi4_data->board; - - regulator_enable(rmi4_data->regulator); - enable_irq(pdata->irq_number); - rmi4_data->touch_stopped = false; - - retval = synaptics_rmi4_i2c_block_read(rmi4_data, - rmi4_data->fn01_data_base_addr + 1, - &intr_status, - rmi4_data->number_of_interrupt_register); - if (retval < 0) - return retval; - - retval = synaptics_rmi4_i2c_byte_write(rmi4_data, - rmi4_data->fn01_ctrl_base_addr + 1, - (intr_status | TOUCHPAD_CTRL_INTR)); - if (retval < 0) - return retval; + schedule_work(&rmi4_data->resume_wq_handler); return 0; } diff --git a/drivers/staging/ste_rmi4/synaptics_i2c_rmi4.h b/drivers/staging/ste_rmi4/synaptics_i2c_rmi4.h index 384436ef806..973abc97374 100644 --- a/drivers/staging/ste_rmi4/synaptics_i2c_rmi4.h +++ b/drivers/staging/ste_rmi4/synaptics_i2c_rmi4.h @@ -42,6 +42,7 @@ struct synaptics_rmi4_platform_data { int irq_type; bool x_flip; bool y_flip; + bool regulator_en; }; #endif |