Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit fd335ab0 authored by Lothar Waßmann's avatar Lothar Waßmann Committed by Dmitry Torokhov
Browse files

Input: edt-ft5x06 - add support for M09 firmware version



There is a new firmware version for the EDT-FT5x06 chip.
Add support for detecting the firmware version and handle the
differences appropriately.

Signed-off-by: default avatarLothar Waßmann <LW@KARO-electronics.de>
Signed-off-by: default avatarDmitry Torokhov <dmitry.torokhov@gmail.com>
parent ee3e946e
Loading
Loading
Loading
Loading
+276 −82
Original line number Diff line number Diff line
/*
 * Copyright (C) 2012 Simon Budig, <simon.budig@kernelconcepts.de>
 * Daniel Wagener <daniel.wagener@kernelconcepts.de> (M09 firmware support)
 * Lothar Waßmann <LW@KARO-electronics.de> (DT support)
 *
 * This software is licensed under the terms of the GNU General Public
@@ -47,6 +48,14 @@
#define WORK_REGISTER_NUM_X		0x33
#define WORK_REGISTER_NUM_Y		0x34

#define M09_REGISTER_THRESHOLD		0x80
#define M09_REGISTER_GAIN		0x92
#define M09_REGISTER_OFFSET		0x93
#define M09_REGISTER_NUM_X		0x94
#define M09_REGISTER_NUM_Y		0x95

#define NO_REGISTER			0xff

#define WORK_REGISTER_OPMODE		0x3c
#define FACTORY_REGISTER_OPMODE		0x01

@@ -61,6 +70,20 @@
#define EDT_RAW_DATA_RETRIES		100
#define EDT_RAW_DATA_DELAY		1 /* msec */

enum edt_ver {
	M06,
	M09,
};

struct edt_reg_addr {
	int reg_threshold;
	int reg_report_rate;
	int reg_gain;
	int reg_offset;
	int reg_num_x;
	int reg_num_y;
};

struct edt_ft5x06_ts_data {
	struct i2c_client *client;
	struct input_dev *input;
@@ -85,6 +108,9 @@ struct edt_ft5x06_ts_data {
	int report_rate;

	char name[EDT_NAME_LEN];

	struct edt_reg_addr reg_addr;
	enum edt_ver version;
};

static int edt_ft5x06_ts_readwrite(struct i2c_client *client,
@@ -142,33 +168,58 @@ 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];
	u8 cmd;
	u8 rdbuf[29];
	int i, type, x, y, id;
	int offset, tplen, datalen;
	int error;

	switch (tsdata->version) {
	case M06:
		cmd = 0xf9; /* tell the controller to send touch data */
		offset = 5; /* where the actual touch data starts */
		tplen = 4;  /* data comes in so called frames */
		datalen = 26; /* how much bytes to listen for */
		break;

	case M09:
		cmd = 0x02;
		offset = 1;
		tplen = 6;
		datalen = 29;
		break;

	default:
		goto out;
	}

	memset(rdbuf, 0, sizeof(rdbuf));

