Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/dtor/input

Pull input updates from Dmitry Torokhov:
 "A new driver for FT5x06 based EDT displays and a couple of other
  driver changes"

* 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/dtor/input:
  Input: synaptics - handle out of bounds values from the hardware
  Input: wacom - add support to Cintiq 22HD
  Input: add driver for FT5x06 based EDT displays
This commit is contained in:
Linus Torvalds 2012-07-30 10:01:45 -07:00
commit f1115bb686
8 changed files with 1033 additions and 3 deletions

View File

@ -0,0 +1,54 @@
EDT ft5x06 based Polytouch devices
----------------------------------
The edt-ft5x06 driver is useful for the EDT "Polytouch" family of capacitive
touch screens. Note that it is *not* suitable for other devices based on the
focaltec ft5x06 devices, since they contain vendor-specific firmware. In
particular this driver is not suitable for the Nook tablet.
It has been tested with the following devices:
* EP0350M06
* EP0430M06
* EP0570M06
* EP0700M06
The driver allows configuration of the touch screen via a set of sysfs files:
/sys/class/input/eventX/device/device/threshold:
allows setting the "click"-threshold in the range from 20 to 80.
/sys/class/input/eventX/device/device/gain:
allows setting the sensitivity in the range from 0 to 31. Note that
lower values indicate higher sensitivity.
/sys/class/input/eventX/device/device/offset:
allows setting the edge compensation in the range from 0 to 31.
/sys/class/input/eventX/device/device/report_rate:
allows setting the report rate in the range from 3 to 14.
For debugging purposes the driver provides a few files in the debug
filesystem (if available in the kernel). In /sys/kernel/debug/edt_ft5x06
you'll find the following files:
num_x, num_y:
(readonly) contains the number of sensor fields in X- and
Y-direction.
mode:
allows switching the sensor between "factory mode" and "operation
mode" by writing "1" or "0" to it. In factory mode (1) it is
possible to get the raw data from the sensor. Note that in factory
mode regular events don't get delivered and the options described
above are unavailable.
raw_data:
contains num_x * num_y big endian 16 bit values describing the raw
values for each sensor field. Note that each read() call on this
files triggers a new readout. It is recommended to provide a buffer
big enough to contain num_x * num_y * 2 bytes.
Note that reading raw_data gives a I/O error when the device is not in factory
mode. The same happens when reading/writing to the parameter files when the
device is not in regular operation mode.

View File

@ -40,11 +40,27 @@
* Note that newer firmware allows querying device for maximum useable
* coordinates.
*/
#define XMIN 0
#define XMAX 6143
#define YMIN 0
#define YMAX 6143
#define XMIN_NOMINAL 1472
#define XMAX_NOMINAL 5472
#define YMIN_NOMINAL 1408
#define YMAX_NOMINAL 4448
/* Size in bits of absolute position values reported by the hardware */
#define ABS_POS_BITS 13
/*
* Any position values from the hardware above the following limits are
* treated as "wrapped around negative" values that have been truncated to
* the 13-bit reporting range of the hardware. These are just reasonable
* guesses and can be adjusted if hardware is found that operates outside
* of these parameters.
*/
#define X_MAX_POSITIVE (((1 << ABS_POS_BITS) + XMAX) / 2)
#define Y_MAX_POSITIVE (((1 << ABS_POS_BITS) + YMAX) / 2)
/*****************************************************************************
* Stuff we need even when we do not want native Synaptics support
@ -588,6 +604,12 @@ static int synaptics_parse_hw_state(const unsigned char buf[],
hw->right = (buf[0] & 0x02) ? 1 : 0;
}
/* Convert wrap-around values to negative */
if (hw->x > X_MAX_POSITIVE)
hw->x -= 1 << ABS_POS_BITS;
if (hw->y > Y_MAX_POSITIVE)
hw->y -= 1 << ABS_POS_BITS;
return 0;
}

View File

