xref: /linux/drivers/input/touchscreen/wdt87xx_i2c.c (revision d55d0b56b9c3a0d94ca6cc1643063610c138dde9)
13e30c11cSHungNien Chen /*
23e30c11cSHungNien Chen  * Weida HiTech WDT87xx TouchScreen I2C driver
33e30c11cSHungNien Chen  *
43e30c11cSHungNien Chen  * Copyright (c) 2015  Weida Hi-Tech Co., Ltd.
53e30c11cSHungNien Chen  * HN Chen <hn.chen@weidahitech.com>
63e30c11cSHungNien Chen  *
73e30c11cSHungNien Chen  * This software is licensed under the terms of the GNU General Public
83e30c11cSHungNien Chen  * License, as published by the Free Software Foundation, and
93e30c11cSHungNien Chen  * may be copied, distributed, and modified under those terms.
103e30c11cSHungNien Chen  */
113e30c11cSHungNien Chen 
123e30c11cSHungNien Chen #include <linux/i2c.h>
133e30c11cSHungNien Chen #include <linux/input.h>
143e30c11cSHungNien Chen #include <linux/interrupt.h>
153e30c11cSHungNien Chen #include <linux/delay.h>
163e30c11cSHungNien Chen #include <linux/irq.h>
173e30c11cSHungNien Chen #include <linux/io.h>
183e30c11cSHungNien Chen #include <linux/module.h>
193e30c11cSHungNien Chen #include <linux/slab.h>
203e30c11cSHungNien Chen #include <linux/firmware.h>
213e30c11cSHungNien Chen #include <linux/input/mt.h>
223e30c11cSHungNien Chen #include <linux/acpi.h>
233e30c11cSHungNien Chen #include <asm/unaligned.h>
243e30c11cSHungNien Chen 
253e30c11cSHungNien Chen #define WDT87XX_NAME		"wdt87xx_i2c"
26*d55d0b56SHungNien Chen #define WDT87XX_DRV_VER		"0.9.6"
273e30c11cSHungNien Chen #define WDT87XX_FW_NAME		"wdt87xx_fw.bin"
283e30c11cSHungNien Chen #define WDT87XX_CFG_NAME	"wdt87xx_cfg.bin"
293e30c11cSHungNien Chen 
303e30c11cSHungNien Chen #define MODE_ACTIVE			0x01
313e30c11cSHungNien Chen #define MODE_READY			0x02
323e30c11cSHungNien Chen #define MODE_IDLE			0x03
333e30c11cSHungNien Chen #define MODE_SLEEP			0x04
343e30c11cSHungNien Chen #define MODE_STOP			0xFF
353e30c11cSHungNien Chen 
363e30c11cSHungNien Chen #define WDT_MAX_FINGER			10
373e30c11cSHungNien Chen #define WDT_RAW_BUF_COUNT		54
383e30c11cSHungNien Chen #define WDT_V1_RAW_BUF_COUNT		74
393e30c11cSHungNien Chen #define WDT_FIRMWARE_ID			0xa9e368f5
403e30c11cSHungNien Chen 
413e30c11cSHungNien Chen #define PG_SIZE				0x1000
423e30c11cSHungNien Chen #define MAX_RETRIES			3
433e30c11cSHungNien Chen 
443e30c11cSHungNien Chen #define MAX_UNIT_AXIS			0x7FFF
453e30c11cSHungNien Chen 
463e30c11cSHungNien Chen #define PKT_READ_SIZE			72
473e30c11cSHungNien Chen #define PKT_WRITE_SIZE			80
483e30c11cSHungNien Chen 
493e30c11cSHungNien Chen /* the finger definition of the report event */
503e30c11cSHungNien Chen #define FINGER_EV_OFFSET_ID		0
513e30c11cSHungNien Chen #define FINGER_EV_OFFSET_X		1
523e30c11cSHungNien Chen #define FINGER_EV_OFFSET_Y		3
533e30c11cSHungNien Chen #define FINGER_EV_SIZE			5
543e30c11cSHungNien Chen 
553e30c11cSHungNien Chen #define FINGER_EV_V1_OFFSET_ID		0
563e30c11cSHungNien Chen #define FINGER_EV_V1_OFFSET_W		1
573e30c11cSHungNien Chen #define FINGER_EV_V1_OFFSET_P		2
583e30c11cSHungNien Chen #define FINGER_EV_V1_OFFSET_X		3
593e30c11cSHungNien Chen #define FINGER_EV_V1_OFFSET_Y		5
603e30c11cSHungNien Chen #define FINGER_EV_V1_SIZE		7
613e30c11cSHungNien Chen 
623e30c11cSHungNien Chen /* The definition of a report packet */
633e30c11cSHungNien Chen #define TOUCH_PK_OFFSET_REPORT_ID	0
643e30c11cSHungNien Chen #define TOUCH_PK_OFFSET_EVENT		1
653e30c11cSHungNien Chen #define TOUCH_PK_OFFSET_SCAN_TIME	51
663e30c11cSHungNien Chen #define TOUCH_PK_OFFSET_FNGR_NUM	53
673e30c11cSHungNien Chen 
683e30c11cSHungNien Chen #define TOUCH_PK_V1_OFFSET_REPORT_ID	0
693e30c11cSHungNien Chen #define TOUCH_PK_V1_OFFSET_EVENT	1
703e30c11cSHungNien Chen #define TOUCH_PK_V1_OFFSET_SCAN_TIME	71
713e30c11cSHungNien Chen #define TOUCH_PK_V1_OFFSET_FNGR_NUM	73
723e30c11cSHungNien Chen 
733e30c11cSHungNien Chen /* The definition of the controller parameters */
743e30c11cSHungNien Chen #define CTL_PARAM_OFFSET_FW_ID		0
753e30c11cSHungNien Chen #define CTL_PARAM_OFFSET_PLAT_ID	2
763e30c11cSHungNien Chen #define CTL_PARAM_OFFSET_XMLS_ID1	4
773e30c11cSHungNien Chen #define CTL_PARAM_OFFSET_XMLS_ID2	6
783e30c11cSHungNien Chen #define CTL_PARAM_OFFSET_PHY_CH_X	8
793e30c11cSHungNien Chen #define CTL_PARAM_OFFSET_PHY_CH_Y	10
803e30c11cSHungNien Chen #define CTL_PARAM_OFFSET_PHY_X0		12
813e30c11cSHungNien Chen #define CTL_PARAM_OFFSET_PHY_X1		14
823e30c11cSHungNien Chen #define CTL_PARAM_OFFSET_PHY_Y0		16
833e30c11cSHungNien Chen #define CTL_PARAM_OFFSET_PHY_Y1		18
843e30c11cSHungNien Chen #define CTL_PARAM_OFFSET_PHY_W		22
853e30c11cSHungNien Chen #define CTL_PARAM_OFFSET_PHY_H		24
86*d55d0b56SHungNien Chen #define CTL_PARAM_OFFSET_FACTOR		32
873e30c11cSHungNien Chen 
883e30c11cSHungNien Chen /* Communication commands */
893e30c11cSHungNien Chen #define PACKET_SIZE			56
903e30c11cSHungNien Chen #define VND_REQ_READ			0x06
913e30c11cSHungNien Chen #define VND_READ_DATA			0x07
923e30c11cSHungNien Chen #define VND_REQ_WRITE			0x08
933e30c11cSHungNien Chen 
943e30c11cSHungNien Chen #define VND_CMD_START			0x00
953e30c11cSHungNien Chen #define VND_CMD_STOP			0x01
963e30c11cSHungNien Chen #define VND_CMD_RESET			0x09
973e30c11cSHungNien Chen 
983e30c11cSHungNien Chen #define VND_CMD_ERASE			0x1A
993e30c11cSHungNien Chen 
1003e30c11cSHungNien Chen #define VND_GET_CHECKSUM		0x66
1013e30c11cSHungNien Chen 
1023e30c11cSHungNien Chen #define VND_SET_DATA			0x83
1033e30c11cSHungNien Chen #define VND_SET_COMMAND_DATA		0x84
1043e30c11cSHungNien Chen #define VND_SET_CHECKSUM_CALC		0x86
1053e30c11cSHungNien Chen #define VND_SET_CHECKSUM_LENGTH		0x87
1063e30c11cSHungNien Chen 
1073e30c11cSHungNien Chen #define VND_CMD_SFLCK			0xFC
1083e30c11cSHungNien Chen #define VND_CMD_SFUNL			0xFD
1093e30c11cSHungNien Chen 
1103e30c11cSHungNien Chen #define CMD_SFLCK_KEY			0xC39B
1113e30c11cSHungNien Chen #define CMD_SFUNL_KEY			0x95DA
1123e30c11cSHungNien Chen 
1133e30c11cSHungNien Chen #define STRIDX_PLATFORM_ID		0x80
1143e30c11cSHungNien Chen #define STRIDX_PARAMETERS		0x81
1153e30c11cSHungNien Chen 
1163e30c11cSHungNien Chen #define CMD_BUF_SIZE			8
1173e30c11cSHungNien Chen #define PKT_BUF_SIZE			64
1183e30c11cSHungNien Chen 
1193e30c11cSHungNien Chen /* The definition of the command packet */
1203e30c11cSHungNien Chen #define CMD_REPORT_ID_OFFSET		0x0
1213e30c11cSHungNien Chen #define CMD_TYPE_OFFSET			0x1
1223e30c11cSHungNien Chen #define CMD_INDEX_OFFSET		0x2
1233e30c11cSHungNien Chen #define CMD_KEY_OFFSET			0x3
1243e30c11cSHungNien Chen #define CMD_LENGTH_OFFSET		0x4
1253e30c11cSHungNien Chen #define CMD_DATA_OFFSET			0x8
1263e30c11cSHungNien Chen 
1273e30c11cSHungNien Chen /* The definition of firmware chunk tags */
1283e30c11cSHungNien Chen #define FOURCC_ID_RIFF			0x46464952
1293e30c11cSHungNien Chen #define FOURCC_ID_WHIF			0x46494857
1303e30c11cSHungNien Chen #define FOURCC_ID_FRMT			0x544D5246
1313e30c11cSHungNien Chen #define FOURCC_ID_FRWR			0x52575246
1323e30c11cSHungNien Chen #define FOURCC_ID_CNFG			0x47464E43
1333e30c11cSHungNien Chen 
1343e30c11cSHungNien Chen #define CHUNK_ID_FRMT			FOURCC_ID_FRMT
1353e30c11cSHungNien Chen #define CHUNK_ID_FRWR			FOURCC_ID_FRWR
1363e30c11cSHungNien Chen #define CHUNK_ID_CNFG			FOURCC_ID_CNFG
1373e30c11cSHungNien Chen 
1383e30c11cSHungNien Chen #define FW_FOURCC1_OFFSET		0
1393e30c11cSHungNien Chen #define FW_SIZE_OFFSET			4
1403e30c11cSHungNien Chen #define FW_FOURCC2_OFFSET		8
1413e30c11cSHungNien Chen #define FW_PAYLOAD_OFFSET		40
1423e30c11cSHungNien Chen 
1433e30c11cSHungNien Chen #define FW_CHUNK_ID_OFFSET		0
1443e30c11cSHungNien Chen #define FW_CHUNK_SIZE_OFFSET		4
1453e30c11cSHungNien Chen #define FW_CHUNK_TGT_START_OFFSET	8
1463e30c11cSHungNien Chen #define FW_CHUNK_PAYLOAD_LEN_OFFSET	12
1473e30c11cSHungNien Chen #define FW_CHUNK_SRC_START_OFFSET	16
1483e30c11cSHungNien Chen #define FW_CHUNK_VERSION_OFFSET		20
1493e30c11cSHungNien Chen #define FW_CHUNK_ATTR_OFFSET		24
1503e30c11cSHungNien Chen #define FW_CHUNK_PAYLOAD_OFFSET		32
1513e30c11cSHungNien Chen 
1523e30c11cSHungNien Chen /* Controller requires minimum 300us between commands */
1533e30c11cSHungNien Chen #define WDT_COMMAND_DELAY_MS		2
1543e30c11cSHungNien Chen #define WDT_FLASH_WRITE_DELAY_MS	4
1553e30c11cSHungNien Chen 
1563e30c11cSHungNien Chen struct wdt87xx_sys_param {
1573e30c11cSHungNien Chen 	u16	fw_id;
1583e30c11cSHungNien Chen 	u16	plat_id;
1593e30c11cSHungNien Chen 	u16	xmls_id1;
1603e30c11cSHungNien Chen 	u16	xmls_id2;
1613e30c11cSHungNien Chen 	u16	phy_ch_x;
1623e30c11cSHungNien Chen 	u16	phy_ch_y;
1633e30c11cSHungNien Chen 	u16	phy_w;
1643e30c11cSHungNien Chen 	u16	phy_h;
165*d55d0b56SHungNien Chen 	u16	scaling_factor;
1663e30c11cSHungNien Chen 	u32	max_x;
1673e30c11cSHungNien Chen 	u32	max_y;
1683e30c11cSHungNien Chen };
1693e30c11cSHungNien Chen 
1703e30c11cSHungNien Chen struct wdt87xx_data {
1713e30c11cSHungNien Chen 	struct i2c_client		*client;
1723e30c11cSHungNien Chen 	struct input_dev		*input;
1733e30c11cSHungNien Chen 	/* Mutex for fw update to prevent concurrent access */
1743e30c11cSHungNien Chen 	struct mutex			fw_mutex;
1753e30c11cSHungNien Chen 	struct wdt87xx_sys_param	param;
1763e30c11cSHungNien Chen 	u8				phys[32];
1773e30c11cSHungNien Chen };
1783e30c11cSHungNien Chen 
1793e30c11cSHungNien Chen static int wdt87xx_i2c_xfer(struct i2c_client *client,
1803e30c11cSHungNien Chen 			    void *txdata, size_t txlen,
1813e30c11cSHungNien Chen 			    void *rxdata, size_t rxlen)
1823e30c11cSHungNien Chen {
1833e30c11cSHungNien Chen 	struct i2c_msg msgs[] = {
1843e30c11cSHungNien Chen 		{
1853e30c11cSHungNien Chen 			.addr	= client->addr,
1863e30c11cSHungNien Chen 			.flags	= 0,
1873e30c11cSHungNien Chen 			.len	= txlen,
1883e30c11cSHungNien Chen 			.buf	= txdata,
1893e30c11cSHungNien Chen 		},
1903e30c11cSHungNien Chen 		{
1913e30c11cSHungNien Chen 			.addr	= client->addr,
1923e30c11cSHungNien Chen 			.flags	= I2C_M_RD,
1933e30c11cSHungNien Chen 			.len	= rxlen,
1943e30c11cSHungNien Chen 			.buf	= rxdata,
1953e30c11cSHungNien Chen 		},
1963e30c11cSHungNien Chen 	};
1973e30c11cSHungNien Chen 	int error;
1983e30c11cSHungNien Chen 	int ret;
1993e30c11cSHungNien Chen 
2003e30c11cSHungNien Chen 	ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
2013e30c11cSHungNien Chen 	if (ret != ARRAY_SIZE(msgs)) {
2023e30c11cSHungNien Chen 		error = ret < 0 ? ret : -EIO;
2033e30c11cSHungNien Chen 		dev_err(&client->dev, "%s: i2c transfer failed: %d\n",
2043e30c11cSHungNien Chen 			__func__, error);
2053e30c11cSHungNien Chen 		return error;
2063e30c11cSHungNien Chen 	}
2073e30c11cSHungNien Chen 
2083e30c11cSHungNien Chen 	return 0;
2093e30c11cSHungNien Chen }
2103e30c11cSHungNien Chen 
2113e30c11cSHungNien Chen static int wdt87xx_get_string(struct i2c_client *client, u8 str_idx,
2123e30c11cSHungNien Chen 			      u8 *buf, size_t len)
2133e30c11cSHungNien Chen {
2143e30c11cSHungNien Chen 	u8 tx_buf[] = { 0x22, 0x00, 0x13, 0x0E, str_idx, 0x23, 0x00 };
2153e30c11cSHungNien Chen 	u8 rx_buf[PKT_WRITE_SIZE];
2163e30c11cSHungNien Chen 	size_t rx_len = len + 2;
2173e30c11cSHungNien Chen 	int error;
2183e30c11cSHungNien Chen 
2193e30c11cSHungNien Chen 	if (rx_len > sizeof(rx_buf))
2203e30c11cSHungNien Chen 		return -EINVAL;
2213e30c11cSHungNien Chen 
2223e30c11cSHungNien Chen 	error = wdt87xx_i2c_xfer(client, tx_buf, sizeof(tx_buf),
2233e30c11cSHungNien Chen 				 rx_buf, rx_len);
2243e30c11cSHungNien Chen 	if (error) {
2253e30c11cSHungNien Chen 		dev_err(&client->dev, "get string failed: %d\n", error);
2263e30c11cSHungNien Chen 		return error;
2273e30c11cSHungNien Chen 	}
2283e30c11cSHungNien Chen 
2293e30c11cSHungNien Chen 	if (rx_buf[1] != 0x03) {
2303e30c11cSHungNien Chen 		dev_err(&client->dev, "unexpected response to get string: %d\n",
2313e30c11cSHungNien Chen 			rx_buf[1]);
2323e30c11cSHungNien Chen 		return -EINVAL;
2333e30c11cSHungNien Chen 	}
2343e30c11cSHungNien Chen 
2353e30c11cSHungNien Chen 	rx_len = min_t(size_t, len, rx_buf[0]);
2363e30c11cSHungNien Chen 	memcpy(buf, &rx_buf[2], rx_len);
2373e30c11cSHungNien Chen 
2383e30c11cSHungNien Chen 	mdelay(WDT_COMMAND_DELAY_MS);
2393e30c11cSHungNien Chen 
2403e30c11cSHungNien Chen 	return 0;
2413e30c11cSHungNien Chen }
2423e30c11cSHungNien Chen 
2433e30c11cSHungNien Chen static int wdt87xx_get_feature(struct i2c_client *client,
2443e30c11cSHungNien Chen 			       u8 *buf, size_t buf_size)
2453e30c11cSHungNien Chen {
2463e30c11cSHungNien Chen 	u8 tx_buf[8];
2473e30c11cSHungNien Chen 	u8 rx_buf[PKT_WRITE_SIZE];
2483e30c11cSHungNien Chen 	size_t tx_len = 0;
2493e30c11cSHungNien Chen 	size_t rx_len = buf_size + 2;
2503e30c11cSHungNien Chen 	int error;
2513e30c11cSHungNien Chen 
2523e30c11cSHungNien Chen 	if (rx_len > sizeof(rx_buf))
2533e30c11cSHungNien Chen 		return -EINVAL;
2543e30c11cSHungNien Chen 
2553e30c11cSHungNien Chen 	/* Get feature command packet */
2563e30c11cSHungNien Chen 	tx_buf[tx_len++] = 0x22;
2573e30c11cSHungNien Chen 	tx_buf[tx_len++] = 0x00;
2583e30c11cSHungNien Chen 	if (buf[CMD_REPORT_ID_OFFSET] > 0xF) {
2593e30c11cSHungNien Chen 		tx_buf[tx_len++] = 0x30;
2603e30c11cSHungNien Chen 		tx_buf[tx_len++] = 0x02;
2613e30c11cSHungNien Chen 		tx_buf[tx_len++] = buf[CMD_REPORT_ID_OFFSET];
2623e30c11cSHungNien Chen 	} else {
2633e30c11cSHungNien Chen 		tx_buf[tx_len++] = 0x30 | buf[CMD_REPORT_ID_OFFSET];
2643e30c11cSHungNien Chen 		tx_buf[tx_len++] = 0x02;
2653e30c11cSHungNien Chen 	}
2663e30c11cSHungNien Chen 	tx_buf[tx_len++] = 0x23;
2673e30c11cSHungNien Chen 	tx_buf[tx_len++] = 0x00;
2683e30c11cSHungNien Chen 
2693e30c11cSHungNien Chen 	error = wdt87xx_i2c_xfer(client, tx_buf, tx_len, rx_buf, rx_len);
2703e30c11cSHungNien Chen 	if (error) {
2713e30c11cSHungNien Chen 		dev_err(&client->dev, "get feature failed: %d\n", error);
2723e30c11cSHungNien Chen 		return error;
2733e30c11cSHungNien Chen 	}
2743e30c11cSHungNien Chen 
2753e30c11cSHungNien Chen 	rx_len = min_t(size_t, buf_size, get_unaligned_le16(rx_buf));
2763e30c11cSHungNien Chen 	memcpy(buf, &rx_buf[2], rx_len);
2773e30c11cSHungNien Chen 
2783e30c11cSHungNien Chen 	mdelay(WDT_COMMAND_DELAY_MS);
2793e30c11cSHungNien Chen 
2803e30c11cSHungNien Chen 	return 0;
2813e30c11cSHungNien Chen }
2823e30c11cSHungNien Chen 
2833e30c11cSHungNien Chen static int wdt87xx_set_feature(struct i2c_client *client,
2843e30c11cSHungNien Chen 			       const u8 *buf, size_t buf_size)
2853e30c11cSHungNien Chen {
2863e30c11cSHungNien Chen 	u8 tx_buf[PKT_WRITE_SIZE];
2873e30c11cSHungNien Chen 	int tx_len = 0;
2883e30c11cSHungNien Chen 	int error;
2893e30c11cSHungNien Chen 
2903e30c11cSHungNien Chen 	/* Set feature command packet */
2913e30c11cSHungNien Chen 	tx_buf[tx_len++] = 0x22;
2923e30c11cSHungNien Chen 	tx_buf[tx_len++] = 0x00;
2933e30c11cSHungNien Chen 	if (buf[CMD_REPORT_ID_OFFSET] > 0xF) {
2943e30c11cSHungNien Chen 		tx_buf[tx_len++] = 0x30;
2953e30c11cSHungNien Chen 		tx_buf[tx_len++] = 0x03;
2963e30c11cSHungNien Chen 		tx_buf[tx_len++] = buf[CMD_REPORT_ID_OFFSET];
2973e30c11cSHungNien Chen 	} else {
2983e30c11cSHungNien Chen 		tx_buf[tx_len++] = 0x30 | buf[CMD_REPORT_ID_OFFSET];
2993e30c11cSHungNien Chen 		tx_buf[tx_len++] = 0x03;
3003e30c11cSHungNien Chen 	}
3013e30c11cSHungNien Chen 	tx_buf[tx_len++] = 0x23;
3023e30c11cSHungNien Chen 	tx_buf[tx_len++] = 0x00;
3033e30c11cSHungNien Chen 	tx_buf[tx_len++] = (buf_size & 0xFF);
3043e30c11cSHungNien Chen 	tx_buf[tx_len++] = ((buf_size & 0xFF00) >> 8);
3053e30c11cSHungNien Chen 
3063e30c11cSHungNien Chen 	if (tx_len + buf_size > sizeof(tx_buf))
3073e30c11cSHungNien Chen 		return -EINVAL;
3083e30c11cSHungNien Chen 
3093e30c11cSHungNien Chen 	memcpy(&tx_buf[tx_len], buf, buf_size);
3103e30c11cSHungNien Chen 	tx_len += buf_size;
3113e30c11cSHungNien Chen 
3123e30c11cSHungNien Chen 	error = i2c_master_send(client, tx_buf, tx_len);
3133e30c11cSHungNien Chen 	if (error < 0) {
3143e30c11cSHungNien Chen 		dev_err(&client->dev, "set feature failed: %d\n", error);
3153e30c11cSHungNien Chen 		return error;
3163e30c11cSHungNien Chen 	}
3173e30c11cSHungNien Chen 
3183e30c11cSHungNien Chen 	mdelay(WDT_COMMAND_DELAY_MS);
3193e30c11cSHungNien Chen 
3203e30c11cSHungNien Chen 	return 0;
3213e30c11cSHungNien Chen }
3223e30c11cSHungNien Chen 
3233e30c11cSHungNien Chen static int wdt87xx_send_command(struct i2c_client *client, int cmd, int value)
3243e30c11cSHungNien Chen {
3253e30c11cSHungNien Chen 	u8 cmd_buf[CMD_BUF_SIZE];
3263e30c11cSHungNien Chen 
3273e30c11cSHungNien Chen 	/* Set the command packet */
3283e30c11cSHungNien Chen 	cmd_buf[CMD_REPORT_ID_OFFSET] = VND_REQ_WRITE;
3293e30c11cSHungNien Chen 	cmd_buf[CMD_TYPE_OFFSET] = VND_SET_COMMAND_DATA;
3303e30c11cSHungNien Chen 	put_unaligned_le16((u16)cmd, &cmd_buf[CMD_INDEX_OFFSET]);
3313e30c11cSHungNien Chen 
3323e30c11cSHungNien Chen 	switch (cmd) {
3333e30c11cSHungNien Chen 	case VND_CMD_START:
3343e30c11cSHungNien Chen 	case VND_CMD_STOP:
3353e30c11cSHungNien Chen 	case VND_CMD_RESET:
3363e30c11cSHungNien Chen 		/* Mode selector */
3373e30c11cSHungNien Chen 		put_unaligned_le32((value & 0xFF), &cmd_buf[CMD_LENGTH_OFFSET]);
3383e30c11cSHungNien Chen 		break;
3393e30c11cSHungNien Chen 
3403e30c11cSHungNien Chen 	case VND_CMD_SFLCK:
3413e30c11cSHungNien Chen 		put_unaligned_le16(CMD_SFLCK_KEY, &cmd_buf[CMD_KEY_OFFSET]);
3423e30c11cSHungNien Chen 		break;
3433e30c11cSHungNien Chen 
3443e30c11cSHungNien Chen 	case VND_CMD_SFUNL:
3453e30c11cSHungNien Chen 		put_unaligned_le16(CMD_SFUNL_KEY, &cmd_buf[CMD_KEY_OFFSET]);
3463e30c11cSHungNien Chen 		break;
3473e30c11cSHungNien Chen 
3483e30c11cSHungNien Chen 	case VND_CMD_ERASE:
3493e30c11cSHungNien Chen 	case VND_SET_CHECKSUM_CALC:
3503e30c11cSHungNien Chen 	case VND_SET_CHECKSUM_LENGTH:
3513e30c11cSHungNien Chen 		put_unaligned_le32(value, &cmd_buf[CMD_KEY_OFFSET]);
3523e30c11cSHungNien Chen 		break;
3533e30c11cSHungNien Chen 
3543e30c11cSHungNien Chen 	default:
3553e30c11cSHungNien Chen 		cmd_buf[CMD_REPORT_ID_OFFSET] = 0;
3563e30c11cSHungNien Chen 		dev_err(&client->dev, "Invalid command: %d\n", cmd);
3573e30c11cSHungNien Chen 		return -EINVAL;
3583e30c11cSHungNien Chen 	}
3593e30c11cSHungNien Chen 
3603e30c11cSHungNien Chen 	return wdt87xx_set_feature(client, cmd_buf, sizeof(cmd_buf));
3613e30c11cSHungNien Chen }
3623e30c11cSHungNien Chen 
3633e30c11cSHungNien Chen static int wdt87xx_sw_reset(struct i2c_client *client)
3643e30c11cSHungNien Chen {
3653e30c11cSHungNien Chen 	int error;
3663e30c11cSHungNien Chen 
3673e30c11cSHungNien Chen 	dev_dbg(&client->dev, "resetting device now\n");
3683e30c11cSHungNien Chen 
3693e30c11cSHungNien Chen 	error = wdt87xx_send_command(client, VND_CMD_RESET, 0);
3703e30c11cSHungNien Chen 	if (error) {
3713e30c11cSHungNien Chen 		dev_err(&client->dev, "reset failed\n");
3723e30c11cSHungNien Chen 		return error;
3733e30c11cSHungNien Chen 	}
3743e30c11cSHungNien Chen 
3753e30c11cSHungNien Chen 	/* Wait the device to be ready */
3763e30c11cSHungNien Chen 	msleep(200);
3773e30c11cSHungNien Chen 
3783e30c11cSHungNien Chen 	return 0;
3793e30c11cSHungNien Chen }
3803e30c11cSHungNien Chen 
3813e30c11cSHungNien Chen static const void *wdt87xx_get_fw_chunk(const struct firmware *fw, u32 id)
3823e30c11cSHungNien Chen {
3833e30c11cSHungNien Chen 	size_t pos = FW_PAYLOAD_OFFSET;
3843e30c11cSHungNien Chen 	u32 chunk_id, chunk_size;
3853e30c11cSHungNien Chen 
3863e30c11cSHungNien Chen 	while (pos < fw->size) {
3873e30c11cSHungNien Chen 		chunk_id = get_unaligned_le32(fw->data +
3883e30c11cSHungNien Chen 					      pos + FW_CHUNK_ID_OFFSET);
3893e30c11cSHungNien Chen 		if (chunk_id == id)
3903e30c11cSHungNien Chen 			return fw->data + pos;
3913e30c11cSHungNien Chen 
3923e30c11cSHungNien Chen 		chunk_size = get_unaligned_le32(fw->data +
3933e30c11cSHungNien Chen 						pos + FW_CHUNK_SIZE_OFFSET);
3943e30c11cSHungNien Chen 		pos += chunk_size + 2 * sizeof(u32); /* chunk ID + size */
3953e30c11cSHungNien Chen 	}
3963e30c11cSHungNien Chen 
3973e30c11cSHungNien Chen 	return NULL;
3983e30c11cSHungNien Chen }
3993e30c11cSHungNien Chen 
4003e30c11cSHungNien Chen static int wdt87xx_get_sysparam(struct i2c_client *client,
4013e30c11cSHungNien Chen 				struct wdt87xx_sys_param *param)
4023e30c11cSHungNien Chen {
4033e30c11cSHungNien Chen 	u8 buf[PKT_READ_SIZE];
4043e30c11cSHungNien Chen 	int error;
4053e30c11cSHungNien Chen 
406*d55d0b56SHungNien Chen 	error = wdt87xx_get_string(client, STRIDX_PARAMETERS, buf, 34);
4073e30c11cSHungNien Chen 	if (error) {
4083e30c11cSHungNien Chen 		dev_err(&client->dev, "failed to get parameters\n");
4093e30c11cSHungNien Chen 		return error;
4103e30c11cSHungNien Chen 	}
4113e30c11cSHungNien Chen 
4123e30c11cSHungNien Chen 	param->xmls_id1 = get_unaligned_le16(buf + CTL_PARAM_OFFSET_XMLS_ID1);
4133e30c11cSHungNien Chen 	param->xmls_id2 = get_unaligned_le16(buf + CTL_PARAM_OFFSET_XMLS_ID2);
4143e30c11cSHungNien Chen 	param->phy_ch_x = get_unaligned_le16(buf + CTL_PARAM_OFFSET_PHY_CH_X);
4153e30c11cSHungNien Chen 	param->phy_ch_y = get_unaligned_le16(buf + CTL_PARAM_OFFSET_PHY_CH_Y);
4163e30c11cSHungNien Chen 	param->phy_w = get_unaligned_le16(buf + CTL_PARAM_OFFSET_PHY_W) / 10;
4173e30c11cSHungNien Chen 	param->phy_h = get_unaligned_le16(buf + CTL_PARAM_OFFSET_PHY_H) / 10;
4183e30c11cSHungNien Chen 
419*d55d0b56SHungNien Chen 	/* Get the scaling factor of pixel to logical coordinate */
420*d55d0b56SHungNien Chen 	param->scaling_factor =
421*d55d0b56SHungNien Chen 			get_unaligned_le16(buf + CTL_PARAM_OFFSET_FACTOR);
422*d55d0b56SHungNien Chen 
4233e30c11cSHungNien Chen 	param->max_x = MAX_UNIT_AXIS;
4243e30c11cSHungNien Chen 	param->max_y = DIV_ROUND_CLOSEST(MAX_UNIT_AXIS * param->phy_h,
4253e30c11cSHungNien Chen 					 param->phy_w);
4263e30c11cSHungNien Chen 
4273e30c11cSHungNien Chen 	error = wdt87xx_get_string(client, STRIDX_PLATFORM_ID, buf, 8);
4283e30c11cSHungNien Chen 	if (error) {
4293e30c11cSHungNien Chen 		dev_err(&client->dev, "failed to get platform id\n");
4303e30c11cSHungNien Chen 		return error;
4313e30c11cSHungNien Chen 	}
4323e30c11cSHungNien Chen 
4333e30c11cSHungNien Chen 	param->plat_id = buf[1];
4343e30c11cSHungNien Chen 
4353e30c11cSHungNien Chen 	buf[0] = 0xf2;
4363e30c11cSHungNien Chen 	error = wdt87xx_get_feature(client, buf, 16);
4373e30c11cSHungNien Chen 	if (error) {
4383e30c11cSHungNien Chen 		dev_err(&client->dev, "failed to get firmware id\n");
4393e30c11cSHungNien Chen 		return error;
4403e30c11cSHungNien Chen 	}
4413e30c11cSHungNien Chen 
4423e30c11cSHungNien Chen 	if (buf[0] != 0xf2) {
4433e30c11cSHungNien Chen 		dev_err(&client->dev, "wrong id of fw response: 0x%x\n",
4443e30c11cSHungNien Chen 			buf[0]);
4453e30c11cSHungNien Chen 		return -EINVAL;
4463e30c11cSHungNien Chen 	}
4473e30c11cSHungNien Chen 
4483e30c11cSHungNien Chen 	param->fw_id = get_unaligned_le16(&buf[1]);
4493e30c11cSHungNien Chen 
4503e30c11cSHungNien Chen 	dev_info(&client->dev,
45157ff96e0SDmitry Torokhov 		 "fw_id: 0x%x, plat_id: 0x%x, xml_id1: %04x, xml_id2: %04x\n",
4523e30c11cSHungNien Chen 		 param->fw_id, param->plat_id,
4533e30c11cSHungNien Chen 		 param->xmls_id1, param->xmls_id2);
4543e30c11cSHungNien Chen 
4553e30c11cSHungNien Chen 	return 0;
4563e30c11cSHungNien Chen }
4573e30c11cSHungNien Chen 
4583e30c11cSHungNien Chen static int wdt87xx_validate_firmware(struct wdt87xx_data *wdt,
4593e30c11cSHungNien Chen 				     const struct firmware *fw)
4603e30c11cSHungNien Chen {
4613e30c11cSHungNien Chen 	const void *fw_chunk;
4623e30c11cSHungNien Chen 	u32 data1, data2;
4633e30c11cSHungNien Chen 	u32 size;
4643e30c11cSHungNien Chen 	u8 fw_chip_id;
4653e30c11cSHungNien Chen 	u8 chip_id;
4663e30c11cSHungNien Chen 
4673e30c11cSHungNien Chen 	data1 = get_unaligned_le32(fw->data + FW_FOURCC1_OFFSET);
4683e30c11cSHungNien Chen 	data2 = get_unaligned_le32(fw->data + FW_FOURCC2_OFFSET);
4693e30c11cSHungNien Chen 	if (data1 != FOURCC_ID_RIFF || data2 != FOURCC_ID_WHIF) {
4703e30c11cSHungNien Chen 		dev_err(&wdt->client->dev, "check fw tag failed\n");
4713e30c11cSHungNien Chen 		return -EINVAL;
4723e30c11cSHungNien Chen 	}
4733e30c11cSHungNien Chen 
4743e30c11cSHungNien Chen 	size = get_unaligned_le32(fw->data + FW_SIZE_OFFSET);
4753e30c11cSHungNien Chen 	if (size != fw->size) {
4763e30c11cSHungNien Chen 		dev_err(&wdt->client->dev,
47711ddba28SDmitry Torokhov 			"fw size mismatch: expected %d, actual %zu\n",
4783e30c11cSHungNien Chen 			size, fw->size);
4793e30c11cSHungNien Chen 		return -EINVAL;
4803e30c11cSHungNien Chen 	}
4813e30c11cSHungNien Chen 
4823e30c11cSHungNien Chen 	/*
4833e30c11cSHungNien Chen 	 * Get the chip_id from the firmware. Make sure that it is the
4843e30c11cSHungNien Chen 	 * right controller to do the firmware and config update.
4853e30c11cSHungNien Chen 	 */
4863e30c11cSHungNien Chen 	fw_chunk = wdt87xx_get_fw_chunk(fw, CHUNK_ID_FRWR);
4873e30c11cSHungNien Chen 	if (!fw_chunk) {
4883e30c11cSHungNien Chen 		dev_err(&wdt->client->dev,
4893e30c11cSHungNien Chen 			"unable to locate firmware chunk\n");
4903e30c11cSHungNien Chen 		return -EINVAL;
4913e30c11cSHungNien Chen 	}
4923e30c11cSHungNien Chen 
4933e30c11cSHungNien Chen 	fw_chip_id = (get_unaligned_le32(fw_chunk +
4943e30c11cSHungNien Chen 					 FW_CHUNK_VERSION_OFFSET) >> 12) & 0xF;
4953e30c11cSHungNien Chen 	chip_id = (wdt->param.fw_id >> 12) & 0xF;
4963e30c11cSHungNien Chen 
4973e30c11cSHungNien Chen 	if (fw_chip_id != chip_id) {
4983e30c11cSHungNien Chen 		dev_err(&wdt->client->dev,
4993e30c11cSHungNien Chen 			"fw version mismatch: fw %d vs. chip %d\n",
5003e30c11cSHungNien Chen 			fw_chip_id, chip_id);
5013e30c11cSHungNien Chen 		return -ENODEV;
5023e30c11cSHungNien Chen 	}
5033e30c11cSHungNien Chen 
5043e30c11cSHungNien Chen 	return 0;
5053e30c11cSHungNien Chen }
5063e30c11cSHungNien Chen 
5073e30c11cSHungNien Chen static int wdt87xx_validate_fw_chunk(const void *data, int id)
5083e30c11cSHungNien Chen {
5093e30c11cSHungNien Chen 	if (id == CHUNK_ID_FRWR) {
5103e30c11cSHungNien Chen 		u32 fw_id;
5113e30c11cSHungNien Chen 
5123e30c11cSHungNien Chen 		fw_id = get_unaligned_le32(data + FW_CHUNK_PAYLOAD_OFFSET);
5133e30c11cSHungNien Chen 		if (fw_id != WDT_FIRMWARE_ID)
5143e30c11cSHungNien Chen 			return -EINVAL;
5153e30c11cSHungNien Chen 	}
5163e30c11cSHungNien Chen 
5173e30c11cSHungNien Chen 	return 0;
5183e30c11cSHungNien Chen }
5193e30c11cSHungNien Chen 
5203e30c11cSHungNien Chen static int wdt87xx_write_data(struct i2c_client *client, const char *data,
5213e30c11cSHungNien Chen 			      u32 address, int length)
5223e30c11cSHungNien Chen {
5233e30c11cSHungNien Chen 	u16 packet_size;
5243e30c11cSHungNien Chen 	int count = 0;
5253e30c11cSHungNien Chen 	int error;
5263e30c11cSHungNien Chen 	u8 pkt_buf[PKT_BUF_SIZE];
5273e30c11cSHungNien Chen 
5283e30c11cSHungNien Chen 	/* Address and length should be 4 bytes aligned */
5293e30c11cSHungNien Chen 	if ((address & 0x3) != 0 || (length & 0x3) != 0) {
5303e30c11cSHungNien Chen 		dev_err(&client->dev,
5313e30c11cSHungNien Chen 			"addr & len must be 4 bytes aligned %x, %x\n",
5323e30c11cSHungNien Chen 			address, length);
5333e30c11cSHungNien Chen 		return -EINVAL;
5343e30c11cSHungNien Chen 	}
5353e30c11cSHungNien Chen 
5363e30c11cSHungNien Chen 	while (length) {
5373e30c11cSHungNien Chen 		packet_size = min(length, PACKET_SIZE);
5383e30c11cSHungNien Chen 
5393e30c11cSHungNien Chen 		pkt_buf[CMD_REPORT_ID_OFFSET] = VND_REQ_WRITE;
5403e30c11cSHungNien Chen 		pkt_buf[CMD_TYPE_OFFSET] = VND_SET_DATA;
5413e30c11cSHungNien Chen 		put_unaligned_le16(packet_size, &pkt_buf[CMD_INDEX_OFFSET]);
5423e30c11cSHungNien Chen 		put_unaligned_le32(address, &pkt_buf[CMD_LENGTH_OFFSET]);
5433e30c11cSHungNien Chen 		memcpy(&pkt_buf[CMD_DATA_OFFSET], data, packet_size);
5443e30c11cSHungNien Chen 
5453e30c11cSHungNien Chen 		error = wdt87xx_set_feature(client, pkt_buf, sizeof(pkt_buf));
5463e30c11cSHungNien Chen 		if (error)
5473e30c11cSHungNien Chen 			return error;
5483e30c11cSHungNien Chen 
5493e30c11cSHungNien Chen 		length -= packet_size;
5503e30c11cSHungNien Chen 		data += packet_size;
5513e30c11cSHungNien Chen 		address += packet_size;
5523e30c11cSHungNien Chen 
5533e30c11cSHungNien Chen 		/* Wait for the controller to finish the write */
5543e30c11cSHungNien Chen 		mdelay(WDT_FLASH_WRITE_DELAY_MS);
5553e30c11cSHungNien Chen 
5563e30c11cSHungNien Chen 		if ((++count % 32) == 0) {
5573e30c11cSHungNien Chen 			/* Delay for fw to clear watch dog */
5583e30c11cSHungNien Chen 			msleep(20);
5593e30c11cSHungNien Chen 		}
5603e30c11cSHungNien Chen 	}
5613e30c11cSHungNien Chen 
5623e30c11cSHungNien Chen 	return 0;
5633e30c11cSHungNien Chen }
5643e30c11cSHungNien Chen 
5653e30c11cSHungNien Chen static u16 misr(u16 cur_value, u8 new_value)
5663e30c11cSHungNien Chen {
5673e30c11cSHungNien Chen 	u32 a, b;
5683e30c11cSHungNien Chen 	u32 bit0;
5693e30c11cSHungNien Chen 	u32 y;
5703e30c11cSHungNien Chen 
5713e30c11cSHungNien Chen 	a = cur_value;
5723e30c11cSHungNien Chen 	b = new_value;
5733e30c11cSHungNien Chen 	bit0 = a ^ (b & 1);
5743e30c11cSHungNien Chen 	bit0 ^= a >> 1;
5753e30c11cSHungNien Chen 	bit0 ^= a >> 2;
5763e30c11cSHungNien Chen 	bit0 ^= a >> 4;
5773e30c11cSHungNien Chen 	bit0 ^= a >> 5;
5783e30c11cSHungNien Chen 	bit0 ^= a >> 7;
5793e30c11cSHungNien Chen 	bit0 ^= a >> 11;
5803e30c11cSHungNien Chen 	bit0 ^= a >> 15;
5813e30c11cSHungNien Chen 	y = (a << 1) ^ b;
5823e30c11cSHungNien Chen 	y = (y & ~1) | (bit0 & 1);
5833e30c11cSHungNien Chen 
5843e30c11cSHungNien Chen 	return (u16)y;
5853e30c11cSHungNien Chen }
5863e30c11cSHungNien Chen 
5873e30c11cSHungNien Chen static u16 wdt87xx_calculate_checksum(const u8 *data, size_t length)
5883e30c11cSHungNien Chen {
5893e30c11cSHungNien Chen 	u16 checksum = 0;
5903e30c11cSHungNien Chen 	size_t i;
5913e30c11cSHungNien Chen 
5923e30c11cSHungNien Chen 	for (i = 0; i < length; i++)
5933e30c11cSHungNien Chen 		checksum = misr(checksum, data[i]);
5943e30c11cSHungNien Chen 
5953e30c11cSHungNien Chen 	return checksum;
5963e30c11cSHungNien Chen }
5973e30c11cSHungNien Chen 
5983e30c11cSHungNien Chen static int wdt87xx_get_checksum(struct i2c_client *client, u16 *checksum,
5993e30c11cSHungNien Chen 				u32 address, int length)
6003e30c11cSHungNien Chen {
6013e30c11cSHungNien Chen 	int error;
6023e30c11cSHungNien Chen 	int time_delay;
6033e30c11cSHungNien Chen 	u8 pkt_buf[PKT_BUF_SIZE];
6043e30c11cSHungNien Chen 	u8 cmd_buf[CMD_BUF_SIZE];
6053e30c11cSHungNien Chen 
6063e30c11cSHungNien Chen 	error = wdt87xx_send_command(client, VND_SET_CHECKSUM_LENGTH, length);
6073e30c11cSHungNien Chen 	if (error) {
6083e30c11cSHungNien Chen 		dev_err(&client->dev, "failed to set checksum length\n");
6093e30c11cSHungNien Chen 		return error;
6103e30c11cSHungNien Chen 	}
6113e30c11cSHungNien Chen 
6123e30c11cSHungNien Chen 	error = wdt87xx_send_command(client, VND_SET_CHECKSUM_CALC, address);
6133e30c11cSHungNien Chen 	if (error) {
6143e30c11cSHungNien Chen 		dev_err(&client->dev, "failed to set checksum address\n");
6153e30c11cSHungNien Chen 		return error;
6163e30c11cSHungNien Chen 	}
6173e30c11cSHungNien Chen 
6183e30c11cSHungNien Chen 	/* Wait the operation to complete */
6193e30c11cSHungNien Chen 	time_delay = DIV_ROUND_UP(length, 1024);
6203e30c11cSHungNien Chen 	msleep(time_delay * 30);
6213e30c11cSHungNien Chen 
6223e30c11cSHungNien Chen 	memset(cmd_buf, 0, sizeof(cmd_buf));
6233e30c11cSHungNien Chen 	cmd_buf[CMD_REPORT_ID_OFFSET] = VND_REQ_READ;
6243e30c11cSHungNien Chen 	cmd_buf[CMD_TYPE_OFFSET] = VND_GET_CHECKSUM;
6253e30c11cSHungNien Chen 	error = wdt87xx_set_feature(client, cmd_buf, sizeof(cmd_buf));
6263e30c11cSHungNien Chen 	if (error) {
6273e30c11cSHungNien Chen 		dev_err(&client->dev, "failed to request checksum\n");
6283e30c11cSHungNien Chen 		return error;
6293e30c11cSHungNien Chen 	}
6303e30c11cSHungNien Chen 
6313e30c11cSHungNien Chen 	memset(pkt_buf, 0, sizeof(pkt_buf));
6323e30c11cSHungNien Chen 	pkt_buf[CMD_REPORT_ID_OFFSET] = VND_READ_DATA;
6333e30c11cSHungNien Chen 	error = wdt87xx_get_feature(client, pkt_buf, sizeof(pkt_buf));
6343e30c11cSHungNien Chen 	if (error) {
6353e30c11cSHungNien Chen 		dev_err(&client->dev, "failed to read checksum\n");
6363e30c11cSHungNien Chen 		return error;
6373e30c11cSHungNien Chen 	}
6383e30c11cSHungNien Chen 
6393e30c11cSHungNien Chen 	*checksum = get_unaligned_le16(&pkt_buf[CMD_DATA_OFFSET]);
6403e30c11cSHungNien Chen 	return 0;
6413e30c11cSHungNien Chen }
6423e30c11cSHungNien Chen 
6433e30c11cSHungNien Chen static int wdt87xx_write_firmware(struct i2c_client *client, const void *chunk)
6443e30c11cSHungNien Chen {
6453e30c11cSHungNien Chen 	u32 start_addr = get_unaligned_le32(chunk + FW_CHUNK_TGT_START_OFFSET);
6463e30c11cSHungNien Chen 	u32 size = get_unaligned_le32(chunk + FW_CHUNK_PAYLOAD_LEN_OFFSET);
6473e30c11cSHungNien Chen 	const void *data = chunk + FW_CHUNK_PAYLOAD_OFFSET;
6483e30c11cSHungNien Chen 	int error;
6493e30c11cSHungNien Chen 	int err1;
6503e30c11cSHungNien Chen 	int page_size;
6513e30c11cSHungNien Chen 	int retry = 0;
6523e30c11cSHungNien Chen 	u16 device_checksum, firmware_checksum;
6533e30c11cSHungNien Chen 
6543e30c11cSHungNien Chen 	dev_dbg(&client->dev, "start 4k page program\n");
6553e30c11cSHungNien Chen 
6563e30c11cSHungNien Chen 	error = wdt87xx_send_command(client, VND_CMD_STOP, MODE_STOP);
6573e30c11cSHungNien Chen 	if (error) {
6583e30c11cSHungNien Chen 		dev_err(&client->dev, "stop report mode failed\n");
6593e30c11cSHungNien Chen 		return error;
6603e30c11cSHungNien Chen 	}
6613e30c11cSHungNien Chen 
6623e30c11cSHungNien Chen 	error = wdt87xx_send_command(client, VND_CMD_SFUNL, 0);
6633e30c11cSHungNien Chen 	if (error) {
6643e30c11cSHungNien Chen 		dev_err(&client->dev, "unlock failed\n");
6653e30c11cSHungNien Chen 		goto out_enable_reporting;
6663e30c11cSHungNien Chen 	}
6673e30c11cSHungNien Chen 
6683e30c11cSHungNien Chen 	mdelay(10);
6693e30c11cSHungNien Chen 
6703e30c11cSHungNien Chen 	while (size) {
6713e30c11cSHungNien Chen 		dev_dbg(&client->dev, "%s: %x, %x\n", __func__,
6723e30c11cSHungNien Chen 			start_addr, size);
6733e30c11cSHungNien Chen 
6743e30c11cSHungNien Chen 		page_size = min_t(u32, size, PG_SIZE);
6753e30c11cSHungNien Chen 		size -= page_size;
6763e30c11cSHungNien Chen 
6773e30c11cSHungNien Chen 		for (retry = 0; retry < MAX_RETRIES; retry++) {
6783e30c11cSHungNien Chen 			error = wdt87xx_send_command(client, VND_CMD_ERASE,
6793e30c11cSHungNien Chen 						     start_addr);
6803e30c11cSHungNien Chen 			if (error) {
6813e30c11cSHungNien Chen 				dev_err(&client->dev,
6823e30c11cSHungNien Chen 					"erase failed at %#08x\n", start_addr);
6833e30c11cSHungNien Chen 				break;
6843e30c11cSHungNien Chen 			}
6853e30c11cSHungNien Chen 
6863e30c11cSHungNien Chen 			msleep(50);
6873e30c11cSHungNien Chen 
6883e30c11cSHungNien Chen 			error = wdt87xx_write_data(client, data, start_addr,
6893e30c11cSHungNien Chen 						   page_size);
6903e30c11cSHungNien Chen 			if (error) {
6913e30c11cSHungNien Chen 				dev_err(&client->dev,
6923e30c11cSHungNien Chen 					"write failed at %#08x (%d bytes)\n",
6933e30c11cSHungNien Chen 					start_addr, page_size);
6943e30c11cSHungNien Chen 				break;
6953e30c11cSHungNien Chen 			}
6963e30c11cSHungNien Chen 
6973e30c11cSHungNien Chen 			error = wdt87xx_get_checksum(client, &device_checksum,
6983e30c11cSHungNien Chen 						     start_addr, page_size);
6993e30c11cSHungNien Chen 			if (error) {
7003e30c11cSHungNien Chen 				dev_err(&client->dev,
7013e30c11cSHungNien Chen 					"failed to retrieve checksum for %#08x (len: %d)\n",
7023e30c11cSHungNien Chen 					start_addr, page_size);
7033e30c11cSHungNien Chen 				break;
7043e30c11cSHungNien Chen 			}
7053e30c11cSHungNien Chen 
7063e30c11cSHungNien Chen 			firmware_checksum =
7073e30c11cSHungNien Chen 				wdt87xx_calculate_checksum(data, page_size);
7083e30c11cSHungNien Chen 
7093e30c11cSHungNien Chen 			if (device_checksum == firmware_checksum)
7103e30c11cSHungNien Chen 				break;
7113e30c11cSHungNien Chen 
7123e30c11cSHungNien Chen 			dev_err(&client->dev,
7133e30c11cSHungNien Chen 				"checksum fail: %d vs %d, retry %d\n",
7143e30c11cSHungNien Chen 				device_checksum, firmware_checksum, retry);
7153e30c11cSHungNien Chen 		}
7163e30c11cSHungNien Chen 
7173e30c11cSHungNien Chen 		if (retry == MAX_RETRIES) {
7183e30c11cSHungNien Chen 			dev_err(&client->dev, "page write failed\n");
7193e30c11cSHungNien Chen 			error = -EIO;
7203e30c11cSHungNien Chen 			goto out_lock_device;
7213e30c11cSHungNien Chen 		}
7223e30c11cSHungNien Chen 
7233e30c11cSHungNien Chen 		start_addr = start_addr + page_size;
7243e30c11cSHungNien Chen 		data = data + page_size;
7253e30c11cSHungNien Chen 	}
7263e30c11cSHungNien Chen 
7273e30c11cSHungNien Chen out_lock_device:
7283e30c11cSHungNien Chen 	err1 = wdt87xx_send_command(client, VND_CMD_SFLCK, 0);
7293e30c11cSHungNien Chen 	if (err1)
7303e30c11cSHungNien Chen 		dev_err(&client->dev, "lock failed\n");
7313e30c11cSHungNien Chen 
7323e30c11cSHungNien Chen 	mdelay(10);
7333e30c11cSHungNien Chen 
7343e30c11cSHungNien Chen out_enable_reporting:
7353e30c11cSHungNien Chen 	err1 = wdt87xx_send_command(client, VND_CMD_START, 0);
7363e30c11cSHungNien Chen 	if (err1)
7373e30c11cSHungNien Chen 		dev_err(&client->dev, "start to report failed\n");
7383e30c11cSHungNien Chen 
7393e30c11cSHungNien Chen 	return error ? error : err1;
7403e30c11cSHungNien Chen }
7413e30c11cSHungNien Chen 
7423e30c11cSHungNien Chen static int wdt87xx_load_chunk(struct i2c_client *client,
7433e30c11cSHungNien Chen 			      const struct firmware *fw, u32 ck_id)
7443e30c11cSHungNien Chen {
7453e30c11cSHungNien Chen 	const void *chunk;
7463e30c11cSHungNien Chen 	int error;
7473e30c11cSHungNien Chen 
7483e30c11cSHungNien Chen 	chunk = wdt87xx_get_fw_chunk(fw, ck_id);
7493e30c11cSHungNien Chen 	if (!chunk) {
7503e30c11cSHungNien Chen 		dev_err(&client->dev, "unable to locate chunk (type %d)\n",
7513e30c11cSHungNien Chen 			ck_id);
7523e30c11cSHungNien Chen 		return -EINVAL;
7533e30c11cSHungNien Chen 	}
7543e30c11cSHungNien Chen 
7553e30c11cSHungNien Chen 	error = wdt87xx_validate_fw_chunk(chunk, ck_id);
7563e30c11cSHungNien Chen 	if (error) {
7573e30c11cSHungNien Chen 		dev_err(&client->dev, "invalid chunk (type %d): %d\n",
7583e30c11cSHungNien Chen 			ck_id, error);
7593e30c11cSHungNien Chen 		return error;
7603e30c11cSHungNien Chen 	}
7613e30c11cSHungNien Chen 
7623e30c11cSHungNien Chen 	error = wdt87xx_write_firmware(client, chunk);
7633e30c11cSHungNien Chen 	if (error) {
7643e30c11cSHungNien Chen 		dev_err(&client->dev,
7653e30c11cSHungNien Chen 			"failed to write fw chunk (type %d): %d\n",
7663e30c11cSHungNien Chen 			ck_id, error);
7673e30c11cSHungNien Chen 		return error;
7683e30c11cSHungNien Chen 	}
7693e30c11cSHungNien Chen 
7703e30c11cSHungNien Chen 	return 0;
7713e30c11cSHungNien Chen }
7723e30c11cSHungNien Chen 
7733e30c11cSHungNien Chen static int wdt87xx_do_update_firmware(struct i2c_client *client,
7743e30c11cSHungNien Chen 				      const struct firmware *fw,
7753e30c11cSHungNien Chen 				      unsigned int chunk_id)
7763e30c11cSHungNien Chen {
7773e30c11cSHungNien Chen 	struct wdt87xx_data *wdt = i2c_get_clientdata(client);
7783e30c11cSHungNien Chen 	int error;
7793e30c11cSHungNien Chen 
7803e30c11cSHungNien Chen 	error = wdt87xx_validate_firmware(wdt, fw);
7813e30c11cSHungNien Chen 	if (error)
7823e30c11cSHungNien Chen 		return error;
7833e30c11cSHungNien Chen 
7843e30c11cSHungNien Chen 	error = mutex_lock_interruptible(&wdt->fw_mutex);
7853e30c11cSHungNien Chen 	if (error)
7863e30c11cSHungNien Chen 		return error;
7873e30c11cSHungNien Chen 
7883e30c11cSHungNien Chen 	disable_irq(client->irq);
7893e30c11cSHungNien Chen 
7903e30c11cSHungNien Chen 	error = wdt87xx_load_chunk(client, fw, chunk_id);
7913e30c11cSHungNien Chen 	if (error) {
7923e30c11cSHungNien Chen 		dev_err(&client->dev,
7933e30c11cSHungNien Chen 			"firmware load failed (type: %d): %d\n",
7943e30c11cSHungNien Chen 			chunk_id, error);
7953e30c11cSHungNien Chen 		goto out;
7963e30c11cSHungNien Chen 	}
7973e30c11cSHungNien Chen 
7983e30c11cSHungNien Chen 	error = wdt87xx_sw_reset(client);
7993e30c11cSHungNien Chen 	if (error) {
8003e30c11cSHungNien Chen 		dev_err(&client->dev, "soft reset failed: %d\n", error);
8013e30c11cSHungNien Chen 		goto out;
8023e30c11cSHungNien Chen 	}
8033e30c11cSHungNien Chen 
8043e30c11cSHungNien Chen 	/* Refresh the parameters */
8053e30c11cSHungNien Chen 	error = wdt87xx_get_sysparam(client, &wdt->param);
8063e30c11cSHungNien Chen 	if (error)
8073e30c11cSHungNien Chen 		dev_err(&client->dev,
8083e30c11cSHungNien Chen 			"failed to refresh system paramaters: %d\n", error);
8093e30c11cSHungNien Chen out:
8103e30c11cSHungNien Chen 	enable_irq(client->irq);
8113e30c11cSHungNien Chen 	mutex_unlock(&wdt->fw_mutex);
8123e30c11cSHungNien Chen 
8133e30c11cSHungNien Chen 	return error ? error : 0;
8143e30c11cSHungNien Chen }
8153e30c11cSHungNien Chen 
8163e30c11cSHungNien Chen static int wdt87xx_update_firmware(struct device *dev,
8173e30c11cSHungNien Chen 				   const char *fw_name, unsigned int chunk_id)
8183e30c11cSHungNien Chen {
8193e30c11cSHungNien Chen 	struct i2c_client *client = to_i2c_client(dev);
8203e30c11cSHungNien Chen 	const struct firmware *fw;
8213e30c11cSHungNien Chen 	int error;
8223e30c11cSHungNien Chen 
8233e30c11cSHungNien Chen 	error = request_firmware(&fw, fw_name, dev);
8243e30c11cSHungNien Chen 	if (error) {
8253e30c11cSHungNien Chen 		dev_err(&client->dev, "unable to retrieve firmware %s: %d\n",
8263e30c11cSHungNien Chen 			fw_name, error);
8273e30c11cSHungNien Chen 		return error;
8283e30c11cSHungNien Chen 	}
8293e30c11cSHungNien Chen 
8303e30c11cSHungNien Chen 	error = wdt87xx_do_update_firmware(client, fw, chunk_id);
8313e30c11cSHungNien Chen 
8323e30c11cSHungNien Chen 	release_firmware(fw);
8333e30c11cSHungNien Chen 
8343e30c11cSHungNien Chen 	return error ? error : 0;
8353e30c11cSHungNien Chen }
8363e30c11cSHungNien Chen 
8373e30c11cSHungNien Chen static ssize_t config_csum_show(struct device *dev,
8383e30c11cSHungNien Chen 				struct device_attribute *attr, char *buf)
8393e30c11cSHungNien Chen {
8403e30c11cSHungNien Chen 	struct i2c_client *client = to_i2c_client(dev);
8413e30c11cSHungNien Chen 	struct wdt87xx_data *wdt = i2c_get_clientdata(client);
8423e30c11cSHungNien Chen 	u32 cfg_csum;
8433e30c11cSHungNien Chen 
8443e30c11cSHungNien Chen 	cfg_csum = wdt->param.xmls_id1;
8453e30c11cSHungNien Chen 	cfg_csum = (cfg_csum << 16) | wdt->param.xmls_id2;
8463e30c11cSHungNien Chen 
8473e30c11cSHungNien Chen 	return scnprintf(buf, PAGE_SIZE, "%x\n", cfg_csum);
8483e30c11cSHungNien Chen }
8493e30c11cSHungNien Chen 
8503e30c11cSHungNien Chen static ssize_t fw_version_show(struct device *dev,
8513e30c11cSHungNien Chen 			       struct device_attribute *attr, char *buf)
8523e30c11cSHungNien Chen {
8533e30c11cSHungNien Chen 	struct i2c_client *client = to_i2c_client(dev);
8543e30c11cSHungNien Chen 	struct wdt87xx_data *wdt = i2c_get_clientdata(client);
8553e30c11cSHungNien Chen 
8563e30c11cSHungNien Chen 	return scnprintf(buf, PAGE_SIZE, "%x\n", wdt->param.fw_id);
8573e30c11cSHungNien Chen }
8583e30c11cSHungNien Chen 
8593e30c11cSHungNien Chen static ssize_t plat_id_show(struct device *dev,
8603e30c11cSHungNien Chen 			    struct device_attribute *attr, char *buf)
8613e30c11cSHungNien Chen {
8623e30c11cSHungNien Chen 	struct i2c_client *client = to_i2c_client(dev);
8633e30c11cSHungNien Chen 	struct wdt87xx_data *wdt = i2c_get_clientdata(client);
8643e30c11cSHungNien Chen 
8653e30c11cSHungNien Chen 	return scnprintf(buf, PAGE_SIZE, "%x\n", wdt->param.plat_id);
8663e30c11cSHungNien Chen }
8673e30c11cSHungNien Chen 
8683e30c11cSHungNien Chen static ssize_t update_config_store(struct device *dev,
8693e30c11cSHungNien Chen 				   struct device_attribute *attr,
8703e30c11cSHungNien Chen 				   const char *buf, size_t count)
8713e30c11cSHungNien Chen {
8723e30c11cSHungNien Chen 	int error;
8733e30c11cSHungNien Chen 
8743e30c11cSHungNien Chen 	error = wdt87xx_update_firmware(dev, WDT87XX_CFG_NAME, CHUNK_ID_CNFG);
8753e30c11cSHungNien Chen 
8763e30c11cSHungNien Chen 	return error ? error : count;
8773e30c11cSHungNien Chen }
8783e30c11cSHungNien Chen 
8793e30c11cSHungNien Chen static ssize_t update_fw_store(struct device *dev,
8803e30c11cSHungNien Chen 			       struct device_attribute *attr,
8813e30c11cSHungNien Chen 			       const char *buf, size_t count)
8823e30c11cSHungNien Chen {
8833e30c11cSHungNien Chen 	int error;
8843e30c11cSHungNien Chen 
8853e30c11cSHungNien Chen 	error = wdt87xx_update_firmware(dev, WDT87XX_FW_NAME, CHUNK_ID_FRWR);
8863e30c11cSHungNien Chen 
8873e30c11cSHungNien Chen 	return error ? error : count;
8883e30c11cSHungNien Chen }
8893e30c11cSHungNien Chen 
8903e30c11cSHungNien Chen static DEVICE_ATTR_RO(config_csum);
8913e30c11cSHungNien Chen static DEVICE_ATTR_RO(fw_version);
8923e30c11cSHungNien Chen static DEVICE_ATTR_RO(plat_id);
8933e30c11cSHungNien Chen static DEVICE_ATTR_WO(update_config);
8943e30c11cSHungNien Chen static DEVICE_ATTR_WO(update_fw);
8953e30c11cSHungNien Chen 
8963e30c11cSHungNien Chen static struct attribute *wdt87xx_attrs[] = {
8973e30c11cSHungNien Chen 	&dev_attr_config_csum.attr,
8983e30c11cSHungNien Chen 	&dev_attr_fw_version.attr,
8993e30c11cSHungNien Chen 	&dev_attr_plat_id.attr,
9003e30c11cSHungNien Chen 	&dev_attr_update_config.attr,
9013e30c11cSHungNien Chen 	&dev_attr_update_fw.attr,
9023e30c11cSHungNien Chen 	NULL
9033e30c11cSHungNien Chen };
9043e30c11cSHungNien Chen 
9053e30c11cSHungNien Chen static const struct attribute_group wdt87xx_attr_group = {
9063e30c11cSHungNien Chen 	.attrs = wdt87xx_attrs,
9073e30c11cSHungNien Chen };
9083e30c11cSHungNien Chen 
9093e30c11cSHungNien Chen static void wdt87xx_report_contact(struct input_dev *input,
9103e30c11cSHungNien Chen 				   struct wdt87xx_sys_param *param,
9113e30c11cSHungNien Chen 				   u8 *buf)
9123e30c11cSHungNien Chen {
9133e30c11cSHungNien Chen 	int finger_id;
914*d55d0b56SHungNien Chen 	u32 x, y, w;
915*d55d0b56SHungNien Chen 	u8 p;
9163e30c11cSHungNien Chen 
9173e30c11cSHungNien Chen 	finger_id = (buf[FINGER_EV_V1_OFFSET_ID] >> 3) - 1;
9183e30c11cSHungNien Chen 	if (finger_id < 0)
9193e30c11cSHungNien Chen 		return;
9203e30c11cSHungNien Chen 
9213e30c11cSHungNien Chen 	/* Check if this is an active contact */
9223e30c11cSHungNien Chen 	if (!(buf[FINGER_EV_V1_OFFSET_ID] & 0x1))
9233e30c11cSHungNien Chen 		return;
9243e30c11cSHungNien Chen 
9253e30c11cSHungNien Chen 	w = buf[FINGER_EV_V1_OFFSET_W];
926*d55d0b56SHungNien Chen 	w *= param->scaling_factor;
927*d55d0b56SHungNien Chen 
9283e30c11cSHungNien Chen 	p = buf[FINGER_EV_V1_OFFSET_P];
9293e30c11cSHungNien Chen 
9303e30c11cSHungNien Chen 	x = get_unaligned_le16(buf + FINGER_EV_V1_OFFSET_X);
9313e30c11cSHungNien Chen 
9323e30c11cSHungNien Chen 	y = get_unaligned_le16(buf + FINGER_EV_V1_OFFSET_Y);
9333e30c11cSHungNien Chen 	y = DIV_ROUND_CLOSEST(y * param->phy_h, param->phy_w);
9343e30c11cSHungNien Chen 
9353e30c11cSHungNien Chen 	/* Refuse incorrect coordinates */
9363e30c11cSHungNien Chen 	if (x > param->max_x || y > param->max_y)
9373e30c11cSHungNien Chen 		return;
9383e30c11cSHungNien Chen 
9393e30c11cSHungNien Chen 	dev_dbg(input->dev.parent, "tip on (%d), x(%d), y(%d)\n",
9403e30c11cSHungNien Chen 		finger_id, x, y);
9413e30c11cSHungNien Chen 
9423e30c11cSHungNien Chen 	input_mt_slot(input, finger_id);
9433e30c11cSHungNien Chen 	input_mt_report_slot_state(input, MT_TOOL_FINGER, 1);
9443e30c11cSHungNien Chen 	input_report_abs(input, ABS_MT_TOUCH_MAJOR, w);
9453e30c11cSHungNien Chen 	input_report_abs(input, ABS_MT_PRESSURE, p);
9463e30c11cSHungNien Chen 	input_report_abs(input, ABS_MT_POSITION_X, x);
9473e30c11cSHungNien Chen 	input_report_abs(input, ABS_MT_POSITION_Y, y);
9483e30c11cSHungNien Chen }
9493e30c11cSHungNien Chen 
9503e30c11cSHungNien Chen static irqreturn_t wdt87xx_ts_interrupt(int irq, void *dev_id)
9513e30c11cSHungNien Chen {
9523e30c11cSHungNien Chen 	struct wdt87xx_data *wdt = dev_id;
9533e30c11cSHungNien Chen 	struct i2c_client *client = wdt->client;
9543e30c11cSHungNien Chen 	int i, fingers;
9553e30c11cSHungNien Chen 	int error;
9563e30c11cSHungNien Chen 	u8 raw_buf[WDT_V1_RAW_BUF_COUNT] = {0};
9573e30c11cSHungNien Chen 
9583e30c11cSHungNien Chen 	error = i2c_master_recv(client, raw_buf, WDT_V1_RAW_BUF_COUNT);
9593e30c11cSHungNien Chen 	if (error < 0) {
9603e30c11cSHungNien Chen 		dev_err(&client->dev, "read v1 raw data failed: %d\n", error);
9613e30c11cSHungNien Chen 		goto irq_exit;
9623e30c11cSHungNien Chen 	}
9633e30c11cSHungNien Chen 
9643e30c11cSHungNien Chen 	fingers = raw_buf[TOUCH_PK_V1_OFFSET_FNGR_NUM];
9653e30c11cSHungNien Chen 	if (!fingers)
9663e30c11cSHungNien Chen 		goto irq_exit;
9673e30c11cSHungNien Chen 
9683e30c11cSHungNien Chen 	for (i = 0; i < WDT_MAX_FINGER; i++)
9693e30c11cSHungNien Chen 		wdt87xx_report_contact(wdt->input,
9703e30c11cSHungNien Chen 				       &wdt->param,
9713e30c11cSHungNien Chen 				       &raw_buf[TOUCH_PK_V1_OFFSET_EVENT +
9723e30c11cSHungNien Chen 						i * FINGER_EV_V1_SIZE]);
9733e30c11cSHungNien Chen 
9743e30c11cSHungNien Chen 	input_mt_sync_frame(wdt->input);
9753e30c11cSHungNien Chen 	input_sync(wdt->input);
9763e30c11cSHungNien Chen 
9773e30c11cSHungNien Chen irq_exit:
9783e30c11cSHungNien Chen 	return IRQ_HANDLED;
9793e30c11cSHungNien Chen }
9803e30c11cSHungNien Chen 
9813e30c11cSHungNien Chen static int wdt87xx_ts_create_input_device(struct wdt87xx_data *wdt)
9823e30c11cSHungNien Chen {
9833e30c11cSHungNien Chen 	struct device *dev = &wdt->client->dev;
9843e30c11cSHungNien Chen 	struct input_dev *input;
9853e30c11cSHungNien Chen 	unsigned int res = DIV_ROUND_CLOSEST(MAX_UNIT_AXIS, wdt->param.phy_w);
9863e30c11cSHungNien Chen 	int error;
9873e30c11cSHungNien Chen 
9883e30c11cSHungNien Chen 	input = devm_input_allocate_device(dev);
9893e30c11cSHungNien Chen 	if (!input) {
9903e30c11cSHungNien Chen 		dev_err(dev, "failed to allocate input device\n");
9913e30c11cSHungNien Chen 		return -ENOMEM;
9923e30c11cSHungNien Chen 	}
9933e30c11cSHungNien Chen 	wdt->input = input;
9943e30c11cSHungNien Chen 
9953e30c11cSHungNien Chen 	input->name = "WDT87xx Touchscreen";
9963e30c11cSHungNien Chen 	input->id.bustype = BUS_I2C;
9973e30c11cSHungNien Chen 	input->phys = wdt->phys;
9983e30c11cSHungNien Chen 
9993e30c11cSHungNien Chen 	input_set_abs_params(input, ABS_MT_POSITION_X, 0,
10003e30c11cSHungNien Chen 			     wdt->param.max_x, 0, 0);
10013e30c11cSHungNien Chen 	input_set_abs_params(input, ABS_MT_POSITION_Y, 0,
10023e30c11cSHungNien Chen 			     wdt->param.max_y, 0, 0);
10033e30c11cSHungNien Chen 	input_abs_set_res(input, ABS_MT_POSITION_X, res);
10043e30c11cSHungNien Chen 	input_abs_set_res(input, ABS_MT_POSITION_Y, res);
10053e30c11cSHungNien Chen 
1006*d55d0b56SHungNien Chen 	input_set_abs_params(input, ABS_MT_TOUCH_MAJOR,
1007*d55d0b56SHungNien Chen 			     0, wdt->param.max_x, 0, 0);
10083e30c11cSHungNien Chen 	input_set_abs_params(input, ABS_MT_PRESSURE, 0, 0xFF, 0, 0);
10093e30c11cSHungNien Chen 
10103e30c11cSHungNien Chen 	input_mt_init_slots(input, WDT_MAX_FINGER,
10113e30c11cSHungNien Chen 			    INPUT_MT_DIRECT | INPUT_MT_DROP_UNUSED);
10123e30c11cSHungNien Chen 
10133e30c11cSHungNien Chen 	error = input_register_device(input);
10143e30c11cSHungNien Chen 	if (error) {
10153e30c11cSHungNien Chen 		dev_err(dev, "failed to register input device: %d\n", error);
10163e30c11cSHungNien Chen 		return error;
10173e30c11cSHungNien Chen 	}
10183e30c11cSHungNien Chen 
10193e30c11cSHungNien Chen 	return 0;
10203e30c11cSHungNien Chen }
10213e30c11cSHungNien Chen 
10223e30c11cSHungNien Chen static int wdt87xx_ts_probe(struct i2c_client *client,
10233e30c11cSHungNien Chen 			    const struct i2c_device_id *id)
10243e30c11cSHungNien Chen {
10253e30c11cSHungNien Chen 	struct wdt87xx_data *wdt;
10263e30c11cSHungNien Chen 	int error;
10273e30c11cSHungNien Chen 
10283e30c11cSHungNien Chen 	dev_dbg(&client->dev, "adapter=%d, client irq: %d\n",
10293e30c11cSHungNien Chen 		client->adapter->nr, client->irq);
10303e30c11cSHungNien Chen 
10313e30c11cSHungNien Chen 	/* Check if the I2C function is ok in this adaptor */
10323e30c11cSHungNien Chen 	if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
10333e30c11cSHungNien Chen 		return -ENXIO;
10343e30c11cSHungNien Chen 
10353e30c11cSHungNien Chen 	wdt = devm_kzalloc(&client->dev, sizeof(*wdt), GFP_KERNEL);
10363e30c11cSHungNien Chen 	if (!wdt)
10373e30c11cSHungNien Chen 		return -ENOMEM;
10383e30c11cSHungNien Chen 
10393e30c11cSHungNien Chen 	wdt->client = client;
10403e30c11cSHungNien Chen 	mutex_init(&wdt->fw_mutex);
10413e30c11cSHungNien Chen 	i2c_set_clientdata(client, wdt);
10423e30c11cSHungNien Chen 
10433e30c11cSHungNien Chen 	snprintf(wdt->phys, sizeof(wdt->phys), "i2c-%u-%04x/input0",
10443e30c11cSHungNien Chen 		 client->adapter->nr, client->addr);
10453e30c11cSHungNien Chen 
10463e30c11cSHungNien Chen 	error = wdt87xx_get_sysparam(client, &wdt->param);
10473e30c11cSHungNien Chen 	if (error)
10483e30c11cSHungNien Chen 		return error;
10493e30c11cSHungNien Chen 
10503e30c11cSHungNien Chen 	error = wdt87xx_ts_create_input_device(wdt);
10513e30c11cSHungNien Chen 	if (error)
10523e30c11cSHungNien Chen 		return error;
10533e30c11cSHungNien Chen 
10543e30c11cSHungNien Chen 	error = devm_request_threaded_irq(&client->dev, client->irq,
10553e30c11cSHungNien Chen 					  NULL, wdt87xx_ts_interrupt,
10563e30c11cSHungNien Chen 					  IRQF_ONESHOT,
10573e30c11cSHungNien Chen 					  client->name, wdt);
10583e30c11cSHungNien Chen 	if (error) {
10593e30c11cSHungNien Chen 		dev_err(&client->dev, "request irq failed: %d\n", error);
10603e30c11cSHungNien Chen 		return error;
10613e30c11cSHungNien Chen 	}
10623e30c11cSHungNien Chen 
10633e30c11cSHungNien Chen 	error = sysfs_create_group(&client->dev.kobj, &wdt87xx_attr_group);
10643e30c11cSHungNien Chen 	if (error) {
10653e30c11cSHungNien Chen 		dev_err(&client->dev, "create sysfs failed: %d\n", error);
10663e30c11cSHungNien Chen 		return error;
10673e30c11cSHungNien Chen 	}
10683e30c11cSHungNien Chen 
10693e30c11cSHungNien Chen 	return 0;
10703e30c11cSHungNien Chen }
10713e30c11cSHungNien Chen 
10723e30c11cSHungNien Chen static int wdt87xx_ts_remove(struct i2c_client *client)
10733e30c11cSHungNien Chen {
10743e30c11cSHungNien Chen 	sysfs_remove_group(&client->dev.kobj, &wdt87xx_attr_group);
10753e30c11cSHungNien Chen 
10763e30c11cSHungNien Chen 	return 0;
10773e30c11cSHungNien Chen }
10783e30c11cSHungNien Chen 
10793e30c11cSHungNien Chen static int __maybe_unused wdt87xx_suspend(struct device *dev)
10803e30c11cSHungNien Chen {
10813e30c11cSHungNien Chen 	struct i2c_client *client = to_i2c_client(dev);
10823e30c11cSHungNien Chen 	int error;
10833e30c11cSHungNien Chen 
10843e30c11cSHungNien Chen 	disable_irq(client->irq);
10853e30c11cSHungNien Chen 
10863e30c11cSHungNien Chen 	error = wdt87xx_send_command(client, VND_CMD_STOP, MODE_IDLE);
10873e30c11cSHungNien Chen 	if (error) {
10883e30c11cSHungNien Chen 		enable_irq(client->irq);
10893e30c11cSHungNien Chen 		dev_err(&client->dev,
10903e30c11cSHungNien Chen 			"failed to stop device when suspending: %d\n",
10913e30c11cSHungNien Chen 			error);
10923e30c11cSHungNien Chen 		return error;
10933e30c11cSHungNien Chen 	}
10943e30c11cSHungNien Chen 
10953e30c11cSHungNien Chen 	return 0;
10963e30c11cSHungNien Chen }
10973e30c11cSHungNien Chen 
10983e30c11cSHungNien Chen static int __maybe_unused wdt87xx_resume(struct device *dev)
10993e30c11cSHungNien Chen {
11003e30c11cSHungNien Chen 	struct i2c_client *client = to_i2c_client(dev);
11013e30c11cSHungNien Chen 	int error;
11023e30c11cSHungNien Chen 
11033e30c11cSHungNien Chen 	/*
11043e30c11cSHungNien Chen 	 * The chip may have been reset while system is resuming,
11053e30c11cSHungNien Chen 	 * give it some time to settle.
11063e30c11cSHungNien Chen 	 */
11073e30c11cSHungNien Chen 	mdelay(100);
11083e30c11cSHungNien Chen 
11093e30c11cSHungNien Chen 	error = wdt87xx_send_command(client, VND_CMD_START, 0);
11103e30c11cSHungNien Chen 	if (error)
11113e30c11cSHungNien Chen 		dev_err(&client->dev,
11123e30c11cSHungNien Chen 			"failed to start device when resuming: %d\n",
11133e30c11cSHungNien Chen 			error);
11143e30c11cSHungNien Chen 
11153e30c11cSHungNien Chen 	enable_irq(client->irq);
11163e30c11cSHungNien Chen 
11173e30c11cSHungNien Chen 	return 0;
11183e30c11cSHungNien Chen }
11193e30c11cSHungNien Chen 
11203e30c11cSHungNien Chen static SIMPLE_DEV_PM_OPS(wdt87xx_pm_ops, wdt87xx_suspend, wdt87xx_resume);
11213e30c11cSHungNien Chen 
11223e30c11cSHungNien Chen static const struct i2c_device_id wdt87xx_dev_id[] = {
11233e30c11cSHungNien Chen 	{ WDT87XX_NAME, 0 },
11243e30c11cSHungNien Chen 	{ }
11253e30c11cSHungNien Chen };
11263e30c11cSHungNien Chen MODULE_DEVICE_TABLE(i2c, wdt87xx_dev_id);
11273e30c11cSHungNien Chen 
11283e30c11cSHungNien Chen static const struct acpi_device_id wdt87xx_acpi_id[] = {
11293e30c11cSHungNien Chen 	{ "WDHT0001", 0 },
11303e30c11cSHungNien Chen 	{ }
11313e30c11cSHungNien Chen };
11323e30c11cSHungNien Chen MODULE_DEVICE_TABLE(acpi, wdt87xx_acpi_id);
11333e30c11cSHungNien Chen 
11343e30c11cSHungNien Chen static struct i2c_driver wdt87xx_driver = {
11353e30c11cSHungNien Chen 	.probe		= wdt87xx_ts_probe,
11363e30c11cSHungNien Chen 	.remove		= wdt87xx_ts_remove,
11373e30c11cSHungNien Chen 	.id_table	= wdt87xx_dev_id,
11383e30c11cSHungNien Chen 	.driver	= {
11393e30c11cSHungNien Chen 		.name	= WDT87XX_NAME,
11403e30c11cSHungNien Chen 		.pm     = &wdt87xx_pm_ops,
11413e30c11cSHungNien Chen 		.acpi_match_table = ACPI_PTR(wdt87xx_acpi_id),
11423e30c11cSHungNien Chen 	},
11433e30c11cSHungNien Chen };
11443e30c11cSHungNien Chen module_i2c_driver(wdt87xx_driver);
11453e30c11cSHungNien Chen 
11463e30c11cSHungNien Chen MODULE_AUTHOR("HN Chen <hn.chen@weidahitech.com>");
11473e30c11cSHungNien Chen MODULE_DESCRIPTION("WeidaHiTech WDT87XX Touchscreen driver");
11483e30c11cSHungNien Chen MODULE_VERSION(WDT87XX_DRV_VER);
11493e30c11cSHungNien Chen MODULE_LICENSE("GPL");
1150