	error = edt_ft5x06_ts_readwrite(tsdata->client,
					sizeof(cmd), &cmd,
					sizeof(rdbuf), rdbuf);
					datalen, 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",
	/* M09 does not send header or CRC */
	if (tsdata->version == M06) {
		if (rdbuf[0] != 0xaa || rdbuf[1] != 0xaa ||
			rdbuf[2] != datalen) {
			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))
		if (!edt_ft5x06_ts_check_crc(tsdata, rdbuf, datalen))
			goto out;
	}

	for (i = 0; i < MAX_SUPPORT_POINTS; i++) {
		u8 *buf = &rdbuf[i * 4 + 5];
		u8 *buf = &rdbuf[i * tplen + offset];
		bool down;

		type = buf[0] >> 6;
@@ -176,8 +227,8 @@ static irqreturn_t edt_ft5x06_ts_isr(int irq, void *dev_id)
		if (type == TOUCH_EVENT_RESERVED)
			continue;

		/* ignore TOUCH_DOWN events, might have bogus coordinates */
		if (type == TOUCH_EVENT_DOWN)
		/* M06 sometimes sends bogus coordinates in TOUCH_DOWN */
		if (tsdata->version == M06 && type == TOUCH_EVENT_DOWN)
			continue;

		x = ((buf[0] << 8) | buf[1]) & 0x0fff;
@@ -207,12 +258,25 @@ static int edt_ft5x06_register_write(struct edt_ft5x06_ts_data *tsdata,
{
	u8 wrbuf[4];

	switch (tsdata->version) {
	case M06:
		wrbuf[0] = tsdata->factory_mode ? 0xf3 : 0xfc;
		wrbuf[1] = tsdata->factory_mode ? addr & 0x7f : addr & 0x3f;
		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);
	case M09:
		wrbuf[0] = addr;
		wrbuf[1] = value;

		return edt_ft5x06_ts_readwrite(tsdata->client, 3,
					wrbuf, 0, NULL);

	return edt_ft5x06_ts_readwrite(tsdata->client, 4, wrbuf, 0, NULL);
	default:
		return -EINVAL;
	}
}

static int edt_ft5x06_register_read(struct edt_ft5x06_ts_data *tsdata,
@@ -221,20 +285,36 @@ static int edt_ft5x06_register_read(struct edt_ft5x06_ts_data *tsdata,
	u8 wrbuf[2], rdbuf[2];
	int error;

	switch (tsdata->version) {
	case M06:
		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)
		error = edt_ft5x06_ts_readwrite(tsdata->client,
						2, wrbuf, 2, rdbuf);
		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]);
				wrbuf[0] ^ wrbuf[1] ^ rdbuf[0],
				rdbuf[1]);
			return -EIO;
		}
		break;

	case M09:
		wrbuf[0] = addr;
		error = edt_ft5x06_ts_readwrite(tsdata->client, 1,
						wrbuf, 1, rdbuf);
		if (error)
			return error;
		break;

	default:
		return -EINVAL;
	}

	return rdbuf[0];
}
@@ -244,19 +324,21 @@ struct edt_ft5x06_attribute {
	size_t field_offset;
	u8 limit_low;
	u8 limit_high;
	u8 addr;
	u8 addr_m06;
	u8 addr_m09;
};

#define EDT_ATTR(_field, _mode, _addr, _limit_low, _limit_high)		\
#define EDT_ATTR(_field, _mode, _addr_m06, _addr_m09,			\
		_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),	\
		.field_offset = offsetof(struct edt_ft5x06_ts_data, _field), \
		.addr_m06 = _addr_m06,					\
		.addr_m09 = _addr_m09,					\
		.limit_low = _limit_low,				\
		.limit_high = _limit_high,				\
		.addr = _addr,						\
	}

