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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 10673e30c11cSHungNien Chen static int wdt87xx_ts_probe(struct i2c_client *client, 10683e30c11cSHungNien Chen const struct i2c_device_id *id) 10693e30c11cSHungNien Chen { 10703e30c11cSHungNien Chen struct wdt87xx_data *wdt; 10713e30c11cSHungNien Chen int error; 10723e30c11cSHungNien Chen 10733e30c11cSHungNien Chen dev_dbg(&client->dev, "adapter=%d, client irq: %d\n", 10743e30c11cSHungNien Chen client->adapter->nr, client->irq); 10753e30c11cSHungNien Chen 10763e30c11cSHungNien Chen /* Check if the I2C function is ok in this adaptor */ 10773e30c11cSHungNien Chen if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) 10783e30c11cSHungNien Chen return -ENXIO; 10793e30c11cSHungNien Chen 10803e30c11cSHungNien Chen wdt = devm_kzalloc(&client->dev, sizeof(*wdt), GFP_KERNEL); 10813e30c11cSHungNien Chen if (!wdt) 10823e30c11cSHungNien Chen return -ENOMEM; 10833e30c11cSHungNien Chen 10843e30c11cSHungNien Chen wdt->client = client; 10853e30c11cSHungNien Chen mutex_init(&wdt->fw_mutex); 10863e30c11cSHungNien Chen i2c_set_clientdata(client, wdt); 10873e30c11cSHungNien Chen 10883e30c11cSHungNien Chen snprintf(wdt->phys, sizeof(wdt->phys), "i2c-%u-%04x/input0", 10893e30c11cSHungNien Chen client->adapter->nr, client->addr); 10903e30c11cSHungNien Chen 10913e30c11cSHungNien Chen error = wdt87xx_get_sysparam(client, &wdt->param); 10923e30c11cSHungNien Chen if (error) 10933e30c11cSHungNien Chen return error; 10943e30c11cSHungNien Chen 10953e30c11cSHungNien Chen error = wdt87xx_ts_create_input_device(wdt); 10963e30c11cSHungNien Chen if (error) 10973e30c11cSHungNien Chen return error; 10983e30c11cSHungNien Chen 10993e30c11cSHungNien Chen error = devm_request_threaded_irq(&client->dev, client->irq, 11003e30c11cSHungNien Chen NULL, wdt87xx_ts_interrupt, 11013e30c11cSHungNien Chen IRQF_ONESHOT, 11023e30c11cSHungNien Chen client->name, wdt); 11033e30c11cSHungNien Chen if (error) { 11043e30c11cSHungNien Chen dev_err(&client->dev, "request irq failed: %d\n", error); 11053e30c11cSHungNien Chen return error; 11063e30c11cSHungNien Chen } 11073e30c11cSHungNien Chen 11085f2ae049SAndi Shyti error = devm_device_add_group(&client->dev, &wdt87xx_attr_group); 11093e30c11cSHungNien Chen if (error) { 11103e30c11cSHungNien Chen dev_err(&client->dev, "create sysfs failed: %d\n", error); 11113e30c11cSHungNien Chen return error; 11123e30c11cSHungNien Chen } 11133e30c11cSHungNien Chen 11143e30c11cSHungNien Chen return 0; 11153e30c11cSHungNien Chen } 11163e30c11cSHungNien Chen 11173e30c11cSHungNien Chen static int __maybe_unused wdt87xx_suspend(struct device *dev) 11183e30c11cSHungNien Chen { 11193e30c11cSHungNien Chen struct i2c_client *client = to_i2c_client(dev); 11203e30c11cSHungNien Chen int error; 11213e30c11cSHungNien Chen 11223e30c11cSHungNien Chen disable_irq(client->irq); 11233e30c11cSHungNien Chen 11243e30c11cSHungNien Chen error = wdt87xx_send_command(client, VND_CMD_STOP, MODE_IDLE); 11253e30c11cSHungNien Chen if (error) { 11263e30c11cSHungNien Chen enable_irq(client->irq); 11273e30c11cSHungNien Chen dev_err(&client->dev, 11283e30c11cSHungNien Chen "failed to stop device when suspending: %d\n", 11293e30c11cSHungNien Chen error); 11303e30c11cSHungNien Chen return error; 11313e30c11cSHungNien Chen } 11323e30c11cSHungNien Chen 11333e30c11cSHungNien Chen return 0; 11343e30c11cSHungNien Chen } 11353e30c11cSHungNien Chen 11363e30c11cSHungNien Chen static int __maybe_unused wdt87xx_resume(struct device *dev) 11373e30c11cSHungNien Chen { 11383e30c11cSHungNien Chen struct i2c_client *client = to_i2c_client(dev); 11393e30c11cSHungNien Chen int error; 11403e30c11cSHungNien Chen 11413e30c11cSHungNien Chen /* 11423e30c11cSHungNien Chen * The chip may have been reset while system is resuming, 11433e30c11cSHungNien Chen * give it some time to settle. 11443e30c11cSHungNien Chen */ 1145*e58650b5SJia-Ju Bai msleep(100); 11463e30c11cSHungNien Chen 11473e30c11cSHungNien Chen error = wdt87xx_send_command(client, VND_CMD_START, 0); 11483e30c11cSHungNien Chen if (error) 11493e30c11cSHungNien Chen dev_err(&client->dev, 11503e30c11cSHungNien Chen "failed to start device when resuming: %d\n", 11513e30c11cSHungNien Chen error); 11523e30c11cSHungNien Chen 11533e30c11cSHungNien Chen enable_irq(client->irq); 11543e30c11cSHungNien Chen 11553e30c11cSHungNien Chen return 0; 11563e30c11cSHungNien Chen } 11573e30c11cSHungNien Chen 11583e30c11cSHungNien Chen static SIMPLE_DEV_PM_OPS(wdt87xx_pm_ops, wdt87xx_suspend, wdt87xx_resume); 11593e30c11cSHungNien Chen 11603e30c11cSHungNien Chen static const struct i2c_device_id wdt87xx_dev_id[] = { 11613e30c11cSHungNien Chen { WDT87XX_NAME, 0 }, 11623e30c11cSHungNien Chen { } 11633e30c11cSHungNien Chen }; 11643e30c11cSHungNien Chen MODULE_DEVICE_TABLE(i2c, wdt87xx_dev_id); 11653e30c11cSHungNien Chen 11663e30c11cSHungNien Chen static const struct acpi_device_id wdt87xx_acpi_id[] = { 11673e30c11cSHungNien Chen { "WDHT0001", 0 }, 11683e30c11cSHungNien Chen { } 11693e30c11cSHungNien Chen }; 11703e30c11cSHungNien Chen MODULE_DEVICE_TABLE(acpi, wdt87xx_acpi_id); 11713e30c11cSHungNien Chen 11723e30c11cSHungNien Chen static struct i2c_driver wdt87xx_driver = { 11733e30c11cSHungNien Chen .probe = wdt87xx_ts_probe, 11743e30c11cSHungNien Chen .id_table = wdt87xx_dev_id, 11753e30c11cSHungNien Chen .driver = { 11763e30c11cSHungNien Chen .name = WDT87XX_NAME, 11773e30c11cSHungNien Chen .pm = &wdt87xx_pm_ops, 11783e30c11cSHungNien Chen .acpi_match_table = ACPI_PTR(wdt87xx_acpi_id), 11793e30c11cSHungNien Chen }, 11803e30c11cSHungNien Chen }; 11813e30c11cSHungNien Chen module_i2c_driver(wdt87xx_driver); 11823e30c11cSHungNien Chen 11833e30c11cSHungNien Chen MODULE_AUTHOR("HN Chen <hn.chen@weidahitech.com>"); 11843e30c11cSHungNien Chen MODULE_DESCRIPTION("WeidaHiTech WDT87XX Touchscreen driver"); 11853e30c11cSHungNien Chen MODULE_LICENSE("GPL"); 1186