Hi Olivier, > This driver adds support for the EDT touchscreens based on the > FocalTech chips. > > Some part of the driver are based on patch for this chip sent by > Simon Budig to the linux-input mailing list. > > Signed-off-by: Olivier Sobrie <olivier@xxxxxxxxx> > --- > drivers/input/touchscreen/Kconfig | 12 + > drivers/input/touchscreen/Makefile | 1 + > drivers/input/touchscreen/edt-ft5x06.c | 816 ++++++++++++++++++++++++++++++++ > include/linux/input/edt-ft5x06.h | 8 + > 4 files changed, 837 insertions(+) > create mode 100644 drivers/input/touchscreen/edt-ft5x06.c > create mode 100644 include/linux/input/edt-ft5x06.h > > diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig > index f67657b..032cbea 100644 > --- a/drivers/input/touchscreen/Kconfig > +++ b/drivers/input/touchscreen/Kconfig > @@ -216,6 +216,18 @@ config TOUCHSCREEN_DYNAPRO > To compile this driver as a module, choose M here: the > module will be called dynapro. > > +config TOUCHSCREEN_EDT_FT5X06 > + tristate "EDT FocalTech FT5x06 I2C Touchscreen support" > + 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_HAMPSHIRE > tristate "Hampshire serial touchscreen" > select SERIO > diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile > index eb8bfe1..bed430d7 100644 > --- a/drivers/input/touchscreen/Makefile > +++ b/drivers/input/touchscreen/Makefile > @@ -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 > diff --git a/drivers/input/touchscreen/edt-ft5x06.c b/drivers/input/touchscreen/edt-ft5x06.c > new file mode 100644 > index 0000000..43c72a0 > --- /dev/null > +++ b/drivers/input/touchscreen/edt-ft5x06.c > @@ -0,0 +1,816 @@ > +/* > + * Part of this code is inspired from a first version of a driver for > + * this chip sent by Simon Budig to the linux-input mailing list > + */ Replacing the above with the standard GPL header with both of your names ought to resolve any eventual issues, no? > + > +#include <linux/module.h> > +#include <linux/interrupt.h> > +#include <linux/input.h> > +#include <linux/i2c.h> > +#include <linux/uaccess.h> > +#include <linux/delay.h> > +#include <linux/slab.h> > +#include <linux/gpio.h> > +#include <linux/input/mt.h> > +#include <linux/input/edt-ft5x06.h> > + > +/* Commands in working mode */ > +#define CMD_RDWR_REG 0xfc > +#define CMD_GET_TOUCH_DATA 0xf9 > +#define CMD_GET_FW_VERSION 0xbb > + > +/* Commands in factory mode */ > +#define CMD_RDWR_REG_FACTORY 0xf3 > +#define CMD_RDATA_SHOW_FACTORY 0xf5 > + > +/* Registers in working mode */ > +#define W_REGISTER_THRESHOLD 0x00 > +#define W_REGISTER_RATE 0x08 > +#define W_REGISTER_GAIN 0x30 > +#define W_REGISTER_OFFSET 0x31 > +#define W_REGISTER_NUM_X 0x33 > +#define W_REGISTER_NUM_Y 0x34 > +#define W_REGISTER_OPMODE 0x3c > + > +/* Registers in factory mode */ > +#define F_REGISTER_OPMODE 0x01 > +#define F_REGISTER_RAWDATA 0x08 > + > +/* Various defines */ > +#define SENSOR_RESOLUTION 64 > +#define MAX_TOUCHES 5 > +#define CHANGE_MODE_RETRIES 10 > +#define CHANGE_MODE_DELAY 5 > +#define RAW_DATA_RETRIES 5 > +#define RAW_DATA_DELAY 20 > +#define MODEL_FW_LEN 22 > +#define RX_TOUCH_DATA_HEADER 0xaaaa > + > +/* Events */ > +#define EVENT_TOUCH_DOWN (0 << 2) > +#define EVENT_TOUCH_UP (1 << 2) > +#define EVENT_TOUCH_ON (2 << 2) > +#define EVENT_TOUCH_RESERVED (3 << 2) > + > +#define FOREACH_TOUCH(p, frm) \ > + for (p = &frm->p[0]; p < &frm->p[frm->ntouches]; p++) Foreach is a great construct in many places, but a) it is only used once, and b) C is preferred. > + > +struct ft5x06 { > + struct i2c_client *client; > + struct input_dev *input; > + int gpio_reset; > + char model[MODEL_FW_LEN]; > + char fw_version[MODEL_FW_LEN]; > + int num_x; > + int num_y; > + /* mutex used to prevent access problems when switching between > + * modes */ "serialize mode switch"? > + struct mutex mutex; > + bool factory_mode; > +}; > + > +struct tx_write_reg { > + u8 addr; > + u8 val; > + u8 crc; > +} __packed; > + > +struct rx_read_reg { > + u8 val; > + u8 crc; > +} __packed; > + > +struct rx_touch_data { > + __be16 header; > + u8 len; > + u8 ntouches; > + u8 reserved; > + struct ft5x06_xy_coordinate { > +#if defined(__LITTLE_ENDIAN_BITFIELD) > + u8 x_high:4; > + u8 event:4; > +#else > + u8 event:4; > + u8 x_high:4; > +#endif > + u8 x_low; > + > +#if defined(__LITTLE_ENDIAN_BITFIELD) > + u8 y_high:4; > + u8 tid:4; > +#else > + u8 tid:4; > + u8 y_high:4; > +#endif Here, OTOH, a define would make the code cleaner. Something like "u8 NIBBLES(a, b)" perhaps? > + u8 y_low; > + } __packed p[MAX_TOUCHES]; > + u8 crc; > +} __packed; > + > +static int ft5x06_i2c_cmd(const struct i2c_client *client, u8 cmd, > + void *wr_buf, size_t wr_len, > + void *rd_buf, size_t rd_len) > +{ > + struct i2c_msg msgs[3]; > + int i = 1, rc; > + > + msgs[0].addr = client->addr; > + msgs[0].flags = 0; > + msgs[0].len = sizeof(cmd); > + msgs[0].buf = &cmd; > + > + if (wr_len) { > + msgs[i].addr = client->addr; > + msgs[i].flags = 0; > + msgs[i].len = wr_len; > + msgs[i].buf = wr_buf; > + i++; > + } > + > + if (rd_len) { > + msgs[i].addr = client->addr; > + msgs[i].flags = I2C_M_RD; > + msgs[i].len = rd_len; > + msgs[i].buf = rd_buf; > + i++; > + } > + > + rc = i2c_transfer(client->adapter, msgs, i); > + if (rc != i) { > + dev_err(&client->dev, "i2c_transfer failed, error: %d\n", > + rc); > + return -EIO; > + } > + > + return rc; > +} > + > +static int ft5x06_check_frame_crc(const struct rx_touch_data *frm) > +{ > + const u8 *_frm = (const void *) frm; > + u8 crc = 0; > + int i; > + > + for (i = 0; i < sizeof(struct rx_touch_data); i++) > + crc ^= _frm[i]; > + > + return crc ? -EINVAL : 0; > +} I suoppose this is what the "reserved" field is for? Maybe there is a better name for that field? > + > +static void ft5x06_report_events(const struct ft5x06 *priv, > + const struct rx_touch_data *frm) > +{ > + struct device *dev = &priv->client->dev; > + struct input_dev *input = priv->input; > + int x, y; > + const struct ft5x06_xy_coordinate *p; > + char touch[MAX_TOUCHES]; > + int i; > + > + if (frm->header != RX_TOUCH_DATA_HEADER > + || frm->len != sizeof(struct rx_touch_data) > + || frm->ntouches > MAX_TOUCHES) { > + dev_dbg(dev, > + "Invalid frame header (%04x), len (%d) or ntouches (%d)\n", > + frm->header, frm->len, frm->ntouches); > + return; > + } > + > + if (ft5x06_check_frame_crc(frm) < 0) { > + dev_dbg(dev, "Invalid frame CRC\n"); > + return; > + } > + > + memset(touch, 0x00, sizeof(touch)); > + > + FOREACH_TOUCH(p, frm) { > + if (p->event == EVENT_TOUCH_RESERVED) > + continue; > + > + if (p->tid > MAX_TOUCHES - 1) > + continue; > + > + touch[p->tid] = 1; > + > + input_mt_slot(input, p->tid); > + input_mt_report_slot_state(input, MT_TOOL_FINGER, > + p->event != EVENT_TOUCH_UP); > + > + if (p->event != EVENT_TOUCH_UP) { > + x = (p->x_high << 8) | p->x_low; > + y = (p->y_high << 8) | p->y_low; > + > + input_report_abs(input, ABS_MT_POSITION_X, x); > + input_report_abs(input, ABS_MT_POSITION_Y, y); > + } > + } > + > + /* This loop is needed because the hardware doesn't always > + * report the 'TOUCH_UP' event */ > + for (i = 0; i < MAX_TOUCHES; i++) { > + if (touch[i]) > + continue; > + > + input_mt_slot(input, i); > + input_mt_report_slot_state(input, MT_TOOL_FINGER, 0); > + } And we aren't so lucky that p->tid is filled in for all these, or that p is not filled in at all for these? > + > + input_mt_report_pointer_emulation(input, false); > + input_sync(input); > +} > + > +static irqreturn_t ft5x06_isr(int irq, void *irq_data) > +{ > + struct ft5x06 *priv = irq_data; > + struct i2c_client *client = priv->client; > + struct device *dev = &client->dev; > + struct rx_touch_data frm; > + int rc; > + > + memset(&frm, 0, sizeof(frm)); > + > + rc = ft5x06_i2c_cmd(client, CMD_GET_TOUCH_DATA, NULL, 0, > + &frm, sizeof(frm)); > + if (rc < 0) { > + dev_err(dev, "Unable to get touch data, error: %d\n", rc); > + goto out; > + } > + > + ft5x06_report_events(priv, &frm); > + > +out: > + return IRQ_HANDLED; > +} > + > +static int ft5x06_register_write(const struct ft5x06 *priv, u8 addr, > + u8 value) > +{ > + struct tx_write_reg wr_reg; > + u8 cmd; > + > + if (priv->factory_mode) { > + cmd = CMD_RDWR_REG_FACTORY; > + wr_reg.addr = addr & 0x7f; > + } else { > + cmd = CMD_RDWR_REG; > + wr_reg.addr = addr & 0x3f; > + } > + > + wr_reg.val = value; > + wr_reg.crc = cmd ^ wr_reg.addr ^ wr_reg.val; > + > + return ft5x06_i2c_cmd(priv->client, cmd, &wr_reg, sizeof(wr_reg), > + NULL, 0); > +} > + > +static int ft5x06_register_read(const struct ft5x06 *priv, u8 addr) > +{ > + struct i2c_client *client = priv->client; > + struct device *dev = &client->dev; > + struct rx_read_reg rd_reg; > + int rc; > + u8 cmd; > + > + if (priv->factory_mode) { > + cmd = CMD_RDWR_REG_FACTORY; > + addr = (addr & 0x7f) | 0x80; > + } else { > + cmd = CMD_RDWR_REG; > + addr = (addr & 0x3f) | 0x40; > + } > + > + rc = ft5x06_i2c_cmd(client, cmd, &addr, sizeof(addr), > + &rd_reg, sizeof(rd_reg)); > + > + if ((cmd ^ addr ^ rd_reg.val) != rd_reg.crc) > + dev_err(dev, "crc error: 0x%02x expected, got 0x%02x\n", > + (cmd ^ addr ^ rd_reg.val), rd_reg.crc); > + > + return (rc < 0) ? rc : rd_reg.val; > +} > + > +static ssize_t ft5x06_setting_show(struct device *dev, u8 addr, char *buf) > +{ > + struct ft5x06 *priv = dev_get_drvdata(dev); > + int rc = 0; > + > + mutex_lock(&priv->mutex); > + > + if (priv->factory_mode) { > + dev_err(dev, "Cannot get registers in factory mode\n"); > + rc = -EIO; > + goto out; > + } > + > + rc = ft5x06_register_read(priv, addr); > + if (rc < 0) { > + dev_err(dev, "Unable to get register value, error: %d\n", > + rc); > + goto out; > + } > + > + rc = sprintf(buf, "%d\n", rc); > + > +out: > + mutex_unlock(&priv->mutex); > + return rc; > +} > + > +static ssize_t ft5x06_setting_store(struct device *dev, u8 addr, > + unsigned int val) > +{ > + struct ft5x06 *priv = dev_get_drvdata(dev); > + int rc = 0; > + > + mutex_lock(&priv->mutex); > + > + if (priv->factory_mode) { > + dev_err(dev, "Cannot set registers in factory mode\n"); > + rc = -EIO; > + goto out; > + } > + > + rc = ft5x06_register_write(priv, addr, val); > + if (rc < 0) { > + dev_err(dev, "Unable to set register value, error: %d\n", > + rc); > + goto out; > + } > + > +out: > + mutex_unlock(&priv->mutex); > + return rc; > +} > + > +static unsigned int get_value(const char *buf, unsigned int min, > + unsigned int max) > +{ > + unsigned int val; > + > + if (kstrtouint(buf, 10, &val)) > + return -EINVAL; > + > + if ((val < min) || (val > max)) > + return -EINVAL; > + > + return val; > +} > + > +static ssize_t ft5x06_store_rate(struct device *dev, > + struct device_attribute *attr, > + const char *buf, size_t count) > +{ > + struct ft5x06 *priv = dev_get_drvdata(dev); > + unsigned int max = 12; > + unsigned int val; > + > + if (!strcmp(priv->model, "EP0570M06") > + || !strcmp(priv->model, "EP0700M06")) > + max = 8; > + > + val = get_value(buf, 3, max); > + if (val < 0) { > + dev_err(dev, "Invalid value for rate (min: 3, max: %d)\n", > + max); > + return val; > + } > + > + return ft5x06_setting_store(dev, W_REGISTER_RATE, val) ? : count; > +} > + > +static ssize_t ft5x06_show_rate(struct device *dev, > + struct device_attribute *attr, > + char *buf) > +{ > + return ft5x06_setting_show(dev, W_REGISTER_RATE, buf); > +} > +static DEVICE_ATTR(rate, 0664, ft5x06_show_rate, ft5x06_store_rate); > + > +#define ft5x06_setting(name, addr, min, max) \ > +static ssize_t ft5x06_store_##name(struct device *dev, \ > + struct device_attribute *attr, \ > + const char *buf, size_t count) \ > +{ \ > + unsigned int val = get_value(buf, min, max); \ > + if (val < 0) { \ Ain't gonna happen... > + dev_err(dev, "Invalid value for " #name \ > + "(min: %d, max: %d)\n", min, max); \ > + return val; \ > + } \ > + return ft5x06_setting_store(dev, addr, val) ? : count; \ > +} \ > +static ssize_t ft5x06_show_##name(struct device *dev, \ > + struct device_attribute *attr, \ > + char *buf) \ > +{ \ > + return ft5x06_setting_show(dev, addr, buf); \ > +} \ > +static DEVICE_ATTR(name, 0664, ft5x06_show_##name, ft5x06_store_##name) > + > +ft5x06_setting(threshold, W_REGISTER_THRESHOLD, 20, 80); > +ft5x06_setting(gain, W_REGISTER_GAIN, 0, 31); > +ft5x06_setting(offset, W_REGISTER_OFFSET, 0, 31); Question: will the driver work without fiddling with these settings? If so, are they really necessary? Maybe some part could be moved to debugfs instead? > + > +static ssize_t ft5x06_factory_mode_show(struct device *dev, > + struct device_attribute *attr, > + char *buf) > +{ > + struct ft5x06 *priv = dev_get_drvdata(dev); > + return sprintf(buf, "%d\n", priv->factory_mode); > +} > + > +static int ft5x06_change_mode(struct device *dev, bool factory_mode) > +{ > + struct ft5x06 *priv = dev_get_drvdata(dev); > + u8 reg, new_reg, val; > + int i, rc; > + > + if (factory_mode) { > + reg = W_REGISTER_OPMODE; > + new_reg = F_REGISTER_OPMODE; > + val = 0x03; > + } else { > + reg = F_REGISTER_OPMODE; > + new_reg = W_REGISTER_OPMODE; > + val = 0x01; > + } > + > + rc = ft5x06_register_write(priv, reg, val); > + if (rc < 0) { > + dev_err(dev, "Failed to change mode, error: %d\n", rc); > + return rc; > + } > + > + priv->factory_mode = factory_mode; > + > + i = CHANGE_MODE_RETRIES; > + do { > + mdelay(CHANGE_MODE_DELAY); > + rc = ft5x06_register_read(priv, new_reg); > + if (rc == val) > + break; > + } while (--i); With the break inside, this looks like a standard while/for loop. > + > + if (rc != val) { > + priv->factory_mode = factory_mode ? false : true; Double boolean logic. > + dev_err(dev, "Not in %s mode after %d ms.\n", > + factory_mode ? "factory" : "working", > + CHANGE_MODE_RETRIES * CHANGE_MODE_DELAY); > + return -EIO; > + } > + > + return 0; > +} > + > +static ssize_t ft5x06_factory_mode_store(struct device *dev, > + struct device_attribute *attr, > + const char *buf, size_t count) > +{ > + struct ft5x06 *priv = dev_get_drvdata(dev); > + struct i2c_client *client = to_i2c_client(dev); > + unsigned int fmode; > + int rc; > + > + if (kstrtouint(buf, 10, &fmode)) > + return -EINVAL; > + > + if (fmode > 1) { > + dev_err(dev, "Invalid mode\n"); > + return -EINVAL; > + } > + > + if (fmode == priv->factory_mode) > + return count; > + > + mutex_lock(&priv->mutex); > + > + if (fmode) > + disable_irq(client->irq); > + > + rc = ft5x06_change_mode(dev, fmode); > + > + if (!priv->factory_mode) > + enable_irq(client->irq); Leaving it off on error? When is it started again? Why not use the error code for this path? > + > + mutex_unlock(&priv->mutex); > + return rc ? : count; > +} > +static DEVICE_ATTR(factory_mode, 0664, ft5x06_factory_mode_show, > + ft5x06_factory_mode_store); > + > +static ssize_t ft5x06_raw_data_show(struct device *dev, > + struct device_attribute *attr, > + char *buf) > +{ > + struct ft5x06 *priv = dev_get_drvdata(dev); > + int i, rc; > + char *ptr, wrbuf[2]; > + > + mutex_lock(&priv->mutex); > + > + if (!priv->factory_mode) { > + dev_err(dev, "Raw data not available in work mode\n"); > + rc = -EIO; > + goto out; > + } > + > + rc = ft5x06_register_write(priv, F_REGISTER_RAWDATA, 0x01); > + if (rc < 0) { > + dev_err(dev, > + "Error writing in rawdata register, error: %d\n", > + rc); > + goto out; > + } > + > + i = RAW_DATA_RETRIES; > + do { > + rc = ft5x06_register_read(priv, F_REGISTER_RAWDATA); > + if (rc < 1) > + break; > + msleep(RAW_DATA_DELAY); > + } while (--i); Recurrent pattern, function. > + > + if (rc < 0) { > + rc = (rc < 0) ? rc : -ETIMEDOUT; > + dev_err(dev, "Waiting time exceeded or error: %d\n", rc); > + goto out; > + } > + > + ptr = buf; > + wrbuf[0] = 0x0e; > + for (i = 0; i <= priv->num_x; i++) { > + wrbuf[1] = i; > + rc = ft5x06_i2c_cmd(priv->client, CMD_RDATA_SHOW_FACTORY, > + wrbuf, sizeof(wrbuf), > + ptr, priv->num_y * 2); > + if (rc < 0) > + goto out; > + > + ptr += priv->num_y * 2; > + } > + > + rc = ptr - buf; > +out: > + mutex_unlock(&priv->mutex); > + return rc; > +} > +static DEVICE_ATTR(raw_data, 0444, ft5x06_raw_data_show, NULL); Same comments + debugfs. > + > +static struct attribute *ft5x06_attrs[] = { > + &dev_attr_gain.attr, > + &dev_attr_offset.attr, > + &dev_attr_threshold.attr, > + &dev_attr_rate.attr, > + &dev_attr_factory_mode.attr, > + &dev_attr_raw_data.attr, > + NULL > +}; Stopping here. Clearly, there is a lot of work behind this driver, but not all of it needs or should be carried in the kernel. A general cleanup and reduction of sysfs usage would be good, if possible. Thanks, Henrik > + > +static const struct attribute_group ft5x06_attr_group = { > + .attrs = ft5x06_attrs, > +}; > + > +static > +int __devinit ft5x06_get_model_and_fw(const struct i2c_client *client, > + char model[MODEL_FW_LEN], > + char fw_version[MODEL_FW_LEN]) > +{ > + const struct device *dev = &client->dev; > + u8 buf[MODEL_FW_LEN]; > + int rc; > + char *tmp; > + > + rc = ft5x06_i2c_cmd(client, CMD_GET_FW_VERSION, NULL, 0, > + buf, MODEL_FW_LEN); > + if (rc < 0) { > + dev_err(dev, "Failed to get firmware version, error: %d\n", > + rc); > + return rc; > + } > + > + /* Format: 0xbb,EPxx0M06*Azz_YYMMDD$ (22 chars) */ > + if (buf[0] != 0xbb || buf[MODEL_FW_LEN-1] != 0x24) > + return -EINVAL; > + > + buf[MODEL_FW_LEN-1] = 0x00; > + > + tmp = strchr(buf, '*'); > + if (tmp) { > + tmp[0] = '\0'; > + strlcpy(fw_version, ++tmp, MODEL_FW_LEN); > + } > + > + strlcpy(model, &buf[1], MODEL_FW_LEN); > + > + return 0; > +} > + > +static int __devinit ft5x06_reset(struct ft5x06 *priv) > +{ > + struct i2c_client *client = priv->client; > + struct device *dev = &client->dev; > + struct edt_ft5x06_platform_data *pdata = dev->platform_data; > + int rc; > + > + priv->gpio_reset = pdata->gpio_reset; > + if (!gpio_is_valid(priv->gpio_reset)) > + return 0; > + > + rc = gpio_request_one(priv->gpio_reset, GPIOF_OUT_INIT_LOW, > + "edt-ft5x06 reset pin"); > + if (rc < 0) { > + dev_err(dev, > + "Failed to get reset pin (GPIO %d), error %d\n", > + priv->gpio_reset, rc); > + return rc; > + } > + > + mdelay(50); > + gpio_set_value(priv->gpio_reset, 1); > + mdelay(100); > + > + return 0; > +} > + > +static void __devinit ft5x06_init_input_dev(struct ft5x06 *priv) > +{ > + struct i2c_client *client = priv->client; > + struct device *dev = &client->dev; > + struct input_dev *input = priv->input; > + > + priv->num_x = ft5x06_register_read(priv, W_REGISTER_NUM_X); > + priv->num_y = ft5x06_register_read(priv, W_REGISTER_NUM_Y); > + > + __set_bit(EV_SYN, input->evbit); > + __set_bit(EV_KEY, input->evbit); > + __set_bit(EV_ABS, input->evbit); > + __set_bit(BTN_TOUCH, input->keybit); > + > + /* Single touch */ > + input_set_abs_params(input, ABS_X, 0, > + priv->num_x * SENSOR_RESOLUTION - 1, 0, 0); > + input_set_abs_params(input, ABS_Y, 0, > + priv->num_y * SENSOR_RESOLUTION - 1, 0, 0); > + > + /* Multi touch */ > + input_mt_init_slots(input, MAX_TOUCHES); > + input_set_abs_params(input, ABS_MT_POSITION_X, 0, > + priv->num_x * SENSOR_RESOLUTION - 1, 0, 0); > + input_set_abs_params(input, ABS_MT_POSITION_Y, 0, > + priv->num_y * SENSOR_RESOLUTION - 1, 0, 0); > + > + input->name = priv->model; > + input->id.bustype = BUS_I2C; > + input->dev.parent = dev; > + > + input_set_drvdata(input, priv); > + > + dev_dbg(dev, "Model: %s, Firmware: %s, %dx%d sensors\n", > + priv->model, priv->fw_version ? : "Unknown", > + priv->num_x, priv->num_y); > +} > + > +static int __devinit ft5x06_i2c_probe(struct i2c_client *client, > + const struct i2c_device_id *id) > +{ > + struct ft5x06 *priv; > + struct device *dev = &client->dev; > + int rc; > + > + dev_dbg(dev, "Probing for EDT FT5x06 I2C\n"); > + > + if (!client->irq) { > + dev_err(dev, "No IRQ?\n"); > + return -EINVAL; > + } > + > + priv = kzalloc(sizeof(*priv), GFP_KERNEL); > + if (!priv) { > + dev_err(dev, "Failed to allocate driver data!\n"); > + rc = -ENOMEM; > + goto err_mem; > + } > + > + priv->input = input_allocate_device(); > + if (!priv->input) { > + dev_err(dev, "Failed to allocate input device!\n"); > + rc = -ENOMEM; > + goto err_mem; > + } > + > + dev_set_drvdata(dev, priv); > + priv->client = client; > + mutex_init(&priv->mutex); > + > + rc = ft5x06_reset(priv); > + if (rc) > + goto err_reset; > + > + rc = ft5x06_get_model_and_fw(client, priv->model, > + priv->fw_version); > + if (rc) > + goto err_reset; > + > + ft5x06_init_input_dev(priv); > + > + rc = request_threaded_irq(client->irq, NULL, ft5x06_isr, > + IRQF_TRIGGER_FALLING, > + client->name, priv); > + if (rc) { > + dev_err(dev, "Unable to get touchscreen IRQ, error: %d\n", > + rc); > + goto err_irq; > + } > + > + rc = sysfs_create_group(&dev->kobj, &ft5x06_attr_group); > + if (rc) > + goto err_sysfs; > + > + rc = input_register_device(priv->input); > + if (rc) > + goto err_register; > + > + device_init_wakeup(dev, 1); > + > + dev_dbg(dev, "EDT FT5x06 initialized (IRQ %d)\n", client->irq); > + > + return 0; > + > +err_register: > + sysfs_remove_group(&dev->kobj, &ft5x06_attr_group); > +err_sysfs: > + free_irq(client->irq, priv); > +err_irq: > + if (gpio_is_valid(priv->gpio_reset)) > + gpio_free(priv->gpio_reset); > +err_reset: > + input_free_device(priv->input); > +err_mem: > + kfree(priv); > + return rc; > +} > + > +static int __devexit ft5x06_i2c_remove(struct i2c_client *client) > +{ > + struct ft5x06 *priv = dev_get_drvdata(&client->dev); > + struct device *dev = &client->dev; > + > + sysfs_remove_group(&dev->kobj, &ft5x06_attr_group); > + > + free_irq(client->irq, priv); > + > + input_unregister_device(priv->input); > + > + if (gpio_is_valid(priv->gpio_reset)) > + gpio_free(priv->gpio_reset); > + > + kfree(priv); > + > + return 0; > +} > + > +#ifdef CONFIG_PM_SLEEP > +static int ft5x06_i2c_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 ft5x06_i2c_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(ft5x06_pm, ft5x06_i2c_suspend, ft5x06_i2c_resume); > + > +static const struct i2c_device_id ft5x06_i2c_id[] = { > + { "edt-ft5x06", 0 }, > + { } > +}; > +MODULE_DEVICE_TABLE(i2c, ft5x06_i2c_id); > + > +static struct i2c_driver ft5x06_i2c_driver = { > + .driver = { > + .owner = THIS_MODULE, > + .name = "edt-ft5x06_i2c", > + .pm = &ft5x06_pm, > + }, > + .id_table = ft5x06_i2c_id, > + .probe = ft5x06_i2c_probe, > + .remove = __devexit_p(ft5x06_i2c_remove), > +}; > +module_i2c_driver(ft5x06_i2c_driver); > + > +MODULE_AUTHOR("Olivier Sobrie <olivier@xxxxxxxxx>"); > +MODULE_DESCRIPTION("EDT FT5x06 I2C Touchscreen Driver"); > +MODULE_LICENSE("GPL"); > diff --git a/include/linux/input/edt-ft5x06.h b/include/linux/input/edt-ft5x06.h > new file mode 100644 > index 0000000..e687ffb > --- /dev/null > +++ b/include/linux/input/edt-ft5x06.h > @@ -0,0 +1,8 @@ > +#ifndef _EDT_FT5X06_H > +#define _EDT_FT5X06_H > + > +struct edt_ft5x06_platform_data { > + unsigned int gpio_reset; > +}; > + > +#endif > -- > 1.7.9.5 > -- To unsubscribe from this list: send the line "unsubscribe linux-input" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html