static ssize_t edt_ft5x06_setting_show(struct device *dev,
@@ -271,6 +353,7 @@ static ssize_t edt_ft5x06_setting_show(struct device *dev,
	int val;
	size_t count = 0;
	int error = 0;
	u8 addr;

	mutex_lock(&tsdata->mutex);

@@ -279,7 +362,22 @@ static ssize_t edt_ft5x06_setting_show(struct device *dev,
		goto out;
	}

	val = edt_ft5x06_register_read(tsdata, attr->addr);
	switch (tsdata->version) {
	case M06:
		addr = attr->addr_m06;
		break;

	case M09:
		addr = attr->addr_m09;
		break;

	default:
		error = -ENODEV;
		goto out;
	}

	if (addr != NO_REGISTER) {
		val = edt_ft5x06_register_read(tsdata, addr);
		if (val < 0) {
			error = val;
			dev_err(&tsdata->client->dev,
@@ -287,6 +385,9 @@ static ssize_t edt_ft5x06_setting_show(struct device *dev,
				dattr->attr.name, error);
			goto out;
		}
	} else {
		val = *field;
	}

	if (val != *field) {
		dev_warn(&tsdata->client->dev,
@@ -312,6 +413,7 @@ static ssize_t edt_ft5x06_setting_store(struct device *dev,
	u8 *field = (u8 *)tsdata + attr->field_offset;
	unsigned int val;
	int error;
	u8 addr;

	mutex_lock(&tsdata->mutex);

@@ -329,14 +431,29 @@ static ssize_t edt_ft5x06_setting_store(struct device *dev,
		goto out;
	}

	error = edt_ft5x06_register_write(tsdata, attr->addr, val);
	switch (tsdata->version) {
	case M06:
		addr = attr->addr_m06;
		break;

	case M09:
		addr = attr->addr_m09;
		break;

	default:
		error = -ENODEV;
		goto out;
	}

	if (addr != NO_REGISTER) {
		error = edt_ft5x06_register_write(tsdata, 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:
@@ -344,12 +461,14 @@ static ssize_t edt_ft5x06_setting_store(struct device *dev,
	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 EDT_ATTR(gain, S_IWUSR | S_IRUGO, WORK_REGISTER_GAIN,
		M09_REGISTER_GAIN, 0, 31);
static EDT_ATTR(offset, S_IWUSR | S_IRUGO, WORK_REGISTER_OFFSET,
		M09_REGISTER_OFFSET, 0, 31);
static EDT_ATTR(threshold, S_IWUSR | S_IRUGO, WORK_REGISTER_THRESHOLD,
		M09_REGISTER_THRESHOLD, 20, 80);
static EDT_ATTR(report_rate, S_IWUSR | S_IRUGO, WORK_REGISTER_REPORT_RATE,
		NO_REGISTER, 3, 14);

static struct attribute *edt_ft5x06_attrs[] = {
	&edt_ft5x06_attr_gain.dattr.attr,
@@ -384,6 +503,9 @@ static int edt_ft5x06_factory_mode(struct edt_ft5x06_ts_data *tsdata)
	}

	/* mode register is 0x3c when in the work mode */
	if (tsdata->version == M09)
		goto m09_out;

	error = edt_ft5x06_register_write(tsdata, WORK_REGISTER_OPMODE, 0x03);
	if (error) {
		dev_err(&client->dev,
@@ -416,12 +538,18 @@ static int edt_ft5x06_factory_mode(struct edt_ft5x06_ts_data *tsdata)
	enable_irq(client->irq);

	return error;

m09_out:
	dev_err(&client->dev, "No factory mode support for M09\n");
	return -EINVAL;

}

static int edt_ft5x06_work_mode(struct edt_ft5x06_ts_data *tsdata)
{
	struct i2c_client *client = tsdata->client;
	int retries = EDT_SWITCH_MODE_RETRIES;
	struct edt_reg_addr *reg_addr = &tsdata->reg_addr;
	int ret;
	int error;

@@ -454,13 +582,14 @@ static int edt_ft5x06_work_mode(struct edt_ft5x06_ts_data *tsdata)
	tsdata->raw_buffer = NULL;

	/* restore parameters */
	edt_ft5x06_register_write(tsdata, WORK_REGISTER_THRESHOLD,
	edt_ft5x06_register_write(tsdata, reg_addr->reg_threshold,
				  tsdata->threshold);
	edt_ft5x06_register_write(tsdata, WORK_REGISTER_GAIN,
	edt_ft5x06_register_write(tsdata, reg_addr->reg_gain,
				  tsdata->gain);
	edt_ft5x06_register_write(tsdata, WORK_REGISTER_OFFSET,
	edt_ft5x06_register_write(tsdata, reg_addr->reg_offset,
				  tsdata->offset);
	edt_ft5x06_register_write(tsdata, WORK_REGISTER_REPORT_RATE,
	if (reg_addr->reg_report_rate)
		edt_ft5x06_register_write(tsdata, reg_addr->reg_report_rate,
				  tsdata->report_rate);

	enable_irq(client->irq);
@@ -663,18 +792,30 @@ static int edt_ft5x06_ts_reset(struct i2c_client *client,
}

static int edt_ft5x06_ts_identify(struct i2c_client *client,
					    char *model_name,
					struct edt_ft5x06_ts_data *tsdata,
					char *fw_version)
{
	u8 rdbuf[EDT_NAME_LEN];
	char *p;
	int error;
	char *model_name = tsdata->name;

	/* see what we find if we assume it is a M06 *
	 * if we get less than EDT_NAME_LEN, we don't want
	 * to have garbage in there
	 */
	memset(rdbuf, 0, sizeof(rdbuf));
	error = edt_ft5x06_ts_readwrite(client, 1, "\xbb",
					EDT_NAME_LEN - 1, rdbuf);
	if (error)
		return error;

	/* if we find something consistent, stay with that assumption
	 * at least M09 won't send 3 bytes here
	 */
	if (!(strnicmp(rdbuf + 1, "EP0", 3))) {
		tsdata->version = M06;

		/* remove last '$' end marker */
		rdbuf[EDT_NAME_LEN - 1] = '\0';
		if (rdbuf[EDT_NAME_LEN - 2] == '$')
@@ -684,9 +825,27 @@ static int edt_ft5x06_ts_identify(struct i2c_client *client,
		p = strchr(rdbuf, '*');
		if (p)
			*p++ = '\0';

		strlcpy(model_name, rdbuf + 1, EDT_NAME_LEN);
		strlcpy(fw_version, p ? p : "", EDT_NAME_LEN);
	} else {
		/* since there are only two versions around (M06, M09) */
		tsdata->version = M09;

		error = edt_ft5x06_ts_readwrite(client, 1, "\xA6",
						2, rdbuf);
		if (error)
			return error;

		strlcpy(fw_version, rdbuf, 2);

		error = edt_ft5x06_ts_readwrite(client, 1, "\xA8",
						1, rdbuf);
		if (error)
			return error;

		snprintf(model_name, EDT_NAME_LEN, "EP0%i%i0M09",
			rdbuf[0] >> 4, rdbuf[0] & 0x0F);
	}

	return 0;
}
@@ -705,36 +864,69 @@ static int edt_ft5x06_ts_identify(struct i2c_client *client,
static void edt_ft5x06_ts_get_dt_defaults(struct device_node *np,
					struct edt_ft5x06_ts_data *tsdata)
{
	EDT_GET_PROP(threshold, WORK_REGISTER_THRESHOLD);
	EDT_GET_PROP(gain, WORK_REGISTER_GAIN);
	EDT_GET_PROP(offset, WORK_REGISTER_OFFSET);
	struct edt_reg_addr *reg_addr = &tsdata->reg_addr;

	EDT_GET_PROP(threshold, reg_addr->reg_threshold);
	EDT_GET_PROP(gain, reg_addr->reg_gain);
	EDT_GET_PROP(offset, reg_addr->reg_offset);
}

static void
edt_ft5x06_ts_get_defaults(struct edt_ft5x06_ts_data *tsdata,
			   const struct edt_ft5x06_platform_data *pdata)
{
	struct edt_reg_addr *reg_addr = &tsdata->reg_addr;

	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);
	EDT_ATTR_CHECKSET(threshold, reg_addr->reg_threshold);
	EDT_ATTR_CHECKSET(gain, reg_addr->reg_gain);
	EDT_ATTR_CHECKSET(offset, reg_addr->reg_offset);
	if (reg_addr->reg_report_rate != NO_REGISTER)
		EDT_ATTR_CHECKSET(report_rate, reg_addr->reg_report_rate);
}

static void
edt_ft5x06_ts_get_parameters(struct edt_ft5x06_ts_data *tsdata)
{
	struct edt_reg_addr *reg_addr = &tsdata->reg_addr;

	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);
						     reg_addr->reg_threshold);
	tsdata->gain = edt_ft5x06_register_read(tsdata, reg_addr->reg_gain);
	tsdata->offset = edt_ft5x06_register_read(tsdata, reg_addr->reg_offset);
	if (reg_addr->reg_report_rate != NO_REGISTER)
		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);
						reg_addr->reg_report_rate);
	tsdata->num_x = edt_ft5x06_register_read(tsdata, reg_addr->reg_num_x);
	tsdata->num_y = edt_ft5x06_register_read(tsdata, reg_addr->reg_num_y);
}

static void
edt_ft5x06_ts_set_regs(struct edt_ft5x06_ts_data *tsdata)
{
	struct edt_reg_addr *reg_addr = &tsdata->reg_addr;

	switch (tsdata->version) {
	case M06:
		reg_addr->reg_threshold = WORK_REGISTER_THRESHOLD;
		reg_addr->reg_report_rate = WORK_REGISTER_REPORT_RATE;
		reg_addr->reg_gain = WORK_REGISTER_GAIN;
		reg_addr->reg_offset = WORK_REGISTER_OFFSET;
		reg_addr->reg_num_x = WORK_REGISTER_NUM_X;
		reg_addr->reg_num_y = WORK_REGISTER_NUM_Y;
		break;

	case M09:
		reg_addr->reg_threshold = M09_REGISTER_THRESHOLD;
		reg_addr->reg_gain = M09_REGISTER_GAIN;
		reg_addr->reg_offset = M09_REGISTER_OFFSET;
		reg_addr->reg_num_x = M09_REGISTER_NUM_X;
		reg_addr->reg_num_y = M09_REGISTER_NUM_Y;
		break;
	}
}

#ifdef CONFIG_OF
@@ -818,12 +1010,14 @@ static int edt_ft5x06_ts_probe(struct i2c_client *client,
	tsdata->input = input;
	tsdata->factory_mode = false;

	error = edt_ft5x06_ts_identify(client, tsdata->name, fw_version);
	error = edt_ft5x06_ts_identify(client, tsdata, fw_version);
	if (error) {
		dev_err(&client->dev, "touchscreen probe failed\n");
		return error;
	}

	edt_ft5x06_ts_set_regs(tsdata);

	if (!pdata)
		edt_ft5x06_ts_get_dt_defaults(client->dev.of_node, tsdata);
	else