diff mbox

[v15,09/12] input: cyapa: add gen5 trackpad device firmware update function support

Message ID 1418624603-19054-10-git-send-email-dudley.dulixin@gmail.com (mailing list archive)
State New, archived
Headers show

Commit Message

Dudley Du Dec. 15, 2014, 6:23 a.m. UTC
Add firmware image update function supported for gen5 trackpad device,
it can be used through sysfs update_fw interface.
TEST=test on Chromebooks.

Signed-off-by: Dudley Du <dudley.dulixin@gmail.com>
---
 drivers/input/mouse/Kconfig      |   1 +
 drivers/input/mouse/cyapa_gen5.c | 292 ++++++++++++++++++++++++++++++++++++++-
 2 files changed, 292 insertions(+), 1 deletion(-)

Comments

Jeremiah Mahler Dec. 15, 2014, 2:12 p.m. UTC | #1
Dudley,

On Mon, Dec 15, 2014 at 02:23:20PM +0800, Dudley Du wrote:
> Add firmware image update function supported for gen5 trackpad device,
> it can be used through sysfs update_fw interface.
> TEST=test on Chromebooks.
> 
> Signed-off-by: Dudley Du <dudley.dulixin@gmail.com>
> ---
>  drivers/input/mouse/Kconfig      |   1 +
>  drivers/input/mouse/cyapa_gen5.c | 292 ++++++++++++++++++++++++++++++++++++++-
>  2 files changed, 292 insertions(+), 1 deletion(-)
> 
> diff --git a/drivers/input/mouse/Kconfig b/drivers/input/mouse/Kconfig
> index d8b46b0..728490e 100644
> --- a/drivers/input/mouse/Kconfig
> +++ b/drivers/input/mouse/Kconfig
> @@ -206,6 +206,7 @@ config MOUSE_BCM5974
>  config MOUSE_CYAPA
>  	tristate "Cypress APA I2C Trackpad support"
>  	depends on I2C
> +	select CRC_ITU_T
>  	help
>  	  This driver adds support for Cypress All Points Addressable (APA)
>  	  I2C Trackpads, including the ones used in 2012 Samsung Chromebooks.
> diff --git a/drivers/input/mouse/cyapa_gen5.c b/drivers/input/mouse/cyapa_gen5.c
> index 1ac264d..e89a952 100644
> --- a/drivers/input/mouse/cyapa_gen5.c
> +++ b/drivers/input/mouse/cyapa_gen5.c
> @@ -18,6 +18,7 @@
>  #include <linux/completion.h>
>  #include <linux/slab.h>
>  #include <linux/unaligned/access_ok.h>
> +#include <linux/crc-itu-t.h>
>  #include "cyapa.h"
>  
>  
> @@ -911,7 +912,87 @@ static int cyapa_gen5_state_parse(struct cyapa *cyapa, u8 *reg_data, int len)
>  	return -EAGAIN;
>  }
>  
> -bool cyapa_gen5_sort_bl_exit_data(struct cyapa *cyapa, u8 *buf, int len)
> +static int cyapa_gen5_bl_initiate(struct cyapa *cyapa,
> +		const struct firmware *fw)
> +{
> +	u16 length = 0;
> +	u16 data_len = 0;
> +	u16 meta_data_crc = 0;
> +	u16 cmd_crc = 0;
> +	u8 bl_gen5_activate[18 + CYAPA_TSG_FLASH_MAP_BLOCK_SIZE + 3];
> +	int bl_gen5_activate_size = 0;
> +	u8 resp_data[11];
> +	int resp_len;
> +	struct cyapa_tsg_bin_image *image;
> +	int records_num;
> +	u8 *data;
> +	int error;
> +
> +	/* Try to dump all buffered report data before send any command. */
                                                       any send command.

> +	cyapa_empty_pip_output_data(cyapa, NULL, NULL, NULL);
> +
> +	bl_gen5_activate_size = sizeof(bl_gen5_activate);
> +	memset(bl_gen5_activate, 0, bl_gen5_activate_size);
> +
> +	/* Output Report Register Address[15:0] = 0004h */
> +	bl_gen5_activate[0] = 0x04;
> +	bl_gen5_activate[1] = 0x00;
> +
> +	/* Total command length[15:0] */
> +	length = bl_gen5_activate_size - 2;
> +	put_unaligned_le16(length, &bl_gen5_activate[2]);
> +	bl_gen5_activate[4] = 0x40;  /* Report ID = 40h */
> +	bl_gen5_activate[5] = 0x00;  /* RSVD = 00h */
> +
> +	bl_gen5_activate[6] = GEN5_SOP_KEY;  /* SOP = 01h */
> +	bl_gen5_activate[7] = 0x48;  /* Command Code = 48h */
> +
> +	/* 8 Key bytes and block size */
> +	data_len = CYAPA_TSG_BL_KEY_SIZE + CYAPA_TSG_FLASH_MAP_BLOCK_SIZE;
> +	/* Data Length[15:0] */
> +	put_unaligned_le16(data_len, &bl_gen5_activate[8]);
> +	bl_gen5_activate[10] = 0xa5;  /* Key Byte 0 */
> +	bl_gen5_activate[11] = 0x01;
> +	bl_gen5_activate[12] = 0x02;  /*     .      */
> +	bl_gen5_activate[13] = 0x03;  /*     .      */
> +	bl_gen5_activate[14] = 0xff;  /*     .      */
> +	bl_gen5_activate[15] = 0xfe;
> +	bl_gen5_activate[16] = 0xfd;
> +	bl_gen5_activate[17] = 0x5a;  /* Key Byte 7 */
> +
I like the descriptions of what these magic values are.
I just wish there was a cleaner way to build up these buffers.

> +	/* Copy 60 bytes Meta Data Row Parameters */
> +	image = (struct cyapa_tsg_bin_image *)fw->data;
> +	records_num = (fw->size - sizeof(struct cyapa_tsg_bin_image_head)) /
> +				sizeof(struct cyapa_tsg_bin_image_data_record);
> +	/* APP_INTEGRITY row is always the last row block */
> +	data = image->records[records_num - 1].record_data;
> +	memcpy(&bl_gen5_activate[18], data, CYAPA_TSG_FLASH_MAP_METADATA_SIZE);
> +
> +	meta_data_crc = crc_itu_t(0xffff, &bl_gen5_activate[18],
> +				CYAPA_TSG_FLASH_MAP_METADATA_SIZE);
> +	/* Meta Data CRC[15:0] */
> +	put_unaligned_le16(meta_data_crc,
> +		&bl_gen5_activate[18 + CYAPA_TSG_FLASH_MAP_METADATA_SIZE]);
> +
> +	cmd_crc = crc_itu_t(0xffff, &bl_gen5_activate[6], 4 + data_len);
> +	put_unaligned_le16(cmd_crc,
> +		&bl_gen5_activate[bl_gen5_activate_size - 3]);  /* CRC[15:0] */
> +	bl_gen5_activate[bl_gen5_activate_size - 1] = GEN5_EOP_KEY;
> +
> +	resp_len = sizeof(resp_data);
> +	error = cyapa_i2c_pip_cmd_irq_sync(cyapa,
> +			bl_gen5_activate, sizeof(bl_gen5_activate),
> +			resp_data, &resp_len, 12000,
> +			cyapa_gen5_sort_tsg_pip_bl_resp_data, true);
> +	if (error || resp_len != GEN5_BL_INITIATE_RESP_LEN ||
> +			resp_data[2] != GEN5_BL_RESP_REPORT_ID ||
> +			!GEN5_CMD_COMPLETE_SUCCESS(resp_data[5]))
> +		return error ? error : -EAGAIN;
> +
> +	return 0;
> +}
> +
> +static bool cyapa_gen5_sort_bl_exit_data(struct cyapa *cyapa, u8 *buf, int len)
>  {
>  	if (buf == NULL || len < GEN5_RESP_LENGTH_SIZE)
>  		return false;
> @@ -960,6 +1041,210 @@ static int cyapa_gen5_bl_exit(struct cyapa *cyapa)
>  	return -ENODEV;
>  }
>  
> +static int cyapa_gen5_bl_enter(struct cyapa *cyapa)
> +{
> +	int error;
> +	u8 cmd[] = { 0x04, 0x00, 0x05, 0x00, 0x2F, 0x00, 0x01 };
> +	u8 resp_data[2];
> +	int resp_len;
> +
> +	error = cyapa_poll_state(cyapa, 500);
> +	if (error < 0)
> +		return error;
> +	if (cyapa->gen != CYAPA_GEN5)
> +		return -EINVAL;
> +
> +	/* Already in Gen5 BL. Skipping exit. */
> +	if (cyapa->state == CYAPA_STATE_GEN5_BL)
> +		return 0;
> +
> +	if (cyapa->state != CYAPA_STATE_GEN5_APP)
> +		return -EAGAIN;
> +
> +	/* Try to dump all buffered report data before send any command. */
> +	cyapa_empty_pip_output_data(cyapa, NULL, NULL, NULL);
> +
> +	/*
> +	 * Send bootloader enter command to trackpad device,
> +	 * after enter bootloader, the response data is two bytes of 0x00 0x00.
> +	 */
> +	resp_len = sizeof(resp_data);
> +	memset(resp_data, 0, resp_len);
> +	error = cyapa_i2c_pip_cmd_irq_sync(cyapa,
> +			cmd, sizeof(cmd),
> +			resp_data, &resp_len,
> +			5000, cyapa_gen5_sort_application_launch_data,
> +			true);
> +	if (error || resp_data[0] != 0x00 || resp_data[1] != 0x00)
> +		return error < 0 ? error : -EAGAIN;
> +
> +	cyapa->state = CYAPA_STATE_GEN5_BL;
> +	return 0;
> +}
> +
> +static int cyapa_gen5_check_fw(struct cyapa *cyapa, const struct firmware *fw)
> +{
> +	int i;
> +	struct cyapa_tsg_bin_image *image;
> +	int flash_records_count;
> +	u16 expected_app_crc;
> +	u16 expected_app_integrity_crc;
> +	u16 app_crc = 0;
> +	u16 app_integrity_crc = 0;
> +	u16 row_num;
> +	u8 *data;
> +	u32 app_start;
> +	u16 app_len;
> +	u32 img_start;
> +	u16 img_len;
> +	int record_index;
> +	struct device *dev = &cyapa->client->dev;
> +
> +	image = (struct cyapa_tsg_bin_image *)fw->data;
> +	flash_records_count = (fw->size -
> +			sizeof(struct cyapa_tsg_bin_image_head)) /
> +			sizeof(struct cyapa_tsg_bin_image_data_record);
> +
> +	/* APP_INTEGRITY row is always the last row block,
> +	 * and the row id must be 0x01ff */
> +	row_num = get_unaligned_be16(
> +			&image->records[flash_records_count - 1].row_number);
> +	if (image->records[flash_records_count - 1].flash_array_id != 0x00 &&
> +			row_num != 0x01ff) {
> +		dev_err(dev, "%s: invalid app_integrity data.\n", __func__);
> +		return -EINVAL;
> +	}
> +	data = image->records[flash_records_count - 1].record_data;
> +	app_start = get_unaligned_le32(&data[4]);
> +	app_len = get_unaligned_le16(&data[8]);
> +	expected_app_crc = get_unaligned_le16(&data[10]);
> +	img_start = get_unaligned_le32(&data[16]);
> +	img_len = get_unaligned_le16(&data[20]);
> +	expected_app_integrity_crc = get_unaligned_le16(&data[60]);

I wish there was a cleaner and more descriptive way to avoid these magic
constants.  Not sure if there is though :-)

