aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/input/keyboard
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/input/keyboard')
-rw-r--r--drivers/input/keyboard/Kconfig9
-rw-r--r--drivers/input/keyboard/Makefile2
-rw-r--r--drivers/input/keyboard/cypress-touchkey-firmware.c322
-rw-r--r--drivers/input/keyboard/cypress-touchkey-firmware.h212
-rwxr-xr-xdrivers/input/keyboard/cypress-touchkey.c632
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");