@ -464,7 +464,7 @@ static void wacom_intuos_general(struct wacom_wac *wacom)
t = (data[6] << 2) | ((data[7] >> 6) & 3);
if ((features->type >= INTUOS4S && features->type <= INTUOS4L) ||
(features->type >= INTUOS5S && features->type <= INTUOS5L) ||
features->type == WACOM_21UX2 || features->type == WACOM_24HD) {
(features->type >= WACOM_21UX2 && features->type <= WACOM_24HD)) {
t = (t << 1) | (data[1] & 1);
}
input_report_abs(input, ABS_PRESSURE, t);
@ -614,7 +614,7 @@ static int wacom_intuos_irq(struct wacom_wac *wacom)
input_report_abs(input, ABS_MISC, 0);
}
} else {
if (features->type == WACOM_21UX2) {
if (features->type == WACOM_21UX2 || features->type == WACOM_22HD) {
input_report_key(input, BTN_0, (data[5] & 0x01));
input_report_key(input, BTN_1, (data[6] & 0x01));
input_report_key(input, BTN_2, (data[6] & 0x02));
@ -633,6 +633,12 @@ static int wacom_intuos_irq(struct wacom_wac *wacom)
input_report_key(input, BTN_Z, (data[8] & 0x20));
input_report_key(input, BTN_BASE, (data[8] & 0x40));
input_report_key(input, BTN_BASE2, (data[8] & 0x80));
if (features->type == WACOM_22HD) {
input_report_key(input, KEY_PROG1, data[9] & 0x01);
input_report_key(input, KEY_PROG2, data[9] & 0x02);
input_report_key(input, KEY_PROG3, data[9] & 0x04);
}
} else {
input_report_key(input, BTN_0, (data[5] & 0x01));
input_report_key(input, BTN_1, (data[5] & 0x02));
@ -1231,6 +1237,7 @@ void wacom_wac_irq(struct wacom_wac *wacom_wac, size_t len)
case CINTIQ:
case WACOM_BEE:
case WACOM_21UX2:
case WACOM_22HD:
case WACOM_24HD:
sync = wacom_intuos_irq(wacom_wac);
break;
@ -1432,6 +1439,12 @@ int wacom_setup_input_capabilities(struct input_dev *input_dev,
wacom_setup_cintiq(wacom_wac);
break;
case WACOM_22HD:
__set_bit(KEY_PROG1, input_dev->keybit);
__set_bit(KEY_PROG2, input_dev->keybit);
__set_bit(KEY_PROG3, input_dev->keybit);
/* fall through */
case WACOM_21UX2:
__set_bit(BTN_A, input_dev->keybit);
__set_bit(BTN_B, input_dev->keybit);
@ -1858,6 +1871,9 @@ static const struct wacom_features wacom_features_0xF0 =
static const struct wacom_features wacom_features_0xCC =
{ "Wacom Cintiq 21UX2", WACOM_PKGLEN_INTUOS, 87200, 65600, 2047,
63, WACOM_21UX2, WACOM_INTUOS3_RES, WACOM_INTUOS3_RES };
static const struct wacom_features wacom_features_0xFA =
{ "Wacom Cintiq 22HD", WACOM_PKGLEN_INTUOS, 95840, 54260, 2047,
63, WACOM_22HD, WACOM_INTUOS3_RES, WACOM_INTUOS3_RES };
static const struct wacom_features wacom_features_0x90 =
{ "Wacom ISDv4 90", WACOM_PKGLEN_GRAPHIRE, 26202, 16325, 255,
0, TABLETPC, WACOM_INTUOS_RES, WACOM_INTUOS_RES };
@ -2075,6 +2091,7 @@ const struct usb_device_id wacom_ids[] = {
{ USB_DEVICE_WACOM(0xEF) },
{ USB_DEVICE_WACOM(0x47) },
{ USB_DEVICE_WACOM(0xF4) },
{ USB_DEVICE_WACOM(0xFA) },
{ USB_DEVICE_LENOVO(0x6004) },
{ }
};

View File

@ -73,8 +73,9 @@ enum {
INTUOS5S,
INTUOS5,
INTUOS5L,
WACOM_24HD,
WACOM_21UX2,
WACOM_22HD,
WACOM_24HD,
CINTIQ,
WACOM_BEE,
WACOM_MO,

View File

@ -472,6 +472,19 @@ config TOUCHSCREEN_PENMOUNT
To compile this driver as a module, choose M here: the
module will be called penmount.
config TOUCHSCREEN_EDT_FT5X06
tristate "EDT FocalTech FT5x06 I2C Touchscreen support"
depends on I2C
help
Say Y here if you have an EDT "Polytouch" touchscreen based
on the FocalTech FT5x06 family of controllers connected to
your system.
If unsure, say N.
To compile this driver as a module, choose M here: the
module will be called edt-ft5x06.
config TOUCHSCREEN_MIGOR
tristate "Renesas MIGO-R touchscreen"
depends on SH_MIGOR && I2C

View File

@ -24,6 +24,7 @@ obj-$(CONFIG_TOUCHSCREEN_CYTTSP_SPI) += cyttsp_spi.o
obj-$(CONFIG_TOUCHSCREEN_DA9034) += da9034-ts.o
obj-$(CONFIG_TOUCHSCREEN_DA9052) += da9052_tsi.o
obj-$(CONFIG_TOUCHSCREEN_DYNAPRO) += dynapro.o
obj-$(CONFIG_TOUCHSCREEN_EDT_FT5X06) += edt-ft5x06.o
obj-$(CONFIG_TOUCHSCREEN_HAMPSHIRE) += hampshire.o
obj-$(CONFIG_TOUCHSCREEN_GUNZE) += gunze.o
obj-$(CONFIG_TOUCHSCREEN_EETI) += eeti_ts.o

View File

@ -0,0 +1,898 @@
/*
* Copyright (C) 2012 Simon Budig, <simon.budig@kernelconcepts.de>
*
* 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.
*
* You should have received a copy of the GNU General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*
* This is a driver for the EDT "Polytouch" family of touch controllers
* based on the FocalTech FT5x06 line of chips.
*
* Development of this driver has been sponsored by Glyn:
* http://www.glyn.com/Products/Displays
*/
#include <linux/module.h>
#include <linux/ratelimit.h>
#include <linux/interrupt.h>
#include <linux/input.h>
#include <linux/i2c.h>
#include <linux/uaccess.h>
#include <linux/delay.h>
#include <linux/debugfs.h>
#include <linux/slab.h>
#include <linux/gpio.h>
#include <linux/input/mt.h>
#include <linux/input/edt-ft5x06.h>
#define MAX_SUPPORT_POINTS 5
#define WORK_REGISTER_THRESHOLD 0x00
#define WORK_REGISTER_REPORT_RATE 0x08
#define WORK_REGISTER_GAIN 0x30
#define WORK_REGISTER_OFFSET 0x31
#define WORK_REGISTER_NUM_X 0x33
#define WORK_REGISTER_NUM_Y 0x34
#define WORK_REGISTER_OPMODE 0x3c
#define FACTORY_REGISTER_OPMODE 0x01
#define TOUCH_EVENT_DOWN 0x00
#define TOUCH_EVENT_UP 0x01
#define TOUCH_EVENT_ON 0x02
#define TOUCH_EVENT_RESERVED 0x03
#define EDT_NAME_LEN 23
#define EDT_SWITCH_MODE_RETRIES 10
#define EDT_SWITCH_MODE_DELAY 5 /* msec */
#define EDT_RAW_DATA_RETRIES 100
#define EDT_RAW_DATA_DELAY 1 /* msec */
struct edt_ft5x06_ts_data {
struct i2c_client *client;
struct input_dev *input;
u16 num_x;
u16 num_y;
#if defined(CONFIG_DEBUG_FS)
struct dentry *debug_dir;
u8 *raw_buffer;
size_t raw_bufsize;
#endif
struct mutex mutex;
bool factory_mode;
int threshold;
int gain;
int offset;
int report_rate;
char name[EDT_NAME_LEN];
};
static int edt_ft5x06_ts_readwrite(struct i2c_client *client,
u16 wr_len, u8 *wr_buf,
u16 rd_len, u8 *rd_buf)
{
struct i2c_msg wrmsg[2];
int i = 0;
int ret;
if (wr_len) {
wrmsg[i].addr = client->addr;
wrmsg[i].flags = 0;
wrmsg[i].len = wr_len;
wrmsg[i].buf = wr_buf;
i++;
}
if (rd_len) {
wrmsg[i].addr = client->addr;
wrmsg[i].flags = I2C_M_RD;
wrmsg[i].len = rd_len;
wrmsg[i].buf = rd_buf;
i++;
}
ret = i2c_transfer(client->adapter, wrmsg, i);
if (ret < 0)
return ret;
if (ret != i)
return -EIO;
return 0;
}
static bool edt_ft5x06_ts_check_crc(struct edt_ft5x06_ts_data *tsdata,
u8 *buf, int buflen)
{
int i;
u8 crc = 0;
for (i = 0; i < buflen - 1; i++)
crc ^= buf[i];
if (crc != buf[buflen-1]) {
dev_err_ratelimited(&tsdata->client->dev,
"crc error: 0x%02x expected, got 0x%02x\n",
crc, buf[buflen-1]);
return false;
}
return true;
}
static irqreturn_t edt_ft5x06_ts_isr(int irq, void *dev_id)
{
struct edt_ft5x06_ts_data *tsdata = dev_id;
struct device *dev = &tsdata->client->dev;
u8 cmd = 0xf9;
u8 rdbuf[26];
int i, type, x, y, id;
int error;
memset(rdbuf, 0, sizeof(rdbuf));
error = edt_ft5x06_ts_readwrite(tsdata->client,
sizeof(cmd), &cmd,
sizeof(rdbuf), rdbuf);
if (error) {
dev_err_ratelimited(dev, "Unable to fetch data, error: %d\n",
error);
goto out;
}
if (rdbuf[0] != 0xaa || rdbuf[1] != 0xaa || rdbuf[2] != 26) {
dev_err_ratelimited(dev, "Unexpected header: %02x%02x%02x!\n",
rdbuf[0], rdbuf[1], rdbuf[2]);
goto out;
}
if (!edt_ft5x06_ts_check_crc(tsdata, rdbuf, 26))
goto out;
for (i = 0; i < MAX_SUPPORT_POINTS; i++) {
u8 *buf = &rdbuf[i * 4 + 5];
bool down;
type = buf[0] >> 6;
/* ignore Reserved events */
if (type == TOUCH_EVENT_RESERVED)
continue;
x = ((buf[0] << 8) | buf[1]) & 0x0fff;
y = ((buf[2] << 8) | buf[3]) & 0x0fff;
id = (buf[2] >> 4) & 0x0f;
down = (type != TOUCH_EVENT_UP);
input_mt_slot(tsdata->input, id);
input_mt_report_slot_state(tsdata->input, MT_TOOL_FINGER, down);
if (!down)
continue;
input_report_abs(tsdata->input, ABS_MT_POSITION_X, x);
input_report_abs(tsdata->input, ABS_MT_POSITION_Y, y);
}
input_mt_report_pointer_emulation(tsdata->input, true);
input_sync(tsdata->input);
out:
return IRQ_HANDLED;
}
static int edt_ft5x06_register_write(struct edt_ft5x06_ts_data *tsdata,
u8 addr, u8 value)
{
u8 wrbuf[4];
wrbuf[0] = tsdata->factory_mode ? 0xf3 : 0xfc;
wrbuf[1] = tsdata->factory_mode ? addr & 0x7f : addr & 0x3f;
wrbuf[2] = value;
wrbuf[3] = wrbuf[0] ^ wrbuf[1] ^ wrbuf[2];
return edt_ft5x06_ts_readwrite(tsdata->client, 4, wrbuf, 0, NULL);
}
static int edt_ft5x06_register_read(struct edt_ft5x06_ts_data *tsdata,
u8 addr)
{
u8 wrbuf[2], rdbuf[2];
int error;
wrbuf[0] = tsdata->factory_mode ? 0xf3 : 0xfc;
wrbuf[1] = tsdata->factory_mode ? addr & 0x7f : addr & 0x3f;
wrbuf[1] |= tsdata->factory_mode ? 0x80 : 0x40;
error = edt_ft5x06_ts_readwrite(tsdata->client, 2, wrbuf, 2, rdbuf);
if (error)
return error;
if ((wrbuf[0] ^ wrbuf[1] ^ rdbuf[0]) != rdbuf[1]) {
dev_err(&tsdata->client->dev,
"crc error: 0x%02x expected, got 0x%02x\n",
wrbuf[0] ^ wrbuf[1] ^ rdbuf[0], rdbuf[1]);
return -EIO;
}
return rdbuf[0];
}
struct edt_ft5x06_attribute {
struct device_attribute dattr;
size_t field_offset;
u8 limit_low;
u8 limit_high;
u8 addr;
};
#define EDT_ATTR(_field, _mode, _addr, _limit_low, _limit_high) \
struct edt_ft5x06_attribute edt_ft5x06_attr_##_field = { \
.dattr = __ATTR(_field, _mode, \
edt_ft5x06_setting_show, \
edt_ft5x06_setting_store), \
.field_offset = \
offsetof(struct edt_ft5x06_ts_data, _field), \
.limit_low = _limit_low, \
.limit_high = _limit_high, \
.addr = _addr, \
}
static ssize_t edt_ft5x06_setting_show(struct device *dev,
struct device_attribute *dattr,
char *buf)
{
struct i2c_client *client = to_i2c_client(dev);
struct edt_ft5x06_ts_data *tsdata = i2c_get_clientdata(client);
struct edt_ft5x06_attribute *attr =
container_of(dattr, struct edt_ft5x06_attribute, dattr);
u8 *field = (u8 *)((char *)tsdata + attr->field_offset);
int val;
size_t count = 0;
int error = 0;
mutex_lock(&tsdata->mutex);
if (tsdata->factory_mode) {
error = -EIO;
goto out;
}
val = edt_ft5x06_register_read(tsdata, attr->addr);
if (val < 0) {
error = val;
dev_err(&tsdata->client->dev,
"Failed to fetch attribute %s, error %d\n",
dattr->attr.name, error);
goto out;
}
if (val != *field) {
dev_warn(&tsdata->client->dev,
"%s: read (%d) and stored value (%d) differ\n",
dattr->attr.name, val, *field);
*field = val;
}
count = scnprintf(buf, PAGE_SIZE, "%d\n", val);
out:
mutex_unlock(&tsdata->mutex);
return error ?: count;
}
static ssize_t edt_ft5x06_setting_store(struct device *dev,
struct device_attribute *dattr,
const char *buf, size_t count)
{
struct i2c_client *client = to_i2c_client(dev);
struct edt_ft5x06_ts_data *tsdata = i2c_get_clientdata(client);
struct edt_ft5x06_attribute *attr =
container_of(dattr, struct edt_ft5x06_attribute, dattr);
u8 *field = (u8 *)((char *)tsdata + attr->field_offset);
unsigned int val;
int error;
mutex_lock(&tsdata->mutex);
if (tsdata->factory_mode) {
error = -EIO;
goto out;
}
error = kstrtouint(buf, 0, &val);
if (error)
goto out;
if (val < attr->limit_low || val > attr->limit_high) {
error = -ERANGE;
goto out;
}
error = edt_ft5x06_register_write(tsdata, attr->addr, val);
if (error) {
dev_err(&tsdata->client->dev,
"Failed to update attribute %s, error: %d\n",
dattr->attr.name, error);
goto out;
}
*field = val;
out:
mutex_unlock(&tsdata->mutex);
return error ?: count;
}
static EDT_ATTR(gain, S_IWUSR | S_IRUGO, WORK_REGISTER_GAIN, 0, 31);
static EDT_ATTR(offset, S_IWUSR | S_IRUGO, WORK_REGISTER_OFFSET, 0, 31);
static EDT_ATTR(threshold, S_IWUSR | S_IRUGO,
WORK_REGISTER_THRESHOLD, 20, 80);
static EDT_ATTR(report_rate, S_IWUSR | S_IRUGO,
WORK_REGISTER_REPORT_RATE, 3, 14);
static struct attribute *edt_ft5x06_attrs[] = {
&edt_ft5x06_attr_gain.dattr.attr,
&edt_ft5x06_attr_offset.dattr.attr,
&edt_ft5x06_attr_threshold.dattr.attr,
&edt_ft5x06_attr_report_rate.dattr.attr,
NULL
};
static const struct attribute_group edt_ft5x06_attr_group = {
.attrs = edt_ft5x06_attrs,
};
#ifdef CONFIG_DEBUG_FS
static int edt_ft5x06_factory_mode(struct edt_ft5x06_ts_data *tsdata)
{
struct i2c_client *client = tsdata->client;
int retries = EDT_SWITCH_MODE_RETRIES;
int ret;
int error;
disable_irq(client->irq);
if (!tsdata->raw_buffer) {
tsdata->raw_bufsize = tsdata->num_x * tsdata->num_y *
sizeof(u16);
tsdata->raw_buffer = kzalloc(tsdata->raw_bufsize, GFP_KERNEL);
if (!tsdata->raw_buffer) {
error = -ENOMEM;
goto err_out;
}
}
/* mode register is 0x3c when in the work mode */
error = edt_ft5x06_register_write(tsdata, WORK_REGISTER_OPMODE, 0x03);
if (error) {
dev_err(&client->dev,
"failed to switch to factory mode, error %d\n", error);
goto err_out;
}
tsdata->factory_mode = true;
do {
mdelay(EDT_SWITCH_MODE_DELAY);
/* mode register is 0x01 when in factory mode */
ret = edt_ft5x06_register_read(tsdata, FACTORY_REGISTER_OPMODE);
if (ret == 0x03)
break;
} while (--retries > 0);
if (retries == 0) {
dev_err(&client->dev, "not in factory mode after %dms.\n",
EDT_SWITCH_MODE_RETRIES * EDT_SWITCH_MODE_DELAY);
error = -EIO;
goto err_out;
}
return 0;
err_out:
kfree(tsdata->raw_buffer);
tsdata->raw_buffer = NULL;
tsdata->factory_mode = false;
enable_irq(client->irq);
return error;
}
static int edt_ft5x06_work_mode(struct edt_ft5x06_ts_data *tsdata)
{
struct i2c_client *client = tsdata->client;
int retries = EDT_SWITCH_MODE_RETRIES;
int ret;
int error;
/* mode register is 0x01 when in the factory mode */
error = edt_ft5x06_register_write(tsdata, FACTORY_REGISTER_OPMODE, 0x1);
if (error) {
dev_err(&client->dev,
"failed to switch to work mode, error: %d\n", error);
return error;
}
tsdata->factory_mode = false;
do {
mdelay(EDT_SWITCH_MODE_DELAY);
/* mode register is 0x01 when in factory mode */
ret = edt_ft5x06_register_read(tsdata, WORK_REGISTER_OPMODE);
if (ret == 0x01)
break;
} while (--retries > 0);
if (retries == 0) {
dev_err(&client->dev, "not in work mode after %dms.\n",
EDT_SWITCH_MODE_RETRIES * EDT_SWITCH_MODE_DELAY);
tsdata->factory_mode = true;
return -EIO;
}
if (tsdata->raw_buffer)
kfree(tsdata->raw_buffer);
tsdata->raw_buffer = NULL;
/* restore parameters */
edt_ft5x06_register_write(tsdata, WORK_REGISTER_THRESHOLD,
tsdata->threshold);
edt_ft5x06_register_write(tsdata, WORK_REGISTER_GAIN,
tsdata->gain);
edt_ft5x06_register_write(tsdata, WORK_REGISTER_OFFSET,
tsdata->offset);
edt_ft5x06_register_write(tsdata, WORK_REGISTER_REPORT_RATE,
tsdata->report_rate);
enable_irq(client->irq);
return 0;
}
static int edt_ft5x06_debugfs_mode_get(void *data, u64 *mode)
{
struct edt_ft5x06_ts_data *tsdata = data;
*mode = tsdata->factory_mode;
return 0;
};
static int edt_ft5x06_debugfs_mode_set(void *data, u64 mode)
{
struct edt_ft5x06_ts_data *tsdata = data;
int retval = 0;
if (mode > 1)
return -ERANGE;
mutex_lock(&tsdata->mutex);
if (mode != tsdata->factory_mode) {
retval = mode ? edt_ft5x06_factory_mode(tsdata) :
edt_ft5x06_work_mode(tsdata);
}
mutex_unlock(&tsdata->mutex);
return retval;
};
DEFINE_SIMPLE_ATTRIBUTE(debugfs_mode_fops, edt_ft5x06_debugfs_mode_get,
edt_ft5x06_debugfs_mode_set, "%llu\n");
static int edt_ft5x06_debugfs_raw_data_open(struct inode *inode,
struct file *file)
{
file->private_data = inode->i_private;
return 0;
}
static ssize_t edt_ft5x06_debugfs_raw_data_read(struct file *file,
char __user *buf, size_t count, loff_t *off)
{
struct edt_ft5x06_ts_data *tsdata = file->private_data;
struct i2c_client *client = tsdata->client;
int retries = EDT_RAW_DATA_RETRIES;
int val, i, error;
size_t read = 0;
int colbytes;
char wrbuf[3];
u8 *rdbuf;
if (*off < 0 || *off >= tsdata->raw_bufsize)
return 0;
mutex_lock(&tsdata->mutex);
if (!tsdata->factory_mode || !tsdata->raw_buffer) {
error = -EIO;
goto out;
}
error = edt_ft5x06_register_write(tsdata, 0x08, 0x01);
if (error) {
dev_dbg(&client->dev,
"failed to write 0x08 register, error %d\n", error);
goto out;
}
do {
msleep(EDT_RAW_DATA_DELAY);
val = edt_ft5x06_register_read(tsdata, 0x08);
if (val < 1)
break;
} while (--retries > 0);
if (val < 0) {
error = val;
dev_dbg(&client->dev,
"failed to read 0x08 register, error %d\n", error);
goto out;
}
if (retries == 0) {
dev_dbg(&client->dev,
"timed out waiting for register to settle\n");
error = -ETIMEDOUT;
goto out;
}
rdbuf = tsdata->raw_buffer;
colbytes = tsdata->num_y * sizeof(u16);
wrbuf[0] = 0xf5;
wrbuf[1] = 0x0e;
for (i = 0; i < tsdata->num_x; i++) {
wrbuf[2] = i; /* column index */
error = edt_ft5x06_ts_readwrite(tsdata->client,
sizeof(wrbuf), wrbuf,
colbytes, rdbuf);
if (error)
goto out;
rdbuf += colbytes;
}
read = min_t(size_t, count, tsdata->raw_bufsize - *off);
error = copy_to_user(buf, tsdata->raw_buffer + *off, read);
if (!error)
*off += read;
out:
mutex_unlock(&tsdata->mutex);
return error ?: read;
};
static const struct file_operations debugfs_raw_data_fops = {
.open = edt_ft5x06_debugfs_raw_data_open,
.read = edt_ft5x06_debugfs_raw_data_read,
};
static void __devinit
edt_ft5x06_ts_prepare_debugfs(struct edt_ft5x06_ts_data *tsdata,
const char *debugfs_name)
{
tsdata->debug_dir = debugfs_create_dir(debugfs_name, NULL);
if (!tsdata->debug_dir)
return;
debugfs_create_u16("num_x", S_IRUSR, tsdata->debug_dir, &tsdata->num_x);
debugfs_create_u16("num_y", S_IRUSR, tsdata->debug_dir, &tsdata->num_y);
debugfs_create_file("mode", S_IRUSR | S_IWUSR,
tsdata->debug_dir, tsdata, &debugfs_mode_fops);
debugfs_create_file("raw_data", S_IRUSR,
tsdata->debug_dir, tsdata, &debugfs_raw_data_fops);
}
static void __devexit
edt_ft5x06_ts_teardown_debugfs(struct edt_ft5x06_ts_data *tsdata)
{
if (tsdata->debug_dir)
debugfs_remove_recursive(tsdata->debug_dir);
}
#else
static inline void
edt_ft5x06_ts_prepare_debugfs(struct edt_ft5x06_ts_data *tsdata,
const char *debugfs_name)
{
}
static inline void
edt_ft5x06_ts_teardown_debugfs(struct edt_ft5x06_ts_data *tsdata)
{
}
#endif /* CONFIG_DEBUGFS */
static int __devinit edt_ft5x06_ts_reset(struct i2c_client *client,
int reset_pin)
{
int error;
if (gpio_is_valid(reset_pin)) {
/* this pulls reset down, enabling the low active reset */
error = gpio_request_one(reset_pin, GPIOF_OUT_INIT_LOW,
"edt-ft5x06 reset");
if (error) {
dev_err(&client->dev,
"Failed to request GPIO %d as reset pin, error %d\n",
reset_pin, error);
return error;
}
mdelay(50);
gpio_set_value(reset_pin, 1);
mdelay(100);
}
return 0;
}
static int __devinit edt_ft5x06_ts_identify(struct i2c_client *client,
char *model_name,
char *fw_version)
{
u8 rdbuf[EDT_NAME_LEN];
char *p;
int error;
error = edt_ft5x06_ts_readwrite(client, 1, "\xbb",
EDT_NAME_LEN - 1, rdbuf);
if (error)
return error;
/* remove last '$' end marker */
rdbuf[EDT_NAME_LEN - 1] = '\0';
if (rdbuf[EDT_NAME_LEN - 2] == '$')
rdbuf[EDT_NAME_LEN - 2] = '\0';
/* look for Model/Version separator */
p = strchr(rdbuf, '*');
if (p)
*p++ = '\0';
strlcpy(model_name, rdbuf + 1, EDT_NAME_LEN);
strlcpy(fw_version, p ? p : "", EDT_NAME_LEN);
return 0;
}
#define EDT_ATTR_CHECKSET(name, reg) \
if (pdata->name >= edt_ft5x06_attr_##name.limit_low && \
pdata->name <= edt_ft5x06_attr_##name.limit_high) \
edt_ft5x06_register_write(tsdata, reg, pdata->name)
static void __devinit
edt_ft5x06_ts_get_defaults(struct edt_ft5x06_ts_data *tsdata,
const struct edt_ft5x06_platform_data *pdata)
{
if (!pdata->use_parameters)
return;
/* pick up defaults from the platform data */
EDT_ATTR_CHECKSET(threshold, WORK_REGISTER_THRESHOLD);
EDT_ATTR_CHECKSET(gain, WORK_REGISTER_GAIN);
EDT_ATTR_CHECKSET(offset, WORK_REGISTER_OFFSET);
EDT_ATTR_CHECKSET(report_rate, WORK_REGISTER_REPORT_RATE);
}
static void __devinit
edt_ft5x06_ts_get_parameters(struct edt_ft5x06_ts_data *tsdata)
{
tsdata->threshold = edt_ft5x06_register_read(tsdata,
WORK_REGISTER_THRESHOLD);
tsdata->gain = edt_ft5x06_register_read(tsdata, WORK_REGISTER_GAIN);
tsdata->offset = edt_ft5x06_register_read(tsdata, WORK_REGISTER_OFFSET);
tsdata->report_rate = edt_ft5x06_register_read(tsdata,
WORK_REGISTER_REPORT_RATE);
tsdata->num_x = edt_ft5x06_register_read(tsdata, WORK_REGISTER_NUM_X);
tsdata->num_y = edt_ft5x06_register_read(tsdata, WORK_REGISTER_NUM_Y);
}
static int __devinit edt_ft5x06_ts_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
const struct edt_ft5x06_platform_data *pdata =
client->dev.platform_data;
struct edt_ft5x06_ts_data *tsdata;
struct input_dev *input;
int error;
char fw_version[EDT_NAME_LEN];
dev_dbg(&client->dev, "probing for EDT FT5x06 I2C\n");
if (!pdata) {
dev_err(&client->dev, "no platform data?\n");
return -EINVAL;
}
error = edt_ft5x06_ts_reset(client, pdata->reset_pin);
if (error)
return error;
if (gpio_is_valid(pdata->irq_pin)) {
error = gpio_request_one(pdata->irq_pin,
GPIOF_IN, "edt-ft5x06 irq");
if (error) {
dev_err(&client->dev,
"Failed to request GPIO %d, error %d\n",
pdata->irq_pin, error);
return error;
}
}
tsdata = kzalloc(sizeof(*tsdata), GFP_KERNEL);
input = input_allocate_device();
if (!tsdata || !input) {
dev_err(&client->dev, "failed to allocate driver data.\n");
error = -ENOMEM;
goto err_free_mem;
}
mutex_init(&tsdata->mutex);
tsdata->client = client;
tsdata->input = input;
tsdata->factory_mode = false;
error = edt_ft5x06_ts_identify(client, tsdata->name, fw_version);
if (error) {
dev_err(&client->dev, "touchscreen probe failed\n");
goto err_free_mem;
}
edt_ft5x06_ts_get_defaults(tsdata, pdata);
edt_ft5x06_ts_get_parameters(tsdata);
dev_dbg(&client->dev,
"Model \"%s\", Rev. \"%s\", %dx%d sensors\n",
tsdata->name, fw_version, tsdata->num_x, tsdata->num_y);
input->name = tsdata->name;
input->id.bustype = BUS_I2C;
input->dev.parent = &client->dev;
__set_bit(EV_SYN, input->evbit);
__set_bit(EV_KEY, input->evbit);
__set_bit(EV_ABS, input->evbit);
__set_bit(BTN_TOUCH, input->keybit);
input_set_abs_params(input, ABS_X, 0, tsdata->num_x * 64 - 1, 0, 0);
input_set_abs_params(input, ABS_Y, 0, tsdata->num_y * 64 - 1, 0, 0);
input_set_abs_params(input, ABS_MT_POSITION_X,
0, tsdata->num_x * 64 - 1, 0, 0);
input_set_abs_params(input, ABS_MT_POSITION_Y,
0, tsdata->num_y * 64 - 1, 0, 0);
error = input_mt_init_slots(input, MAX_SUPPORT_POINTS);
if (error) {
dev_err(&client->dev, "Unable to init MT slots.\n");
goto err_free_mem;
}
input_set_drvdata(input, tsdata);
i2c_set_clientdata(client, tsdata);
error = request_threaded_irq(client->irq, NULL, edt_ft5x06_ts_isr,
IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
client->name, tsdata);
if (error) {
dev_err(&client->dev, "Unable to request touchscreen IRQ.\n");
goto err_free_mem;
}
error = sysfs_create_group(&client->dev.kobj, &edt_ft5x06_attr_group);
if (error)
goto err_free_irq;
error = input_register_device(input);
if (error)
goto err_remove_attrs;
edt_ft5x06_ts_prepare_debugfs(tsdata, dev_driver_string(&client->dev));
device_init_wakeup(&client->dev, 1);
dev_dbg(&client->dev,
"EDT FT5x06 initialized: IRQ pin %d, Reset pin %d.\n",
pdata->irq_pin, pdata->reset_pin);
return 0;
err_remove_attrs:
sysfs_remove_group(&client->dev.kobj, &edt_ft5x06_attr_group);
err_free_irq:
free_irq(client->irq, tsdata);
err_free_mem:
input_free_device(input);
kfree(tsdata);
if (gpio_is_valid(pdata->irq_pin))
gpio_free(pdata->irq_pin);
return error;
}
static int __devexit edt_ft5x06_ts_remove(struct i2c_client *client)
{
const struct edt_ft5x06_platform_data *pdata =
dev_get_platdata(&client->dev);
struct edt_ft5x06_ts_data *tsdata = i2c_get_clientdata(client);
edt_ft5x06_ts_teardown_debugfs(tsdata);
sysfs_remove_group(&client->dev.kobj, &edt_ft5x06_attr_group);
free_irq(client->irq, tsdata);
input_unregister_device(tsdata->input);
if (gpio_is_valid(pdata->irq_pin))
gpio_free(pdata->irq_pin);
if (gpio_is_valid(pdata->reset_pin))
gpio_free(pdata->reset_pin);
kfree(tsdata->raw_buffer);
kfree(tsdata);
return 0;
}
#ifdef CONFIG_PM_SLEEP
static int edt_ft5x06_ts_suspend(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
if (device_may_wakeup(dev))
enable_irq_wake(client->irq);
return 0;
}
static int edt_ft5x06_ts_resume(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
if (device_may_wakeup(dev))
disable_irq_wake(client->irq);
return 0;
}
#endif
static SIMPLE_DEV_PM_OPS(edt_ft5x06_ts_pm_ops,
edt_ft5x06_ts_suspend, edt_ft5x06_ts_resume);
static const struct i2c_device_id edt_ft5x06_ts_id[] = {
{ "edt-ft5x06", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, edt_ft5x06_ts_id);
static struct i2c_driver edt_ft5x06_ts_driver = {
.driver = {
.owner = THIS_MODULE,
.name = "edt_ft5x06",
.pm = &edt_ft5x06_ts_pm_ops,
},
.id_table = edt_ft5x06_ts_id,
.probe = edt_ft5x06_ts_probe,
.remove = __devexit_p(edt_ft5x06_ts_remove),
};
module_i2c_driver(edt_ft5x06_ts_driver);
MODULE_AUTHOR("Simon Budig <simon.budig@kernelconcepts.de>");
MODULE_DESCRIPTION("EDT FT5x06 I2C Touchscreen Driver");
MODULE_LICENSE("GPL");

View File

@ -0,0 +1,24 @@
#ifndef _EDT_FT5X06_H
#define _EDT_FT5X06_H
/*
* Copyright (c) 2012 Simon Budig, <simon.budig@kernelconcepts.de>
*
* 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.
*/
struct edt_ft5x06_platform_data {
int irq_pin;
int reset_pin;
/* startup defaults for operational parameters */
bool use_parameters;
u8 gain;
u8 threshold;
u8 offset;
u8 report_rate;
};
#endif /* _EDT_FT5X06_H */