linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
From: Nick Dyer <nick.dyer@itdev.co.uk>
To: Dmitry Torokhov <dmitry.torokhov@gmail.com>
Cc: Daniel Kurtz <djkurtz@chromium.org>,
	Henrik Rydberg <rydberg@euromail.se>,
	Joonyoung Shim <jy0922.shim@samsung.com>,
	Alan Bowens <Alan.Bowens@atmel.com>,
	linux-input@vger.kernel.org, linux-kernel@vger.kernel.org,
	Peter Meerwald <pmeerw@pmeerw.net>,
	Benson Leung <bleung@chromium.org>,
	Olof Johansson <olofj@chromium.org>,
	Nick Dyer <nick.dyer@itdev.co.uk>
Subject: [PATCH 13/51] Input: atmel_mxt_ts - Calculate and check CRC in config file
Date: Thu, 27 Jun 2013 13:48:48 +0100	[thread overview]
Message-ID: <1372337366-9286-14-git-send-email-nick.dyer@itdev.co.uk> (raw)
In-Reply-To: <1372337366-9286-1-git-send-email-nick.dyer@itdev.co.uk>

By validating the checksum, we can identify if the configuration is corrupt.
In addition, this patch writes the configuration in a short series of block
writes rather than as many individual values.

Signed-off-by: Nick Dyer <nick.dyer@itdev.co.uk>
Acked-by: Benson Leung <bleung@chromium.org>
---
 drivers/input/touchscreen/atmel_mxt_ts.c |  224 +++++++++++++++++++++++-------
 1 file changed, 171 insertions(+), 53 deletions(-)

diff --git a/drivers/input/touchscreen/atmel_mxt_ts.c b/drivers/input/touchscreen/atmel_mxt_ts.c
index 2054c64..751f446 100644
--- a/drivers/input/touchscreen/atmel_mxt_ts.c
+++ b/drivers/input/touchscreen/atmel_mxt_ts.c
@@ -53,6 +53,8 @@
 #define MXT_OBJECT_START	0x07
 
 #define MXT_OBJECT_SIZE		6
+#define MXT_INFO_CHECKSUM_SIZE	3
+#define MXT_MAX_BLOCK_WRITE	256
 
 /* Object types */
 #define MXT_DEBUG_DIAGNOSTIC_T37	37
@@ -263,11 +265,14 @@ struct mxt_data {
 	unsigned int max_x;
 	unsigned int max_y;
 	bool in_bootloader;
+	u16 mem_size;
 	u32 config_crc;
+	u32 info_crc;
 
 	/* Cached parameters from object table */
 	u8 T6_reportid;
 	u16 T6_address;
+	u16 T7_address;
 	u8 T9_reportid_min;
 	u8 T9_reportid_max;
 	u8 T19_reportid;
@@ -768,6 +773,45 @@ static void mxt_update_crc(struct mxt_data *data, u8 cmd, u8 value)
 	mxt_wait_for_completion(data, &data->crc_completion, MXT_CRC_TIMEOUT);
 }
 
