aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/input
diff options
context:
space:
mode:
authorBrian Swetland <swetland@google.com>2010-11-14 20:35:55 -0800
committerArve Hjønnevåg <arve@android.com>2011-11-17 17:46:57 -0800
commit9f8aecea1596ee19465ee1fe2a244854480575f2 (patch)
tree04d358faa8f62b70e720689ee2d0453d372a4b51 /drivers/input
parent79b1b9917c2b33bde1d7b385d44fcc69571fbb19 (diff)
downloadkernel_samsung_crespo-9f8aecea1596ee19465ee1fe2a244854480575f2.zip
kernel_samsung_crespo-9f8aecea1596ee19465ee1fe2a244854480575f2.tar.gz
kernel_samsung_crespo-9f8aecea1596ee19465ee1fe2a244854480575f2.tar.bz2
input: mxt224: Atmel MaxTouch 224 driver
Adding driver to support the Atmel MaxTouch 224 multi-touch controller. Change-Id: Ibf8de98c1f102e09f6204e7b57e49bcaf154f0af Signed-off-by: Rom Lemarchand <rlemarchand@sta.samsung.com>
Diffstat (limited to 'drivers/input')
-rw-r--r--drivers/input/touchscreen/Kconfig12
-rw-r--r--drivers/input/touchscreen/Makefile1
-rw-r--r--drivers/input/touchscreen/mxt224.c648
3 files changed, 661 insertions, 0 deletions
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..07143b1
--- /dev/null
+++ b/drivers/input/touchscreen/mxt224.c
@@ -0,0 +1,648 @@
+/*
+ * 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>
+
+#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;
+
+ 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_TOUCH_MAJOR,
+ data->fingers[i].z);
+ input_report_abs(data->input_dev, ABS_MT_WIDTH_MAJOR,
+ data->fingers[i].w);
+ input_report_abs(data->input_dev, ABS_MT_TRACKING_ID, i);
+ input_mt_sync(data->input_dev);
+
+ if (data->fingers[i].z == 0)
+ data->fingers[i].z = -1;
+ }
+ data->finger_mask = 0;
+
+ 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 = 0;
+ 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 = 0;
+ 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++) {
+ if (data->fingers[i].z == -1)
+ continue;
+ data->fingers[i].z = 0;
+ }
+ 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_TOUCH_MAJOR, pdata->min_z,
+ pdata->max_z, 0, 0);
+ input_set_abs_params(input_dev, ABS_MT_WIDTH_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");