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