+static void mxt_calc_crc24(u32 *crc, u8 firstbyte, u8 secondbyte)
+{
+	static const unsigned int crcpoly = 0x80001B;
+	u32 result;
+	u32 data_word;
+
+	data_word = (secondbyte << 8) | firstbyte;
+	result = ((*crc << 1) ^ data_word);
+
+	if (result & 0x1000000)
+		result ^= crcpoly;
+
+	*crc = result;
+}
+
+static u32 mxt_calculate_crc(u8 *base, off_t start_off, off_t end_off)
+{
+	u32 crc = 0;
+	u8 *ptr = base + start_off;
+	u8 *last_val = base + end_off - 1;
+
+	if (end_off < start_off)
+		return -EINVAL;
+
+	while (ptr < last_val) {
+		mxt_calc_crc24(&crc, *ptr, *(ptr + 1));
+		ptr += 2;
+	}
+
+	/* if len is odd, fill the last byte with 0 */
+	if (ptr == last_val)
+		mxt_calc_crc24(&crc, *ptr, 0);
+
+	/* Mask to 24-bit */
+	crc &= 0x00FFFFFF;
+
+	return crc;
+}
+
 /*
  * mxt_check_reg_init - download configuration to chip
  *
@@ -795,9 +839,13 @@ static int mxt_check_reg_init(struct mxt_data *data)
 	const struct firmware *cfg = NULL;
 	int ret;
 	int offset;
-	int pos;
+	int data_pos;
+	int byte_offset;
 	int i;
-	u32 info_crc, config_crc;
+	int cfg_start_ofs;
+	u32 info_crc, config_crc, calculated_crc;
+	u8 *config_mem;
+	size_t config_mem_size;
 	unsigned int type, instance, size;
 	u8 val;
 	u16 reg;
@@ -817,11 +865,11 @@ static int mxt_check_reg_init(struct mxt_data *data)
 		goto release;
 	}
 
-	pos = strlen(MXT_CFG_MAGIC);
+	data_pos = strlen(MXT_CFG_MAGIC);
 
 	/* Load information block and check */
 	for (i = 0; i < sizeof(struct mxt_info); i++) {
-		ret = sscanf(cfg->data + pos, "%hhx%n",
+		ret = sscanf(cfg->data + data_pos, "%hhx%n",
 			     (unsigned char *)&cfg_info + i,
 			     &offset);
 		if (ret != 1) {
@@ -830,7 +878,7 @@ static int mxt_check_reg_init(struct mxt_data *data)
 			goto release;
 		}
 
-		pos += offset;
+		data_pos += offset;
 	}
 
 	if (cfg_info.family_id != data->info.family_id) {
@@ -845,123 +893,182 @@ static int mxt_check_reg_init(struct mxt_data *data)
 		goto release;
 	}
 
-	if (cfg_info.version != data->info.version)
-		dev_err(dev, "Warning: version mismatch!\n");
-
-	if (cfg_info.build != data->info.build)
-		dev_err(dev, "Warning: build num mismatch!\n");
-
-	ret = sscanf(cfg->data + pos, "%x%n", &info_crc, &offset);
+	/* Read CRCs */
+	ret = sscanf(cfg->data + data_pos, "%x%n", &info_crc, &offset);
 	if (ret != 1) {
 		dev_err(dev, "Bad format: failed to parse Info CRC\n");
 		ret = -EINVAL;
 		goto release;
 	}
-	pos += offset;
+	data_pos += offset;
 
-	/* Check config CRC */
-	ret = sscanf(cfg->data + pos, "%x%n", &config_crc, &offset);
+	ret = sscanf(cfg->data + data_pos, "%x%n", &config_crc, &offset);
 	if (ret != 1) {
 		dev_err(dev, "Bad format: failed to parse Config CRC\n");
 		ret = -EINVAL;
 		goto release;
 	}
-	pos += offset;
+	data_pos += offset;
+
+	/* The Info Block CRC is calculated over mxt_info and the object table
+	 * If it does not match then we are trying to load the configuration
+	 * from a different chip or firmware version, so the configuration CRC
+	 * is invalid anyway. */
+	if (info_crc == data->info_crc) {
+		if (config_crc == 0 || data->config_crc == 0) {
+			dev_info(dev, "CRC zero, attempting to apply config\n");
+		} else if (config_crc == data->config_crc) {
+			dev_info(dev, "Config CRC 0x%06X: OK\n",
+				 data->config_crc);
+			ret = 0;
+			goto release;
+		} else {
+			dev_info(dev, "Config CRC 0x%06X: does not match file 0x%06X\n",
+				 data->config_crc, config_crc);
+		}
+	} else {
+		dev_warn(dev,
+			 "Warning: Info CRC error - device=0x%06X file=0x%06X\n",
+			data->info_crc, info_crc);
+	}
 
-	if (data->config_crc == config_crc) {
-		dev_info(dev, "Config CRC 0x%06X: OK\n", config_crc);
-		ret = 0;
+	/* Malloc memory to store configuration */
+	cfg_start_ofs = MXT_OBJECT_START
+		+ data->info.object_num * sizeof(struct mxt_object)
+		+ MXT_INFO_CHECKSUM_SIZE;
+	config_mem_size = data->mem_size - cfg_start_ofs;
+	config_mem = kzalloc(config_mem_size, GFP_KERNEL);
+	if (!config_mem) {
+		dev_err(dev, "Failed to allocate memory\n");
+		ret = -ENOMEM;
 		goto release;
-	} else {
-		dev_info(dev, "Config CRC 0x%06X: does not match file 0x%06X\n",
-			 data->config_crc, config_crc);
 	}
 
-	while (pos < cfg->size) {
+	while (data_pos < cfg->size) {
 		/* Read type, instance, length */
-		ret = sscanf(cfg->data + pos, "%x %x %x%n",
+		ret = sscanf(cfg->data + data_pos, "%x %x %x%n",
 			     &type, &instance, &size, &offset);
 		if (ret == 0) {
 			/* EOF */
-			ret = 1;
-			goto release;
+			break;
 		} else if (ret != 3) {
 			dev_err(dev, "Bad format: failed to parse object\n");
 			ret = -EINVAL;
-			goto release;
+			goto release_mem;
 		}
-		pos += offset;
+		data_pos += offset;
 
 		object = mxt_get_object(data, type);
 		if (!object) {
 			/* Skip object */
 			for (i = 0; i < size; i++) {
-				ret = sscanf(cfg->data + pos, "%hhx%n",
+				ret = sscanf(cfg->data + data_pos, "%hhx%n",
 					     &val,
 					     &offset);
-				pos += offset;
+				data_pos += offset;
 			}
 			continue;
 		}
 
 		if (size > mxt_obj_size(object)) {
-			dev_err(dev, "Discarding %u byte(s) in T%u\n",
-				size - mxt_obj_size(object), type);
+			/* Either we are in fallback mode due to wrong
+			 * config or config from a later fw version,
+			 * or the file is corrupt or hand-edited */
+			dev_warn(dev, "Discarding %u byte(s) in T%u\n",
+				 size - mxt_obj_size(object), type);
+		} else if (mxt_obj_size(object) > size) {
+			/* If firmware is upgraded, new bytes may be added to
+			 * end of objects. It is generally forward compatible
+			 * to zero these bytes - previous behaviour will be
+			 * retained. However this does invalidate the CRC and
+			 * will force fallback mode until the configuration is
+			 * updated. We warn here but do nothing else - the
+			 * malloc has zeroed the entire configuration. */
+			dev_warn(dev, "Zeroing %u byte(s) in T%d\n",
+				 mxt_obj_size(object) - size, type);
 		}
 
 		if (instance >= mxt_obj_instances(object)) {
 			dev_err(dev, "Object instances exceeded!\n");
 			ret = -EINVAL;
-			goto release;
+			goto release_mem;
 		}
 
 		reg = object->start_address + mxt_obj_size(object) * instance;
 
 		for (i = 0; i < size; i++) {
-			ret = sscanf(cfg->data + pos, "%hhx%n",
+			ret = sscanf(cfg->data + data_pos, "%hhx%n",
 				     &val,
 				     &offset);
 			if (ret != 1) {
 				dev_err(dev, "Bad format in T%d\n", type);
 				ret = -EINVAL;
-				goto release;
+				goto release_mem;
 			}
-			pos += offset;
+			data_pos += offset;
 
 			if (i > mxt_obj_size(object))
 				continue;
 
-			ret = mxt_write_reg(data->client, reg + i, val);
-			if (ret)
-				goto release;
+			byte_offset = reg + i - cfg_start_ofs;
 
+			if ((byte_offset >= 0)
+			    && (byte_offset <= config_mem_size)) {
+				*(config_mem + byte_offset) = val;
+			} else {
+				dev_err(dev, "Bad object: reg:%d, T%d, ofs=%d\n",
+					reg, object->type, byte_offset);
+				ret = -EINVAL;
+				goto release_mem;
+			}
 		}
+	}
 
-		/* If firmware is upgraded, new bytes may be added to end of
-		 * objects. It is generally forward compatible to zero these
-		 * bytes - previous behaviour will be retained. However
-		 * this does invalidate the CRC and will force a config
-		 * download every time until the configuration is updated */
-		if (size < mxt_obj_size(object)) {
-			dev_info(dev, "Zeroing %u byte(s) in T%d\n",
-				 mxt_obj_size(object) - size, type);
+	/* calculate crc of the received configs (not the raw config file) */
+	if (data->T7_address < cfg_start_ofs) {
+		dev_err(dev, "Bad T7 address, T7addr = %x, config offset %x\n",
+			data->T7_address, cfg_start_ofs);
+		ret = 0;
+		goto release_mem;
+	}
 
-			for (i = size + 1; i < mxt_obj_size(object); i++) {
-				ret = mxt_write_reg(data->client, reg + i, 0);
-				if (ret)
-					goto release;
-			}
+	calculated_crc = mxt_calculate_crc(config_mem,
+					   data->T7_address - cfg_start_ofs,
+					   config_mem_size);
+
+	if (config_crc > 0 && (config_crc != calculated_crc))
+		dev_warn(dev, "Config CRC error, calculated=%06X, file=%06X\n",
+			 calculated_crc, config_crc);
+
+	/* Write configuration as blocks */
+	byte_offset = 0;
+	while (byte_offset < config_mem_size) {
+		size = config_mem_size - byte_offset;
+
+		if (size > MXT_MAX_BLOCK_WRITE)
+			size = MXT_MAX_BLOCK_WRITE;
+
+		ret = __mxt_write_reg(data->client,
+				      cfg_start_ofs + byte_offset,
+				      size, config_mem + byte_offset);
+		if (ret != 0) {
+			dev_err(dev, "Config write error, ret=%d\n", ret);
+			goto release_mem;
 		}
+
+		byte_offset += size;
 	}
 
 	mxt_update_crc(data, MXT_COMMAND_BACKUPNV, MXT_BACKUP_VALUE);
 
 	ret = mxt_soft_reset(data);
 	if (ret)
-		goto release;
+		goto release_mem;
 
 	dev_info(dev, "Config written\n");
 
+release_mem:
+	kfree(config_mem);
 release:
 	release_firmware(cfg);
 	return ret;
@@ -1023,6 +1130,7 @@ static int mxt_get_object_table(struct mxt_data *data)
 	int error;
 	int i;
 	u8 reportid;
+	u16 end_address;
 
 	table_size = data->info.object_num * sizeof(struct mxt_object);
 	error = __mxt_read_reg(client, MXT_OBJECT_START, table_size,
@@ -1032,6 +1140,7 @@ static int mxt_get_object_table(struct mxt_data *data)
 
 	/* Valid Report IDs start counting from 1 */
 	reportid = 1;
+	data->mem_size = 0;
 	for (i = 0; i < data->info.object_num; i++) {
 		struct mxt_object *object = data->object_table + i;
 		u8 min_id, max_id;
@@ -1059,6 +1168,9 @@ static int mxt_get_object_table(struct mxt_data *data)
 			data->T6_reportid = min_id;
 			data->T6_address = object->start_address;
 			break;
+		case MXT_GEN_POWER_T7:
+			data->T7_address = object->start_address;
+			break;
 		case MXT_TOUCH_MULTI_T9:
 			data->T9_reportid_min = min_id;
 			data->T9_reportid_max = max_id;
@@ -1067,6 +1179,12 @@ static int mxt_get_object_table(struct mxt_data *data)
 			data->T19_reportid = min_id;
 			break;
 		}
+
+		end_address = object->start_address
+			+ mxt_obj_size(object) * mxt_obj_instances(object) - 1;
+
+		if (end_address >= data->mem_size)
+			data->mem_size = end_address + 1;
 	}
 
 	return 0;
-- 
1.7.10.4


  parent reply	other threads:[~2013-06-27 12:50 UTC|newest]

Thread overview: 87+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2013-06-27 12:48 Atmel updates to atmel_mxt_ts touch controller driver - v6 Nick Dyer
2013-06-27 12:48 ` [PATCH 01/51] Input: atmel_mxt_ts - Remove unnecessary platform data Nick Dyer
2013-06-27 12:48 ` [PATCH 02/51] Input: atmel_mxt_ts - Improve T19 GPIO keys handling Nick Dyer
2013-07-18 17:11   ` rydberg
2013-06-27 12:48 ` [PATCH 03/51] Input: atmel_mxt_ts - Return IRQ_NONE when interrupt handler fails Nick Dyer
2013-06-27 12:48 ` [PATCH 04/51] Input: atmel_mxt_ts - define helper functions for size and instances Nick Dyer
2013-06-27 12:48 ` [PATCH 05/51] Input: atmel_mxt_ts - Select FW_LOADER for firmware code Nick Dyer
2013-06-27 12:48 ` [PATCH 06/51] Input: atmel_mxt_ts - wait for CHG assert in mxt_check_bootloader Nick Dyer
2013-06-27 12:48 ` [PATCH 07/51] Input: atmel_mxt_ts - wait for CHG after bootloader resets Nick Dyer
2013-06-27 12:48 ` [PATCH 08/51] Input: atmel_mxt_ts - Initialise IRQ before probing Nick Dyer
2013-07-18 17:13   ` rydberg
2013-06-27 12:48 ` [PATCH 09/51] Input: atmel_mxt_ts - Make wait-after-reset period compatible with all chips Nick Dyer
2013-06-27 12:48 ` [PATCH 10/51] Input: atmel_mxt_ts - Improve error reporting and debug Nick Dyer
2013-06-27 12:48 ` [PATCH 11/51] Input: atmel_mxt_ts - Implement CRC check for configuration data Nick Dyer
2013-09-18 16:59   ` [11/51] " Martin Fuzzey
2013-06-27 12:48 ` [PATCH 12/51] Input: atmel_mxt_ts - Download device config using firmware loader Nick Dyer
2013-09-18 17:08   ` [12/51] " Martin Fuzzey
2013-06-27 12:48 ` Nick Dyer [this message]
2013-06-27 12:48 ` [PATCH 14/51] Input: atmel_mxt_ts - Add additional bootloader addresses Nick Dyer
2013-06-27 12:48 ` [PATCH 15/51] Input: atmel_mxt_ts - Read and report bootloader version Nick Dyer
2013-06-27 12:48 ` [PATCH 16/51] Input: atmel_mxt_ts - Implement bootloader frame retries Nick Dyer
2013-06-27 12:48 ` [PATCH 17/51] Input: atmel_mxt_ts - Improve bootloader progress output Nick Dyer
2013-06-27 12:48 ` [PATCH 18/51] Input: atmel_mxt_ts - Add check for incorrect firmware file format Nick Dyer
2013-06-27 12:48 ` [PATCH 19/51] Input: atmel_mxt_ts - Read screen config from chip Nick Dyer
2013-06-27 12:48 ` [PATCH 20/51] Input: atmel_mxt_ts - Set default irqflags when there is no pdata Nick Dyer
2013-07-18 17:17   ` rydberg
2013-09-16  2:25     ` Dmitry Torokhov
2014-05-22 14:29       ` Nick Dyer
2014-05-23 16:37         ` Yufeng Shen
2014-05-24 12:41           ` Nick Dyer
2014-05-26 18:17             ` Yufeng Shen
2014-05-26  5:23           ` Dmitry Torokhov
2014-05-26 18:13             ` Yufeng Shen
2013-06-27 12:48 ` [PATCH 21/51] Input: atmel_mxt_ts - Use deep sleep mode when stopped Nick Dyer
2013-06-27 12:48 ` [PATCH 22/51] Input: atmel_mxt_ts - Add shutdown function Nick Dyer
2013-07-07  5:29   ` Dmitry Torokhov
2013-07-08  9:56     ` Nick Dyer
2013-07-10 16:55       ` Dmitry Torokhov
2013-07-10 18:32         ` Nick Dyer
2013-06-27 12:48 ` [PATCH 23/51] Input: atmel_mxt_ts - Rename pressure to amplitude to match spec Nick Dyer
2013-06-27 12:48 ` [PATCH 24/51] Input: atmel_mxt_ts - Rename touchscreen defines to include T9 Nick Dyer
2013-06-27 12:49 ` [PATCH 25/51] Input: atmel_mxt_ts - Handle multiple input reports in one message Nick Dyer
2013-07-18 17:18   ` rydberg
2013-06-27 12:49 ` [PATCH 26/51] Input: atmel_mxt_ts - Move input device init into separate function Nick Dyer
2013-07-07  5:34   ` Dmitry Torokhov
2013-07-08  9:41     ` Nick Dyer
2013-07-10 16:53       ` Dmitry Torokhov
2013-07-18 17:20   ` rydberg
2013-06-27 12:49 ` [PATCH 27/51] Input: atmel_mxt_ts - Handle APP_CRC_FAIL on startup Nick Dyer
2013-06-27 12:49 ` [PATCH 28/51] Input: atmel_mxt_ts - Handle bootloader previously unlocked Nick Dyer
2013-06-27 12:49 ` [PATCH 29/51] Input: atmel_mxt_ts - Add bootloader addresses for new chips Nick Dyer
2013-06-27 12:49 ` [PATCH 30/51] Input: atmel_mxt_ts - Recover from bootloader on probe Nick Dyer
2013-06-27 12:49 ` [PATCH 31/51] Input: atmel_mxt_ts - Add support for dynamic message size Nick Dyer
2013-06-27 12:49 ` [PATCH 32/51] Input: atmel_mxt_ts - Decode T6 status messages Nick Dyer
2013-06-27 12:49 ` [PATCH 33/51] Input: atmel_mxt_ts - Split message handler into separate functions Nick Dyer
2013-06-27 12:49 ` [PATCH 34/51] Input: atmel_mxt_ts - Implement T44 message handling Nick Dyer
2013-06-27 12:49 ` [PATCH 35/51] Input: atmel_mxt_ts - Output status from T48 Noise Supression Nick Dyer
2013-06-27 12:49 ` [PATCH 36/51] Input: atmel_mxt_ts - Output status from T42 Touch Suppression Nick Dyer
2013-06-27 12:49 ` [PATCH 37/51] Input: atmel_mxt_ts - Implement vector/orientation support Nick Dyer
2013-07-18 17:20   ` rydberg
2013-08-15 16:18     ` Nick Dyer
2013-06-27 12:49 ` [PATCH 38/51] Input: atmel_mxt_ts - implement I2C retries Nick Dyer
2013-06-27 12:49 ` [PATCH 39/51] Input: atmel_mxt_ts - Implement T63 Active Stylus support Nick Dyer
2013-07-18 17:21   ` rydberg
2013-06-27 12:49 ` [PATCH 40/51] Input: atmel_mxt_ts - Implement support for T15 Key Array Nick Dyer
2013-06-27 12:49 ` [PATCH 41/51] Input: atmel_mxt_ts - Remove unused defines Nick Dyer
2013-06-27 12:49 ` [PATCH 42/51] Input: atmel_mxt_ts - Verify Information Block checksum on probe Nick Dyer
2013-06-27 12:49 ` [PATCH 43/51] Input: atmel_mxt_ts - Use T18 RETRIGEN to handle IRQF_TRIGGER_LOW Nick Dyer
2013-06-27 12:49 ` [PATCH 44/51] Input: atmel_mxt_ts - Handle reports from T47 Stylus object Nick Dyer
2013-07-18 17:23   ` rydberg
2013-06-27 12:49 ` [PATCH 45/51] Input: atmel_mxt_ts - Release touch state during suspend Nick Dyer
2013-07-18 17:29   ` rydberg
2013-08-15 15:52     ` Nick Dyer
2013-06-27 12:49 ` [PATCH 46/51] Input: atmel_mxt_ts - Initialize power config before and after downloading cfg Nick Dyer
2013-06-27 12:49 ` [PATCH 47/51] Input: atmel_mxt_ts - Add regulator control support Nick Dyer
2013-06-27 12:49 ` [PATCH 48/51] Input: atmel_mxt_ts - Implement support for T100 touch object Nick Dyer
2013-06-27 12:49 ` [PATCH 49/51] Input: atmel_mxt_ts - Allow specification of firmware file name Nick Dyer
2013-06-27 12:49 ` [PATCH 50/51] Input: atmel_mxt_ts - Handle cfg filename via pdata/sysfs Nick Dyer
2013-06-27 12:49 ` [PATCH 51/51] Input: atmel_mxt_ts - Only use first T9 instance Nick Dyer
2013-06-27 15:17 ` Atmel updates to atmel_mxt_ts touch controller driver - v6 Nick Dyer
2013-07-18 19:47 ` rydberg
2013-08-15 15:55   ` Nick Dyer
2013-08-15 16:07     ` Dmitry Torokhov
2013-08-19 14:24       ` Nick Dyer
2013-09-10 13:58       ` Nick Dyer
     [not found]         ` <CAPDwgkOo7FYLujk16kJM=BqmXRKvZ2S_LOYURW0cXT5t=cmi6w@mail.gmail.com>
2014-01-15 10:44           ` Nick Dyer
     [not found]             ` <CAPDwgkOsP4K7r0-Mo-U52X9knRbGgbuD4dGptmj3x-LMTN75BA@mail.gmail.com>
2014-01-17 20:01               ` Nick Dyer

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=1372337366-9286-14-git-send-email-nick.dyer@itdev.co.uk \
    --to=nick.dyer@itdev.co.uk \
    --cc=Alan.Bowens@atmel.com \
    --cc=bleung@chromium.org \
    --cc=djkurtz@chromium.org \
    --cc=dmitry.torokhov@gmail.com \
    --cc=jy0922.shim@samsung.com \
    --cc=linux-input@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=olofj@chromium.org \
    --cc=pmeerw@pmeerw.net \
    --cc=rydberg@euromail.se \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).