diff options
author | Brian Swetland <swetland@google.com> | 2010-11-14 20:35:55 -0800 |
---|---|---|
committer | Arve Hjønnevåg <arve@android.com> | 2011-11-17 17:46:57 -0800 |
commit | 9f8aecea1596ee19465ee1fe2a244854480575f2 (patch) | |
tree | 04d358faa8f62b70e720689ee2d0453d372a4b51 /drivers/input | |
parent | 79b1b9917c2b33bde1d7b385d44fcc69571fbb19 (diff) | |
download | kernel_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/Kconfig | 12 | ||||
-rw-r--r-- | drivers/input/touchscreen/Makefile | 1 | ||||
-rw-r--r-- | drivers/input/touchscreen/mxt224.c | 648 |
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"); |