input: atmel_mxt_ts: Support runtime selection of config data

Add support for dynamic configuration data update if the target can
support more than one Atmel chip. Platform data contains an array of
supported config data along with family ID, variant ID and firmware
version. Driver searches for matching family ID, variant ID and
firmware version, and uses the corresponding config data to program.

Change-Id: I684da4bdef56a3445acab3bb8fe47dd5279bebe5
Signed-off-by: Jing Lin <jinglin@codeaurora.org>
diff --git a/arch/arm/mach-msm/board-8930.c b/arch/arm/mach-msm/board-8930.c
index 929f280..fc91337 100644
--- a/arch/arm/mach-msm/board-8930.c
+++ b/arch/arm/mach-msm/board-8930.c
@@ -1554,9 +1554,20 @@
 	return;
 }
 
+static struct mxt_config_info mxt_config_array[] = {
+	{
+		.config			= mxt_config_data_8930,
+		.config_length		= ARRAY_SIZE(mxt_config_data_8930),
+		.family_id		= 0x81,
+		.variant_id		= 0x01,
+		.version		= 0x10,
+		.build			= 0xAA,
+	},
+};
+
 static struct mxt_platform_data mxt_platform_data_8930 = {
-	.config			= mxt_config_data_8930,
-	.config_length		= ARRAY_SIZE(mxt_config_data_8930),
+	.config_array		= mxt_config_array,
+	.config_array_size	= ARRAY_SIZE(mxt_config_array),
 	.x_size			= 1067,
 	.y_size			= 566,
 	.irqflags		= IRQF_TRIGGER_FALLING,
diff --git a/arch/arm/mach-msm/board-8960.c b/arch/arm/mach-msm/board-8960.c
index d464375..d7d8e4d 100644
--- a/arch/arm/mach-msm/board-8960.c
+++ b/arch/arm/mach-msm/board-8960.c
@@ -1397,8 +1397,8 @@
 	},
 };
 
-/* configuration data */
-static const u8 mxt_config_data[] = {
+/* configuration data for mxt1386 */
+static const u8 mxt1386_config_data[] = {
 	/* T6 Object */
 	0, 0, 0, 0, 0, 0,
 	/* T38 Object */
@@ -1472,9 +1472,20 @@
 	gpio_free(MXT_TS_LDO_EN_GPIO);
 }
 
+static struct mxt_config_info mxt_config_array[] = {
+	{
+		.config		= mxt1386_config_data,
+		.config_length	= ARRAY_SIZE(mxt1386_config_data),
+		.family_id	= 0xA0,
+		.variant_id	= 0x0,
+		.version	= 0x10,
+		.build		= 0xAA,
+	},
+};
+
 static struct mxt_platform_data mxt_platform_data = {
-	.config			= mxt_config_data,
-	.config_length		= ARRAY_SIZE(mxt_config_data),
+	.config_array		= mxt_config_array,
+	.config_array_size	= ARRAY_SIZE(mxt_config_array),
 	.x_size			= 1365,
 	.y_size			= 767,
 	.irqflags		= IRQF_TRIGGER_FALLING,
diff --git a/drivers/input/touchscreen/atmel_mxt_ts.c b/drivers/input/touchscreen/atmel_mxt_ts.c
index 2453b6b..3133de4 100644
--- a/drivers/input/touchscreen/atmel_mxt_ts.c
+++ b/drivers/input/touchscreen/atmel_mxt_ts.c
@@ -292,6 +292,7 @@
 	struct i2c_client *client;
 	struct input_dev *input_dev;
 	const struct mxt_platform_data *pdata;
+	const struct mxt_config_info *config_info;
 	struct mxt_object *object_table;
 	struct mxt_info info;
 	struct mxt_finger finger[MXT_MAX_FINGER];
@@ -312,6 +313,8 @@
 	u8 t9_min_reportid;
 	u8 t15_max_reportid;
 	u8 t15_min_reportid;
+	u8 curr_cfg_version;
+	int cfg_version_idx;
 };
 
 static bool mxt_object_readable(unsigned int type)
@@ -750,13 +753,13 @@
 
 static int mxt_check_reg_init(struct mxt_data *data)
 {
-	const struct mxt_platform_data *pdata = data->pdata;
+	const struct mxt_config_info *config_info = data->config_info;
 	struct mxt_object *object;
 	struct device *dev = &data->client->dev;
 	int index = 0;
 	int i, j, config_offset;
 
-	if (!pdata->config) {
+	if (!config_info) {
 		dev_dbg(dev, "No cfg data defined, skipping reg init\n");
 		return 0;
 	}
@@ -769,12 +772,12 @@
 
 		for (j = 0; j < object->size + 1; j++) {
 			config_offset = index + j;
-			if (config_offset > pdata->config_length) {
+			if (config_offset > config_info->config_length) {
 				dev_err(dev, "Not enough config data!\n");
 				return -EINVAL;
 			}
 			mxt_write_object(data, object->type, j,
-					 pdata->config[config_offset]);
+					 config_info->config[config_offset]);
 		}
 		index += object->size + 1;
 	}
@@ -846,6 +849,7 @@
 	u16 reg;
 	u8 reportid = 0;
 	u8 buf[MXT_OBJECT_SIZE];
+	bool found_t38 = false;
 
 	for (i = 0; i < data->info.object_num; i++) {
 		struct mxt_object *object = data->object_table + i;
@@ -866,11 +870,94 @@
 					(object->instances + 1);
 			object->max_reportid = reportid;
 		}
+
+		/* Calculate index for config major version in config array.
+		 * Major version is the first byte in object T38.
+		 */
+		if (object->type == MXT_SPT_USERDATA_T38)
+			found_t38 = true;
+		if (!found_t38 && mxt_object_writable(object->type))
+			data->cfg_version_idx += object->size + 1;
 	}
 
 	return 0;
 }
 
