[PATCH 10/40] Input: atmel_mxt_ts - Improve bootloader support

From: Nick Dyer
Date: Fri Feb 22 2013 - 13:12:16 EST


Add additional bootloader addresses
Read and report bootloader version
Increase fw reset time for greater reliability
Further bootloader improvements
Handle APP_CRC_FAIL on startup
Handle bootloader frame CRC failure
Recover gracefully from flash aborted halfway through

Signed-off-by: Nick Dyer <nick.dyer@xxxxxxxxxxx>
---
drivers/input/touchscreen/Kconfig | 1 +
drivers/input/touchscreen/atmel_mxt_ts.c | 318 +++++++++++++++++++++++-------
2 files changed, 244 insertions(+), 75 deletions(-)

diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig
index 9a647ee..a4f98c8 100644
--- a/drivers/input/touchscreen/Kconfig
+++ b/drivers/input/touchscreen/Kconfig
@@ -89,6 +89,7 @@ config TOUCHSCREEN_AD7879_SPI
config TOUCHSCREEN_ATMEL_MXT
tristate "Atmel mXT I2C Touchscreen"
depends on I2C
+ select FW_LOADER
help
Say Y here if you have Atmel mXT series I2C touchscreen,
such as AT42QT602240/ATMXT224, connected to your system.
diff --git a/drivers/input/touchscreen/atmel_mxt_ts.c b/drivers/input/touchscreen/atmel_mxt_ts.c
index 302c03e..16af68d 100644
--- a/drivers/input/touchscreen/atmel_mxt_ts.c
+++ b/drivers/input/touchscreen/atmel_mxt_ts.c
@@ -27,12 +27,6 @@
#define MXT_VER_21 21
#define MXT_VER_22 22

-/* Slave addresses */
-#define MXT_APP_LOW 0x4a
-#define MXT_APP_HIGH 0x4b
-#define MXT_BOOT_LOW 0x24
-#define MXT_BOOT_HIGH 0x25
-
/* Firmware files */
#define MXT_FW_NAME "maxtouch.fw"
#define MXT_CFG_NAME "maxtouch.cfg"
@@ -204,6 +198,8 @@
#define MXT_FRAME_CRC_PASS 0x04
#define MXT_APP_CRC_FAIL 0x40 /* valid 7 8 bit only */
#define MXT_BOOT_STATUS_MASK 0x3f
+#define MXT_BOOT_EXTENDED_ID (1 << 5)
+#define MXT_BOOT_ID_MASK 0x1f

/* Touch status */
#define MXT_UNGRIP (1 << 0)
@@ -249,12 +245,15 @@ struct mxt_message {
u8 message[7];
};

