Ft5x06 controllers are single chip capacitive touch panel controller ICs with a built-in 8 bit Micro Controller Unit. It supports multi-touch capability and can detect up to five touches. Change-Id: I39eb1175d473d1f2c463e1c4a0a1606307da9dc0 Signed-off-by: Mohan Pallaka <mpallaka@codeaurora.org>
655 lines
15 KiB
C
655 lines
15 KiB
C
/*
|
|
*
|
|
* 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);
|
|
|
|
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, ®_addr, 1, ®_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, ®_addr, 1, ®_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, ®_addr, 1, ®_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");
|