+static int mxt_search_config_array(struct mxt_data *data, bool version_match)
+{
+
+	const struct mxt_platform_data *pdata = data->pdata;
+	const struct mxt_config_info *cfg_info;
+	struct mxt_info *info = &data->info;
+	int i;
+	u8 cfg_version;
+
+	for (i = 0; i < pdata->config_array_size; i++) {
+
+		cfg_info = &pdata->config_array[i];
+
+		if (!cfg_info->config || !cfg_info->config_length)
+			continue;
+
+		if (info->family_id == cfg_info->family_id &&
+			info->variant_id == cfg_info->variant_id &&
+			info->version == cfg_info->version &&
+			info->build == cfg_info->build) {
+
+			cfg_version = cfg_info->config[data->cfg_version_idx];
+			if (data->curr_cfg_version == cfg_version ||
+				!version_match) {
+				data->config_info = cfg_info;
+				return 0;
+			}
+		}
+	}
+
+	dev_info(&data->client->dev,
+		"Config not found: F: %d, V: %d, FW: %d.%d.%d, CFG: %d\n",
+		info->family_id, info->variant_id,
+		info->version >> 4, info->version & 0xF, info->build,
+		data->curr_cfg_version);
+	return -EINVAL;
+}
+
+static int mxt_get_config(struct mxt_data *data)
+{
+	const struct mxt_platform_data *pdata = data->pdata;
+	struct device *dev = &data->client->dev;
+	struct mxt_object *object;
+	int error;
+
+	if (!pdata->config_array || !pdata->config_array_size) {
+		dev_dbg(dev, "No cfg data provided by platform data\n");
+		return 0;
+	}
+
+	/* Get current config version */
+	object = mxt_get_object(data, MXT_SPT_USERDATA_T38);
+	if (!object) {
+		dev_err(dev, "Unable to obtain USERDATA object\n");
+		return -EINVAL;
+	}
+
+	error = mxt_read_reg(data->client, object->start_address,
+				&data->curr_cfg_version);
+	if (error) {
+		dev_err(dev, "Unable to read config version\n");
+		return error;
+	}
+
+	/* It is possible that the config data on the controller is not
+	 * versioned and the version number returns 0. In this case,
+	 * find a match without the config version checking.
+	 */
+	error = mxt_search_config_array(data,
+				data->curr_cfg_version != 0 ? true : false);
+	if (error)
+		return error;
+
+	return 0;
+}
 static void mxt_reset_delay(struct mxt_data *data)
 {
 	struct mxt_info *info = &data->info;
@@ -919,6 +1006,11 @@
 	if (error)
 		goto free_object_table;
 
+	/* Get config data from platform data */
+	error = mxt_get_config(data);
+	if (error)
+		dev_dbg(&client->dev, "Config info not found.\n");
+
 	/* Check register init values */
 	error = mxt_check_reg_init(data);
 	if (error)
diff --git a/include/linux/i2c/atmel_mxt_ts.h b/include/linux/i2c/atmel_mxt_ts.h
index 9aa557a..7ae8342 100644
--- a/include/linux/i2c/atmel_mxt_ts.h
+++ b/include/linux/i2c/atmel_mxt_ts.h
@@ -29,10 +29,20 @@
 /* MXT_TOUCH_KEYARRAY_T15 */
 #define MXT_KEYARRAY_MAX_KEYS	32
 
-/* The platform data for the Atmel maXTouch touchscreen driver */
-struct mxt_platform_data {
+/* Config data for a given maXTouch controller with a specific firmware */
+struct mxt_config_info {
 	const u8 *config;
 	size_t config_length;
+	u8 family_id;
+	u8 variant_id;
+	u8 version;
+	u8 build;
+};
+
+/* The platform data for the Atmel maXTouch touchscreen driver */
+struct mxt_platform_data {
+	const struct mxt_config_info *config_array;
+	size_t config_array_size;
 
 	unsigned int x_size;
 	unsigned int y_size;