Re: [PATCH] input: edt-ft5x06 - Touchscreen driver for FT5x06 based EDT displays

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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


[Index of Archives]     [Linux Media Devel]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Linux Wireless Networking]     [Linux Omap]

  Powered by Linux