> +
> +	if ((app_start + app_len + img_start + img_len) %
> +			CYAPA_TSG_FW_ROW_SIZE) {
> +		dev_err(dev, "%s: invalid image alignment.\n", __func__);
> +		return -EINVAL;
> +	}
> +
> +	/* Verify app_integrity crc */
> +	app_integrity_crc = crc_itu_t(0xffff, data,
> +			CYAPA_TSG_APP_INTEGRITY_SIZE);
> +	if (app_integrity_crc != expected_app_integrity_crc) {
> +		dev_err(dev, "%s: invalid app_integrity crc.\n", __func__);
> +		return -EINVAL;
> +	}
> +
> +	/*
> +	 * Verify application image CRC
> +	 */
> +	record_index = app_start / CYAPA_TSG_FW_ROW_SIZE -
> +				CYAPA_TSG_IMG_START_ROW_NUM;
> +	data = (u8 *)&image->records[record_index].record_data;
> +	app_crc = crc_itu_t(0xffff, data, CYAPA_TSG_FW_ROW_SIZE);
> +	for (i = 1; i < (app_len / CYAPA_TSG_FW_ROW_SIZE); i++) {
> +		data = (u8 *)&image->records[++record_index].record_data;
> +		app_crc = crc_itu_t(app_crc, data, CYAPA_TSG_FW_ROW_SIZE);
> +	}
> +
> +	if (app_crc != expected_app_crc) {
> +		dev_err(dev, "%s: invalid firmware app crc check.\n", __func__);
> +		return -EINVAL;
> +	}
> +
> +	return 0;
> +}
> +
> +static int cyapa_gen5_write_fw_block(struct cyapa *cyapa,
> +		struct cyapa_tsg_bin_image_data_record *flash_record)
> +{
> +	u8 flash_array_id;
> +	u16 flash_row_id;
> +	u16 record_len;
> +	u8 *record_data;
> +	u8 cmd[144];  /* 13 + 128+ 3 */
                                 ^
                               space

What does does 144 have to do with?

> +	u16 cmd_len;
> +	u16 data_len;
> +	u16 crc;
> +	u8 resp_data[11];
> +	int resp_len;
> +	int error;
> +
> +	flash_array_id = flash_record->flash_array_id;
> +	flash_row_id = get_unaligned_be16(&flash_record->row_number);
> +	record_len = get_unaligned_be16(&flash_record->record_len);
> +	record_data = flash_record->record_data;
> +
> +	cmd_len = sizeof(cmd) - 2; /* Not include 2 bytes regisetr address. */
                                                          register

                                   /* Don't include 2 byte register address. */

> +	memset(cmd, 0, cmd_len + 2);
> +	cmd[0] = 0x04;  /* Register address */
> +	cmd[1] = 0x00;
> +
> +	put_unaligned_le16(cmd_len, &cmd[2]);
> +	cmd[4] = 0x40;  /* Report id 40h */
> +	cmd[5] = 0x00;
> +
> +	cmd[6] = GEN5_SOP_KEY;  /* SOP = 01h */
> +	cmd[7] = 0x39;  /* Command code = 39h */
> +	/* 1 (Flash Array ID) + 2 (Flash Row ID) + 128 (flash data) */
> +	data_len = 3 + record_len;
> +	put_unaligned_le16(data_len, &cmd[8]);
> +	cmd[10] = flash_array_id;  /* Flash Array ID = 00h */
> +	put_unaligned_le16(flash_row_id, &cmd[11]);
> +
> +	memcpy(&cmd[13], record_data, record_len);
> +	crc = crc_itu_t(0xffff, &cmd[6], 4 + data_len);
> +	put_unaligned_le16(crc, &cmd[2 + cmd_len - 3]);
> +	cmd[2 + cmd_len - 1] = GEN5_EOP_KEY;
> +
> +	resp_len = sizeof(resp_data);
> +	error = cyapa_i2c_pip_cmd_irq_sync(cyapa,
> +			cmd, sizeof(cmd),
> +			resp_data, &resp_len,
> +			500, cyapa_gen5_sort_tsg_pip_bl_resp_data, true);
> +	if (error || resp_len != GEN5_BL_BLOCK_WRITE_RESP_LEN ||
> +			resp_data[2] != GEN5_BL_RESP_REPORT_ID ||
> +			!GEN5_CMD_COMPLETE_SUCCESS(resp_data[5]))
> +		return error < 0 ? error : -EAGAIN;
> +
> +	return 0;
> +}
> +
> +static int cyapa_gen5_do_fw_update(struct cyapa *cyapa,
> +		const struct firmware *fw)
> +{
> +	struct device *dev = &cyapa->client->dev;
> +	struct cyapa_tsg_bin_image *image =
> +		(struct cyapa_tsg_bin_image *)fw->data;
> +	struct cyapa_tsg_bin_image_data_record *flash_record;
> +	int flash_records_count;
> +	int i;
> +	int error;
> +
> +	/* Try to dump all buffered data if exists before send commands. */
> +	cyapa_empty_pip_output_data(cyapa, NULL, NULL, NULL);
> +
> +	flash_records_count =
> +		(fw->size - sizeof(struct cyapa_tsg_bin_image_head)) /
> +			sizeof(struct cyapa_tsg_bin_image_data_record);
> +	/*
> +	 * The last flash row 0x01ff has been written through bl_initiate
> +	 *  command, so DO NOT write flash 0x01ff to trackpad device.
> +	 */
> +	for (i = 0; i < (flash_records_count - 1); i++) {
> +		flash_record = &image->records[i];
> +		error = cyapa_gen5_write_fw_block(cyapa, flash_record);
> +		if (error) {
> +			dev_err(dev, "%s: Gen5 FW update aborted: %d\n",
> +				__func__, error);
> +			return error;
> +		}
> +	}
> +
> +	return 0;
> +}
> +
>  static int cyapa_gen5_change_power_state(struct cyapa *cyapa, u8 power_state)
>  {
>  	u8 cmd[8] = { 0x04, 0x00, 0x06, 0x00, 0x2f, 0x00, 0x08, 0x01 };
> @@ -1648,6 +1933,11 @@ static int cyapa_gen5_irq_handler(struct cyapa *cyapa)
>  }
>  
>  const struct cyapa_dev_ops cyapa_gen5_ops = {
> +	.check_fw = cyapa_gen5_check_fw,
> +	.bl_enter = cyapa_gen5_bl_enter,
> +	.bl_initiate = cyapa_gen5_bl_initiate,
> +	.update_fw = cyapa_gen5_do_fw_update,
> +
>  	.initialize = cyapa_gen5_initialize,
>  
>  	.state_parse = cyapa_gen5_state_parse,
> -- 
> 1.9.1
> 
> --
> To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
> Please read the FAQ at  http://www.tux.org/lkml/
Jeremiah Mahler Dec. 16, 2014, 1:56 p.m. UTC | #2
Dudley,

On Mon, Dec 15, 2014 at 02:23:20PM +0800, Dudley Du wrote:
> Add firmware image update function supported for gen5 trackpad device,
> it can be used through sysfs update_fw interface.
> TEST=test on Chromebooks.
> 
> Signed-off-by: Dudley Du <dudley.dulixin@gmail.com>
> ---
>  drivers/input/mouse/Kconfig      |   1 +
>  drivers/input/mouse/cyapa_gen5.c | 292 ++++++++++++++++++++++++++++++++++++++-
>  2 files changed, 292 insertions(+), 1 deletion(-)
> 
> diff --git a/drivers/input/mouse/Kconfig b/drivers/input/mouse/Kconfig
> index d8b46b0..728490e 100644
> --- a/drivers/input/mouse/Kconfig
> +++ b/drivers/input/mouse/Kconfig
> @@ -206,6 +206,7 @@ config MOUSE_BCM5974
>  config MOUSE_CYAPA
>  	tristate "Cypress APA I2C Trackpad support"
>  	depends on I2C
> +	select CRC_ITU_T
>  	help

Just found out that if I2C_DESIGNWARE_PCI isn't enabled the touchpad
won't work.  Verify this on your machines.  Then perhaps add a depends
for I2C_DESIGNWARE_PCI instead of I2C since it would include the former.

[...]
Benson Leung Dec. 16, 2014, 8:24 p.m. UTC | #3
On Tue, Dec 16, 2014 at 5:56 AM, Jeremiah Mahler <jmmahler@gmail.com> wrote:
> On Mon, Dec 15, 2014 at 02:23:20PM +0800, Dudley Du wrote:
>> Add firmware image update function supported for gen5 trackpad device,
>> it can be used through sysfs update_fw interface.
>> TEST=test on Chromebooks.
>>
>> Signed-off-by: Dudley Du <dudley.dulixin@gmail.com>
>> ---
>>  drivers/input/mouse/Kconfig      |   1 +
>>  drivers/input/mouse/cyapa_gen5.c | 292 ++++++++++++++++++++++++++++++++++++++-
>>  2 files changed, 292 insertions(+), 1 deletion(-)
>>
>> diff --git a/drivers/input/mouse/Kconfig b/drivers/input/mouse/Kconfig
>> index d8b46b0..728490e 100644
>> --- a/drivers/input/mouse/Kconfig
>> +++ b/drivers/input/mouse/Kconfig
>> @@ -206,6 +206,7 @@ config MOUSE_BCM5974
>>  config MOUSE_CYAPA
>>       tristate "Cypress APA I2C Trackpad support"
>>       depends on I2C
>> +     select CRC_ITU_T
>>       help
>
> Just found out that if I2C_DESIGNWARE_PCI isn't enabled the touchpad
> won't work.  Verify this on your machines.  Then perhaps add a depends
> for I2C_DESIGNWARE_PCI instead of I2C since it would include the former.

This isn't strictly true on all devices, though. This is true on
DESIGNWARE_PCI based devices like the Acer C720 and the HP Chromebook
14, but on other platforms that use Cypress trackpads, such as ARM
platforms like the Samsung Chromebook Series 3 DESIGNWARE_PCI is not
required, and will just result in a driver that's never used being
built.

The specific I2C bus that's being used here shouldn't matter here...
that's more of a platform issue. In the case with Chromebooks, it
might make sense to change drivers/platform/chrome/Kconfig so that
CHROMEOS_LAPTOP depends on I2C_DESIGNWARE_PCI, maybe.
Jeremiah Mahler Dec. 17, 2014, 6:48 a.m. UTC | #4
all,

On Tue, Dec 16, 2014 at 12:24:37PM -0800, Benson Leung wrote:
> On Tue, Dec 16, 2014 at 5:56 AM, Jeremiah Mahler <jmmahler@gmail.com> wrote:
> > On Mon, Dec 15, 2014 at 02:23:20PM +0800, Dudley Du wrote:
> >> Add firmware image update function supported for gen5 trackpad device,
> >> it can be used through sysfs update_fw interface.
> >> TEST=test on Chromebooks.
> >>
> >> Signed-off-by: Dudley Du <dudley.dulixin@gmail.com>
> >> ---
> >>  drivers/input/mouse/Kconfig      |   1 +
> >>  drivers/input/mouse/cyapa_gen5.c | 292 ++++++++++++++++++++++++++++++++++++++-
> >>  2 files changed, 292 insertions(+), 1 deletion(-)
> >>
> >> diff --git a/drivers/input/mouse/Kconfig b/drivers/input/mouse/Kconfig
> >> index d8b46b0..728490e 100644
> >> --- a/drivers/input/mouse/Kconfig
> >> +++ b/drivers/input/mouse/Kconfig
> >> @@ -206,6 +206,7 @@ config MOUSE_BCM5974
> >>  config MOUSE_CYAPA
> >>       tristate "Cypress APA I2C Trackpad support"
> >>       depends on I2C
> >> +     select CRC_ITU_T
> >>       help
> >
> > Just found out that if I2C_DESIGNWARE_PCI isn't enabled the touchpad
> > won't work.  Verify this on your machines.  Then perhaps add a depends
> > for I2C_DESIGNWARE_PCI instead of I2C since it would include the former.
> 
> This isn't strictly true on all devices, though. This is true on
> DESIGNWARE_PCI based devices like the Acer C720 and the HP Chromebook
> 14, but on other platforms that use Cypress trackpads, such as ARM
> platforms like the Samsung Chromebook Series 3 DESIGNWARE_PCI is not
> required, and will just result in a driver that's never used being
> built.
> 
> The specific I2C bus that's being used here shouldn't matter here...
> that's more of a platform issue. In the case with Chromebooks, it
> might make sense to change drivers/platform/chrome/Kconfig so that
> CHROMEOS_LAPTOP depends on I2C_DESIGNWARE_PCI, maybe.
> 

So DESIGNWARE_PCI isn't required for all devices.  But if MOUSE_CYAPA is
enabled some devices won't work unless I2C_DESIGNWARE_PCI is enabled.  I
wonder if MOUSE_CYAPA should be broken up in to separate working
configurations?  MOUSE_CYAPA_GEN5, MOUSE_CYAPA_GEN3, ?

> -- 
> Benson Leung
> Software Engineer, Chrome OS
> Google Inc.
> bleung@google.com
Benson Leung Dec. 17, 2014, 3:38 p.m. UTC | #5
On Tue, Dec 16, 2014 at 10:48 PM, Jeremiah Mahler <jmmahler@gmail.com> wrote:
> So DESIGNWARE_PCI isn't required for all devices.  But if MOUSE_CYAPA is
> enabled some devices won't work unless I2C_DESIGNWARE_PCI is enabled.  I
> wonder if MOUSE_CYAPA should be broken up in to separate working
> configurations?  MOUSE_CYAPA_GEN5, MOUSE_CYAPA_GEN3, ?

Splitting into two configs doesn't help either. Currently, all Cypress
devices available for Chromebooks are Gen 3. This includes the ARM
Samsung Chromebook which doesn't use I2C_DESIGNWARE_PCI, and the
C720/HP Chromebook 14/Dell Chromebook 11 which do require
I2C_DESIGNWARE_PCI.

The right thing to do would be to add the the I2C_DESIGNWARE_PCI
depend for the CHROMEOS_LAPTOP config. The chromeos_laptop driver
actually refers to I2C_DESIGNWARE_PCI by name, so it is appropriate
there.
diff mbox

Patch

diff --git a/drivers/input/mouse/Kconfig b/drivers/input/mouse/Kconfig
index d8b46b0..728490e 100644
--- a/drivers/input/mouse/Kconfig
+++ b/drivers/input/mouse/Kconfig
@@ -206,6 +206,7 @@  config MOUSE_BCM5974
 config MOUSE_CYAPA
 	tristate "Cypress APA I2C Trackpad support"
 	depends on I2C
+	select CRC_ITU_T
 	help
 	  This driver adds support for Cypress All Points Addressable (APA)
 	  I2C Trackpads, including the ones used in 2012 Samsung Chromebooks.
diff --git a/drivers/input/mouse/cyapa_gen5.c b/drivers/input/mouse/cyapa_gen5.c
index 1ac264d..e89a952 100644
--- a/drivers/input/mouse/cyapa_gen5.c
+++ b/drivers/input/mouse/cyapa_gen5.c
@@ -18,6 +18,7 @@ 
 #include <linux/completion.h>
 #include <linux/slab.h>
 #include <linux/unaligned/access_ok.h>
+#include <linux/crc-itu-t.h>
 #include "cyapa.h"
 
 
@@ -911,7 +912,87 @@  static int cyapa_gen5_state_parse(struct cyapa *cyapa, u8 *reg_data, int len)
 	return -EAGAIN;
 }
 
