blob: f01de35c0bf15b60f79ea25c808c65925fc451ec [file] [log] [blame]
/*
* drivers/input/touchscreen/tm1510.c
*
* Copyright (C) 2004-2010, Ambarella, Inc.
* Zhenwu Xue <zwxue@ambarella.com>
*
*
* 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.
*/
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/input.h>
#include <linux/interrupt.h>
#include <linux/i2c.h>
#include <linux/i2c/tm1510.h>
#ifdef CONFIG_DEBUG_TOUCHSCREEN
#define TM1510_DEBUG(format, arg...) printk(format , ## arg)
#else
#define TM1510_DEBUG(format, arg...)
#endif
#define MAX_X 3930
#define MAX_Y 2384
#define MAX_Z 16
typedef enum {
TM_FINGER_STATE = 0x01,
TM_X1_HIGH,
TM_Y1_HIGH,
TM_XY1_LOW,
TM_W1,
TM_Z1,
TM_X2_HIGH,
TM_Y2_HIGH,
TM_XY2_LOW,
TM_W2,
TM_Z2,
TM_IRQ_STATUS_ABS = 0x14,
TM_FINGER_STATE_ABS = 0x15,
TM_DEV_CNTL = 0x25,
TM_IRQ_ENABLE_ABS = 0x26,
TM_RESET = 0x6f,
TM_FAMILY_CODE = 0x7c,
} tm1510_sub_addr_t;
#define NUM_DATA 32
struct tm1510 {
char phys[32];
struct input_dev *input;
struct i2c_client *client;
struct workqueue_struct *workqueue;
struct work_struct report_worker;
u8 reg_data[NUM_DATA];
int irq;
struct tm1510_fix_data fix;
int (*get_pendown_state)(void);
void (*clear_penirq)(void);
};
static int tm1510_reset(struct tm1510 *tm)
{
u8 buf[1] = {0x00};
if (i2c_smbus_write_i2c_block_data(tm->client,
TM_RESET, 1, buf)) {
printk("I2C Error: %s\n", __func__);
return -EIO;
}
buf[0] = 0x80;
if (i2c_smbus_write_i2c_block_data(tm->client,
TM_DEV_CNTL, 1, buf)) {
printk("I2C Error: %s\n", __func__);
return -EIO;
} else {
return 0;
}
}
static int tm1510_read_family_code(struct tm1510 *tm, u8 *family_code)
{
if (i2c_smbus_read_i2c_block_data(tm->client,
TM_FAMILY_CODE, 1, family_code) != 1) {
printk("I2C Error: %s\n", __func__);
return -EIO;
} else {
return 0;
}
}
static int tm1510_config_irq(struct tm1510 *tm)
{
u8 buf[5] = {0x04, 0x01, 0x0b, 0x06, 0x06};
if (i2c_smbus_write_i2c_block_data(tm->client,
TM_IRQ_ENABLE_ABS, 5, buf)) {
printk("I2C Error: %s\n", __func__);
return -EIO;
} else {
return 0;
}
}
static inline int tm1510_read_all(struct tm1510 *tm)
{
if (i2c_smbus_read_i2c_block_data(tm->client,
TM_IRQ_STATUS_ABS, NUM_DATA, tm->reg_data) != NUM_DATA) {
printk("I2C Error: %s\n", __func__);
return -EIO;
} else {
return 0;
}
}
static void tm1510_send_event(struct tm1510 *tm)
{
struct input_dev *input = tm->input;
u8 finger_state[2];
static int prev_touch = 0;
static int curr_touch = 0;
int event = 0;
finger_state[0] = tm->reg_data[TM_FINGER_STATE] & 0x03;
finger_state[1] = (tm->reg_data[TM_FINGER_STATE] & 0x0c) >> 2;
curr_touch = 0;
if (finger_state[0] == 1 || finger_state[0] == 2)
curr_touch++;
if (finger_state[1] == 1 || finger_state[1] == 2)
curr_touch++;
/* Button Pressed */
if (!prev_touch && curr_touch) {
input_report_key(input, BTN_TOUCH, curr_touch);
TM1510_DEBUG("Finger Pressed\n");
}
/* Button Released */
if (prev_touch && !curr_touch) {
event = 1;
input_report_abs(input, ABS_PRESSURE, 0);
input_report_key(input, BTN_TOUCH, 0);
input_report_abs(input, ABS_MT_TOUCH_MAJOR, 0);
input_mt_sync(input);
TM1510_DEBUG("Finger Released\n\n\n");
}
if (curr_touch) {
u8 x1h, x1l, y1h, y1l;
u32 x1, y1;
x1h = tm->reg_data[TM_X1_HIGH];
x1l = tm->reg_data[TM_XY1_LOW] & 0x0f;
y1h = tm->reg_data[TM_Y1_HIGH];
y1l = (tm->reg_data[TM_XY1_LOW] & 0xf0) >> 4;
x1 = (x1h << 4) | x1l;
y1 = (y1h << 4) | y1l;
TM1510_DEBUG("Finger1 Raw: (%d, %d)\n", x1, y1);
if (x1 < tm->fix.x_min) {
x1 = tm->fix.x_min;
}
if (x1 > tm->fix.x_max) {
x1 = tm->fix.x_max;
}
if (y1 < tm->fix.y_min) {
y1 = tm->fix.y_min;
}
if (y1 > tm->fix.y_max) {
y1 = tm->fix.y_max;
}
if (tm->fix.x_invert)
x1 = tm->fix.x_max - x1 + tm->fix.x_min;
if (tm->fix.y_invert)
y1 = tm->fix.y_max - y1 + tm->fix.y_min;
event = 1;
input_report_abs(input, ABS_PRESSURE, MAX_Z);
input_report_abs(input, ABS_X, x1);
input_report_abs(input, ABS_Y, y1);
input_report_abs(input, ABS_MT_TOUCH_MAJOR, MAX_Z);
input_report_abs(input, ABS_MT_POSITION_X, x1);
input_report_abs(input, ABS_MT_POSITION_Y, y1);
input_mt_sync(input);
TM1510_DEBUG("Finger1 Calibrated: (%d, %d)\n", x1, y1);
}
if (curr_touch >= 2) {
u8 x2h, x2l, y2h, y2l;
u32 x2, y2;
x2h = tm->reg_data[TM_X2_HIGH];
x2l = tm->reg_data[TM_XY2_LOW] & 0x0f;
y2h = tm->reg_data[TM_Y2_HIGH];
y2l = (tm->reg_data[TM_XY2_LOW] & 0xf0) >> 4;
x2 = (x2h << 4) | x2l;
y2 = (y2h << 4) | y2l;
TM1510_DEBUG("Finger2 Raw: (%d, %d)\n", x2, y2);
if (x2 < tm->fix.x_min) {
x2 = tm->fix.x_min;
}
if (x2 > tm->fix.x_max) {
x2 = tm->fix.x_max;
}
if (y2 < tm->fix.y_min) {
y2 = tm->fix.y_min;
}
if (y2 > tm->fix.y_max) {
y2 = tm->fix.y_max;
}
if (tm->fix.x_invert)
x2 = tm->fix.x_max - x2 + tm->fix.x_min;
if (tm->fix.y_invert)
y2 = tm->fix.y_max - y2 + tm->fix.y_min;
event = 1;
input_report_abs(input, ABS_MT_TOUCH_MAJOR, MAX_Z);
input_report_abs(input, ABS_MT_POSITION_X, x2);
input_report_abs(input, ABS_MT_POSITION_Y, y2);
input_mt_sync(input);
TM1510_DEBUG("Finger2 Calibrated: (%d, %d)\n", x2, y2);
}
if (event)
input_sync(input);
prev_touch = curr_touch;
}
static irqreturn_t tm1510_irq(int irq, void *handle)
{
struct tm1510 *tm = handle;
if (tm->get_pendown_state && !tm->get_pendown_state())
goto tm1510_irq_exit;
if (tm->clear_penirq) {
tm->clear_penirq();
}
queue_work(tm->workqueue, &tm->report_worker);
tm1510_irq_exit:
return IRQ_HANDLED;
}
static void tm1510_report_worker(struct work_struct *work)
{
struct tm1510 *tm;
tm = container_of(work, struct tm1510, report_worker);
tm1510_read_all(tm);
tm1510_send_event(tm);
}
static int tm1510_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct input_dev *input_dev;
struct tm1510 *tm;
struct tm1510_platform_data *pdata;
int err;
u8 family_code;
tm1510_product_family_t tm1510_family;
pdata = client->dev.platform_data;
if (!pdata) {
dev_err(&client->dev, "platform data is required!\n");
return -EINVAL;
}
pdata->init_platform_hw();
if (!i2c_check_functionality(client->adapter,
I2C_FUNC_SMBUS_WRITE_BYTE_DATA | I2C_FUNC_SMBUS_WRITE_BYTE |
I2C_FUNC_SMBUS_READ_BYTE))
return -EIO;
tm = kzalloc(sizeof(struct tm1510), GFP_KERNEL);
input_dev = input_allocate_device();
if (!tm || !input_dev) {
err = -ENOMEM;
goto err_free_mem;
}
tm->client = client;
i2c_set_clientdata(client, tm);
tm->input = input_dev;
tm->get_pendown_state = pdata->get_pendown_state;
tm->clear_penirq = pdata->clear_penirq;
snprintf(tm->phys, sizeof(tm->phys),
"%s/input0", dev_name(&client->dev));
err = tm1510_reset(tm);
if (err)
goto err_free_mem;
err = tm1510_read_family_code(tm, &family_code);
if (err)
goto err_free_mem;
tm->fix = pdata->fix[TM1510_FAMILY_1];
for (tm1510_family = TM1510_FAMILY_0; tm1510_family < TM1510_FAMILY_END; tm1510_family++) {
if (pdata->fix[tm1510_family].family_code == family_code) {
tm->fix = pdata->fix[tm1510_family];
break;
}
}
err = tm1510_config_irq(tm);
if (err)
goto err_free_mem;
err = tm1510_read_all(tm);
if (err)
goto err_free_mem;
input_dev->name = "Synaptics TM1510 Touchscreen";
input_dev->phys = tm->phys;
input_dev->id.bustype = BUS_I2C;
set_bit(EV_SYN, input_dev->evbit);
set_bit(EV_KEY, input_dev->evbit);
set_bit(BTN_TOUCH, input_dev->keybit);
set_bit(EV_ABS, input_dev->evbit);
input_set_abs_params(input_dev, ABS_PRESSURE, 0, MAX_Z, 0, 0);
input_set_abs_params(input_dev, ABS_X, tm->fix.x_min, tm->fix.x_max, 0, 0);
input_set_abs_params(input_dev, ABS_Y, tm->fix.y_min, tm->fix.y_max, 0, 0);
input_set_abs_params(input_dev, ABS_MT_TOUCH_MAJOR, 0, MAX_Z, 0, 0);
input_set_abs_params(input_dev, ABS_MT_POSITION_X, tm->fix.x_min, tm->fix.x_max, 0, 0);
input_set_abs_params(input_dev, ABS_MT_POSITION_Y, tm->fix.y_min, tm->fix.y_max, 0, 0);
tm->workqueue = create_singlethread_workqueue("tm1510");
INIT_WORK(&tm->report_worker, tm1510_report_worker);
tm->irq = client->irq;
err = request_irq(tm->irq, tm1510_irq, IRQF_TRIGGER_FALLING,
client->dev.driver->name, tm);
if (err < 0) {
dev_err(&client->dev, "irq %d busy?\n", tm->irq);
goto err_free_mem;
}
err = input_register_device(input_dev);
if (err)
goto err_free_irq;
dev_info(&client->dev, "Family code: 0x%02x, Family: %d", family_code, tm1510_family);
return 0;
err_free_irq:
free_irq(tm->irq, tm);
err_free_mem:
input_free_device(input_dev);
kfree(tm);
return err;
}
static int tm1510_remove(struct i2c_client *client)
{
struct tm1510 *tm = i2c_get_clientdata(client);
struct tm1510_platform_data *pdata = client->dev.platform_data;
pdata->exit_platform_hw();
destroy_workqueue(tm->workqueue);
free_irq(tm->irq, tm);
input_unregister_device(tm->input);
kfree(tm);
return 0;
}
static struct i2c_device_id tm1510_idtable[] = {
{ "tm1510", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, tm1510_idtable);
static struct i2c_driver tm1510_driver = {
.driver = {
.owner = THIS_MODULE,
.name = "tm1510"
},
.id_table = tm1510_idtable,
.probe = tm1510_probe,
.remove = tm1510_remove,
};
static int __init tm1510_init(void)
{
return i2c_add_driver(&tm1510_driver);
}
static void __exit tm1510_exit(void)
{
i2c_del_driver(&tm1510_driver);
}
module_init(tm1510_init);
module_exit(tm1510_exit);
MODULE_AUTHOR("Zhenwu Xue <zwxue@ambarella.com>");
MODULE_DESCRIPTION("Synaptics TM1510 TouchScreen Driver");
MODULE_LICENSE("GPL");