+enum mxt_device_state { INIT, APPMODE, BOOTLOADER, FAILED };
+
/* Each client has this additional data */
struct mxt_data {
struct i2c_client *client;
struct input_dev *input_dev;
char phys[64]; /* device physical location */
const struct mxt_platform_data *pdata;
+ enum mxt_device_state state;
struct mxt_object *object_table;
u16 mem_size;
struct mxt_info info;
@@ -265,6 +264,7 @@ struct mxt_data {
bool debug_enabled;
u32 config_crc;
u32 info_crc;
+ u8 bootloader_addr;

/* Cached parameters from object table */
u8 T6_reportid;
@@ -274,6 +274,21 @@ struct mxt_data {
u8 T9_reportid_max;
};

+/* I2C slave address pairs */
+struct mxt_i2c_address_pair {
+ u8 bootloader;
+ u8 application;
+};
+
+static const struct mxt_i2c_address_pair mxt_i2c_addresses[] = {
+ { 0x24, 0x4a },
+ { 0x25, 0x4b },
+ { 0x26, 0x4c },
+ { 0x27, 0x4d },
+ { 0x34, 0x5a },
+ { 0x35, 0x5b },
+};
+
static bool mxt_object_readable(unsigned int type)
{
switch (type) {
@@ -313,59 +328,165 @@ static void mxt_dump_message(struct device *dev, struct mxt_message *message)
sizeof(struct mxt_message), message);
}

-static int mxt_check_bootloader(struct i2c_client *client,
- unsigned int state)
+static int mxt_bootloader_read(struct mxt_data *data, u8 *val, unsigned int count)
+{
+ int ret;
+ struct i2c_msg msg;
+
+ msg.addr = data->bootloader_addr;
+ msg.flags = data->client->flags & I2C_M_TEN;
+ msg.flags |= I2C_M_RD;
+ msg.len = count;
+ msg.buf = val;
+
+ ret = i2c_transfer(data->client->adapter, &msg, 1);
+
+ return (ret == 1) ? 0 : ret;
+}
+
+static int mxt_bootloader_write(struct mxt_data *data, const u8 * const val,
+ unsigned int count)
+{
+ int ret;
+ struct i2c_msg msg;
+
+ msg.addr = data->bootloader_addr;
+ msg.flags = data->client->flags & I2C_M_TEN;
+ msg.len = count;
+ msg.buf = (u8 *)val;
+
+ ret = i2c_transfer(data->client->adapter, &msg, 1);
+
+ return (ret == 1) ? 0 : ret;
+}
+
+static int mxt_get_bootloader_address(struct mxt_data *data)
+{
+ struct i2c_client *client = data->client;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(mxt_i2c_addresses); i++) {
+ if (mxt_i2c_addresses[i].application == client->addr) {
+ data->bootloader_addr = mxt_i2c_addresses[i].bootloader;
+
+ dev_info(&client->dev, "Bootloader i2c addr: 0x%02x\n",
+ data->bootloader_addr);
+
+ return 0;
+ }
+ }
+
+ dev_err(&client->dev, "Address 0x%02x not found in address table\n",
+ client->addr);
+ return -EINVAL;
+}
+
+static int mxt_probe_bootloader(struct mxt_data *data)
{
+ struct device *dev = &data->client->dev;
+ int ret;
u8 val;
+ bool crc_failure;

-recheck:
- if (i2c_master_recv(client, &val, 1) != 1) {
- dev_err(&client->dev, "%s: i2c recv failed\n", __func__);
+ ret = mxt_get_bootloader_address(data);
+ if (ret)
+ return ret;
+
+ ret = mxt_bootloader_read(data, &val, 1);
+ if (ret) {
+ dev_err(dev, "%s: i2c recv failed\n", __func__);
return -EIO;
}

+ /* Check app crc fail mode */
+ crc_failure = (val & ~MXT_BOOT_STATUS_MASK) == MXT_APP_CRC_FAIL;
+
+ dev_err(dev, "Detected bootloader, status:%02X%s\n",
+ val, crc_failure ? ", APP_CRC_FAIL" : "");
+
+ return 0;
+}
+
+static u8 mxt_get_bootloader_version(struct mxt_data *data, u8 val)
+{
+ struct device *dev = &data->client->dev;
+ u8 buf[3];
+
+ if (val & MXT_BOOT_EXTENDED_ID) {
+ if (mxt_bootloader_read(data, &buf[0], 3) != 0) {
+ dev_err(dev, "%s: i2c failure\n", __func__);
+ return -EIO;
+ }
+
+ dev_info(dev, "Bootloader ID:%d Version:%d\n", buf[1], buf[2]);
+
+ return buf[0];
+ } else {
+ dev_info(dev, "Bootloader ID:%d\n", val & MXT_BOOT_ID_MASK);
+
+ return val;
+ }
+}
+
+static int mxt_check_bootloader(struct mxt_data *data,
+ unsigned int state)
+{
+ struct device *dev = &data->client->dev;
+ int ret;
+ u8 val;
+
+recheck:
+ ret = mxt_bootloader_read(data, &val, 1);
+ if (ret) {
+ dev_err(dev, "%s: i2c recv failed, ret=%d\n",
+ __func__, ret);
+ return ret;
+ }
+
+ if (state == MXT_WAITING_BOOTLOAD_CMD)
+ val = mxt_get_bootloader_version(data, val);
+
switch (state) {
case MXT_WAITING_BOOTLOAD_CMD:
+ val &= ~MXT_BOOT_STATUS_MASK;
+ break;
case MXT_WAITING_FRAME_DATA:
+ case MXT_APP_CRC_FAIL:
val &= ~MXT_BOOT_STATUS_MASK;
break;
case MXT_FRAME_CRC_PASS:
if (val == MXT_FRAME_CRC_CHECK)
goto recheck;
+ if (val == MXT_FRAME_CRC_FAIL) {
+ dev_err(dev, "Bootloader CRC fail\n");
+ return -EINVAL;
+ }
break;
default:
return -EINVAL;
}

if (val != state) {
- dev_err(&client->dev, "Invalid bootloader mode state\n");
+ dev_err(dev, "Invalid bootloader mode state 0x%02X\n", val);
return -EINVAL;
}

return 0;
}

-static int mxt_unlock_bootloader(struct i2c_client *client)
+static int mxt_unlock_bootloader(struct mxt_data *data)
{
+ int ret;
u8 buf[2];

buf[0] = MXT_UNLOCK_CMD_LSB;
buf[1] = MXT_UNLOCK_CMD_MSB;

- if (i2c_master_send(client, buf, 2) != 2) {
- dev_err(&client->dev, "%s: i2c send failed\n", __func__);
- return -EIO;
- }
-
- return 0;
-}
-
-static int mxt_fw_write(struct i2c_client *client,
- const u8 *data, unsigned int frame_size)
-{
- if (i2c_master_send(client, data, frame_size) != frame_size) {
- dev_err(&client->dev, "%s: i2c send failed\n", __func__);
- return -EIO;
+ ret = mxt_bootloader_write(data, buf, 2);
+ if (ret) {
+ dev_err(&data->client->dev, "%s: i2c send failed, ret=%d\n",
+ __func__, ret);
+ return ret;
}

return 0;
@@ -1060,8 +1181,18 @@ static int mxt_initialize(struct mxt_data *data)
u8 val;

error = mxt_get_info(data);
- if (error)
- return error;
+ if (error) {
+ error = mxt_probe_bootloader(data);
+
+ if (error) {
+ return error;
+ } else {
+ data->state = BOOTLOADER;
+ return 0;
+ }
+ }
+
+ data->state = APPMODE;

data->object_table = kcalloc(info->object_num,
sizeof(struct mxt_object),
@@ -1211,71 +1342,99 @@ done:
static int mxt_load_fw(struct device *dev, const char *fn)
{
struct mxt_data *data = dev_get_drvdata(dev);
- struct i2c_client *client = data->client;
const struct firmware *fw = NULL;
unsigned int frame_size;
unsigned int pos = 0;
+ unsigned int retry = 0;
+ unsigned int frame = 0;
int ret;

ret = request_firmware(&fw, fn, dev);
- if (ret) {
+ if (ret < 0) {
dev_err(dev, "Unable to open firmware %s\n", fn);
return ret;
}

- /* Change to the bootloader mode */
- ret = mxt_soft_reset(data, MXT_BOOT_VALUE);
- if (ret)
- return ret;
+ if (data->state != BOOTLOADER) {
+ /* Change to the bootloader mode */
+ ret = mxt_soft_reset(data, MXT_BOOT_VALUE);
+ if (ret)
+ goto release_firmware;

- /* Change to slave address of bootloader */
- if (client->addr == MXT_APP_LOW)
- client->addr = MXT_BOOT_LOW;
- else
- client->addr = MXT_BOOT_HIGH;
+ ret = mxt_get_bootloader_address(data);
+ if (ret)
+ goto release_firmware;

- ret = mxt_check_bootloader(client, MXT_WAITING_BOOTLOAD_CMD);
- if (ret)
- goto out;
+ data->state = BOOTLOADER;
+ }
+
+ ret = mxt_check_bootloader(data, MXT_WAITING_BOOTLOAD_CMD);
+ if (ret) {
+ /* Bootloader may still be unlocked from previous update
+ * attempt */
+ ret = mxt_check_bootloader(data, MXT_WAITING_FRAME_DATA);
+ if (ret) {
+ data->state = FAILED;
+ goto release_firmware;
+ }
+ } else {
+ dev_info(dev, "Unlocking bootloader\n");

- /* Unlock bootloader */
- mxt_unlock_bootloader(client);
+ /* Unlock bootloader */
+ ret = mxt_unlock_bootloader(data);
+ if (ret) {
+ data->state = FAILED;
+ goto release_firmware;
+ }
+ }

while (pos < fw->size) {
- ret = mxt_check_bootloader(client,
- MXT_WAITING_FRAME_DATA);
- if (ret)
- goto out;
+ ret = mxt_check_bootloader(data, MXT_WAITING_FRAME_DATA);
+ if (ret) {
+ data->state = FAILED;
+ goto release_firmware;
+ }

frame_size = ((*(fw->data + pos) << 8) | *(fw->data + pos + 1));

- /* We should add 2 at frame size as the the firmware data is not
- * included the CRC bytes.
- */
+ /* Take account of CRC bytes */
frame_size += 2;

/* Write one frame to device */
- mxt_fw_write(client, fw->data + pos, frame_size);
+ ret = mxt_bootloader_write(data, fw->data + pos, frame_size);
+ if (ret) {
+ data->state = FAILED;
+ goto release_firmware;
+ }

- ret = mxt_check_bootloader(client,
- MXT_FRAME_CRC_PASS);
- if (ret)
- goto out;
+ ret = mxt_check_bootloader(data, MXT_FRAME_CRC_PASS);
+ if (ret) {
+ retry++;

- pos += frame_size;
+ /* Back off by 20ms per retry */
+ msleep(retry * 20);

- dev_dbg(dev, "Updated %d bytes / %zd bytes\n", pos, fw->size);
+ if (retry > 20) {
+ data->state = FAILED;
+ goto release_firmware;
+ }
+ } else {
+ retry = 0;
+ pos += frame_size;
+ frame++;
+ }
+
+ if (frame % 10 == 0)
+ dev_info(dev, "Updated %d frames, %d/%zd bytes\n",
+ frame, pos, fw->size);
}

-out:
- release_firmware(fw);
+ dev_info(dev, "Finished, sent %d frames, %zd bytes\n", frame, pos);

- /* Change to slave address of application */
- if (client->addr == MXT_BOOT_LOW)
- client->addr = MXT_APP_LOW;
- else
- client->addr = MXT_APP_HIGH;
+ data->state = INIT;

+release_firmware:
+ release_firmware(fw);
return ret;
}

@@ -1293,7 +1452,7 @@ static ssize_t mxt_update_fw_store(struct device *dev,
dev_err(dev, "The firmware update failed(%d)\n", error);
count = error;
} else {
- dev_dbg(dev, "The firmware update succeeded\n");
+ dev_info(dev, "The firmware update succeeded\n");

/* Wait for reset */
msleep(MXT_FWRESET_TIME);
@@ -1303,11 +1462,13 @@ static ssize_t mxt_update_fw_store(struct device *dev,
mxt_initialize(data);
}

- enable_irq(data->irq);
+ if (data->state == APPMODE) {
+ enable_irq(data->irq);

- error = mxt_make_highchg(data);
- if (error)
- return error;
+ error = mxt_make_highchg(data);
+ if (error)
+ return error;
+ }

return count;
}
@@ -1345,6 +1506,11 @@ static ssize_t mxt_debug_enable_store(struct device *dev,
static int mxt_check_mem_access_params(struct mxt_data *data, loff_t off,
size_t *count)
{
+ if (data->state != APPMODE) {
+ dev_err(&data->client->dev, "Not in APPMODE\n");
+ return -EINVAL;
+ }
+
if (off >= data->mem_size)
return -EIO;

@@ -1462,6 +1628,8 @@ static int mxt_probe(struct i2c_client *client,
goto err_free_mem;
}

+ data->state = INIT;
+
input_dev->name = "Atmel maXTouch Touchscreen";
snprintf(data->phys, sizeof(data->phys), "i2c-%u-%04x/input0",
client->adapter->nr, client->addr);
@@ -1520,10 +1688,10 @@ static int mxt_probe(struct i2c_client *client,
goto err_free_object;
}

- error = mxt_make_highchg(data);
- if (error) {
- dev_err(&client->dev, "Error %d clearing messages\n", error);
- goto err_free_irq;
+ if (data->state == APPMODE) {
+ error = mxt_make_highchg(data);
+ if (error)
+ goto err_free_irq;
}

error = input_register_device(input_dev);
--
1.7.10.4

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/