-bool cyapa_gen5_sort_bl_exit_data(struct cyapa *cyapa, u8 *buf, int len)
+static int cyapa_gen5_bl_initiate(struct cyapa *cyapa,
+		const struct firmware *fw)
+{
+	u16 length = 0;
+	u16 data_len = 0;
+	u16 meta_data_crc = 0;
+	u16 cmd_crc = 0;
+	u8 bl_gen5_activate[18 + CYAPA_TSG_FLASH_MAP_BLOCK_SIZE + 3];
+	int bl_gen5_activate_size = 0;
+	u8 resp_data[11];
+	int resp_len;
+	struct cyapa_tsg_bin_image *image;
+	int records_num;
+	u8 *data;
+	int error;
+
+	/* Try to dump all buffered report data before send any command. */
+	cyapa_empty_pip_output_data(cyapa, NULL, NULL, NULL);
+
+	bl_gen5_activate_size = sizeof(bl_gen5_activate);
+	memset(bl_gen5_activate, 0, bl_gen5_activate_size);
+
+	/* Output Report Register Address[15:0] = 0004h */
+	bl_gen5_activate[0] = 0x04;
+	bl_gen5_activate[1] = 0x00;
+
+	/* Total command length[15:0] */
+	length = bl_gen5_activate_size - 2;
+	put_unaligned_le16(length, &bl_gen5_activate[2]);
+	bl_gen5_activate[4] = 0x40;  /* Report ID = 40h */
+	bl_gen5_activate[5] = 0x00;  /* RSVD = 00h */
+
+	bl_gen5_activate[6] = GEN5_SOP_KEY;  /* SOP = 01h */
+	bl_gen5_activate[7] = 0x48;  /* Command Code = 48h */
+
+	/* 8 Key bytes and block size */
+	data_len = CYAPA_TSG_BL_KEY_SIZE + CYAPA_TSG_FLASH_MAP_BLOCK_SIZE;
+	/* Data Length[15:0] */
+	put_unaligned_le16(data_len, &bl_gen5_activate[8]);
+	bl_gen5_activate[10] = 0xa5;  /* Key Byte 0 */
+	bl_gen5_activate[11] = 0x01;
+	bl_gen5_activate[12] = 0x02;  /*     .      */
+	bl_gen5_activate[13] = 0x03;  /*     .      */
+	bl_gen5_activate[14] = 0xff;  /*     .      */
+	bl_gen5_activate[15] = 0xfe;
+	bl_gen5_activate[16] = 0xfd;
+	bl_gen5_activate[17] = 0x5a;  /* Key Byte 7 */
+
+	/* Copy 60 bytes Meta Data Row Parameters */
+	image = (struct cyapa_tsg_bin_image *)fw->data;
+	records_num = (fw->size - sizeof(struct cyapa_tsg_bin_image_head)) /
+				sizeof(struct cyapa_tsg_bin_image_data_record);
+	/* APP_INTEGRITY row is always the last row block */
+	data = image->records[records_num - 1].record_data;
+	memcpy(&bl_gen5_activate[18], data, CYAPA_TSG_FLASH_MAP_METADATA_SIZE);
+
+	meta_data_crc = crc_itu_t(0xffff, &bl_gen5_activate[18],
+				CYAPA_TSG_FLASH_MAP_METADATA_SIZE);
+	/* Meta Data CRC[15:0] */
+	put_unaligned_le16(meta_data_crc,
+		&bl_gen5_activate[18 + CYAPA_TSG_FLASH_MAP_METADATA_SIZE]);
+
+	cmd_crc = crc_itu_t(0xffff, &bl_gen5_activate[6], 4 + data_len);
+	put_unaligned_le16(cmd_crc,
+		&bl_gen5_activate[bl_gen5_activate_size - 3]);  /* CRC[15:0] */
+	bl_gen5_activate[bl_gen5_activate_size - 1] = GEN5_EOP_KEY;
+
+	resp_len = sizeof(resp_data);
+	error = cyapa_i2c_pip_cmd_irq_sync(cyapa,
+			bl_gen5_activate, sizeof(bl_gen5_activate),
+			resp_data, &resp_len, 12000,
+			cyapa_gen5_sort_tsg_pip_bl_resp_data, true);
+	if (error || resp_len != GEN5_BL_INITIATE_RESP_LEN ||
+			resp_data[2] != GEN5_BL_RESP_REPORT_ID ||
+			!GEN5_CMD_COMPLETE_SUCCESS(resp_data[5]))
+		return error ? error : -EAGAIN;
+
+	return 0;
+}
+
+static bool cyapa_gen5_sort_bl_exit_data(struct cyapa *cyapa, u8 *buf, int len)
 {
 	if (buf == NULL || len < GEN5_RESP_LENGTH_SIZE)
 		return false;
@@ -960,6 +1041,210 @@  static int cyapa_gen5_bl_exit(struct cyapa *cyapa)
 	return -ENODEV;
 }
 
