diff options
Diffstat (limited to 'drivers/input/keyboard')
-rw-r--r-- | drivers/input/keyboard/Kconfig | 9 | ||||
-rw-r--r-- | drivers/input/keyboard/Makefile | 2 | ||||
-rw-r--r-- | drivers/input/keyboard/cypress-touchkey-firmware.c | 322 | ||||
-rw-r--r-- | drivers/input/keyboard/cypress-touchkey-firmware.h | 212 | ||||
-rwxr-xr-x | drivers/input/keyboard/cypress-touchkey.c | 632 |
5 files changed, 1177 insertions, 0 deletions
diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig index b4dee9d..9f629bb 100644 --- a/drivers/input/keyboard/Kconfig +++ b/drivers/input/keyboard/Kconfig @@ -563,4 +563,13 @@ config KEYBOARD_W90P910 To compile this driver as a module, choose M here: the module will be called w90p910_keypad. +config KEYPAD_CYPRESS_TOUCH + tristate "Cypress touch keypad support" + default n + help + Say Y here if you want to use the Cypress touch keypad. + + To compile this driver as a module, choose M here: the + module will be called cypress-touchkey. + endif diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile index ddde0fd..27a47bf 100644 --- a/drivers/input/keyboard/Makefile +++ b/drivers/input/keyboard/Makefile @@ -51,3 +51,5 @@ obj-$(CONFIG_KEYBOARD_TNETV107X) += tnetv107x-keypad.o obj-$(CONFIG_KEYBOARD_TWL4030) += twl4030_keypad.o obj-$(CONFIG_KEYBOARD_XTKBD) += xtkbd.o obj-$(CONFIG_KEYBOARD_W90P910) += w90p910_keypad.o +obj-$(CONFIG_KEYPAD_CYPRESS_TOUCH) += cypress-touchkey.o +obj-$(CONFIG_KEYPAD_CYPRESS_TOUCH) += cypress-touchkey-firmware.o diff --git a/drivers/input/keyboard/cypress-touchkey-firmware.c b/drivers/input/keyboard/cypress-touchkey-firmware.c new file mode 100644 index 0000000..b1be506 --- /dev/null +++ b/drivers/input/keyboard/cypress-touchkey-firmware.c @@ -0,0 +1,322 @@ +/* + * Copyright 2010, Cypress Semiconductor Corporation. + * Copyright (C) 2010-2011, Samsung Electronics Co. Ltd. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301, USA. + * + */ + +#include <linux/string.h> +#include <linux/delay.h> +#include <linux/input/cypress-touchkey.h> +#include <linux/gpio.h> + +#include "cypress-touchkey-firmware.h" + +#define WRITE_BYTE_START 0x90 +#define WRITE_BYTE_END 0xE0 +#define NUM_BLOCK_END 0xE0 + +static void touchkey_init_gpio(struct touchkey_platform_data *pdata) +{ + gpio_direction_input(pdata->sda_pin); + gpio_direction_input(pdata->scl_pin); + gpio_set_value(pdata->en_pin, 1); + mdelay(1); +} + +static void touchkey_run_clk(struct touchkey_platform_data *pdata, + int cycles) +{ + int i; + + for (i = 0; i < cycles; i++) { + gpio_direction_output(pdata->scl_pin, 0); + gpio_direction_output(pdata->scl_pin, 1); + } +} + +static u8 touchkey_get_data(struct touchkey_platform_data *pdata) +{ + gpio_direction_input(pdata->sda_pin); + return !!gpio_get_value(pdata->sda_pin); +} + +static u8 touchkey_read_bit(struct touchkey_platform_data *pdata) +{ + touchkey_run_clk(pdata, 1); + return touchkey_get_data(pdata); +} + +static u8 touchkey_read_byte(struct touchkey_platform_data *pdata) +{ + int i; + u8 byte = 0; + + for (i = 7; i >= 0; i--) + byte |= touchkey_read_bit(pdata) << i; + + return byte; +} + +static void touchkey_send_bits(struct touchkey_platform_data *pdata, + u8 data, int num_bits) +{ + int i; + + for (i = 0; i < num_bits; i++, data <<= 1) { + gpio_direction_output(pdata->sda_pin, !!(data & 0x80)); + gpio_direction_output(pdata->scl_pin, 1); + gpio_direction_output(pdata->scl_pin, 0); + } +} + +static void touchkey_send_vector(struct touchkey_platform_data *pdata, + const struct issp_vector *vector_data) +{ + int i; + u16 num_bits; + + for (i = 0, num_bits = vector_data->num_bits; num_bits > 7; + num_bits -= 8, i++) + touchkey_send_bits(pdata, vector_data->data[i], 8); + + if (num_bits) + touchkey_send_bits(pdata, vector_data->data[i], num_bits); + gpio_direction_output(pdata->sda_pin, 0); + gpio_direction_input(pdata->sda_pin); +} + +static bool touchkey_wait_transaction(struct touchkey_platform_data *pdata) +{ + int i; + + for (i = 0; i <= TRANSITION_TIMEOUT; i++) { + gpio_direction_output(pdata->scl_pin, 0); + if (touchkey_get_data(pdata)) + break; + gpio_direction_output(pdata->scl_pin, 1); + } + + if (i == TRANSITION_TIMEOUT) + return true; + + for (i = 0; i <= TRANSITION_TIMEOUT; i++) { + gpio_direction_output(pdata->scl_pin, 0); + if (!touchkey_get_data(pdata)) + break; + gpio_direction_output(pdata->scl_pin, 1); + } + + return i == TRANSITION_TIMEOUT; +} + +static int touchkey_issp_pwr_init(struct touchkey_platform_data *pdata) +{ + touchkey_init_gpio(pdata); + if (touchkey_wait_transaction(pdata)) + return -1; + + gpio_direction_output(pdata->scl_pin, 0); + + touchkey_send_vector(pdata, &wait_and_poll_end); + touchkey_send_vector(pdata, &id_setup_1); + if (touchkey_wait_transaction(pdata)) + return -1; + + touchkey_send_vector(pdata, &wait_and_poll_end); + return 0; +} + +static int touchkey_read_status(struct touchkey_platform_data *pdata) +{ + unsigned char target_status; + + touchkey_send_vector(pdata, &tsync_enable); + + touchkey_send_vector(pdata, &read_id_v1); + touchkey_run_clk(pdata, 2); + target_status = touchkey_read_byte(pdata); + touchkey_run_clk(pdata, 1); + + touchkey_send_bits(pdata, 0, 1); + touchkey_send_vector(pdata, &tsync_disable); + + return target_status; +} + +static int touchkey_erase_target(struct touchkey_platform_data *pdata) +{ + touchkey_send_vector(pdata, &erase); + if (touchkey_wait_transaction(pdata)) + return -1; + + touchkey_send_vector(pdata, &wait_and_poll_end); + return 0; +} + +static u16 touchkey_load_target(struct touchkey_platform_data *pdata, + const u8 *target_data_output, int len) +{ + u16 checksum_data = 0; + u8 addr; + int i; + + for (i = 0, addr = 0; i < len; i++, addr += 2) { + checksum_data += target_data_output[i]; + touchkey_send_bits(pdata, WRITE_BYTE_START, 4); + touchkey_send_bits(pdata, addr, 7); + touchkey_send_bits(pdata, target_data_output[i], 8); + touchkey_send_bits(pdata, WRITE_BYTE_END, 3); + } + + return checksum_data; +} + +static int touchkey_program_target_block(struct touchkey_platform_data *pdata, + u8 block) +{ + touchkey_send_vector(pdata, &tsync_enable); + touchkey_send_vector(pdata, &set_block_num); + touchkey_send_bits(pdata, block, 8); + touchkey_send_bits(pdata, NUM_BLOCK_END, 3); + touchkey_send_vector(pdata, &tsync_disable); + touchkey_send_vector(pdata, &program_and_verify); + + if (touchkey_wait_transaction(pdata)) + return -1; + + touchkey_send_vector(pdata, &wait_and_poll_end); + return 0; +} + +static int touchkey_target_bank_checksum(struct touchkey_platform_data *pdata, + u16 *checksum) +{ + touchkey_send_vector(pdata, &checksum_setup); + if (touchkey_wait_transaction(pdata)) + return -1; + + touchkey_send_vector(pdata, &wait_and_poll_end); + touchkey_send_vector(pdata, &tsync_enable); + touchkey_send_vector(pdata, &read_checksum_v1); + touchkey_run_clk(pdata, 2); + *checksum = touchkey_read_byte(pdata) << 8; + + touchkey_run_clk(pdata, 1); + touchkey_send_vector(pdata, &read_checksum_v2); + touchkey_run_clk(pdata, 2); + *checksum |= touchkey_read_byte(pdata); + touchkey_run_clk(pdata, 1); + touchkey_send_bits(pdata, 0x80, 1); + touchkey_send_vector(pdata, &tsync_disable); + + return 0; +} + +static void touchkey_reset_target(struct touchkey_platform_data *pdata) +{ + gpio_direction_input(pdata->scl_pin); + gpio_direction_input(pdata->sda_pin); + gpio_set_value(pdata->en_pin, 0); + mdelay(300); + touchkey_init_gpio(pdata); +} + +static int touchkey_secure_target_flash(struct touchkey_platform_data *pdata) +{ + u8 addr; + int i; + + for (i = 0, addr = 0; i < SECURITY_BYTES_PER_BANK; i++, addr += 2) { + touchkey_send_bits(pdata, WRITE_BYTE_START, 4); + touchkey_send_bits(pdata, addr, 7); + touchkey_send_bits(pdata, 0xFF, 8); + touchkey_send_bits(pdata, WRITE_BYTE_END, 3); + } + + touchkey_send_vector(pdata, &secure); + if (touchkey_wait_transaction(pdata)) + return -1; + + touchkey_send_vector(pdata, &wait_and_poll_end); + return 0; +} + +int touchkey_flash_firmware(struct touchkey_platform_data *pdata, + const u8 *fw_data) +{ + u16 chksumtgt = 0; + u16 chksumdat = 0; + int i; + + gpio_direction_output(pdata->en_pin, 0); + + if (touchkey_issp_pwr_init(pdata)) { + pr_err("%s: error powering up\n", __func__); + goto error_trap; + } + + if (touchkey_erase_target(pdata)) { + pr_err("%s: error erasing flash\n", __func__); + goto error_trap; + } + + for (i = 0; i < BLOCKS_PER_BANK; i++) { + touchkey_send_vector(pdata, &tsync_enable); + touchkey_send_vector(pdata, &read_write_setup); + chksumdat += touchkey_load_target(pdata, + fw_data + i * BLOCK_SIZE, BLOCK_SIZE); + + if (touchkey_program_target_block(pdata, i)) { + pr_err("%s: error programming flash\n", __func__); + goto error_trap; + } + + if (touchkey_read_status(pdata)) { + pr_err("%s: error reading status\n", __func__); + goto error_trap; + } + } + + /* security start */ + touchkey_send_vector(pdata, &tsync_enable); + touchkey_send_vector(pdata, &read_write_setup); + if (touchkey_secure_target_flash(pdata)) { + pr_err("%s: error securing flash\n", __func__); + goto error_trap; + } + + if (touchkey_target_bank_checksum(pdata, &chksumtgt)) { + pr_err("%s: error reading checksum\n", __func__); + goto error_trap; + } + + if (chksumtgt != chksumdat) { + pr_err("%s: error powering up touchkey controller\n", __func__); + goto error_trap; + } + + touchkey_reset_target(pdata); + return 0; + +error_trap: + gpio_direction_input(pdata->scl_pin); + gpio_direction_input(pdata->sda_pin); + gpio_set_value(pdata->en_pin, 0); + mdelay(20); + return -1; +} diff --git a/drivers/input/keyboard/cypress-touchkey-firmware.h b/drivers/input/keyboard/cypress-touchkey-firmware.h new file mode 100644 index 0000000..cdfcea4 --- /dev/null +++ b/drivers/input/keyboard/cypress-touchkey-firmware.h @@ -0,0 +1,212 @@ +/* + * Copyright 2010, Cypress Semiconductor Corporation. + * Copyright (C) 2010-2011, Samsung Electronics Co. Ltd. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301, USA. + * + */ + +#include <linux/types.h> + +#ifndef _CYPRESS_TOUCHKEY_FIRMWARE_H__ +#define _CYPRESS_TOUCHKEY_FIRMWARE_H__ + +struct issp_vector { + u16 num_bits; + u8 data[]; +}; + +static const struct issp_vector tsync_enable = { + .data = { + 0xDE, 0xE2, 0x1F, 0x7F, 0x02, 0x7D, 0xC4, 0x09, + 0xF7, 0x00, 0x1F, 0xDE, 0xE0, 0x1C + }, + .num_bits = 110, +}; + +static const struct issp_vector tsync_disable = { + .data = { + 0xDE, 0xE2, 0x1F, 0x71, 0x00, 0x7D, 0xFC, 0x01, + 0xF7, 0x00, 0x1F, 0xDE, 0xE0, 0x1C + }, + .num_bits = 110, +}; + +static const struct issp_vector id_setup_1 = { + .data = { + 0xCA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0D, 0xEE, 0x21, 0xF7, 0xF0, 0x27, 0xDC, 0x40, + 0x9F, 0x70, 0x01, 0xFD, 0xEE, 0x01, 0xE7, 0xC1, + 0xD7, 0x9F, 0x20, 0x7E, 0x7D, 0x88, 0x7D, 0xEE, + 0x21, 0xF7, 0xF0, 0x07, 0xDC, 0x40, 0x1F, 0x70, + 0x01, 0xFD, 0xEE, 0x01, 0xF7, 0xA0, 0x1F, 0xDE, + 0xA0, 0x1F, 0x7B, 0x00, 0x7D, 0xE0, 0x13, 0xF7, + 0xC0, 0x07, 0xDF, 0x28, 0x1F, 0x7D, 0x18, 0x7D, + 0xFE, 0x25, 0xC0 + }, + .num_bits = 594, +}; + +static const struct issp_vector set_block_num = { + .data = { + 0x9f, 0x40, 0x1c + }, + .num_bits = 11, +}; + +static const struct issp_vector read_write_setup = { + .data = { + 0xde, 0xf0, 0x1f, 0x78, 0x00, 0x7d, 0xa0, 0x03, + 0xc0 + }, + .num_bits = 66, +}; + +static const struct issp_vector erase = { + .data = { + 0xde, 0xe2, 0x1f, 0x7f, 0x02, 0x7d, 0xc4, 0x09, + 0xf7, 0x00, 0x1f, 0x9f, 0x07, 0x5e, 0x7c, 0x85, + 0xfd, 0xfc, 0x01, 0xf7, 0x10, 0x07, 0xdc, 0x00, + 0x7f, 0x7b, 0x80, 0x7d, 0xe0, 0x0b, 0xf7, 0xa0, + 0x1f, 0xd7, 0xa0, 0x1f, 0x7b, 0x04, 0x7d, 0xf0, + 0x01, 0xf7, 0xc9, 0x87, 0xdf, 0x48, 0x1f, 0x7f, + 0x89, 0x70 + }, + .num_bits = 396, +}; + +static const struct issp_vector secure = { + .data = { + 0xde, 0xe2, 0x1f, 0x7f, 0x02, 0x7d, 0xc4, 0x09, + 0xf7, 0x00, 0x1f, 0x9f, 0x07, 0x5e, 0x7c, 0x81, + 0xf9, 0xf7, 0x01, 0xf7, 0xf0, 0x07, 0xdc, 0x40, + 0x1f, 0x70, 0x01, 0xfd, 0xee, 0x01, 0xf6, 0xa0, + 0x0f, 0xde, 0x80, 0x7f, 0x7a, 0x80, 0x7d, 0xec, + 0x01, 0xf7, 0x80, 0x27, 0xdf, 0x00, 0x1f, 0x7c, + 0xa0, 0x7d, 0xf4, 0x61, 0xf7, 0xf8, 0x97 + }, + .num_bits = 440, +}; + +static const struct issp_vector read_security_setup = { + .data = { + 0xde, 0xe2, 0x1f, 0x60, 0x88, 0x7d, 0x84, 0x21, + 0xf7, 0xb8, 0x07 + }, + .num_bits = 88, +}; + +static const struct issp_vector read_security_pt1 = { + .data = { + 0xde, 0xe2, 0x1f, 0x72, 0x87, 0x7d, 0xca, 0x01, + 0xf7, 0x28 + }, + .num_bits = 78, +}; + +static const struct issp_vector read_security_pt1_end = { + .data = { + 0xfb, 0x94, 0x03, 0x80 + }, + .num_bits = 25, +}; + +static const struct issp_vector read_security_pt2 = { + .data = { + 0xde, 0xe0, 0x1f, 0x7a, 0x01, 0xfd, 0xea, 0x01, + 0xf7, 0xb0, 0x07, 0xdf, 0x0b, 0xbf, 0x7c, 0xf2, + 0xfd, 0xf4, 0x61, 0xf7, 0xb8, 0x87, 0xdf, 0xe2, + 0x5c + }, + .num_bits = 198, +}; + +static const struct issp_vector read_security_pt3 = { + .data = { + 0xde, 0xe0, 0x1f, 0x7a, 0x01, 0xfd, 0xea, 0x01, + 0xf7, 0xb0, 0x07, 0xdf, 0x0a, 0x7f, 0x7c, 0xc0 + }, + .num_bits = 122, +}; + +static const struct issp_vector read_security_pt3_end = { + .data = { + 0xfb, 0xe8, 0xc3, 0xef, 0xf1, 0x2e + }, + .num_bits = 47, +}; + +static const struct issp_vector checksum_setup = { + .data = { + 0xde, 0xe2, 0x1f, 0x7f, 0x02, 0x7d, 0xc4, 0x09, + 0xf7, 0x00, 0x1f, 0x9f, 0x07, 0x5e, 0x7c, 0x81, + 0xf9, 0xf4, 0x01, 0xf7, 0xf0, 0x07, 0xdc, 0x40, + 0x1f, 0x70, 0x01, 0xfd, 0xee, 0x01, 0xf7, 0xa0, + 0x1f, 0xde, 0xa0, 0x1f, 0x7b, 0x00, 0x7d, 0xe0, + 0x0f, 0xf7, 0xc0, 0x07, 0xdf, 0x28, 0x1f, 0x7d, + 0x18, 0x7d, 0xfe, 0x25, 0xc0 + }, + .num_bits = 418, +}; + +static const struct issp_vector program_and_verify = { + .data = { + 0xde, 0xe2, 0x1f, 0x7f, 0x02, 0x7d, 0xc4, 0x09, + 0xf7, 0x00, 0x1f, 0x9f, 0x07, 0x5e, 0x7c, 0x81, + 0xf9, 0xf7, 0x01, 0xf7, 0xf0, 0x07, 0xdc, 0x40, + 0x1f, 0x70, 0x01, 0xfd, 0xee, 0x01, 0xf6, 0xa0, + 0x0f, 0xde, 0x80, 0x7f, 0x7a, 0x80, 0x7d, 0xec, + 0x01, 0xf7, 0x80, 0x57, 0xdf, 0x00, 0x1f, 0x7c, + 0xa0, 0x7d, 0xf4, 0x61, 0xf7, 0xf8, 0x97 + }, + .num_bits = 440, +}; + +static const struct issp_vector read_id_v1 = { + .data = { + 0xBF, 0x00 + }, + .num_bits = 11, +}; + +static const struct issp_vector wait_and_poll_end = { + .data = { + 0x00, 0x00, 0x00, 0x00 + }, + .num_bits = 30, +}; + +static const struct issp_vector read_checksum_v1 = { + .data = { + 0xBF, 0x20 + }, + .num_bits = 11, +}; + +static const struct issp_vector read_checksum_v2 = { + .data = { + 0xDF, 0x80 + }, + .num_bits = 12, +}; + +#define BLOCK_SIZE 128 +#define BLOCKS_PER_BANK 64 +#define SECURITY_BYTES_PER_BANK 64 +#define TRANSITION_TIMEOUT 0x100000 + +#endif /* _CYPRESS_TOUCHKEY_FIRMWARE_H__ */ diff --git a/drivers/input/keyboard/cypress-touchkey.c b/drivers/input/keyboard/cypress-touchkey.c new file mode 100755 index 0000000..3e4c75d --- /dev/null +++ b/drivers/input/keyboard/cypress-touchkey.c @@ -0,0 +1,632 @@ +/* + * Copyright 2006-2010, Cypress Semiconductor Corporation. + * Copyright (C) 2010, Samsung Electronics Co. Ltd. All Rights Reserved. + * Copyright 2011, Michael Richter (alias neldar) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301, USA. + * + */ +#include <linux/module.h> +#include <linux/init.h> +#include <linux/i2c.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/input.h> +#include <linux/earlysuspend.h> +#include <linux/input/cypress-touchkey.h> +#include <linux/firmware.h> +#include <linux/bln.h> + +#ifdef CONFIG_BLD +#include <linux/bld.h> +#endif + +#define SCANCODE_MASK 0x07 +#define UPDOWN_EVENT_MASK 0x08 +#define ESD_STATE_MASK 0x10 + +#define BACKLIGHT_ON 0x10 +#define BACKLIGHT_OFF 0x20 + +#define OLD_BACKLIGHT_ON 0x1 +#define OLD_BACKLIGHT_OFF 0x2 + +#define DEVICE_NAME "cypress-touchkey" + +#define FW_SIZE 8192 + +struct cypress_touchkey_devdata { + struct i2c_client *client; + struct input_dev *input_dev; + struct touchkey_platform_data *pdata; + struct early_suspend early_suspend; + u8 backlight_on; + u8 backlight_off; + bool is_dead; + bool is_powering_on; + bool has_legacy_keycode; +}; + +static struct cypress_touchkey_devdata *blndevdata; +#ifdef CONFIG_BLD +static struct cypress_touchkey_devdata *blddevdata; +#endif + +static int i2c_touchkey_read_byte(struct cypress_touchkey_devdata *devdata, + u8 *val) +{ + int ret; + int retry = 5; + + while (true) { + ret = i2c_smbus_read_byte(devdata->client); + if (ret >= 0) { + *val = ret; + return 0; + } + + dev_err(&devdata->client->dev, "i2c read error\n"); + if (!retry--) + break; + msleep(10); + } + + return ret; +} + +static int i2c_touchkey_write_byte(struct cypress_touchkey_devdata *devdata, + u8 val) +{ + int ret; + int retry = 2; + + while (true) { + ret = i2c_smbus_write_byte(devdata->client, val); + if (!ret) + return 0; + + dev_err(&devdata->client->dev, "i2c write error\n"); + if (!retry--) + break; + msleep(10); + } + + return ret; +} + +static void all_keys_up(struct cypress_touchkey_devdata *devdata) +{ + int i; + + for (i = 0; i < devdata->pdata->keycode_cnt; i++) + input_report_key(devdata->input_dev, + devdata->pdata->keycode[i], 0); + + input_sync(devdata->input_dev); +} + +static int recovery_routine(struct cypress_touchkey_devdata *devdata) +{ + int ret = -1; + int retry = 10; + u8 data; + int irq_eint; + + if (unlikely(devdata->is_dead)) { + dev_err(&devdata->client->dev, "%s: Device is already dead, " + "skipping recovery\n", __func__); + return -ENODEV; + } + + irq_eint = devdata->client->irq; + + all_keys_up(devdata); + + disable_irq_nosync(irq_eint); + while (retry--) { + devdata->pdata->touchkey_onoff(TOUCHKEY_OFF); + devdata->pdata->touchkey_onoff(TOUCHKEY_ON); + ret = i2c_touchkey_read_byte(devdata, &data); + if (!ret) { + enable_irq(irq_eint); + goto out; + } + dev_err(&devdata->client->dev, "%s: i2c transfer error retry = " + "%d\n", __func__, retry); + } + devdata->is_dead = true; + devdata->pdata->touchkey_onoff(TOUCHKEY_OFF); + dev_err(&devdata->client->dev, "%s: touchkey died\n", __func__); +out: + return ret; +} + +static irqreturn_t touchkey_interrupt_thread(int irq, void *touchkey_devdata) +{ + u8 data; + int i; + int ret; + int scancode; + struct cypress_touchkey_devdata *devdata = touchkey_devdata; + + ret = i2c_touchkey_read_byte(devdata, &data); + if (ret || (data & ESD_STATE_MASK)) { + ret = recovery_routine(devdata); + if (ret) { + dev_err(&devdata->client->dev, "%s: touchkey recovery " + "failed!\n", __func__); + goto err; + } + } + + if (devdata->has_legacy_keycode) { + scancode = (data & SCANCODE_MASK) - 1; + if (scancode < 0 || scancode >= devdata->pdata->keycode_cnt) { + dev_err(&devdata->client->dev, "%s: scancode is out of " + "range\n", __func__); + goto err; + } + input_report_key(devdata->input_dev, + devdata->pdata->keycode[scancode], + !(data & UPDOWN_EVENT_MASK)); + +#if defined(CONFIG_TOUCH_WAKE) || defined(CONFIG_BLD) + if (!(data & UPDOWN_EVENT_MASK)) + { +#ifdef CONFIG_BLD + touchkey_pressed(); +#endif + } +#endif + } else { + for (i = 0; i < devdata->pdata->keycode_cnt; i++) + input_report_key(devdata->input_dev, + devdata->pdata->keycode[i], + !!(data & (1U << i))); + +#if defined(CONFIG_TOUCH_WAKE) || defined(CONFIG_BLD) + for (i = 0; i < devdata->pdata->keycode_cnt; i++) + { + if(!!(data & (1U << i))) + { +#ifdef CONFIG_BLD + touchkey_pressed(); +#endif + break; + } + } +#endif + } + + input_sync(devdata->input_dev); +err: + return IRQ_HANDLED; +} + +static irqreturn_t touchkey_interrupt_handler(int irq, void *touchkey_devdata) +{ + struct cypress_touchkey_devdata *devdata = touchkey_devdata; + + if (devdata->is_powering_on) { + dev_dbg(&devdata->client->dev, "%s: ignoring spurious boot " + "interrupt\n", __func__); + return IRQ_HANDLED; + } + + return IRQ_WAKE_THREAD; +} + +#ifdef CONFIG_HAS_EARLYSUSPEND +static void cypress_touchkey_early_suspend(struct early_suspend *h) +{ + struct cypress_touchkey_devdata *devdata = + container_of(h, struct cypress_touchkey_devdata, early_suspend); + + devdata->is_powering_on = true; + + if (unlikely(devdata->is_dead)) + return; + + disable_irq(devdata->client->irq); + +#ifdef CONFIG_GENERIC_BLN + /* + * Disallow powering off the touchkey controller + * while a led notification is ongoing + */ + if(!bln_is_ongoing()) { + devdata->pdata->touchkey_onoff(TOUCHKEY_OFF); + devdata->pdata->touchkey_sleep_onoff(TOUCHKEY_OFF); + } +#endif + devdata->pdata->touchkey_onoff(TOUCHKEY_OFF); + + all_keys_up(devdata); +} + +static void cypress_touchkey_early_resume(struct early_suspend *h) +{ + struct cypress_touchkey_devdata *devdata = + container_of(h, struct cypress_touchkey_devdata, early_suspend); + + devdata->pdata->touchkey_onoff(TOUCHKEY_ON); + if (i2c_touchkey_write_byte(devdata, devdata->backlight_on)) { + devdata->is_dead = true; + devdata->pdata->touchkey_onoff(TOUCHKEY_OFF); + dev_err(&devdata->client->dev, "%s: touch keypad not responding" + " to commands, disabling\n", __func__); + return; + } + devdata->is_dead = false; + enable_irq(devdata->client->irq); + devdata->is_powering_on = false; +} +#endif + +static void set_device_params(struct cypress_touchkey_devdata *devdata, + u8 *data) +{ + if (data[1] < 0xc4 && (data[1] > 0x8 || + (data[1] == 0x8 && data[2] >= 0x9))) { + devdata->backlight_on = BACKLIGHT_ON; + devdata->backlight_off = BACKLIGHT_OFF; + } else { + devdata->backlight_on = OLD_BACKLIGHT_ON; + devdata->backlight_off = OLD_BACKLIGHT_OFF; + } + + devdata->has_legacy_keycode = data[1] >= 0xc4 || data[1] < 0x9 || + (data[1] == 0x9 && data[2] < 0x9); +} + +static int update_firmware(struct cypress_touchkey_devdata *devdata) +{ + int ret; + const struct firmware *fw = NULL; + struct device *dev = &devdata->input_dev->dev; + int retries = 10; + + dev_info(dev, "%s: Updating " DEVICE_NAME " firmware\n", __func__); + + if (!devdata->pdata->fw_name) { + dev_err(dev, "%s: Device firmware name is not set\n", __func__); + return -EINVAL; + } + + ret = request_firmware(&fw, devdata->pdata->fw_name, dev); + if (ret) { + dev_err(dev, "%s: Can't open firmware file from %s\n", __func__, + devdata->pdata->fw_name); + return ret; + } + + if (fw->size != FW_SIZE) { + dev_err(dev, "%s: Firmware file size invalid\n", __func__); + return -EINVAL; + } + + disable_irq(devdata->client->irq); + /* Lock the i2c bus since the firmware updater accesses it */ + i2c_lock_adapter(devdata->client->adapter); + while (retries--) { + ret = touchkey_flash_firmware(devdata->pdata, fw->data); + if (!ret) + break; + } + if (ret) + dev_err(dev, "%s: Firmware update failed\n", __func__); + i2c_unlock_adapter(devdata->client->adapter); + enable_irq(devdata->client->irq); + + release_firmware(fw); + return ret; +} + +static void enable_touchkey_backlights(void){ + i2c_touchkey_write_byte(blndevdata, blndevdata->backlight_on); +} + +static void disable_touchkey_backlights(void){ + i2c_touchkey_write_byte(blndevdata, blndevdata->backlight_off); +} + +static int cypress_touchkey_open(struct input_dev *input_dev) +{ + struct device *dev = &input_dev->dev; + struct cypress_touchkey_devdata *devdata = dev_get_drvdata(dev); + u8 data[3]; + int ret; + + ret = update_firmware(devdata); + if (ret) + goto done; + + ret = i2c_master_recv(devdata->client, data, sizeof(data)); + if (ret < sizeof(data)) { + if (ret >= 0) + ret = -EIO; + dev_err(dev, "%s: error reading hardware version\n", __func__); + goto done; + } + + dev_info(dev, "%s: hardware rev1 = %#02x, rev2 = %#02x\n", __func__, + data[1], data[2]); + set_device_params(devdata, data); + + devdata->pdata->touchkey_onoff(TOUCHKEY_OFF); + devdata->pdata->touchkey_onoff(TOUCHKEY_ON); + + ret = i2c_touchkey_write_byte(devdata, devdata->backlight_on); + if (ret) { + dev_err(dev, "%s: touch keypad backlight on failed\n", + __func__); + goto done; + } + +done: + input_dev->open = NULL; + return 0; +} + +static void cypress_touchkey_enable_led_notification(void){ + /* is_powering_on signals whether touchkey lights are used for touchmode */ + if (blndevdata->is_powering_on){ + /* reconfigure gpio for sleep mode */ + blndevdata->pdata->touchkey_sleep_onoff(TOUCHKEY_ON); + + /* + * power on the touchkey controller + * This is actually not needed, but it is intentionally + * left for the case that the early_resume() function + * did not power on the touchkey controller for some reasons + */ + blndevdata->pdata->touchkey_onoff(TOUCHKEY_ON); + + /* write to i2cbus, enable backlights */ + enable_touchkey_backlights(); + } + else + pr_info("%s: cannot set notification led, touchkeys are enabled\n",__FUNCTION__); +} + +static void cypress_touchkey_disable_led_notification(void){ + /* + * reconfigure gpio for sleep mode, this has to be done + * independently from the power status + */ + blndevdata->pdata->touchkey_sleep_onoff(TOUCHKEY_OFF); + + /* if touchkeys lights are not used for touchmode */ + if (blndevdata->is_powering_on){ + disable_touchkey_backlights(); + + #if 0 + /* + * power off the touchkey controller + * This is actually not needed, the early_suspend function + * should take care of powering off the touchkey controller + */ + blndevdata->pdata->touchkey_onoff(TOUCHKEY_OFF); + #endif + } +} + +static struct bln_implementation cypress_touchkey_bln = { + .enable = cypress_touchkey_enable_led_notification, + .disable = cypress_touchkey_disable_led_notification, +}; + +#ifdef CONFIG_BLD +static void cypress_touchkey_bld_disable(void) +{ + i2c_touchkey_write_byte(blddevdata, blddevdata->backlight_off); +} + +static void cypress_touchkey_bld_enable(void) +{ + i2c_touchkey_write_byte(blddevdata, blddevdata->backlight_on); +} + +static struct bld_implementation cypress_touchkey_bld = + { + .enable = cypress_touchkey_bld_enable, + .disable = cypress_touchkey_bld_disable, + }; +#endif + +static int cypress_touchkey_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct input_dev *input_dev; + struct cypress_touchkey_devdata *devdata; + u8 data[3]; + int err; + int cnt; + + if (!dev->platform_data) { + dev_err(dev, "%s: Platform data is NULL\n", __func__); + return -EINVAL; + } + + devdata = kzalloc(sizeof(*devdata), GFP_KERNEL); + if (devdata == NULL) { + dev_err(dev, "%s: failed to create our state\n", __func__); + return -ENODEV; + } + + devdata->client = client; + i2c_set_clientdata(client, devdata); + + devdata->pdata = client->dev.platform_data; + if (!devdata->pdata->keycode) { + dev_err(dev, "%s: Invalid platform data\n", __func__); + err = -EINVAL; + goto err_null_keycodes; + } + + strlcpy(devdata->client->name, DEVICE_NAME, I2C_NAME_SIZE); + + input_dev = input_allocate_device(); + if (!input_dev) { + err = -ENOMEM; + goto err_input_alloc_dev; + } + + devdata->input_dev = input_dev; + dev_set_drvdata(&input_dev->dev, devdata); + input_dev->name = DEVICE_NAME; + input_dev->id.bustype = BUS_HOST; + + for (cnt = 0; cnt < devdata->pdata->keycode_cnt; cnt++) + input_set_capability(input_dev, EV_KEY, + devdata->pdata->keycode[cnt]); + + devdata->is_powering_on = true; + + devdata->pdata->touchkey_onoff(TOUCHKEY_ON); + + err = i2c_master_recv(client, data, sizeof(data)); + if (err < sizeof(data)) { + if (err >= 0) + err = -EIO; + dev_err(dev, "%s: error reading hardware version\n", __func__); + goto err_read; + } + + dev_info(dev, "%s: hardware rev1 = %#02x, rev2 = %#02x\n", __func__, + data[1], data[2]); + + if (data[1] != 0xa || data[2] < 0x9) + input_dev->open = cypress_touchkey_open; + + err = input_register_device(input_dev); + if (err) + goto err_input_reg_dev; + + set_device_params(devdata, data); + + err = i2c_touchkey_write_byte(devdata, devdata->backlight_on); + if (err) { + dev_err(dev, "%s: touch keypad backlight on failed\n", + __func__); + /* The device may not be responding because of bad firmware + * Allow the firmware to be reflashed if it needs to be + */ + if (!input_dev->open) + goto err_backlight_on; + } + + err = request_threaded_irq(client->irq, touchkey_interrupt_handler, + touchkey_interrupt_thread, IRQF_TRIGGER_FALLING, + DEVICE_NAME, devdata); + if (err) { + dev_err(dev, "%s: Can't allocate irq.\n", __func__); + goto err_req_irq; + } + +#ifdef CONFIG_HAS_EARLYSUSPEND + devdata->early_suspend.suspend = cypress_touchkey_early_suspend; + devdata->early_suspend.resume = cypress_touchkey_early_resume; +#endif + register_early_suspend(&devdata->early_suspend); + + devdata->is_powering_on = false; + +#ifdef CONFIG_GENERIC_BLN + blndevdata = devdata; + register_bln_implementation(&cypress_touchkey_bln); +#endif + +#ifdef CONFIG_BLD + blddevdata = devdata; + register_bld_implementation(&cypress_touchkey_bld); +#endif + + return 0; + + +err_req_irq: +err_backlight_on: + input_unregister_device(input_dev); + goto touchkey_off; +err_input_reg_dev: +err_read: + input_free_device(input_dev); +touchkey_off: + devdata->pdata->touchkey_onoff(TOUCHKEY_OFF); +err_input_alloc_dev: +err_null_keycodes: + kfree(devdata); + return err; +} + +static int __devexit i2c_touchkey_remove(struct i2c_client *client) +{ + struct cypress_touchkey_devdata *devdata = i2c_get_clientdata(client); + + unregister_early_suspend(&devdata->early_suspend); + /* If the device is dead IRQs are disabled, we need to rebalance them */ + if (unlikely(devdata->is_dead)) + enable_irq(client->irq); + else + devdata->pdata->touchkey_onoff(TOUCHKEY_OFF); + free_irq(client->irq, devdata); + all_keys_up(devdata); + input_unregister_device(devdata->input_dev); + kfree(devdata); + return 0; +} + +static const struct i2c_device_id cypress_touchkey_id[] = { + { CYPRESS_TOUCHKEY_DEV_NAME, 0 }, +}; + +MODULE_DEVICE_TABLE(i2c, cypress_touchkey_id); + +struct i2c_driver touchkey_i2c_driver = { + .driver = { + .name = "cypress_touchkey_driver", + }, + .id_table = cypress_touchkey_id, + .probe = cypress_touchkey_probe, + .remove = __devexit_p(i2c_touchkey_remove), +}; + +static int __init touchkey_init(void) +{ + int ret = 0; + + ret = i2c_add_driver(&touchkey_i2c_driver); + if (ret) + pr_err("%s: cypress touch keypad registration failed. (%d)\n", + __func__, ret); + + return ret; +} + +static void __exit touchkey_exit(void) +{ + i2c_del_driver(&touchkey_i2c_driver); +} + +late_initcall(touchkey_init); +module_exit(touchkey_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("@@@"); +MODULE_DESCRIPTION("cypress touch keypad"); |