diff options
Diffstat (limited to 'drivers/input')
-rw-r--r-- | drivers/input/joystick/xpad.c | 1 | ||||
-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 | ||||
-rw-r--r-- | drivers/input/misc/Kconfig | 13 | ||||
-rw-r--r-- | drivers/input/misc/Makefile | 3 | ||||
-rw-r--r-- | drivers/input/misc/gp2a.c | 641 | ||||
-rw-r--r-- | drivers/input/misc/k3g.c | 707 | ||||
-rw-r--r-- | drivers/input/serio/i8042-x86ia64io.h | 20 | ||||
-rw-r--r-- | drivers/input/tablet/wacom_wac.c | 2 | ||||
-rw-r--r-- | drivers/input/touchscreen/Kconfig | 12 | ||||
-rw-r--r-- | drivers/input/touchscreen/Makefile | 1 | ||||
-rw-r--r-- | drivers/input/touchscreen/mxt224.c | 658 |
15 files changed, 3233 insertions, 2 deletions
diff --git a/drivers/input/joystick/xpad.c b/drivers/input/joystick/xpad.c index 56abf3d..92c7be1 100644 --- a/drivers/input/joystick/xpad.c +++ b/drivers/input/joystick/xpad.c @@ -142,6 +142,7 @@ static const struct xpad_device { { 0x0c12, 0x880a, "Pelican Eclipse PL-2023", 0, XTYPE_XBOX }, { 0x0c12, 0x8810, "Zeroplus Xbox Controller", 0, XTYPE_XBOX }, { 0x0c12, 0x9902, "HAMA VibraX - *FAULTY HARDWARE*", 0, XTYPE_XBOX }, + { 0x0d2f, 0x0002, "Andamiro Pump It Up pad", MAP_DPAD_TO_BUTTONS, XTYPE_XBOX }, { 0x0e4c, 0x1097, "Radica Gamester Controller", 0, XTYPE_XBOX }, { 0x0e4c, 0x2390, "Radica Games Jtech Controller", 0, XTYPE_XBOX }, { 0x0e6f, 0x0003, "Logic3 Freebird wireless Controller", 0, XTYPE_XBOX }, 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"); diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig index 6f4ad1a..ad92e61 100644 --- a/drivers/input/misc/Kconfig +++ b/drivers/input/misc/Kconfig @@ -12,6 +12,12 @@ menuconfig INPUT_MISC if INPUT_MISC +config GYRO_K3G + tristate "K3G driver for s5pc11x" + default n + help + This option enables gyro sensors using K3G driver + config INPUT_88PM860X_ONKEY tristate "88PM860x ONKEY support" depends on MFD_88PM860X @@ -494,4 +500,11 @@ config INPUT_XEN_KBDDEV_FRONTEND To compile this driver as a module, choose M here: the module will be called xen-kbdfront. +config OPTICAL_GP2A + depends on I2C && GENERIC_GPIO + tristate "GP2A ambient light and proximity input device" + default n + help + This option enables proximity & light sensors using gp2a driver. + endif diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile index eb73834..175d548 100644 --- a/drivers/input/misc/Makefile +++ b/drivers/input/misc/Makefile @@ -4,6 +4,7 @@ # Each configuration option enables a list of files. +obj-$(CONFIG_GYRO_K3G) += k3g.o obj-$(CONFIG_INPUT_88PM860X_ONKEY) += 88pm860x_onkey.o obj-$(CONFIG_INPUT_AB8500_PONKEY) += ab8500-ponkey.o obj-$(CONFIG_INPUT_AD714X) += ad714x.o @@ -47,4 +48,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_OPTICAL_GP2A) += gp2a.o diff --git a/drivers/input/misc/gp2a.c b/drivers/input/misc/gp2a.c new file mode 100644 index 0000000..4dc2982 --- /dev/null +++ b/drivers/input/misc/gp2a.c @@ -0,0 +1,641 @@ +/* linux/driver/input/misc/gp2a.c + * Copyright (C) 2010 Samsung Electronics. 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 + * version 2 as published by the Free Software Foundation. + * + * 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 St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/i2c.h> +#include <linux/fs.h> +#include <linux/errno.h> +#include <linux/device.h> +#include <linux/delay.h> +#include <linux/miscdevice.h> +#include <linux/platform_device.h> +#include <linux/leds.h> +#include <linux/gpio.h> +#include <linux/wakelock.h> +#include <linux/slab.h> +#include <linux/input.h> +#include <linux/workqueue.h> +#include <linux/uaccess.h> +#include <linux/gp2a.h> + + +/* Note about power vs enable/disable: + * The chip has two functions, proximity and ambient light sensing. + * There is no separate power enablement to the two functions (unlike + * the Capella CM3602/3623). + * This module implements two drivers: /dev/proximity and /dev/light. + * When either driver is enabled (via sysfs attributes), we give power + * to the chip. When both are disabled, we remove power from the chip. + * In suspend, we remove power if light is disabled but not if proximity is + * enabled (proximity is allowed to wakeup from suspend). + * + * There are no ioctls for either driver interfaces. Output is via + * input device framework and control via sysfs attributes. + */ + + +#define gp2a_dbgmsg(str, args...) pr_debug("%s: " str, __func__, ##args) + +/* ADDSEL is LOW */ +#define REGS_PROX 0x0 /* Read Only */ +#define REGS_GAIN 0x1 /* Write Only */ +#define REGS_HYS 0x2 /* Write Only */ +#define REGS_CYCLE 0x3 /* Write Only */ +#define REGS_OPMOD 0x4 /* Write Only */ + +/* sensor type */ +#define LIGHT 0 +#define PROXIMITY 1 +#define ALL 2 + +#define DELAY_LOWBOUND (5 * NSEC_PER_MSEC) + +/* start time delay for light sensor in nano seconds */ +#define LIGHT_SENSOR_START_TIME_DELAY 50000000 + +static u8 reg_defaults[5] = { + 0x00, /* PROX: read only register */ + 0x08, /* GAIN: large LED drive level */ + 0xC2, /* HYS: receiver sensitivity */ + 0x04, /* CYCLE: */ + 0x01, /* OPMOD: normal operating mode */ +}; + +enum { + LIGHT_ENABLED = BIT(0), + PROXIMITY_ENABLED = BIT(1), +}; + +/* driver data */ +struct gp2a_data { + struct input_dev *proximity_input_dev; + struct input_dev *light_input_dev; + struct gp2a_platform_data *pdata; + struct i2c_client *i2c_client; + int irq; + struct work_struct work_light; + struct hrtimer timer; + ktime_t light_poll_delay; + bool on; + u8 power_state; + struct mutex power_lock; + struct wake_lock prx_wake_lock; + struct workqueue_struct *wq; +}; + +int gp2a_i2c_write(struct gp2a_data *gp2a, u8 reg, u8 *val) +{ + int err = 0; + struct i2c_msg msg[1]; + unsigned char data[2]; + int retry = 10; + struct i2c_client *client = gp2a->i2c_client; + + if ((client == NULL) || (!client->adapter)) + return -ENODEV; + + while (retry--) { + data[0] = reg; + data[1] = *val; + + msg->addr = client->addr; + msg->flags = 0; /* write */ + msg->len = 2; + msg->buf = data; + + err = i2c_transfer(client->adapter, msg, 1); + + if (err >= 0) + return 0; + } + return err; +} + +static void gp2a_light_enable(struct gp2a_data *gp2a) +{ + gp2a_dbgmsg("starting poll timer, delay %lldns\n", + ktime_to_ns(gp2a->light_poll_delay)); + /* + * Set far out of range ABS_MISC value, -1024, to enable real value to + * go through next. + */ + input_abs_set_val(gp2a->light_input_dev, + ABS_MISC, -gp2a->pdata->light_adc_max); + hrtimer_start(&gp2a->timer, ktime_set(0, LIGHT_SENSOR_START_TIME_DELAY), + HRTIMER_MODE_REL); +} + +static void gp2a_light_disable(struct gp2a_data *gp2a) +{ + gp2a_dbgmsg("cancelling poll timer\n"); + hrtimer_cancel(&gp2a->timer); + cancel_work_sync(&gp2a->work_light); +} + +static ssize_t poll_delay_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct gp2a_data *gp2a = dev_get_drvdata(dev); + return sprintf(buf, "%lld\n", ktime_to_ns(gp2a->light_poll_delay)); +} + + +static ssize_t poll_delay_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct gp2a_data *gp2a = dev_get_drvdata(dev); + int64_t new_delay; + int err; + + err = strict_strtoll(buf, 10, &new_delay); + if (err < 0) + return err; + + gp2a_dbgmsg("new delay = %lldns, old delay = %lldns\n", + new_delay, ktime_to_ns(gp2a->light_poll_delay)); + + if (new_delay < DELAY_LOWBOUND) { + gp2a_dbgmsg("new delay less than low bound, so set delay " + "to %lld\n", (int64_t)DELAY_LOWBOUND); + new_delay = DELAY_LOWBOUND; + } + + mutex_lock(&gp2a->power_lock); + if (new_delay != ktime_to_ns(gp2a->light_poll_delay)) { + gp2a->light_poll_delay = ns_to_ktime(new_delay); + if (gp2a->power_state & LIGHT_ENABLED) { + gp2a_light_disable(gp2a); + gp2a_light_enable(gp2a); + } + } + mutex_unlock(&gp2a->power_lock); + + return size; +} + +static ssize_t light_enable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct gp2a_data *gp2a = dev_get_drvdata(dev); + return sprintf(buf, "%d\n", + (gp2a->power_state & LIGHT_ENABLED) ? 1 : 0); +} + +static ssize_t proximity_enable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct gp2a_data *gp2a = dev_get_drvdata(dev); + return sprintf(buf, "%d\n", + (gp2a->power_state & PROXIMITY_ENABLED) ? 1 : 0); +} + +static ssize_t light_enable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct gp2a_data *gp2a = dev_get_drvdata(dev); + bool new_value; + + if (sysfs_streq(buf, "1")) + new_value = true; + else if (sysfs_streq(buf, "0")) + new_value = false; + else { + pr_err("%s: invalid value %d\n", __func__, *buf); + return -EINVAL; + } + + mutex_lock(&gp2a->power_lock); + gp2a_dbgmsg("new_value = %d, old state = %d\n", + new_value, (gp2a->power_state & LIGHT_ENABLED) ? 1 : 0); + if (new_value && !(gp2a->power_state & LIGHT_ENABLED)) { + if (!gp2a->power_state) + gp2a->pdata->power(true); + gp2a->power_state |= LIGHT_ENABLED; + gp2a_light_enable(gp2a); + } else if (!new_value && (gp2a->power_state & LIGHT_ENABLED)) { + gp2a_light_disable(gp2a); + gp2a->power_state &= ~LIGHT_ENABLED; + if (!gp2a->power_state) + gp2a->pdata->power(false); + } + mutex_unlock(&gp2a->power_lock); + return size; +} + +static ssize_t proximity_enable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct gp2a_data *gp2a = dev_get_drvdata(dev); + bool new_value; + + if (sysfs_streq(buf, "1")) + new_value = true; + else if (sysfs_streq(buf, "0")) + new_value = false; + else { + pr_err("%s: invalid value %d\n", __func__, *buf); + return -EINVAL; + } + + mutex_lock(&gp2a->power_lock); + gp2a_dbgmsg("new_value = %d, old state = %d\n", + new_value, (gp2a->power_state & PROXIMITY_ENABLED) ? 1 : 0); + if (new_value && !(gp2a->power_state & PROXIMITY_ENABLED)) { + if (!gp2a->power_state) + gp2a->pdata->power(true); + gp2a->power_state |= PROXIMITY_ENABLED; + enable_irq(gp2a->irq); + enable_irq_wake(gp2a->irq); + gp2a_i2c_write(gp2a, REGS_GAIN, ®_defaults[1]); + gp2a_i2c_write(gp2a, REGS_HYS, ®_defaults[2]); + gp2a_i2c_write(gp2a, REGS_CYCLE, ®_defaults[3]); + gp2a_i2c_write(gp2a, REGS_OPMOD, ®_defaults[4]); + } else if (!new_value && (gp2a->power_state & PROXIMITY_ENABLED)) { + disable_irq_wake(gp2a->irq); + disable_irq(gp2a->irq); + gp2a_i2c_write(gp2a, REGS_OPMOD, ®_defaults[0]); + gp2a->power_state &= ~PROXIMITY_ENABLED; + if (!gp2a->power_state) + gp2a->pdata->power(false); + } + mutex_unlock(&gp2a->power_lock); + return size; +} + +static DEVICE_ATTR(poll_delay, S_IRUGO | S_IWUSR | S_IWGRP, + poll_delay_show, poll_delay_store); + +static struct device_attribute dev_attr_light_enable = + __ATTR(enable, S_IRUGO | S_IWUSR | S_IWGRP, + light_enable_show, light_enable_store); + +static struct device_attribute dev_attr_proximity_enable = + __ATTR(enable, S_IRUGO | S_IWUSR | S_IWGRP, + proximity_enable_show, proximity_enable_store); + +static struct attribute *light_sysfs_attrs[] = { + &dev_attr_light_enable.attr, + &dev_attr_poll_delay.attr, + NULL +}; + +static struct attribute_group light_attribute_group = { + .attrs = light_sysfs_attrs, +}; + +static struct attribute *proximity_sysfs_attrs[] = { + &dev_attr_proximity_enable.attr, + NULL +}; + +static struct attribute_group proximity_attribute_group = { + .attrs = proximity_sysfs_attrs, +}; + +static void gp2a_work_func_light(struct work_struct *work) +{ + struct gp2a_data *gp2a = container_of(work, struct gp2a_data, + work_light); + int adc = gp2a->pdata->light_adc_value(); + if (adc < 0) { + pr_err("adc returned error %d\n", adc); + return; + } + gp2a_dbgmsg("adc returned light value %d\n", adc); + input_report_abs(gp2a->light_input_dev, ABS_MISC, adc); + input_sync(gp2a->light_input_dev); +} + +/* This function is for light sensor. It operates every a few seconds. + * It asks for work to be done on a thread because i2c needs a thread + * context (slow and blocking) and then reschedules the timer to run again. + */ +static enum hrtimer_restart gp2a_timer_func(struct hrtimer *timer) +{ + struct gp2a_data *gp2a = container_of(timer, struct gp2a_data, timer); + queue_work(gp2a->wq, &gp2a->work_light); + hrtimer_forward_now(&gp2a->timer, gp2a->light_poll_delay); + return HRTIMER_RESTART; +} + +/* interrupt happened due to transition/change of near/far proximity state */ +irqreturn_t gp2a_irq_handler(int irq, void *data) +{ + struct gp2a_data *ip = data; + int val = gpio_get_value(ip->pdata->p_out); + if (val < 0) { + pr_err("%s: gpio_get_value error %d\n", __func__, val); + return IRQ_HANDLED; + } + + gp2a_dbgmsg("gp2a: proximity val=%d\n", val); + + /* 0 is close, 1 is far */ + input_report_abs(ip->proximity_input_dev, ABS_DISTANCE, val); + input_sync(ip->proximity_input_dev); + wake_lock_timeout(&ip->prx_wake_lock, 3*HZ); + return IRQ_HANDLED; +} + +static int gp2a_setup_irq(struct gp2a_data *gp2a) +{ + int rc = -EIO; + struct gp2a_platform_data *pdata = gp2a->pdata; + int irq; + + gp2a_dbgmsg("start\n"); + + rc = gpio_request(pdata->p_out, "gpio_proximity_out"); + if (rc < 0) { + pr_err("%s: gpio %d request failed (%d)\n", + __func__, pdata->p_out, rc); + return rc; + } + + rc = gpio_direction_input(pdata->p_out); + if (rc < 0) { + pr_err("%s: failed to set gpio %d as input (%d)\n", + __func__, pdata->p_out, rc); + goto err_gpio_direction_input; + } + + irq = gpio_to_irq(pdata->p_out); + rc = request_irq(irq, + gp2a_irq_handler, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + "proximity_int", + gp2a); + if (rc < 0) { + pr_err("%s: request_irq(%d) failed for gpio %d (%d)\n", + __func__, irq, + pdata->p_out, rc); + goto err_request_irq; + } + + /* start with interrupts disabled */ + disable_irq(irq); + gp2a->irq = irq; + + /* sync input device with proximity gpio pin default value */ + gp2a_irq_handler(gp2a->irq, gp2a); + + gp2a_dbgmsg("success\n"); + + goto done; + +err_request_irq: +err_gpio_direction_input: + gpio_free(pdata->p_out); +done: + return rc; +} + +static int gp2a_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret = -ENODEV; + struct input_dev *input_dev; + struct gp2a_data *gp2a; + struct gp2a_platform_data *pdata = client->dev.platform_data; + + if (!pdata) { + pr_err("%s: missing pdata!\n", __func__); + return ret; + } + if (!pdata->power || !pdata->light_adc_value) { + pr_err("%s: incomplete pdata!\n", __func__); + return ret; + } + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + pr_err("%s: i2c functionality check failed!\n", __func__); + return ret; + } + + gp2a = kzalloc(sizeof(struct gp2a_data), GFP_KERNEL); + if (!gp2a) { + pr_err("%s: failed to alloc memory for module data\n", + __func__); + return -ENOMEM; + } + + gp2a->pdata = pdata; + gp2a->i2c_client = client; + i2c_set_clientdata(client, gp2a); + + + wake_lock_init(&gp2a->prx_wake_lock, WAKE_LOCK_SUSPEND, + "prx_wake_lock"); + mutex_init(&gp2a->power_lock); + + /* allocate proximity input_device */ + input_dev = input_allocate_device(); + if (!input_dev) { + pr_err("%s: could not allocate input device\n", __func__); + goto err_input_allocate_device_proximity; + } + gp2a->proximity_input_dev = input_dev; + input_set_drvdata(input_dev, gp2a); + input_dev->name = "proximity"; + input_set_capability(input_dev, EV_ABS, ABS_DISTANCE); + input_set_abs_params(input_dev, ABS_DISTANCE, 0, 1, 0, 0); + + ret = gp2a_setup_irq(gp2a); + if (ret) { + pr_err("%s: could not setup irq\n", __func__); + input_free_device(input_dev); + goto err_setup_irq; + } + + gp2a_dbgmsg("registering proximity input device\n"); + ret = input_register_device(input_dev); + if (ret < 0) { + pr_err("%s: could not register input device\n", __func__); + input_free_device(input_dev); + goto err_input_register_device_proximity; + } + ret = sysfs_create_group(&input_dev->dev.kobj, + &proximity_attribute_group); + if (ret) { + pr_err("%s: could not create sysfs group\n", __func__); + goto err_sysfs_create_group_proximity; + } + + /* hrtimer settings. we poll for light values using a timer. */ + hrtimer_init(&gp2a->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + gp2a->light_poll_delay = ns_to_ktime(200 * NSEC_PER_MSEC); + gp2a->timer.function = gp2a_timer_func; + + /* the timer just fires off a work queue request. we need a thread + * to read the i2c (can be slow and blocking) + */ + gp2a->wq = create_singlethread_workqueue("gp2a_wq"); + if (!gp2a->wq) { + ret = -ENOMEM; + pr_err("%s: could not create workqueue\n", __func__); + goto err_create_workqueue; + } + /* this is the thread function we run on the work queue */ + INIT_WORK(&gp2a->work_light, gp2a_work_func_light); + + /* allocate lightsensor-level input_device */ + input_dev = input_allocate_device(); + if (!input_dev) { + pr_err("%s: could not allocate input device\n", __func__); + ret = -ENOMEM; + goto err_input_allocate_device_light; + } + input_set_drvdata(input_dev, gp2a); + input_dev->name = "lightsensor-level"; + input_set_capability(input_dev, EV_ABS, ABS_MISC); + input_set_abs_params(input_dev, ABS_MISC, 0, pdata->light_adc_max, + pdata->light_adc_fuzz, 0); + + gp2a_dbgmsg("registering lightsensor-level input device\n"); + ret = input_register_device(input_dev); + if (ret < 0) { + pr_err("%s: could not register input device\n", __func__); + input_free_device(input_dev); + goto err_input_register_device_light; + } + gp2a->light_input_dev = input_dev; + ret = sysfs_create_group(&input_dev->dev.kobj, + &light_attribute_group); + if (ret) { + pr_err("%s: could not create sysfs group\n", __func__); + goto err_sysfs_create_group_light; + } + goto done; + + /* error, unwind it all */ +err_sysfs_create_group_light: + input_unregister_device(gp2a->light_input_dev); +err_input_register_device_light: +err_input_allocate_device_light: + destroy_workqueue(gp2a->wq); +err_create_workqueue: + sysfs_remove_group(&gp2a->proximity_input_dev->dev.kobj, + &proximity_attribute_group); +err_sysfs_create_group_proximity: + input_unregister_device(gp2a->proximity_input_dev); +err_input_register_device_proximity: + free_irq(gp2a->irq, gp2a); + gpio_free(gp2a->pdata->p_out); +err_setup_irq: +err_input_allocate_device_proximity: + mutex_destroy(&gp2a->power_lock); + wake_lock_destroy(&gp2a->prx_wake_lock); + kfree(gp2a); +done: + return ret; +} + +static int gp2a_suspend(struct device *dev) +{ + /* We disable power only if proximity is disabled. If proximity + * is enabled, we leave power on because proximity is allowed + * to wake up device. We remove power without changing + * gp2a->power_state because we use that state in resume + */ + struct i2c_client *client = to_i2c_client(dev); + struct gp2a_data *gp2a = i2c_get_clientdata(client); + if (gp2a->power_state & LIGHT_ENABLED) + gp2a_light_disable(gp2a); + if (gp2a->power_state == LIGHT_ENABLED) + gp2a->pdata->power(false); + return 0; +} + +static int gp2a_resume(struct device *dev) +{ + /* Turn power back on if we were before suspend. */ + struct i2c_client *client = to_i2c_client(dev); + struct gp2a_data *gp2a = i2c_get_clientdata(client); + if (gp2a->power_state == LIGHT_ENABLED) + gp2a->pdata->power(true); + if (gp2a->power_state & LIGHT_ENABLED) + gp2a_light_enable(gp2a); + return 0; +} + +static int gp2a_i2c_remove(struct i2c_client *client) +{ + struct gp2a_data *gp2a = i2c_get_clientdata(client); + sysfs_remove_group(&gp2a->light_input_dev->dev.kobj, + &light_attribute_group); + sysfs_remove_group(&gp2a->proximity_input_dev->dev.kobj, + &proximity_attribute_group); + free_irq(gp2a->irq, gp2a); + destroy_workqueue(gp2a->wq); + input_unregister_device(gp2a->light_input_dev); + input_unregister_device(gp2a->proximity_input_dev); + gpio_free(gp2a->pdata->p_out); + if (gp2a->power_state) { + gp2a->power_state = 0; + if (gp2a->power_state & LIGHT_ENABLED) + gp2a_light_disable(gp2a); + gp2a->pdata->power(false); + } + mutex_destroy(&gp2a->power_lock); + wake_lock_destroy(&gp2a->prx_wake_lock); + kfree(gp2a); + return 0; +} + +static const struct i2c_device_id gp2a_device_id[] = { + {"gp2a", 0}, + {} +}; +MODULE_DEVICE_TABLE(i2c, gp2a_device_id); + +static const struct dev_pm_ops gp2a_pm_ops = { + .suspend = gp2a_suspend, + .resume = gp2a_resume +}; + +static struct i2c_driver gp2a_i2c_driver = { + .driver = { + .name = "gp2a", + .owner = THIS_MODULE, + .pm = &gp2a_pm_ops + }, + .probe = gp2a_i2c_probe, + .remove = gp2a_i2c_remove, + .id_table = gp2a_device_id, +}; + + +static int __init gp2a_init(void) +{ + return i2c_add_driver(&gp2a_i2c_driver); +} + +static void __exit gp2a_exit(void) +{ + i2c_del_driver(&gp2a_i2c_driver); +} + +module_init(gp2a_init); +module_exit(gp2a_exit); + +MODULE_AUTHOR("mjchen@sta.samsung.com"); +MODULE_DESCRIPTION("Optical Sensor driver for gp2ap002a00f"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/misc/k3g.c b/drivers/input/misc/k3g.c new file mode 100644 index 0000000..5540628 --- /dev/null +++ b/drivers/input/misc/k3g.c @@ -0,0 +1,707 @@ +/* + * Copyright (C) 2010, 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. + * + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/uaccess.h> +#include <linux/poll.h> +#include <linux/slab.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <asm/div64.h> +#include <linux/input/k3g.h> +#include <linux/delay.h> + +/* k3g chip id */ +#define DEVICE_ID 0xD3 +/* k3g gyroscope registers */ +#define WHO_AM_I 0x0F +#define CTRL_REG1 0x20 /* power control reg */ +#define CTRL_REG2 0x21 /* power control reg */ +#define CTRL_REG3 0x22 /* power control reg */ +#define CTRL_REG4 0x23 /* interrupt control reg */ +#define CTRL_REG5 0x24 /* interrupt control reg */ +#define OUT_TEMP 0x26 /* Temperature data */ +#define STATUS_REG 0x27 +#define AXISDATA_REG 0x28 +#define OUT_Y_L 0x2A +#define FIFO_CTRL_REG 0x2E +#define FIFO_SRC_REG 0x2F +#define PM_OFF 0x00 +#define PM_NORMAL 0x08 +#define ENABLE_ALL_AXES 0x07 +#define BYPASS_MODE 0x00 +#define FIFO_MODE 0x20 + +#define FIFO_EMPTY 0x20 +#define FSS_MASK 0x1F +#define ODR_MASK 0xF0 +#define ODR105_BW12_5 0x00 /* ODR = 105Hz; BW = 12.5Hz */ +#define ODR105_BW25 0x10 /* ODR = 105Hz; BW = 25Hz */ +#define ODR210_BW12_5 0x40 /* ODR = 210Hz; BW = 12.5Hz */ +#define ODR210_BW25 0x50 /* ODR = 210Hz; BW = 25Hz */ +#define ODR210_BW50 0x60 /* ODR = 210Hz; BW = 50Hz */ +#define ODR210_BW70 0x70 /* ODR = 210Hz; BW = 70Hz */ +#define ODR420_BW20 0x80 /* ODR = 420Hz; BW = 20Hz */ +#define ODR420_BW25 0x90 /* ODR = 420Hz; BW = 25Hz */ +#define ODR420_BW50 0xA0 /* ODR = 420Hz; BW = 50Hz */ +#define ODR420_BW110 0xB0 /* ODR = 420Hz; BW = 110Hz */ +#define ODR840_BW30 0xC0 /* ODR = 840Hz; BW = 30Hz */ +#define ODR840_BW35 0xD0 /* ODR = 840Hz; BW = 35Hz */ +#define ODR840_BW50 0xE0 /* ODR = 840Hz; BW = 50Hz */ +#define ODR840_BW110 0xF0 /* ODR = 840Hz; BW = 110Hz */ + +#define MIN_ST 175 +#define MAX_ST 875 +#define AC (1 << 7) /* register auto-increment bit */ +#define MAX_ENTRY 1 +#define MAX_DELAY (MAX_ENTRY * 9523809LL) + +/* default register setting for device init */ +static const char default_ctrl_regs[] = { + 0x3F, /* 105HZ, PM-normal, xyz enable */ + 0x00, /* normal mode */ + 0x04, /* fifo wtm interrupt on */ + 0xA0, /* block data update, 2000d/s */ + 0x40, /* fifo enable */ +}; + +static const struct odr_delay { + u8 odr; /* odr reg setting */ + u32 delay_ns; /* odr in ns */ +} odr_delay_table[] = { + { ODR840_BW110, 1190476LL }, /* 840Hz */ + { ODR420_BW110, 2380952LL }, /* 420Hz */ + { ODR210_BW70, 4761904LL }, /* 210Hz */ + { ODR105_BW25, 9523809LL }, /* 105Hz */ +}; + +/* + * K3G gyroscope data + * brief structure containing gyroscope values for yaw, pitch and roll in + * signed short + */ +struct k3g_t { + s16 x; + s16 y; + s16 z; +}; + +struct k3g_data { + struct i2c_client *client; + struct input_dev *input_dev; + struct mutex lock; + struct workqueue_struct *k3g_wq; + struct work_struct work; + struct hrtimer timer; + bool enable; + bool drop_next_event; + bool interruptible; /* interrupt or polling? */ + int entries; /* number of fifo entries */ + u8 ctrl_regs[5]; /* saving register settings */ + u32 time_to_read; /* time needed to read one entry */ + ktime_t polling_delay; /* polling time for timer */ +}; + +static int k3g_read_fifo_status(struct k3g_data *k3g_data) +{ + int fifo_status; + + fifo_status = i2c_smbus_read_byte_data(k3g_data->client, FIFO_SRC_REG); + if (fifo_status < 0) { + pr_err("%s: failed to read fifo source register\n", + __func__); + return fifo_status; + } + return (fifo_status & FSS_MASK) + !(fifo_status & FIFO_EMPTY); +} + +static int k3g_restart_fifo(struct k3g_data *k3g_data) +{ + int res = 0; + + res = i2c_smbus_write_byte_data(k3g_data->client, + FIFO_CTRL_REG, BYPASS_MODE); + if (res < 0) { + pr_err("%s : failed to set bypass_mode\n", __func__); + return res; + } + + res = i2c_smbus_write_byte_data(k3g_data->client, + FIFO_CTRL_REG, FIFO_MODE | (k3g_data->entries - 1)); + + if (res < 0) + pr_err("%s : failed to set fifo_mode\n", __func__); + + return res; +} + +static void set_polling_delay(struct k3g_data *k3g_data, int res) +{ + s64 delay_ns; + + delay_ns = k3g_data->entries + 1 - res; + if (delay_ns < 0) + delay_ns = 0; + + delay_ns = delay_ns * k3g_data->time_to_read; + k3g_data->polling_delay = ns_to_ktime(delay_ns); +} + +/* gyroscope data readout */ +static int k3g_read_gyro_values(struct i2c_client *client, + struct k3g_t *data, int total_read) +{ + int err; + struct i2c_msg msg[2]; + u8 reg_buf; + u8 gyro_data[sizeof(*data) * (total_read ? (total_read - 1) : 1)]; + + msg[0].addr = client->addr; + msg[0].buf = ®_buf; + msg[0].flags = 0; + msg[0].len = 1; + + msg[1].addr = client->addr; + msg[1].flags = I2C_M_RD; + msg[1].buf = gyro_data; + + if (total_read > 1) { + reg_buf = AXISDATA_REG | AC; + msg[1].len = sizeof(gyro_data); + + err = i2c_transfer(client->adapter, msg, 2); + if (err != 2) + return (err < 0) ? err : -EIO; + } + + reg_buf = AXISDATA_REG; + msg[1].len = 1; + err = i2c_transfer(client->adapter, msg, 2); + if (err != 2) + return (err < 0) ? err : -EIO; + + reg_buf = OUT_Y_L | AC; + msg[1].len = sizeof(*data); + err = i2c_transfer(client->adapter, msg, 2); + if (err != 2) + return (err < 0) ? err : -EIO; + + data->y = (gyro_data[1] << 8) | gyro_data[0]; + data->z = (gyro_data[3] << 8) | gyro_data[2]; + data->x = (gyro_data[5] << 8) | gyro_data[4]; + + return 0; +} + +static int k3g_report_gyro_values(struct k3g_data *k3g_data) +{ + int res; + struct k3g_t data; + + res = k3g_read_gyro_values(k3g_data->client, &data, + k3g_data->entries + k3g_data->drop_next_event); + if (res < 0) + return res; + + res = k3g_read_fifo_status(k3g_data); + + k3g_data->drop_next_event = !res; + + if (res >= 31 - k3g_data->entries) { + /* reset fifo to start again - data isn't trustworthy, + * our locked read might not have worked and we + * could have done i2c read in mid register update + */ + return k3g_restart_fifo(k3g_data); + } + + input_report_rel(k3g_data->input_dev, REL_RX, data.x); + input_report_rel(k3g_data->input_dev, REL_RY, data.y); + input_report_rel(k3g_data->input_dev, REL_RZ, data.z); + input_sync(k3g_data->input_dev); + + return res; +} + +static enum hrtimer_restart k3g_timer_func(struct hrtimer *timer) +{ + struct k3g_data *k3g_data = container_of(timer, struct k3g_data, timer); + queue_work(k3g_data->k3g_wq, &k3g_data->work); + return HRTIMER_NORESTART; +} + +static void k3g_work_func(struct work_struct *work) +{ + int res; + struct k3g_data *k3g_data = container_of(work, struct k3g_data, work); + + do { + res = k3g_read_fifo_status(k3g_data); + if (res < 0) + return; + + if (res < k3g_data->entries) { + pr_warn("%s: fifo entries are less than we want\n", + __func__); + goto timer_set; + } + + res = k3g_report_gyro_values(k3g_data); + if (res < 0) + return; +timer_set: + set_polling_delay(k3g_data, res); + + } while (!ktime_to_ns(k3g_data->polling_delay)); + + hrtimer_start(&k3g_data->timer, + k3g_data->polling_delay, HRTIMER_MODE_REL); +} + +static irqreturn_t k3g_interrupt_thread(int irq, void *k3g_data_p) +{ + int res; + struct k3g_data *k3g_data = k3g_data_p; + res = k3g_report_gyro_values(k3g_data); + if (res < 0) + pr_err("%s: failed to report gyro values\n", __func__); + + return IRQ_HANDLED; +} + +static ssize_t k3g_show_enable(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct k3g_data *k3g_data = dev_get_drvdata(dev); + return sprintf(buf, "%d\n", k3g_data->enable); +} + +static ssize_t k3g_set_enable(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + int err = 0; + struct k3g_data *k3g_data = dev_get_drvdata(dev); + bool new_enable; + + if (sysfs_streq(buf, "1")) + new_enable = true; + else if (sysfs_streq(buf, "0")) + new_enable = false; + else { + pr_debug("%s: invalid value %d\n", __func__, *buf); + return -EINVAL; + } + + if (new_enable == k3g_data->enable) + return size; + + mutex_lock(&k3g_data->lock); + if (new_enable) { + /* turning on */ + err = i2c_smbus_write_i2c_block_data(k3g_data->client, + CTRL_REG1 | AC, sizeof(k3g_data->ctrl_regs), + k3g_data->ctrl_regs); + if (err < 0) { + err = -EIO; + goto unlock; + } + + /* reset fifo entries */ + err = k3g_restart_fifo(k3g_data); + if (err < 0) { + err = -EIO; + goto turn_off; + } + + if (k3g_data->interruptible) + enable_irq(k3g_data->client->irq); + else { + set_polling_delay(k3g_data, 0); + hrtimer_start(&k3g_data->timer, + k3g_data->polling_delay, HRTIMER_MODE_REL); + } + } else { + if (k3g_data->interruptible) + disable_irq(k3g_data->client->irq); + else { + hrtimer_cancel(&k3g_data->timer); + cancel_work_sync(&k3g_data->work); + } + /* turning off */ + err = i2c_smbus_write_byte_data(k3g_data->client, + CTRL_REG1, 0x00); + if (err < 0) + goto unlock; + } + k3g_data->enable = new_enable; + +turn_off: + if (err < 0) + i2c_smbus_write_byte_data(k3g_data->client, + CTRL_REG1, 0x00); +unlock: + mutex_unlock(&k3g_data->lock); + + return err ? err : size; +} + +static ssize_t k3g_show_delay(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct k3g_data *k3g_data = dev_get_drvdata(dev); + u64 delay; + + delay = k3g_data->time_to_read * k3g_data->entries; + delay = ktime_to_ns(ns_to_ktime(delay)); + + return sprintf(buf, "%lld\n", delay); +} + +static ssize_t k3g_set_delay(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct k3g_data *k3g_data = dev_get_drvdata(dev); + int odr_value = ODR105_BW25; + int res = 0; + int i; + u64 delay_ns; + u8 ctrl; + + res = strict_strtoll(buf, 10, &delay_ns); + if (res < 0) + return res; + + mutex_lock(&k3g_data->lock); + if (!k3g_data->interruptible) + hrtimer_cancel(&k3g_data->timer); + else + disable_irq(k3g_data->client->irq); + + /* round to the nearest supported ODR that is equal or above than + * the requested value + */ + for (i = 0; i < ARRAY_SIZE(odr_delay_table); i++) { + if (delay_ns < odr_delay_table[i].delay_ns) + break; + } + if (i > 0) + i--; + + odr_value = odr_delay_table[i].odr; + delay_ns = odr_delay_table[i].delay_ns; + k3g_data->time_to_read = delay_ns; + k3g_data->entries = 1; + + if (delay_ns >= odr_delay_table[3].delay_ns) { + if (delay_ns >= MAX_DELAY) { + k3g_data->entries = MAX_ENTRY; + delay_ns = MAX_DELAY; + } else { + do_div(delay_ns, odr_delay_table[3].delay_ns); + k3g_data->entries = delay_ns; + } + k3g_data->time_to_read = odr_delay_table[3].delay_ns; + } + + if (odr_value != (k3g_data->ctrl_regs[0] & ODR_MASK)) { + ctrl = (k3g_data->ctrl_regs[0] & ~ODR_MASK); + ctrl |= odr_value; + k3g_data->ctrl_regs[0] = ctrl; + res = i2c_smbus_write_byte_data(k3g_data->client, + CTRL_REG1, ctrl); + } + + /* we see a noise in the first sample or two after we + * change rates. this delay helps eliminate that noise. + */ + msleep((u32)delay_ns * 2 / NSEC_PER_MSEC); + + /* (re)start fifo */ + k3g_restart_fifo(k3g_data); + + if (!k3g_data->interruptible) { + delay_ns = k3g_data->entries * k3g_data->time_to_read; + k3g_data->polling_delay = ns_to_ktime(delay_ns); + if (k3g_data->enable) + hrtimer_start(&k3g_data->timer, + k3g_data->polling_delay, HRTIMER_MODE_REL); + } else + enable_irq(k3g_data->client->irq); + + mutex_unlock(&k3g_data->lock); + + return size; +} + +static DEVICE_ATTR(enable, S_IRUGO | S_IWUSR | S_IWGRP, + k3g_show_enable, k3g_set_enable); +static DEVICE_ATTR(poll_delay, S_IRUGO | S_IWUSR | S_IWGRP, + k3g_show_delay, k3g_set_delay); + +static int k3g_probe(struct i2c_client *client, + const struct i2c_device_id *devid) +{ + int ret; + int err = 0; + struct k3g_data *data; + struct input_dev *input_dev; + + if (client->dev.platform_data == NULL) { + dev_err(&client->dev, "platform data is NULL. exiting.\n"); + err = -ENODEV; + goto exit; + } + + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (data == NULL) { + dev_err(&client->dev, + "failed to allocate memory for module data\n"); + err = -ENOMEM; + goto exit; + } + + data->client = client; + + /* read chip id */ + ret = i2c_smbus_read_byte_data(client, WHO_AM_I); + if (ret != DEVICE_ID) { + if (ret < 0) { + pr_err("%s: i2c for reading chip id failed\n", + __func__); + err = ret; + } else { + pr_err("%s : Device identification failed\n", + __func__); + err = -ENODEV; + } + goto err_read_reg; + } + + mutex_init(&data->lock); + + /* allocate gyro input_device */ + input_dev = input_allocate_device(); + if (!input_dev) { + pr_err("%s: could not allocate input device\n", __func__); + err = -ENOMEM; + goto err_input_allocate_device; + } + + data->input_dev = input_dev; + input_set_drvdata(input_dev, data); + input_dev->name = "gyro"; + /* X */ + input_set_capability(input_dev, EV_REL, REL_RX); + input_set_abs_params(input_dev, REL_RX, -2048, 2047, 0, 0); + /* Y */ + input_set_capability(input_dev, EV_REL, REL_RY); + input_set_abs_params(input_dev, REL_RY, -2048, 2047, 0, 0); + /* Z */ + input_set_capability(input_dev, EV_REL, REL_RZ); + input_set_abs_params(input_dev, REL_RZ, -2048, 2047, 0, 0); + + err = input_register_device(input_dev); + if (err < 0) { + pr_err("%s: could not register input device\n", __func__); + input_free_device(data->input_dev); + goto err_input_register_device; + } + + memcpy(&data->ctrl_regs, &default_ctrl_regs, sizeof(default_ctrl_regs)); + + if (data->client->irq >= 0) { /* interrupt */ + data->interruptible = true; + err = request_threaded_irq(data->client->irq, NULL, + k3g_interrupt_thread, IRQF_TRIGGER_HIGH | IRQF_ONESHOT, + "k3g", data); + if (err < 0) { + pr_err("%s: can't allocate irq.\n", __func__); + goto err_request_irq; + } + disable_irq(data->client->irq); + + } else { /* polling */ + u64 delay_ns; + data->ctrl_regs[2] = 0x00; /* disable interrupt */ + /* hrtimer settings. we poll for gyro values using a timer. */ + hrtimer_init(&data->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + data->polling_delay = ns_to_ktime(200 * NSEC_PER_MSEC); + data->time_to_read = 10000000LL; + delay_ns = ktime_to_ns(data->polling_delay); + do_div(delay_ns, data->time_to_read); + data->entries = delay_ns; + data->timer.function = k3g_timer_func; + + /* the timer just fires off a work queue request. + We need a thread to read i2c (can be slow and blocking). */ + data->k3g_wq = create_singlethread_workqueue("k3g_wq"); + if (!data->k3g_wq) { + err = -ENOMEM; + pr_err("%s: could not create workqueue\n", __func__); + goto err_create_workqueue; + } + /* this is the thread function we run on the work queue */ + INIT_WORK(&data->work, k3g_work_func); + } + + if (device_create_file(&input_dev->dev, + &dev_attr_enable) < 0) { + pr_err("Failed to create device file(%s)!\n", + dev_attr_enable.attr.name); + goto err_device_create_file; + } + + if (device_create_file(&input_dev->dev, + &dev_attr_poll_delay) < 0) { + pr_err("Failed to create device file(%s)!\n", + dev_attr_poll_delay.attr.name); + goto err_device_create_file2; + } + + i2c_set_clientdata(client, data); + dev_set_drvdata(&input_dev->dev, data); + + return 0; + +err_device_create_file2: + device_remove_file(&input_dev->dev, &dev_attr_enable); +err_device_create_file: + if (data->interruptible) { + enable_irq(data->client->irq); + free_irq(data->client->irq, data); + } else + destroy_workqueue(data->k3g_wq); + input_unregister_device(data->input_dev); +err_create_workqueue: +err_request_irq: +err_input_register_device: +err_input_allocate_device: + mutex_destroy(&data->lock); +err_read_reg: + kfree(data); +exit: + return err; +} + +static int k3g_remove(struct i2c_client *client) +{ + int err = 0; + struct k3g_data *k3g_data = i2c_get_clientdata(client); + + device_remove_file(&k3g_data->input_dev->dev, &dev_attr_enable); + device_remove_file(&k3g_data->input_dev->dev, &dev_attr_poll_delay); + + if (k3g_data->enable) + err = i2c_smbus_write_byte_data(k3g_data->client, + CTRL_REG1, 0x00); + if (k3g_data->interruptible) { + if (!k3g_data->enable) /* no disable_irq before free_irq */ + enable_irq(k3g_data->client->irq); + free_irq(k3g_data->client->irq, k3g_data); + + } else { + hrtimer_cancel(&k3g_data->timer); + cancel_work_sync(&k3g_data->work); + destroy_workqueue(k3g_data->k3g_wq); + } + + input_unregister_device(k3g_data->input_dev); + mutex_destroy(&k3g_data->lock); + kfree(k3g_data); + + return err; +} + +static int k3g_suspend(struct device *dev) +{ + int err = 0; + struct i2c_client *client = to_i2c_client(dev); + struct k3g_data *k3g_data = i2c_get_clientdata(client); + + if (k3g_data->enable) { + mutex_lock(&k3g_data->lock); + if (!k3g_data->interruptible) { + hrtimer_cancel(&k3g_data->timer); + cancel_work_sync(&k3g_data->work); + } + err = i2c_smbus_write_byte_data(k3g_data->client, + CTRL_REG1, 0x00); + mutex_unlock(&k3g_data->lock); + } + + return err; +} + +static int k3g_resume(struct device *dev) +{ + int err = 0; + struct i2c_client *client = to_i2c_client(dev); + struct k3g_data *k3g_data = i2c_get_clientdata(client); + + if (k3g_data->enable) { + mutex_lock(&k3g_data->lock); + if (!k3g_data->interruptible) + hrtimer_start(&k3g_data->timer, + k3g_data->polling_delay, HRTIMER_MODE_REL); + err = i2c_smbus_write_i2c_block_data(client, + CTRL_REG1 | AC, sizeof(k3g_data->ctrl_regs), + k3g_data->ctrl_regs); + mutex_unlock(&k3g_data->lock); + } + + return err; +} + +static const struct dev_pm_ops k3g_pm_ops = { + .suspend = k3g_suspend, + .resume = k3g_resume +}; + +static const struct i2c_device_id k3g_id[] = { + { "k3g", 0 }, + { } +}; + +MODULE_DEVICE_TABLE(i2c, k3g_id); + +static struct i2c_driver k3g_driver = { + .probe = k3g_probe, + .remove = __devexit_p(k3g_remove), + .id_table = k3g_id, + .driver = { + .pm = &k3g_pm_ops, + .owner = THIS_MODULE, + .name = "k3g" + }, +}; + +static int __init k3g_init(void) +{ + return i2c_add_driver(&k3g_driver); +} + +static void __exit k3g_exit(void) +{ + i2c_del_driver(&k3g_driver); +} + +module_init(k3g_init); +module_exit(k3g_exit); + +MODULE_DESCRIPTION("k3g digital gyroscope driver"); +MODULE_AUTHOR("Tim SK Lee Samsung Electronics <tim.sk.lee@samsung.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/serio/i8042-x86ia64io.h b/drivers/input/serio/i8042-x86ia64io.h index bb9f5d3..e01fd4c 100644 --- a/drivers/input/serio/i8042-x86ia64io.h +++ b/drivers/input/serio/i8042-x86ia64io.h @@ -177,6 +177,20 @@ static const struct dmi_system_id __initconst i8042_dmi_noloop_table[] = { }, }, { + /* Gigabyte T1005 - defines wrong chassis type ("Other") */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "GIGABYTE"), + DMI_MATCH(DMI_PRODUCT_NAME, "T1005"), + }, + }, + { + /* Gigabyte T1005M/P - defines wrong chassis type ("Other") */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "GIGABYTE"), + DMI_MATCH(DMI_PRODUCT_NAME, "T1005M/P"), + }, + }, + { .matches = { DMI_MATCH(DMI_SYS_VENDOR, "Hewlett-Packard"), DMI_MATCH(DMI_PRODUCT_NAME, "HP Pavilion dv9700"), @@ -321,6 +335,12 @@ static const struct dmi_system_id __initconst i8042_dmi_nomux_table[] = { }, { .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "TOSHIBA"), + DMI_MATCH(DMI_PRODUCT_NAME, "SATELLITE C850D"), + }, + }, + { + .matches = { DMI_MATCH(DMI_SYS_VENDOR, "ALIENWARE"), DMI_MATCH(DMI_PRODUCT_NAME, "Sentia"), }, diff --git a/drivers/input/tablet/wacom_wac.c b/drivers/input/tablet/wacom_wac.c index 08ba5ad..a28ebf0 100644 --- a/drivers/input/tablet/wacom_wac.c +++ b/drivers/input/tablet/wacom_wac.c @@ -242,7 +242,7 @@ static int wacom_graphire_irq(struct wacom_wac *wacom) input_report_abs(input, ABS_X, le16_to_cpup((__le16 *)&data[2])); input_report_abs(input, ABS_Y, le16_to_cpup((__le16 *)&data[4])); if (wacom->tool[0] != BTN_TOOL_MOUSE) { - input_report_abs(input, ABS_PRESSURE, data[6] | ((data[7] & 0x01) << 8)); + input_report_abs(input, ABS_PRESSURE, data[6] | ((data[7] & 0x03) << 8)); input_report_key(input, BTN_TOUCH, data[1] & 0x01); input_report_key(input, BTN_STYLUS, data[1] & 0x02); input_report_key(input, BTN_STYLUS2, data[1] & 0x04); diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig index 4104103..69d8fd2 100644 --- a/drivers/input/touchscreen/Kconfig +++ b/drivers/input/touchscreen/Kconfig @@ -319,6 +319,18 @@ config TOUCHSCREEN_MK712 To compile this driver as a module, choose M here: the module will be called mk712. +config TOUCHSCREEN_MXT224 + tristate "Atmel MaxTouch 224" + depends on I2C + help + Say Y here to enable support for the Atmel MaxTouch 224 touch + controller. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called mxt224. + config TOUCHSCREEN_HP600 tristate "HP Jornada 6xx touchscreen" depends on SH_HP6XX && SH_ADC diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile index 0738f19..ae5aeaa 100644 --- a/drivers/input/touchscreen/Makefile +++ b/drivers/input/touchscreen/Makefile @@ -33,6 +33,7 @@ obj-$(CONFIG_TOUCHSCREEN_MCS5000) += mcs5000_ts.o obj-$(CONFIG_TOUCHSCREEN_MIGOR) += migor_ts.o obj-$(CONFIG_TOUCHSCREEN_MTOUCH) += mtouch.o obj-$(CONFIG_TOUCHSCREEN_MK712) += mk712.o +obj-$(CONFIG_TOUCHSCREEN_MXT224) += mxt224.o obj-$(CONFIG_TOUCHSCREEN_HP600) += hp680_ts_input.o obj-$(CONFIG_TOUCHSCREEN_HP7XX) += jornada720_ts.o obj-$(CONFIG_TOUCHSCREEN_HTCPEN) += htcpen.o diff --git a/drivers/input/touchscreen/mxt224.c b/drivers/input/touchscreen/mxt224.c new file mode 100644 index 0000000..0d9e132 --- /dev/null +++ b/drivers/input/touchscreen/mxt224.c @@ -0,0 +1,658 @@ +/* + * Copyright (C) 2010, 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. + * + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/i2c.h> +#include <linux/delay.h> +#include <linux/earlysuspend.h> +#include <linux/slab.h> +#include <linux/gpio.h> +#include <linux/input/mxt224.h> +#include <asm/unaligned.h> + +#ifdef CONFIG_BLD +#include <linux/bld.h> +#endif + +#define OBJECT_TABLE_START_ADDRESS 7 +#define OBJECT_TABLE_ELEMENT_SIZE 6 + +#define CMD_RESET_OFFSET 0 +#define CMD_BACKUP_OFFSET 1 + +#define DETECT_MSG_MASK 0x80 +#define PRESS_MSG_MASK 0x40 +#define RELEASE_MSG_MASK 0x20 +#define MOVE_MSG_MASK 0x10 +#define SUPPRESS_MSG_MASK 0x02 + +#define ID_BLOCK_SIZE 7 + +struct object_t { + u8 object_type; + u16 i2c_address; + u8 size; + u8 instances; + u8 num_report_ids; +} __packed; + +struct finger_info { + s16 x; + s16 y; + s16 z; + u16 w; +}; + +struct mxt224_data { + struct i2c_client *client; + struct input_dev *input_dev; + struct early_suspend early_suspend; + u32 finger_mask; + int gpio_read_done; + struct object_t *objects; + u8 objects_len; + u8 tsp_version; + const u8 *power_cfg; + u8 finger_type; + u16 msg_proc; + u16 cmd_proc; + u16 msg_object_size; + u32 x_dropbits:2; + u32 y_dropbits:2; + void (*power_on)(void); + void (*power_off)(void); + int num_fingers; + struct finger_info fingers[]; +}; + +static int read_mem(struct mxt224_data *data, u16 reg, u8 len, u8 *buf) +{ + int ret; + u16 le_reg = cpu_to_le16(reg); + struct i2c_msg msg[2] = { + { + .addr = data->client->addr, + .flags = 0, + .len = 2, + .buf = (u8 *)&le_reg, + }, + { + .addr = data->client->addr, + .flags = I2C_M_RD, + .len = len, + .buf = buf, + }, + }; + + ret = i2c_transfer(data->client->adapter, msg, 2); + if (ret < 0) + return ret; + + return ret == 2 ? 0 : -EIO; +} + +static int write_mem(struct mxt224_data *data, u16 reg, u8 len, const u8 *buf) +{ + int ret; + u8 tmp[len + 2]; + + put_unaligned_le16(cpu_to_le16(reg), tmp); + memcpy(tmp + 2, buf, len); + + ret = i2c_master_send(data->client, tmp, sizeof(tmp)); + if (ret < 0) + return ret; + + return ret == sizeof(tmp) ? 0 : -EIO; +} + +static int __devinit mxt224_reset(struct mxt224_data *data) +{ + u8 buf = 1u; + return write_mem(data, data->cmd_proc + CMD_RESET_OFFSET, 1, &buf); +} + +static int __devinit mxt224_backup(struct mxt224_data *data) +{ + u8 buf = 0x55u; + return write_mem(data, data->cmd_proc + CMD_BACKUP_OFFSET, 1, &buf); +} + +static int get_object_info(struct mxt224_data *data, u8 object_type, u16 *size, + u16 *address) +{ + int i; + + for (i = 0; i < data->objects_len; i++) { + if (data->objects[i].object_type == object_type) { + *size = data->objects[i].size + 1; + *address = data->objects[i].i2c_address; + return 0; + } + } + + return -ENODEV; +} + +static int write_config(struct mxt224_data *data, u8 type, const u8 *cfg) +{ + int ret; + u16 address; + u16 size; + + ret = get_object_info(data, type, &size, &address); + + if (ret) + return ret; + + return write_mem(data, address, size, cfg); +} + + +static u32 __devinit crc24(u32 crc, u8 byte1, u8 byte2) +{ + static const u32 crcpoly = 0x80001B; + u32 res; + u16 data_word; + + data_word = (((u16)byte2) << 8) | byte1; + res = (crc << 1) ^ (u32)data_word; + + if (res & 0x1000000) + res ^= crcpoly; + + return res; +} + +static int __devinit calculate_infoblock_crc(struct mxt224_data *data, + u32 *crc_pointer) +{ + u32 crc = 0; + u8 mem[7 + data->objects_len * 6]; + int status; + int i; + + status = read_mem(data, 0, sizeof(mem), mem); + + if (status) + return status; + + for (i = 0; i < sizeof(mem) - 1; i += 2) + crc = crc24(crc, mem[i], mem[i + 1]); + + *crc_pointer = crc24(crc, mem[i], 0) & 0x00FFFFFF; + + return 0; +} + +static int __devinit mxt224_init_touch_driver(struct mxt224_data *data) +{ + struct object_t *object_table; + u32 read_crc = 0; + u32 calc_crc; + u16 crc_address; + u16 dummy; + int i; + u8 id[ID_BLOCK_SIZE]; + int ret; + u8 type_count = 0; + u8 tmp; + + ret = read_mem(data, 0, sizeof(id), id); + if (ret) + return ret; + + dev_info(&data->client->dev, "family = %#02x, variant = %#02x, version " + "= %#02x, build = %d\n", id[0], id[1], id[2], id[3]); + dev_dbg(&data->client->dev, "matrix X size = %d\n", id[4]); + dev_dbg(&data->client->dev, "matrix Y size = %d\n", id[5]); + + data->tsp_version = id[2]; + data->objects_len = id[6]; + + object_table = kmalloc(data->objects_len * sizeof(*object_table), + GFP_KERNEL); + if (!object_table) + return -ENOMEM; + + ret = read_mem(data, OBJECT_TABLE_START_ADDRESS, + data->objects_len * sizeof(*object_table), + (u8 *)object_table); + if (ret) + goto err; + + for (i = 0; i < data->objects_len; i++) { + object_table[i].i2c_address = + le16_to_cpu(object_table[i].i2c_address); + tmp = 0; + if (object_table[i].num_report_ids) { + tmp = type_count + 1; + type_count += object_table[i].num_report_ids * + (object_table[i].instances + 1); + } + switch (object_table[i].object_type) { + case TOUCH_MULTITOUCHSCREEN_T9: + data->finger_type = tmp; + dev_dbg(&data->client->dev, "Finger type = %d\n", + data->finger_type); + break; + case GEN_MESSAGEPROCESSOR_T5: + data->msg_object_size = object_table[i].size + 1; + dev_dbg(&data->client->dev, "Message object size = " + "%d\n", data->msg_object_size); + break; + } + } + + data->objects = object_table; + + /* Verify CRC */ + crc_address = OBJECT_TABLE_START_ADDRESS + + data->objects_len * OBJECT_TABLE_ELEMENT_SIZE; + +#ifdef __BIG_ENDIAN +#error The following code will likely break on a big endian machine +#endif + ret = read_mem(data, crc_address, 3, (u8 *)&read_crc); + if (ret) + goto err; + + read_crc = le32_to_cpu(read_crc); + + ret = calculate_infoblock_crc(data, &calc_crc); + if (ret) + goto err; + + if (read_crc != calc_crc) { + dev_err(&data->client->dev, "CRC error\n"); + ret = -EFAULT; + goto err; + } + + ret = get_object_info(data, GEN_MESSAGEPROCESSOR_T5, &dummy, + &data->msg_proc); + if (ret) + goto err; + + ret = get_object_info(data, GEN_COMMANDPROCESSOR_T6, &dummy, + &data->cmd_proc); + if (ret) + goto err; + + return 0; + +err: + kfree(object_table); + return ret; +} + +static void report_input_data(struct mxt224_data *data) +{ + int i; + int num_fingers_down; + + num_fingers_down = 0; + for (i = 0; i < data->num_fingers; i++) { + if (data->fingers[i].z == -1) + continue; + + input_report_abs(data->input_dev, ABS_MT_POSITION_X, + data->fingers[i].x); + input_report_abs(data->input_dev, ABS_MT_POSITION_Y, + data->fingers[i].y); + input_report_abs(data->input_dev, ABS_MT_PRESSURE, + data->fingers[i].z); + input_report_abs(data->input_dev, ABS_MT_TOUCH_MAJOR, + data->fingers[i].w); + input_report_abs(data->input_dev, ABS_MT_TRACKING_ID, i); + input_mt_sync(data->input_dev); + num_fingers_down++; + +#ifdef CONFIG_BLD + if (system_rev >= 0x30 && data->fingers[i].y > BLD_TOUCHKEYS_POSITION) + { + touchkey_pressed(); + } +#endif + } + data->finger_mask = 0; + + if (num_fingers_down == 0) + input_mt_sync(data->input_dev); + input_sync(data->input_dev); +} + +static irqreturn_t mxt224_irq_thread(int irq, void *ptr) +{ + struct mxt224_data *data = ptr; + int id; + u8 msg[data->msg_object_size]; + + do { + if (read_mem(data, data->msg_proc, sizeof(msg), msg)) + return IRQ_HANDLED; + + id = msg[0] - data->finger_type; + + /* If not a touch event, then keep going */ + if (id < 0 || id >= data->num_fingers) + continue; + + if (data->finger_mask & (1U << id)) + report_input_data(data); + + if (msg[1] & RELEASE_MSG_MASK) { + data->fingers[id].z = -1; + data->fingers[id].w = msg[5]; + data->finger_mask |= 1U << id; + } else if ((msg[1] & DETECT_MSG_MASK) && (msg[1] & + (PRESS_MSG_MASK | MOVE_MSG_MASK))) { + data->fingers[id].z = msg[6]; + data->fingers[id].w = msg[5]; + data->fingers[id].x = ((msg[2] << 4) | (msg[4] >> 4)) >> + data->x_dropbits; + data->fingers[id].y = ((msg[3] << 4) | + (msg[4] & 0xF)) >> data->y_dropbits; + data->finger_mask |= 1U << id; + } else if ((msg[1] & SUPPRESS_MSG_MASK) && + (data->fingers[id].z != -1)) { + data->fingers[id].z = -1; + data->fingers[id].w = msg[5]; + data->finger_mask |= 1U << id; + } else { + dev_dbg(&data->client->dev, "Unknown state %#02x %#02x" + "\n", msg[0], msg[1]); + continue; + } + } while (!gpio_get_value(data->gpio_read_done)); + + if (data->finger_mask) + report_input_data(data); + + return IRQ_HANDLED; +} + +static int mxt224_internal_suspend(struct mxt224_data *data) +{ + static const u8 sleep_power_cfg[3]; + int ret; + int i; + + ret = write_config(data, GEN_POWERCONFIG_T7, sleep_power_cfg); + if (ret) + return ret; + + + for (i = 0; i < data->num_fingers; i++) + data->fingers[i].z = -1; + report_input_data(data); + + data->power_off(); + + return 0; +} + +static int mxt224_internal_resume(struct mxt224_data *data) +{ + int ret; + int i; + + data->power_on(); + + i = 0; + do { + ret = write_config(data, GEN_POWERCONFIG_T7, data->power_cfg); + msleep(20); + i++; + } while (ret && i < 10); + + return ret; +} + +#ifdef CONFIG_HAS_EARLYSUSPEND +#define mxt224_suspend NULL +#define mxt224_resume NULL + +static void mxt224_early_suspend(struct early_suspend *h) +{ + struct mxt224_data *data = container_of(h, struct mxt224_data, + early_suspend); + disable_irq(data->client->irq); + mxt224_internal_suspend(data); +} + +static void mxt224_late_resume(struct early_suspend *h) +{ + struct mxt224_data *data = container_of(h, struct mxt224_data, + early_suspend); + mxt224_internal_resume(data); + enable_irq(data->client->irq); +} +#else +static int mxt224_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct mxt224_data *data = i2c_get_clientdata(client); + + return mxt224_internal_suspend(data); +} + +static int mxt224_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct mxt224_data *data = i2c_get_clientdata(client); + + return mxt224_internal_resume(data); +} +#endif + +static int __devinit mxt224_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct mxt224_platform_data *pdata = client->dev.platform_data; + struct mxt224_data *data; + struct input_dev *input_dev; + int ret; + int i; + + if (!pdata) { + dev_err(&client->dev, "missing platform data\n"); + return -ENODEV; + } + + if (pdata->max_finger_touches <= 0) + return -EINVAL; + + data = kzalloc(sizeof(*data) + pdata->max_finger_touches * + sizeof(*data->fingers), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->num_fingers = pdata->max_finger_touches; + data->power_on = pdata->power_on; + data->power_off = pdata->power_off; + + data->client = client; + i2c_set_clientdata(client, data); + + input_dev = input_allocate_device(); + if (!input_dev) { + ret = -ENOMEM; + dev_err(&client->dev, "input device allocation failed\n"); + goto err_alloc_dev; + } + data->input_dev = input_dev; + input_set_drvdata(input_dev, data); + input_dev->name = "mxt224_ts_input"; + + set_bit(EV_ABS, input_dev->evbit); + + input_set_abs_params(input_dev, ABS_MT_POSITION_X, pdata->min_x, + pdata->max_x, 0, 0); + input_set_abs_params(input_dev, ABS_MT_POSITION_Y, pdata->min_y, + pdata->max_y, 0, 0); + input_set_abs_params(input_dev, ABS_MT_PRESSURE, pdata->min_z, + pdata->max_z, 0, 0); + input_set_abs_params(input_dev, ABS_MT_TOUCH_MAJOR, pdata->min_w, + pdata->max_w, 0, 0); + input_set_abs_params(input_dev, ABS_MT_TRACKING_ID, 0, + data->num_fingers - 1, 0, 0); + + ret = input_register_device(input_dev); + if (ret) { + input_free_device(input_dev); + goto err_reg_dev; + } + + data->gpio_read_done = pdata->gpio_read_done; + ret = gpio_request(data->gpio_read_done, "touch read done"); + if (ret) + goto err_gpio_req; + + data->power_on(); + + ret = mxt224_init_touch_driver(data); + if (ret) { + dev_err(&client->dev, "chip initialization failed\n"); + goto err_init_drv; + } + + for (i = 0; pdata->config[i][0] != RESERVED_T255; i++) { + ret = write_config(data, pdata->config[i][0], + pdata->config[i] + 1); + if (ret) + goto err_config; + + if (pdata->config[i][0] == GEN_POWERCONFIG_T7) + data->power_cfg = pdata->config[i] + 1; + + if (pdata->config[i][0] == TOUCH_MULTITOUCHSCREEN_T9) { + /* Are x and y inverted? */ + if (pdata->config[i][10] & 0x1) { + data->x_dropbits = + (!(pdata->config[i][22] & 0xC)) << 1; + data->y_dropbits = + (!(pdata->config[i][20] & 0xC)) << 1; + } else { + data->x_dropbits = + (!(pdata->config[i][20] & 0xC)) << 1; + data->y_dropbits = + (!(pdata->config[i][22] & 0xC)) << 1; + } + } + } + + ret = mxt224_backup(data); + if (ret) + goto err_backup; + + /* reset the touch IC. */ + ret = mxt224_reset(data); + if (ret) + goto err_reset; + + msleep(60); + + for (i = 0; i < data->num_fingers; i++) + data->fingers[i].z = -1; + + ret = request_threaded_irq(client->irq, NULL, mxt224_irq_thread, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, "mxt224_ts", data); + if (ret < 0) + goto err_irq; + +#ifdef CONFIG_HAS_EARLYSUSPEND + data->early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN + 1; + data->early_suspend.suspend = mxt224_early_suspend; + data->early_suspend.resume = mxt224_late_resume; + register_early_suspend(&data->early_suspend); +#endif + + return 0; + +err_irq: +err_reset: +err_backup: +err_config: + kfree(data->objects); +err_init_drv: + gpio_free(data->gpio_read_done); +err_gpio_req: + data->power_off(); + input_unregister_device(input_dev); +err_reg_dev: +err_alloc_dev: + kfree(data); + return ret; +} + +static int __devexit mxt224_remove(struct i2c_client *client) +{ + struct mxt224_data *data = i2c_get_clientdata(client); + +#ifdef CONFIG_HAS_EARLYSUSPEND + unregister_early_suspend(&data->early_suspend); +#endif + free_irq(client->irq, data); + kfree(data->objects); + gpio_free(data->gpio_read_done); + data->power_off(); + input_unregister_device(data->input_dev); + kfree(data); + + return 0; +} + +static struct i2c_device_id mxt224_idtable[] = { + {MXT224_DEV_NAME, 0}, + {}, +}; + +MODULE_DEVICE_TABLE(i2c, mxt224_idtable); + +static const struct dev_pm_ops mxt224_pm_ops = { + .suspend = mxt224_suspend, + .resume = mxt224_resume, +}; + +static struct i2c_driver mxt224_i2c_driver = { + .id_table = mxt224_idtable, + .probe = mxt224_probe, + .remove = __devexit_p(mxt224_remove), + .driver = { + .owner = THIS_MODULE, + .name = MXT224_DEV_NAME, + .pm = &mxt224_pm_ops, + }, +}; + +static int __init mxt224_init(void) +{ + return i2c_add_driver(&mxt224_i2c_driver); +} + +static void __exit mxt224_exit(void) +{ + i2c_del_driver(&mxt224_i2c_driver); +} +module_init(mxt224_init); +module_exit(mxt224_exit); + +MODULE_DESCRIPTION("Atmel MaXTouch 224 driver"); +MODULE_AUTHOR("Rom Lemarchand <rlemarchand@sta.samsung.com>"); +MODULE_LICENSE("GPL"); |