blob: ffeb8fe7f2038d12097b0b8641632f459623400b [file] [log] [blame]
/*
*
* FocalTech ft5x06 TouchScreen driver.
*
* Copyright (c) 2010 Focal tech Ltd.
* Copyright (c) 2012, Code Aurora Forum. All rights reserved.
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* 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/i2c.h>
#include <linux/input.h>
#include <linux/slab.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/gpio.h>
#include <linux/regulator/consumer.h>
#include <linux/input/ft5x06_ts.h>
#ifdef CONFIG_HAS_EARLYSUSPEND
#include <linux/earlysuspend.h>
/* Early-suspend level */
#define FT5X06_SUSPEND_LEVEL 1
#endif
#define CFG_MAX_TOUCH_POINTS 5
#define FT_STARTUP_DLY 150
#define FT_RESET_DLY 20
#define FT_PRESS 0x7F
#define FT_MAX_ID 0x0F
#define FT_TOUCH_STEP 6
#define FT_TOUCH_X_H_POS 3
#define FT_TOUCH_X_L_POS 4
#define FT_TOUCH_Y_H_POS 5
#define FT_TOUCH_Y_L_POS 6
#define FT_TOUCH_EVENT_POS 3
#define FT_TOUCH_ID_POS 5
#define POINT_READ_BUF (3 + FT_TOUCH_STEP * CFG_MAX_TOUCH_POINTS)
/*register address*/
#define FT5X06_REG_PMODE 0xA5
#define FT5X06_REG_FW_VER 0xA6
#define FT5X06_REG_POINT_RATE 0x88
#define FT5X06_REG_THGROUP 0x80
/* power register bits*/
#define FT5X06_PMODE_ACTIVE 0x00
#define FT5X06_PMODE_MONITOR 0x01
#define FT5X06_PMODE_STANDBY 0x02
#define FT5X06_PMODE_HIBERNATE 0x03
#define FT5X06_VTG_MIN_UV 2600000
#define FT5X06_VTG_MAX_UV 3300000
#define FT5X06_I2C_VTG_MIN_UV 1800000
#define FT5X06_I2C_VTG_MAX_UV 1800000
struct ts_event {
u16 x[CFG_MAX_TOUCH_POINTS]; /*x coordinate */
u16 y[CFG_MAX_TOUCH_POINTS]; /*y coordinate */
/* touch event: 0 -- down; 1-- contact; 2 -- contact */
u8 touch_event[CFG_MAX_TOUCH_POINTS];
u8 finger_id[CFG_MAX_TOUCH_POINTS]; /*touch ID */
u16 pressure;
u8 touch_point;
};
struct ft5x06_ts_data {
struct i2c_client *client;
struct input_dev *input_dev;
struct ts_event event;
const struct ft5x06_ts_platform_data *pdata;
struct regulator *vdd;
struct regulator *vcc_i2c;
#ifdef CONFIG_HAS_EARLYSUSPEND
struct early_suspend early_suspend;
#endif
};
static int ft5x06_i2c_read(struct i2c_client *client, char *writebuf,
int writelen, char *readbuf, int readlen)
{
int ret;
if (writelen > 0) {
struct i2c_msg msgs[] = {
{
.addr = client->addr,
.flags = 0,
.len = writelen,
.buf = writebuf,
},
{
.addr = client->addr,
.flags = I2C_M_RD,
.len = readlen,
.buf = readbuf,
},
};
ret = i2c_transfer(client->adapter, msgs, 2);
if (ret < 0)
dev_err(&client->dev, "%s: i2c read error.\n",
__func__);
} else {
struct i2c_msg msgs[] = {
{
.addr = client->addr,
.flags = I2C_M_RD,
.len = readlen,
.buf = readbuf,
},
};
ret = i2c_transfer(client->adapter, msgs, 1);
if (ret < 0)
dev_err(&client->dev, "%s:i2c read error.\n", __func__);
}
return ret;
}
static int ft5x06_i2c_write(struct i2c_client *client, char *writebuf,
int writelen)
{
int ret;
struct i2c_msg msgs[] = {
{
.addr = client->addr,
.flags = 0,
.len = writelen,
.buf = writebuf,
},
};
ret = i2c_transfer(client->adapter, msgs, 1);
if (ret < 0)
dev_err(&client->dev, "%s: i2c write error.\n", __func__);
return ret;
}
static void ft5x06_report_value(struct ft5x06_ts_data *data)
{
struct ts_event *event = &data->event;
int i;
int fingerdown = 0;
for (i = 0; i < event->touch_point; i++) {
if (event->touch_event[i] == 0 || event->touch_event[i] == 2) {
event->pressure = FT_PRESS;
fingerdown++;
} else {
event->pressure = 0;
}
input_report_abs(data->input_dev, ABS_MT_POSITION_X,
event->x[i]);
input_report_abs(data->input_dev, ABS_MT_POSITION_Y,
event->y[i]);
input_report_abs(data->input_dev, ABS_MT_PRESSURE,
event->pressure);
input_report_abs(data->input_dev, ABS_MT_TRACKING_ID,
event->finger_id[i]);
input_report_abs(data->input_dev, ABS_MT_TOUCH_MAJOR,
event->pressure);
input_mt_sync(data->input_dev);
}
input_report_key(data->input_dev, BTN_TOUCH, !!fingerdown);
input_sync(data->input_dev);
}
static int ft5x06_handle_touchdata(struct ft5x06_ts_data *data)
{
struct ts_event *event = &data->event;
int ret, i;
u8 buf[POINT_READ_BUF] = { 0 };
u8 pointid = FT_MAX_ID;
ret = ft5x06_i2c_read(data->client, buf, 1, buf, POINT_READ_BUF);
if (ret < 0) {
dev_err(&data->client->dev, "%s read touchdata failed.\n",
__func__);
return ret;
}
memset(event, 0, sizeof(struct ts_event));
event->touch_point = 0;
for (i = 0; i < CFG_MAX_TOUCH_POINTS; i++) {
pointid = (buf[FT_TOUCH_ID_POS + FT_TOUCH_STEP * i]) >> 4;
if (pointid >= FT_MAX_ID)
break;
else
event->touch_point++;
event->x[i] =
(s16) (buf[FT_TOUCH_X_H_POS + FT_TOUCH_STEP * i] & 0x0F) <<
8 | (s16) buf[FT_TOUCH_X_L_POS + FT_TOUCH_STEP * i];
event->y[i] =
(s16) (buf[FT_TOUCH_Y_H_POS + FT_TOUCH_STEP * i] & 0x0F) <<
8 | (s16) buf[FT_TOUCH_Y_L_POS + FT_TOUCH_STEP * i];
event->touch_event[i] =
buf[FT_TOUCH_EVENT_POS + FT_TOUCH_STEP * i] >> 6;
event->finger_id[i] =
(buf[FT_TOUCH_ID_POS + FT_TOUCH_STEP * i]) >> 4;
}
ft5x06_report_value(data);
return 0;
}
static irqreturn_t ft5x06_ts_interrupt(int irq, void *dev_id)
{
struct ft5x06_ts_data *data = dev_id;
int rc;
rc = ft5x06_handle_touchdata(data);
if (rc)
pr_err("%s: handling touchdata failed\n", __func__);
return IRQ_HANDLED;
}
static int ft5x06_power_on(struct ft5x06_ts_data *data, bool on)
{
int rc;
if (!on)
goto power_off;
rc = regulator_enable(data->vdd);
if (rc) {
dev_err(&data->client->dev,
"Regulator vdd enable failed rc=%d\n", rc);
return rc;
}
rc = regulator_enable(data->vcc_i2c);
if (rc) {
dev_err(&data->client->dev,
"Regulator vcc_i2c enable failed rc=%d\n", rc);
regulator_disable(data->vdd);
}
return rc;
power_off:
rc = regulator_disable(data->vdd);
if (rc) {
dev_err(&data->client->dev,
"Regulator vdd disable failed rc=%d\n", rc);
return rc;
}
rc = regulator_disable(data->vcc_i2c);
if (rc) {
dev_err(&data->client->dev,
"Regulator vcc_i2c disable failed rc=%d\n", rc);
regulator_enable(data->vdd);
}
return rc;
}
static int ft5x06_power_init(struct ft5x06_ts_data *data, bool on)
{
int rc;
if (!on)
goto pwr_deinit;
data->vdd = regulator_get(&data->client->dev, "vdd");
if (IS_ERR(data->vdd)) {
rc = PTR_ERR(data->vdd);
dev_err(&data->client->dev,
"Regulator get failed vdd rc=%d\n", rc);
return rc;
}
if (regulator_count_voltages(data->vdd) > 0) {
rc = regulator_set_voltage(data->vdd, FT5X06_VTG_MIN_UV,
FT5X06_VTG_MAX_UV);
if (rc) {
dev_err(&data->client->dev,
"Regulator set_vtg failed vdd rc=%d\n", rc);
goto reg_vdd_put;
}
}
data->vcc_i2c = regulator_get(&data->client->dev, "vcc_i2c");
if (IS_ERR(data->vcc_i2c)) {
rc = PTR_ERR(data->vcc_i2c);
dev_err(&data->client->dev,
"Regulator get failed vcc_i2c rc=%d\n", rc);
goto reg_vdd_set_vtg;
}
if (regulator_count_voltages(data->vcc_i2c) > 0) {
rc = regulator_set_voltage(data->vcc_i2c, FT5X06_I2C_VTG_MIN_UV,
FT5X06_I2C_VTG_MAX_UV);
if (rc) {
dev_err(&data->client->dev,
"Regulator set_vtg failed vcc_i2c rc=%d\n", rc);
goto reg_vcc_i2c_put;
}
}
return 0;
reg_vcc_i2c_put:
regulator_put(data->vcc_i2c);
reg_vdd_set_vtg:
if (regulator_count_voltages(data->vdd) > 0)
regulator_set_voltage(data->vdd, 0, FT5X06_VTG_MAX_UV);
reg_vdd_put:
regulator_put(data->vdd);
return rc;
pwr_deinit:
if (regulator_count_voltages(data->vdd) > 0)
regulator_set_voltage(data->vdd, 0, FT5X06_VTG_MAX_UV);
regulator_put(data->vdd);
if (regulator_count_voltages(data->vcc_i2c) > 0)
regulator_set_voltage(data->vcc_i2c, 0, FT5X06_I2C_VTG_MAX_UV);
regulator_put(data->vcc_i2c);
return 0;
}
#ifdef CONFIG_PM
static int ft5x06_ts_suspend(struct device *dev)
{
struct ft5x06_ts_data *data = dev_get_drvdata(dev);
char txbuf[2];
disable_irq(data->client->irq);
if (gpio_is_valid(data->pdata->reset_gpio)) {
txbuf[0] = FT5X06_REG_PMODE;
txbuf[1] = FT5X06_PMODE_HIBERNATE;
ft5x06_i2c_write(data->client, txbuf, sizeof(txbuf));
}
return 0;
}
static int ft5x06_ts_resume(struct device *dev)
{
struct ft5x06_ts_data *data = dev_get_drvdata(dev);
if (gpio_is_valid(data->pdata->reset_gpio)) {
gpio_set_value_cansleep(data->pdata->reset_gpio, 0);
msleep(FT_RESET_DLY);
gpio_set_value_cansleep(data->pdata->reset_gpio, 1);
}
enable_irq(data->client->irq);
return 0;
}
#ifdef CONFIG_HAS_EARLYSUSPEND
static void ft5x06_ts_early_suspend(struct early_suspend *handler)
{
struct ft5x06_ts_data *data = container_of(handler,
struct ft5x06_ts_data,
early_suspend);
ft5x06_ts_suspend(&data->client->dev);
}
static void ft5x06_ts_late_resume(struct early_suspend *handler)
{
struct ft5x06_ts_data *data = container_of(handler,
struct ft5x06_ts_data,
early_suspend);
ft5x06_ts_resume(&data->client->dev);
}
#endif
static const struct dev_pm_ops ft5x06_ts_pm_ops = {
#ifndef CONFIG_HAS_EARLYSUSPEND
.suspend = ft5x06_ts_suspend,
.resume = ft5x06_ts_resume,
#endif
};
#endif
static int ft5x06_ts_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
const struct ft5x06_ts_platform_data *pdata = client->dev.platform_data;
struct ft5x06_ts_data *data;
struct input_dev *input_dev;
u8 reg_value;
u8 reg_addr;
int err;
if (!pdata) {
dev_err(&client->dev, "Invalid pdata\n");
return -EINVAL;
}
if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
dev_err(&client->dev, "I2C not supported\n");
return -ENODEV;
}
data = kzalloc(sizeof(struct ft5x06_ts_data), GFP_KERNEL);
if (!data) {
dev_err(&client->dev, "Not enough memory\n");
return -ENOMEM;
}
input_dev = input_allocate_device();
if (!input_dev) {
err = -ENOMEM;
dev_err(&client->dev, "failed to allocate input device\n");
goto free_mem;
}
data->input_dev = input_dev;
data->client = client;
data->pdata = pdata;
input_dev->name = "ft5x06_ts";
input_dev->id.bustype = BUS_I2C;
input_dev->dev.parent = &client->dev;
input_set_drvdata(input_dev, data);
i2c_set_clientdata(client, data);
__set_bit(EV_KEY, input_dev->evbit);
__set_bit(EV_ABS, input_dev->evbit);
__set_bit(BTN_TOUCH, input_dev->keybit);
__set_bit(INPUT_PROP_DIRECT, input_dev->propbit);
input_set_abs_params(input_dev, ABS_MT_POSITION_X, 0,
pdata->x_max, 0, 0);
input_set_abs_params(input_dev, ABS_MT_POSITION_Y, 0,
pdata->y_max, 0, 0);
input_set_abs_params(input_dev, ABS_MT_TRACKING_ID, 0,
CFG_MAX_TOUCH_POINTS, 0, 0);
input_set_abs_params(input_dev, ABS_MT_TOUCH_MAJOR, 0, FT_PRESS, 0, 0);
input_set_abs_params(input_dev, ABS_MT_PRESSURE, 0, FT_PRESS, 0, 0);
err = input_register_device(input_dev);
if (err) {
dev_err(&client->dev, "Input device registration failed\n");
goto free_inputdev;
}
if (pdata->power_init) {
err = pdata->power_init(true);
if (err) {
dev_err(&client->dev, "power init failed");
goto unreg_inputdev;
}
} else {
err = ft5x06_power_init(data, true);
if (err) {
dev_err(&client->dev, "power init failed");
goto unreg_inputdev;
}
}
if (pdata->power_on) {
err = pdata->power_on(true);
if (err) {
dev_err(&client->dev, "power on failed");
goto pwr_deinit;
}
} else {
err = ft5x06_power_on(data, true);
if (err) {
dev_err(&client->dev, "power on failed");
goto pwr_deinit;
}
}
if (gpio_is_valid(pdata->irq_gpio)) {
err = gpio_request(pdata->irq_gpio, "ft5x06_irq_gpio");
if (err) {
dev_err(&client->dev, "irq gpio request failed");
goto pwr_off;
}
err = gpio_direction_input(pdata->irq_gpio);
if (err) {
dev_err(&client->dev,
"set_direction for irq gpio failed\n");
goto free_irq_gpio;
}
}
if (gpio_is_valid(pdata->reset_gpio)) {
err = gpio_request(pdata->reset_gpio, "ft5x06_reset_gpio");
if (err) {
dev_err(&client->dev, "reset gpio request failed");
goto free_irq_gpio;
}
err = gpio_direction_output(pdata->reset_gpio, 0);
if (err) {
dev_err(&client->dev,
"set_direction for reset gpio failed\n");
goto free_reset_gpio;
}
msleep(FT_RESET_DLY);
gpio_set_value_cansleep(data->pdata->reset_gpio, 1);
}
/* make sure CTP already finish startup process */
msleep(FT_STARTUP_DLY);
/*get some register information */
reg_addr = FT5X06_REG_FW_VER;
err = ft5x06_i2c_read(client, &reg_addr, 1, &reg_value, 1);
if (err)
dev_err(&client->dev, "version read failed");
dev_info(&client->dev, "[FTS] Firmware version = 0x%x\n", reg_value);
reg_addr = FT5X06_REG_POINT_RATE;
ft5x06_i2c_read(client, &reg_addr, 1, &reg_value, 1);
if (err)
dev_err(&client->dev, "report rate read failed");
dev_info(&client->dev, "[FTS] report rate is %dHz.\n", reg_value * 10);
reg_addr = FT5X06_REG_THGROUP;
err = ft5x06_i2c_read(client, &reg_addr, 1, &reg_value, 1);
if (err)
dev_err(&client->dev, "threshold read failed");
dev_dbg(&client->dev, "[FTS] touch threshold is %d.\n", reg_value * 4);
err = request_threaded_irq(client->irq, NULL,
ft5x06_ts_interrupt, pdata->irqflags,
client->dev.driver->name, data);
if (err) {
dev_err(&client->dev, "request irq failed\n");
goto free_reset_gpio;
}
#ifdef CONFIG_HAS_EARLYSUSPEND
data->early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN +
FT5X06_SUSPEND_LEVEL;
data->early_suspend.suspend = ft5x06_ts_early_suspend;
data->early_suspend.resume = ft5x06_ts_late_resume;
register_early_suspend(&data->early_suspend);
#endif
return 0;
free_reset_gpio:
if (gpio_is_valid(pdata->reset_gpio))
gpio_free(pdata->reset_gpio);
free_irq_gpio:
if (gpio_is_valid(pdata->irq_gpio))
gpio_free(pdata->reset_gpio);
pwr_off:
if (pdata->power_on)
pdata->power_on(false);
else
ft5x06_power_on(data, false);
pwr_deinit:
if (pdata->power_init)
pdata->power_init(false);
else
ft5x06_power_init(data, false);
unreg_inputdev:
input_unregister_device(input_dev);
input_dev = NULL;
free_inputdev:
input_free_device(input_dev);
free_mem:
kfree(data);
return err;
}
static int __devexit ft5x06_ts_remove(struct i2c_client *client)
{
struct ft5x06_ts_data *data = i2c_get_clientdata(client);
#ifdef CONFIG_HAS_EARLYSUSPEND
unregister_early_suspend(&data->early_suspend);
#endif
free_irq(client->irq, data);
if (gpio_is_valid(data->pdata->reset_gpio))
gpio_free(data->pdata->reset_gpio);
if (gpio_is_valid(data->pdata->irq_gpio))
gpio_free(data->pdata->reset_gpio);
if (data->pdata->power_on)
data->pdata->power_on(false);
else
ft5x06_power_on(data, false);
if (data->pdata->power_init)
data->pdata->power_init(false);
else
ft5x06_power_init(data, false);
input_unregister_device(data->input_dev);
kfree(data);
return 0;
}
static const struct i2c_device_id ft5x06_ts_id[] = {
{"ft5x06_ts", 0},
{},
};
MODULE_DEVICE_TABLE(i2c, ft5x06_ts_id);
static struct i2c_driver ft5x06_ts_driver = {
.probe = ft5x06_ts_probe,
.remove = __devexit_p(ft5x06_ts_remove),
.driver = {
.name = "ft5x06_ts",
.owner = THIS_MODULE,
#ifdef CONFIG_PM
.pm = &ft5x06_ts_pm_ops,
#endif
},
.id_table = ft5x06_ts_id,
};
static int __init ft5x06_ts_init(void)
{
return i2c_add_driver(&ft5x06_ts_driver);
}
module_init(ft5x06_ts_init);
static void __exit ft5x06_ts_exit(void)
{
i2c_del_driver(&ft5x06_ts_driver);
}
module_exit(ft5x06_ts_exit);
MODULE_DESCRIPTION("FocalTech ft5x06 TouchScreen driver");
MODULE_LICENSE("GPL v2");