+static int cyapa_gen5_bl_enter(struct cyapa *cyapa)
+{
+	int error;
+	u8 cmd[] = { 0x04, 0x00, 0x05, 0x00, 0x2F, 0x00, 0x01 };
+	u8 resp_data[2];
+	int resp_len;
+
+	error = cyapa_poll_state(cyapa, 500);
+	if (error < 0)
+		return error;
+	if (cyapa->gen != CYAPA_GEN5)
+		return -EINVAL;
+
+	/* Already in Gen5 BL. Skipping exit. */
+	if (cyapa->state == CYAPA_STATE_GEN5_BL)
+		return 0;
+
+	if (cyapa->state != CYAPA_STATE_GEN5_APP)
+		return -EAGAIN;
+
+	/* Try to dump all buffered report data before send any command. */
+	cyapa_empty_pip_output_data(cyapa, NULL, NULL, NULL);
+
+	/*
+	 * Send bootloader enter command to trackpad device,
+	 * after enter bootloader, the response data is two bytes of 0x00 0x00.
+	 */
+	resp_len = sizeof(resp_data);
+	memset(resp_data, 0, resp_len);
+	error = cyapa_i2c_pip_cmd_irq_sync(cyapa,
+			cmd, sizeof(cmd),
+			resp_data, &resp_len,
+			5000, cyapa_gen5_sort_application_launch_data,
+			true);
+	if (error || resp_data[0] != 0x00 || resp_data[1] != 0x00)
+		return error < 0 ? error : -EAGAIN;
+
+	cyapa->state = CYAPA_STATE_GEN5_BL;
+	return 0;
+}
+
+static int cyapa_gen5_check_fw(struct cyapa *cyapa, const struct firmware *fw)
+{
+	int i;
+	struct cyapa_tsg_bin_image *image;
+	int flash_records_count;
+	u16 expected_app_crc;
+	u16 expected_app_integrity_crc;
+	u16 app_crc = 0;
+	u16 app_integrity_crc = 0;
+	u16 row_num;
+	u8 *data;
+	u32 app_start;
+	u16 app_len;
+	u32 img_start;
+	u16 img_len;
+	int record_index;
+	struct device *dev = &cyapa->client->dev;
+
+	image = (struct cyapa_tsg_bin_image *)fw->data;
+	flash_records_count = (fw->size -
+			sizeof(struct cyapa_tsg_bin_image_head)) /
+			sizeof(struct cyapa_tsg_bin_image_data_record);
+
+	/* APP_INTEGRITY row is always the last row block,
+	 * and the row id must be 0x01ff */
+	row_num = get_unaligned_be16(
+			&image->records[flash_records_count - 1].row_number);
+	if (image->records[flash_records_count - 1].flash_array_id != 0x00 &&
+			row_num != 0x01ff) {
+		dev_err(dev, "%s: invalid app_integrity data.\n", __func__);
+		return -EINVAL;
+	}
+	data = image->records[flash_records_count - 1].record_data;
+	app_start = get_unaligned_le32(&data[4]);
+	app_len = get_unaligned_le16(&data[8]);
+	expected_app_crc = get_unaligned_le16(&data[10]);
+	img_start = get_unaligned_le32(&data[16]);
+	img_len = get_unaligned_le16(&data[20]);
+	expected_app_integrity_crc = get_unaligned_le16(&data[60]);
+
+	if ((app_start + app_len + img_start + img_len) %
+			CYAPA_TSG_FW_ROW_SIZE) {
+		dev_err(dev, "%s: invalid image alignment.\n", __func__);
+		return -EINVAL;
+	}
+
+	/* Verify app_integrity crc */
+	app_integrity_crc = crc_itu_t(0xffff, data,
+			CYAPA_TSG_APP_INTEGRITY_SIZE);
+	if (app_integrity_crc != expected_app_integrity_crc) {
+		dev_err(dev, "%s: invalid app_integrity crc.\n", __func__);
+		return -EINVAL;
+	}
+
+	/*
+	 * Verify application image CRC
+	 */
+	record_index = app_start / CYAPA_TSG_FW_ROW_SIZE -
+				CYAPA_TSG_IMG_START_ROW_NUM;
+	data = (u8 *)&image->records[record_index].record_data;
+	app_crc = crc_itu_t(0xffff, data, CYAPA_TSG_FW_ROW_SIZE);
+	for (i = 1; i < (app_len / CYAPA_TSG_FW_ROW_SIZE); i++) {
+		data = (u8 *)&image->records[++record_index].record_data;
+		app_crc = crc_itu_t(app_crc, data, CYAPA_TSG_FW_ROW_SIZE);
+	}
+
+	if (app_crc != expected_app_crc) {
+		dev_err(dev, "%s: invalid firmware app crc check.\n", __func__);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int cyapa_gen5_write_fw_block(struct cyapa *cyapa,
+		struct cyapa_tsg_bin_image_data_record *flash_record)
+{
+	u8 flash_array_id;
+	u16 flash_row_id;
+	u16 record_len;
+	u8 *record_data;
+	u8 cmd[144];  /* 13 + 128+ 3 */
+	u16 cmd_len;
+	u16 data_len;
+	u16 crc;
+	u8 resp_data[11];
+	int resp_len;
+	int error;
+
+	flash_array_id = flash_record->flash_array_id;
+	flash_row_id = get_unaligned_be16(&flash_record->row_number);
+	record_len = get_unaligned_be16(&flash_record->record_len);
+	record_data = flash_record->record_data;
+
+	cmd_len = sizeof(cmd) - 2; /* Not include 2 bytes regisetr address. */
+	memset(cmd, 0, cmd_len + 2);
+	cmd[0] = 0x04;  /* Register address */
+	cmd[1] = 0x00;
+
+	put_unaligned_le16(cmd_len, &cmd[2]);
+	cmd[4] = 0x40;  /* Report id 40h */
+	cmd[5] = 0x00;
+
+	cmd[6] = GEN5_SOP_KEY;  /* SOP = 01h */
+	cmd[7] = 0x39;  /* Command code = 39h */
+	/* 1 (Flash Array ID) + 2 (Flash Row ID) + 128 (flash data) */
+	data_len = 3 + record_len;
+	put_unaligned_le16(data_len, &cmd[8]);
+	cmd[10] = flash_array_id;  /* Flash Array ID = 00h */
+	put_unaligned_le16(flash_row_id, &cmd[11]);
+
+	memcpy(&cmd[13], record_data, record_len);
+	crc = crc_itu_t(0xffff, &cmd[6], 4 + data_len);
+	put_unaligned_le16(crc, &cmd[2 + cmd_len - 3]);
+	cmd[2 + cmd_len - 1] = GEN5_EOP_KEY;
+
+	resp_len = sizeof(resp_data);
+	error = cyapa_i2c_pip_cmd_irq_sync(cyapa,
+			cmd, sizeof(cmd),
+			resp_data, &resp_len,
+			500, cyapa_gen5_sort_tsg_pip_bl_resp_data, true);
+	if (error || resp_len != GEN5_BL_BLOCK_WRITE_RESP_LEN ||
+			resp_data[2] != GEN5_BL_RESP_REPORT_ID ||
+			!GEN5_CMD_COMPLETE_SUCCESS(resp_data[5]))
+		return error < 0 ? error : -EAGAIN;
+
+	return 0;
+}
+
+static int cyapa_gen5_do_fw_update(struct cyapa *cyapa,
+		const struct firmware *fw)
+{
+	struct device *dev = &cyapa->client->dev;
+	struct cyapa_tsg_bin_image *image =
+		(struct cyapa_tsg_bin_image *)fw->data;
+	struct cyapa_tsg_bin_image_data_record *flash_record;
+	int flash_records_count;
+	int i;
+	int error;
+
+	/* Try to dump all buffered data if exists before send commands. */
+	cyapa_empty_pip_output_data(cyapa, NULL, NULL, NULL);
+
+	flash_records_count =
+		(fw->size - sizeof(struct cyapa_tsg_bin_image_head)) /
+			sizeof(struct cyapa_tsg_bin_image_data_record);
+	/*
+	 * The last flash row 0x01ff has been written through bl_initiate
+	 *  command, so DO NOT write flash 0x01ff to trackpad device.
+	 */
+	for (i = 0; i < (flash_records_count - 1); i++) {
+		flash_record = &image->records[i];
+		error = cyapa_gen5_write_fw_block(cyapa, flash_record);
+		if (error) {
+			dev_err(dev, "%s: Gen5 FW update aborted: %d\n",
+				__func__, error);
+			return error;
+		}
+	}
+
+	return 0;
+}
+
 static int cyapa_gen5_change_power_state(struct cyapa *cyapa, u8 power_state)
 {
 	u8 cmd[8] = { 0x04, 0x00, 0x06, 0x00, 0x2f, 0x00, 0x08, 0x01 };
@@ -1648,6 +1933,11 @@  static int cyapa_gen5_irq_handler(struct cyapa *cyapa)
 }
 
 const struct cyapa_dev_ops cyapa_gen5_ops = {
+	.check_fw = cyapa_gen5_check_fw,
+	.bl_enter = cyapa_gen5_bl_enter,
+	.bl_initiate = cyapa_gen5_bl_initiate,
+	.update_fw = cyapa_gen5_do_fw_update,
+
 	.initialize = cyapa_gen5_initialize,
 
 	.state_parse = cyapa_gen5_state_parse,