From: HungNien Chen <hn.chen@xxxxxxxxxxxxxxx> To support WDT8752 is main modification of this version and description as below: 1. Add definitions for WDT8752. 2. Modify wdt87xx_get_param to have the right method to communicate with the controller. Also add wdt87xx_get_param_hid for retrieving parameters from the controller. 3. Controller specific function pointers are selected when detected controller and they are used in FW writing. 4. Add functions for WDT8752: wdt8752_send_command, wdt8752_write_data, wdt8752_delay, wdt8752_checksum_check and some minor functions. 5. Modify supend/resume flow to support IDLE and SLEEP. Signed-off-by: HungNien Chen <hn.chen@xxxxxxxxxxxxxxx> --- drivers/input/touchscreen/wdt87xx_i2c.c | 963 +++++++++++++++++++++++++------- 1 file changed, 749 insertions(+), 214 deletions(-) diff --git a/drivers/input/touchscreen/wdt87xx_i2c.c b/drivers/input/touchscreen/wdt87xx_i2c.c index 2519617..27553cd 100644 --- a/drivers/input/touchscreen/wdt87xx_i2c.c +++ b/drivers/input/touchscreen/wdt87xx_i2c.c @@ -22,10 +22,15 @@ #include <linux/acpi.h> #include <asm/unaligned.h> -#define WDT87XX_NAME "wdt87xx_i2c" -#define WDT87XX_DRV_VER "0.9.8" -#define WDT87XX_FW_NAME "wdt87xx_fw.bin" -#define WDT87XX_CFG_NAME "wdt87xx_cfg.bin" +#define WDT87XX_NAME "wdt87xx_i2c" +#define WDT87XX_DRV_VER "0.9.9" +#define WDT87XX_FW_NAME "wdt87xx_fw.bin" +#define WDT87XX_CFG_NAME "wdt87xx_cfg.bin" + +#define PLT_WDT8756 0x00 +#define PLT_WDT8752 0x01 + +#define RPT_ID_TOUCH 0x01 #define MODE_ACTIVE 0x01 #define MODE_READY 0x02 @@ -34,8 +39,7 @@ #define MODE_STOP 0xFF #define WDT_MAX_FINGER 10 -#define WDT_RAW_BUF_COUNT 54 -#define WDT_V1_RAW_BUF_COUNT 74 +#define WDT_RAW_BUF_COUNT 76 #define WDT_FIRMWARE_ID 0xa9e368f5 #define PG_SIZE 0x1000 @@ -43,32 +47,23 @@ #define MAX_UNIT_AXIS 0x7FFF +#define PKT_TX_SIZE 16 #define PKT_READ_SIZE 72 #define PKT_WRITE_SIZE 80 /* the finger definition of the report event */ #define FINGER_EV_OFFSET_ID 0 -#define FINGER_EV_OFFSET_X 1 -#define FINGER_EV_OFFSET_Y 3 -#define FINGER_EV_SIZE 5 - -#define FINGER_EV_V1_OFFSET_ID 0 -#define FINGER_EV_V1_OFFSET_W 1 -#define FINGER_EV_V1_OFFSET_P 2 -#define FINGER_EV_V1_OFFSET_X 3 -#define FINGER_EV_V1_OFFSET_Y 5 -#define FINGER_EV_V1_SIZE 7 +#define FINGER_EV_OFFSET_W 1 +#define FINGER_EV_OFFSET_P 2 +#define FINGER_EV_OFFSET_X 3 +#define FINGER_EV_OFFSET_Y 5 +#define FINGER_EV_SIZE 7 /* The definition of a report packet */ #define TOUCH_PK_OFFSET_REPORT_ID 0 #define TOUCH_PK_OFFSET_EVENT 1 -#define TOUCH_PK_OFFSET_SCAN_TIME 51 -#define TOUCH_PK_OFFSET_FNGR_NUM 53 - -#define TOUCH_PK_V1_OFFSET_REPORT_ID 0 -#define TOUCH_PK_V1_OFFSET_EVENT 1 -#define TOUCH_PK_V1_OFFSET_SCAN_TIME 71 -#define TOUCH_PK_V1_OFFSET_FNGR_NUM 73 +#define TOUCH_PK_OFFSET_SCAN_TIME 71 +#define TOUCH_PK_OFFSET_FNGR_NUM 73 /* The definition of the controller parameters */ #define CTL_PARAM_OFFSET_FW_ID 0 @@ -84,6 +79,7 @@ #define CTL_PARAM_OFFSET_PHY_W 22 #define CTL_PARAM_OFFSET_PHY_H 24 #define CTL_PARAM_OFFSET_FACTOR 32 +#define CTL_PARAM_OFFSET_I2C_CFG 36 /* The definition of the device descriptor */ #define WDT_GD_DEVICE 1 @@ -95,6 +91,8 @@ #define VND_REQ_READ 0x06 #define VND_READ_DATA 0x07 #define VND_REQ_WRITE 0x08 +#define VND_REQ_FW_INFO 0xF2 +#define VND_REQ_CTRLER_INFO 0xF4 #define VND_CMD_START 0x00 #define VND_CMD_STOP 0x01 @@ -104,6 +102,8 @@ #define VND_GET_CHECKSUM 0x66 +#define VND_CMD_DEV_MODE 0x82 + #define VND_SET_DATA 0x83 #define VND_SET_COMMAND_DATA 0x84 #define VND_SET_CHECKSUM_CALC 0x86 @@ -155,12 +155,85 @@ #define FW_CHUNK_PAYLOAD_OFFSET 32 /* Controller requires minimum 300us between commands */ -#define WDT_COMMAND_DELAY_MS 2 -#define WDT_FLASH_WRITE_DELAY_MS 4 -#define WDT_FLASH_ERASE_DELAY_MS 200 -#define WDT_FW_RESET_TIME 2500 +#define WDT_CMD_DELAY_US 300 +#define W8752_ERASE4K_DELAY_MS 500 +#define WDT_FLASH_WRITE_DELAY_MS 2 +#define WDT_FW_RESET_TIME_MS 2500 +#define WDT_POLLING_PERIOD_MS 20 +#define W8756_ERASE4K_DELAY_MS 200 + +/* The definition for WDT8752 */ +#define W8752_READ_OFFSET_MASK 0x10000 +#define W8752_DEV_INFO_READ_OFFSET 0xC +#define W8752_PKT_HEADER_SZ 4 +#define W8752_PKT_SIZE 60 + +#define W8752_STATUS_OK 0x80 +#define W8752_STATUS_BUSY 0xFE + +/* Communication commands of WDT8752 */ +#define W8752_BASIC_COMMAND 0x85 +#define W8752_FW_COMMAND 0x91 + +#define W8755_FW_GET_DEV_INFO 0x73 + +#define W8752_SET_FLASH 0x83 +#define W8752_SET_FLASH_ADDRESS 0x87 +#define W8752_SET_CHECKSUM_CALC 0x88 +#define W8752_GET_CHECKSUM 0x65 + +#define W8752_CMD_SFLOCK 0x00 +#define W8752_CMD_SFUNLOCK 0x01 +#define W8752_CMD_RESET 0x02 +#define W8752_CMD_ERASE4K 0x03 +#define W8752_CMD_DEV_MODE 0x82 + +#define W8752_DM_SENSING 0x1 +#define W8752_DM_DOZE 0x2 +#define W8755_DM_SLEEP 0x3 +#define W8752_DM_COMMAND 0x90 + +#define W8752_SFLOCK_KEY 0x9B +#define W8752_SFUNLOCK_KEY 0xDA + +/* The definition of the command packet of WDT8752 */ +#define CMD_SIZE_OFFSET 0x2 +#define CMD_ID_OFFSET 0x4 +#define CMD_DATA1_OFFSET 0x4 +#define CMD_VALUE_OFFSET 0x5 + +#define W8752_POLLING_PERIOD_US 5000 +#define W8752_FLASH_WRITE_DELAY_US 100 + +#define W8752_PROG_SECTOR_SIZE 0x100 + +#define W8752_HID_DESC_ADDR 0x20 + +/* The definition of controller parameters of WDT8752 */ +#define W8752_PARAM_KEY 0x154f +#define W8752_PARAM_KEY_OFFSET 0x2 +#define W8752_PLAT_ID_OFFSET 0x5 +#define W8752_PARAM_OFFSET 0xA +#define W8752_PARAM_LEN_OFFSET 0xC + +struct i2c_hid_desc { + u16 desc_length; + u16 bcd_version; + u16 rpt_desc_length; + u16 rpt_desc_register; + u16 input_register; + u16 max_input_length; + u16 output_register; + u16 max_output_length; + u16 cmd_register; + u16 data_register; + u16 vendor_id; + u16 product_id; + u16 version_id; + u32 reserved; +} __packed; -struct wdt87xx_sys_param { +struct wdt87xx_param { u16 fw_id; u16 plat_id; u16 xmls_id1; @@ -174,17 +247,31 @@ struct wdt87xx_sys_param { u32 max_y; u16 vendor_id; u16 product_id; -}; + u16 i2c_cfg; +} __packed; struct wdt87xx_data { struct i2c_client *client; struct input_dev *input; /* Mutex for fw update to prevent concurrent access */ struct mutex fw_mutex; - struct wdt87xx_sys_param param; + struct wdt87xx_param param; + struct i2c_hid_desc hid_desc; u8 phys[32]; + u32 plt_id; + bool wake_irq_enabled; + + /* Function ptrs for different controller body */ + int (*send_cmd)(struct i2c_client *client, int cmd, int value); + int (*write_flash)(struct i2c_client *client, const char *data, + u32 addr, size_t len); + int (*chksum_check)(struct i2c_client *client, const char *data, + u32 addr, size_t len); + int (*delay)(struct i2c_client *client, u32 delay); }; +static int wdt8752_set_dev_mode(struct i2c_client *client, u8 mode); + static int wdt87xx_i2c_xfer(struct i2c_client *client, void *txdata, size_t txlen, void *rxdata, size_t rxlen) @@ -214,6 +301,8 @@ static int wdt87xx_i2c_xfer(struct i2c_client *client, return error; } + udelay(WDT_CMD_DELAY_US); + return 0; } @@ -225,8 +314,8 @@ static int wdt87xx_get_desc(struct i2c_client *client, u8 desc_idx, tx_buf[2] |= desc_idx & 0xF; - error = wdt87xx_i2c_xfer(client, tx_buf, sizeof(tx_buf), - buf, len); + error = wdt87xx_i2c_xfer(client, tx_buf, sizeof(tx_buf), buf, len); + if (error) { dev_err(&client->dev, "get desc failed: %d\n", error); return error; @@ -238,8 +327,6 @@ static int wdt87xx_get_desc(struct i2c_client *client, u8 desc_idx, return -EINVAL; } - mdelay(WDT_COMMAND_DELAY_MS); - return 0; } @@ -270,23 +357,23 @@ static int wdt87xx_get_string(struct i2c_client *client, u8 str_idx, rx_len = min_t(size_t, len, rx_buf[0]); memcpy(buf, &rx_buf[2], rx_len); - mdelay(WDT_COMMAND_DELAY_MS); - return 0; } static int wdt87xx_get_feature(struct i2c_client *client, - u8 *buf, size_t buf_size) + u8 *buf, size_t len) { - u8 tx_buf[8]; + u8 tx_buf[PKT_TX_SIZE]; u8 rx_buf[PKT_WRITE_SIZE]; size_t tx_len = 0; - size_t rx_len = buf_size + 2; + size_t rx_len = len + 2; int error; if (rx_len > sizeof(rx_buf)) return -EINVAL; + memset(tx_buf, 0, sizeof(tx_buf)); + /* Get feature command packet */ tx_buf[tx_len++] = 0x22; tx_buf[tx_len++] = 0x00; @@ -307,16 +394,14 @@ static int wdt87xx_get_feature(struct i2c_client *client, return error; } - rx_len = min_t(size_t, buf_size, get_unaligned_le16(rx_buf)); + rx_len = min_t(size_t, len, get_unaligned_le16(rx_buf)); memcpy(buf, &rx_buf[2], rx_len); - mdelay(WDT_COMMAND_DELAY_MS); - return 0; } static int wdt87xx_set_feature(struct i2c_client *client, - const u8 *buf, size_t buf_size) + const u8 *buf, size_t len) { u8 tx_buf[PKT_WRITE_SIZE]; int tx_len = 0; @@ -335,22 +420,21 @@ static int wdt87xx_set_feature(struct i2c_client *client, } tx_buf[tx_len++] = 0x23; tx_buf[tx_len++] = 0x00; - tx_buf[tx_len++] = (buf_size & 0xFF); - tx_buf[tx_len++] = ((buf_size & 0xFF00) >> 8); + tx_buf[tx_len++] = (len & 0xFF); + tx_buf[tx_len++] = ((len & 0xFF00) >> 8); - if (tx_len + buf_size > sizeof(tx_buf)) + if (tx_len + len > sizeof(tx_buf)) return -EINVAL; - memcpy(&tx_buf[tx_len], buf, buf_size); - tx_len += buf_size; + memcpy(&tx_buf[tx_len], buf, len); + tx_len += len; error = i2c_master_send(client, tx_buf, tx_len); if (error < 0) { dev_err(&client->dev, "set feature failed: %d\n", error); return error; } - - mdelay(WDT_COMMAND_DELAY_MS); + udelay(WDT_CMD_DELAY_US); return 0; } @@ -395,20 +479,389 @@ static int wdt87xx_send_command(struct i2c_client *client, int cmd, int value) return wdt87xx_set_feature(client, cmd_buf, sizeof(cmd_buf)); } +static u16 misr(u16 cur_value, u16 new_value) +{ + u32 a, b; + u32 bit0; + u32 y; + + a = cur_value; + b = new_value; + bit0 = a ^ (b & 1); + bit0 ^= a >> 1; + bit0 ^= a >> 2; + bit0 ^= a >> 4; + bit0 ^= a >> 5; + bit0 ^= a >> 7; + bit0 ^= a >> 11; + bit0 ^= a >> 15; + y = (a << 1) ^ b; + y = (y & ~1) | (bit0 & 1); + + return (u16)y; +} + +static u16 wdt87xx_calculate_checksum(const u8 *data, size_t len, int byte_mode) +{ + u16 checksum = 0; + u16 *pdata_u16; + size_t i; + + if (byte_mode) + for (i = 0; i < len; i++) + checksum = misr(checksum, (u16)data[i]); + else { + pdata_u16 = (u16 *)data; + for (i = 0; i < (len >> 1); i++) + checksum = misr(checksum, *pdata_u16++); + } + + return checksum; +} + +static int wdt8752_send_command(struct i2c_client *client, int cmd, int value) +{ + u8 cmd_buf[PKT_BUF_SIZE]; + size_t size = 2; + + /* Set the command packet and the packet size is variable in 8752 */ + cmd_buf[CMD_REPORT_ID_OFFSET] = VND_REQ_WRITE; + cmd_buf[CMD_TYPE_OFFSET] = W8752_BASIC_COMMAND; + + switch (cmd) { + case VND_CMD_STOP: + /* + * Command STOP with STOP value, enter the command loop mode + * for operating the flash in 8752 + */ + if (value == MODE_STOP) + return wdt8752_set_dev_mode(client, W8752_DM_COMMAND); + + /* Remap the value of mode to the right one */ + if (value != MODE_ACTIVE) + value--; + + case VND_CMD_DEV_MODE: + cmd_buf[CMD_TYPE_OFFSET] = W8752_FW_COMMAND; + cmd_buf[CMD_ID_OFFSET] = W8752_CMD_DEV_MODE; + cmd_buf[CMD_VALUE_OFFSET] = value; + break; + + case VND_CMD_START: + return wdt8752_set_dev_mode(client, W8752_DM_SENSING); + + case VND_CMD_RESET: + cmd_buf[CMD_ID_OFFSET] = W8752_CMD_RESET; + size = 1; + break; + + case VND_CMD_SFLCK: + cmd_buf[CMD_ID_OFFSET] = W8752_CMD_SFLOCK; + cmd_buf[CMD_VALUE_OFFSET] = W8752_SFLOCK_KEY; + break; + + case VND_CMD_SFUNL: + cmd_buf[CMD_ID_OFFSET] = W8752_CMD_SFUNLOCK; + cmd_buf[CMD_VALUE_OFFSET] = W8752_SFUNLOCK_KEY; + break; + + case VND_CMD_ERASE: + cmd_buf[CMD_ID_OFFSET] = W8752_CMD_ERASE4K; + put_unaligned_le32(value, &cmd_buf[CMD_VALUE_OFFSET]); + size = 5; + break; + + default: + cmd_buf[CMD_REPORT_ID_OFFSET] = 0; + dev_err(&client->dev, "Invalid command: %d\n", cmd); + return -EINVAL; + } + + put_unaligned_le16(size, &cmd_buf[CMD_SIZE_OFFSET]); + + return wdt87xx_set_feature(client, cmd_buf, W8752_PKT_HEADER_SZ + size); +} + +static int wdt8752_exec_read_pkt(struct i2c_client *client, u8 type, + u8 *data, size_t len, int offset) +{ + u8 pkt_buf[PKT_BUF_SIZE]; + int error; + size_t size; + + /* + * Some vendor commands can read the data structure from controller, + * set the mask to indicate the offset. + */ + if (offset & W8752_READ_OFFSET_MASK) + size = offset & 0xFF; + else + size = len; + + pkt_buf[CMD_REPORT_ID_OFFSET] = VND_REQ_READ; + pkt_buf[CMD_TYPE_OFFSET] = type; + put_unaligned_le16(size, &pkt_buf[CMD_SIZE_OFFSET]); + + error = wdt87xx_set_feature(client, pkt_buf, W8752_PKT_HEADER_SZ); + if (error) + return error; + + pkt_buf[CMD_REPORT_ID_OFFSET] = VND_READ_DATA; + pkt_buf[CMD_TYPE_OFFSET] = type; + error = wdt87xx_get_feature(client, pkt_buf, PKT_BUF_SIZE); + if (error) + return error; + + if (pkt_buf[CMD_REPORT_ID_OFFSET] != VND_READ_DATA) { + dev_err(&client->dev, "wrong id of fw response: 0x%x\n", + pkt_buf[CMD_REPORT_ID_OFFSET]); + return -EINVAL; + } + memcpy(data, &pkt_buf[CMD_DATA1_OFFSET], len); + + return 0; +} + +static int wdt8752_get_device_mode(struct i2c_client *client, u8 *pmode) +{ + u8 cmd_buf[PKT_BUF_SIZE]; + int error; + + error = wdt8752_exec_read_pkt(client, W8755_FW_GET_DEV_INFO, cmd_buf, + W8752_PKT_SIZE, W8752_READ_OFFSET_MASK | + W8752_DEV_INFO_READ_OFFSET); + if (error) + return error; + + *pmode = cmd_buf[0]; + return 0; +} + +static int wdt8752_set_dev_mode(struct i2c_client *client, u8 mode) +{ + int count = 20; + int error; + u8 mode_r; + + do { + error = wdt8752_send_command(client, W8752_CMD_DEV_MODE, mode); + if (error) + return error; + + udelay(W8752_POLLING_PERIOD_US); + error = wdt8752_get_device_mode(client, &mode_r); + if (error) + return error; + } while (mode != mode_r && count-- > 0); + + if (mode != mode_r) { + dev_err(&client->dev, "failed to change mode: 0x%x, 0x%x\n", + mode, mode_r); + return -ETIME; + } + + return 0; +} + +static int wdt8752_exec_write_pkt(struct i2c_client *client, u8 type, u8 *data, + size_t len) +{ + u8 pkt_buf[PKT_BUF_SIZE]; + + pkt_buf[CMD_REPORT_ID_OFFSET] = VND_REQ_WRITE; + pkt_buf[CMD_TYPE_OFFSET] = type; + put_unaligned_le16(len, &pkt_buf[CMD_SIZE_OFFSET]); + + memcpy(&pkt_buf[CMD_DATA1_OFFSET], data, len); + + return wdt87xx_set_feature(client, pkt_buf, W8752_PKT_HEADER_SZ + len); +} + +static int wdt8752_delay(struct i2c_client *client, u32 delay) +{ + u8 rc = W8752_STATUS_BUSY; + u8 raw_buf[PKT_BUF_SIZE]; + int count; + int error; + + count = (delay / WDT_POLLING_PERIOD_MS) + 1; + + do { + msleep(WDT_POLLING_PERIOD_MS); + + error = i2c_master_recv(client, raw_buf, 3); + if (error < 0) { + dev_err(&client->dev, "read raw data failed: (%d)\n", + error); + return error; + } + + rc = raw_buf[2]; + } while (rc != W8752_STATUS_OK && count-- > 0); + + return 0; +} + +static int wdt8752_checksum_check(struct i2c_client *client, const char *data, + u32 addr, size_t len) +{ + u8 pkt_buf[PKT_BUF_SIZE]; + int time_delay; + int error; + u16 dev_chksum, fw_chksum; + + put_unaligned_le32(addr, &pkt_buf[0]); + put_unaligned_le32(len, &pkt_buf[4]); + + error = wdt8752_exec_write_pkt(client, W8752_SET_CHECKSUM_CALC, + pkt_buf, 8); + if (error) { + dev_err(&client->dev, "failed to write chksum_calc\n"); + return error; + } + + /* + * It takes about 2ms for every 1K bytes doing the checksum in FW. + * Wait here for the operation to complete. + */ + time_delay = DIV_ROUND_UP(len, 1024); + error = wdt8752_delay(client, time_delay * 4); + if (error) + return error; + + error = wdt8752_exec_read_pkt(client, W8752_GET_CHECKSUM, pkt_buf, + W8752_PKT_SIZE, 0); + if (error) { + dev_err(&client->dev, "failed to read chksum\n"); + return error; + } + + dev_chksum = get_unaligned_le16(pkt_buf); + + /* Calculate the checksum in u16 */ + fw_chksum = wdt87xx_calculate_checksum(data, len, 0); + if (dev_chksum == fw_chksum) + return 0; + + dev_err(&client->dev, "checksum fail: %d vs %d\n", + dev_chksum, fw_chksum); + return -EAGAIN; +} + +static int wdt8752_flash_write_sector(struct i2c_client *client, u8 *data, + u32 addr, size_t len) +{ + int st_addr; + size_t pkt_size; + u8 *pdata; + int error; + u8 pkt_buf[PKT_BUF_SIZE]; + + /* Address and length should be 4 bytes aligned */ + if ((addr & 0x3) != 0 || (len & 0x3) != 0) { + dev_err(&client->dev, + "addr & len must be 4 bytes aligned %x, %zu\n", + addr, len); + return -EINVAL; + } + + st_addr = addr; + pdata = data; + + put_unaligned_le32(addr, &pkt_buf[0]); + + /* initialize the programming address first */ + error = wdt8752_exec_write_pkt(client, W8752_SET_FLASH_ADDRESS, pkt_buf, + sizeof(u32)); + if (error) { + dev_err(&client->dev, "failed to set flash address: 0x%x\n", + addr); + return error; + } + + while (len) { + pkt_size = min_t(size_t, len, W8752_PKT_SIZE); + + error = wdt8752_exec_write_pkt(client, W8752_SET_FLASH, pdata, + pkt_size); + if (error) { + dev_dbg(&client->dev, "failed to program flash: 0x%x\n", + st_addr); + return error; + } + + len -= pkt_size; + pdata += pkt_size; + st_addr += pkt_size; + + udelay(W8752_FLASH_WRITE_DELAY_US); + } + + return 0; +} + +static int wdt8752_write_data(struct i2c_client *client, const char *data, + u32 addr, size_t len) +{ + int error; + u8 *pdata = (u8 *)data; + u32 write_size; + + if (addr & (W8752_PROG_SECTOR_SIZE - 1)) { + dev_err(&client->dev, "start addr must be sector aligned\n"); + return -EINVAL; + } + + while (len) { + write_size = min_t(size_t, len, W8752_PROG_SECTOR_SIZE); + + error = wdt8752_flash_write_sector(client, pdata, addr, + write_size); + + if (error) + return error; + + pdata += W8752_PROG_SECTOR_SIZE; + addr += W8752_PROG_SECTOR_SIZE; + len -= write_size; + } + + return 0; +} + +static int wdt87xx_delay(struct i2c_client *client, u32 delay) +{ + /* + * According to the spec, 4k erase takes the longest time in operations + * and W8756 have to wait it at most 200 ms + */ + if (delay > W8756_ERASE4K_DELAY_MS) + delay = W8756_ERASE4K_DELAY_MS; + + if (delay > WDT_POLLING_PERIOD_MS) + msleep(delay); + else + udelay(delay * 1000); + + return 0; +} + static int wdt87xx_sw_reset(struct i2c_client *client) { int error; + struct wdt87xx_data *wdt = i2c_get_clientdata(client); dev_dbg(&client->dev, "resetting device now\n"); - error = wdt87xx_send_command(client, VND_CMD_RESET, 0); + error = wdt->send_cmd(client, VND_CMD_RESET, 0); + if (error) { dev_err(&client->dev, "reset failed\n"); return error; } /* Wait the device to be ready */ - msleep(WDT_FW_RESET_TIME); + msleep(WDT_FW_RESET_TIME_MS); return 0; } @@ -426,32 +879,16 @@ static const void *wdt87xx_get_fw_chunk(const struct firmware *fw, u32 id) chunk_size = get_unaligned_le32(fw->data + pos + FW_CHUNK_SIZE_OFFSET); - pos += chunk_size + 2 * sizeof(u32); /* chunk ID + size */ + /* chunk ID + size */ + pos += chunk_size + 2 * sizeof(u32); } return NULL; } -static int wdt87xx_get_sysparam(struct i2c_client *client, - struct wdt87xx_sys_param *param) +static void wdt87xx_parse_param(struct wdt87xx_data *wdt, u8 *buf, size_t len) { - u8 buf[PKT_READ_SIZE]; - int error; - - error = wdt87xx_get_desc(client, WDT_GD_DEVICE, buf, 18); - if (error) { - dev_err(&client->dev, "failed to get device desc\n"); - return error; - } - - param->vendor_id = get_unaligned_le16(buf + DEV_DESC_OFFSET_VID); - param->product_id = get_unaligned_le16(buf + DEV_DESC_OFFSET_PID); - - error = wdt87xx_get_string(client, STRIDX_PARAMETERS, buf, 34); - if (error) { - dev_err(&client->dev, "failed to get parameters\n"); - return error; - } + struct wdt87xx_param *param = &wdt->param; param->xmls_id1 = get_unaligned_le16(buf + CTL_PARAM_OFFSET_XMLS_ID1); param->xmls_id2 = get_unaligned_le16(buf + CTL_PARAM_OFFSET_XMLS_ID2); @@ -460,6 +897,9 @@ static int wdt87xx_get_sysparam(struct i2c_client *client, param->phy_w = get_unaligned_le16(buf + CTL_PARAM_OFFSET_PHY_W) / 10; param->phy_h = get_unaligned_le16(buf + CTL_PARAM_OFFSET_PHY_H) / 10; + /* Get the report mode */ + param->i2c_cfg = get_unaligned_le16(buf + CTL_PARAM_OFFSET_I2C_CFG); + /* Get the scaling factor of pixel to logical coordinate */ param->scaling_factor = get_unaligned_le16(buf + CTL_PARAM_OFFSET_FACTOR); @@ -467,34 +907,61 @@ static int wdt87xx_get_sysparam(struct i2c_client *client, param->max_x = MAX_UNIT_AXIS; param->max_y = DIV_ROUND_CLOSEST(MAX_UNIT_AXIS * param->phy_h, param->phy_w); +} - error = wdt87xx_get_string(client, STRIDX_PLATFORM_ID, buf, 8); - if (error) { - dev_err(&client->dev, "failed to get platform id\n"); +static int wdt87xx_get_param_hid(struct wdt87xx_data *wdt) +{ + u8 buf[PKT_READ_SIZE]; + int error; + struct i2c_client *client = wdt->client; + struct wdt87xx_param *param = &wdt->param; + + put_unaligned_le16(W8752_HID_DESC_ADDR, buf); + + error = wdt87xx_i2c_xfer(client, buf, 2, &wdt->hid_desc, + sizeof(wdt->hid_desc)); + if (error < 0) { + dev_err(&client->dev, "failed to get hid desc\n"); return error; } - param->plat_id = buf[1]; + param->vendor_id = wdt->hid_desc.vendor_id; + param->product_id = wdt->hid_desc.product_id; - buf[0] = 0xf2; - error = wdt87xx_get_feature(client, buf, 16); - if (error) { - dev_err(&client->dev, "failed to get firmware id\n"); + return 0; +} + +static int wdt87xx_get_param_private(struct wdt87xx_data *wdt) +{ + u8 buf[PKT_READ_SIZE]; + int error, str_len; + struct i2c_client *client = wdt->client; + struct wdt87xx_param *param = &wdt->param; + + error = wdt87xx_get_desc(client, WDT_GD_DEVICE, buf, 18); + if (error < 0) { + dev_err(&client->dev, "failed to get device desc\n"); return error; } - if (buf[0] != 0xf2) { - dev_err(&client->dev, "wrong id of fw response: 0x%x\n", - buf[0]); - return -EINVAL; + param->vendor_id = get_unaligned_le16(buf + DEV_DESC_OFFSET_VID); + param->product_id = get_unaligned_le16(buf + DEV_DESC_OFFSET_PID); + + str_len = wdt87xx_get_string(client, STRIDX_PARAMETERS, buf, 38); + if (str_len < 0) { + dev_err(&client->dev, "failed to get parameters\n"); + return str_len; } - param->fw_id = get_unaligned_le16(&buf[1]); + wdt87xx_parse_param(wdt, buf, str_len); - dev_info(&client->dev, - "fw_id: 0x%x, plat_id: 0x%x, xml_id1: %04x, xml_id2: %04x\n", - param->fw_id, param->plat_id, - param->xmls_id1, param->xmls_id2); + error = wdt87xx_get_string(client, STRIDX_PLATFORM_ID, buf, 8); + if (error < 0) { + dev_err(&client->dev, "failed to get platform id\n"); + return error; + } + + param->plat_id = buf[1]; return 0; } @@ -548,8 +1015,15 @@ static int wdt87xx_validate_firmware(struct wdt87xx_data *wdt, return 0; } -static int wdt87xx_validate_fw_chunk(const void *data, int id) +static int wdt87xx_validate_fw_chunk(struct i2c_client *client, + const void *data, int id) { + struct wdt87xx_data *wdt = i2c_get_clientdata(client); + + /* There is no fw_id tag could be checked in 8752 */ + if (wdt->plt_id == PLT_WDT8752) + return 0; + if (id == CHUNK_ID_FRWR) { u32 fw_id; @@ -562,37 +1036,37 @@ static int wdt87xx_validate_fw_chunk(const void *data, int id) } static int wdt87xx_write_data(struct i2c_client *client, const char *data, - u32 address, int length) + u32 addr, size_t len) { - u16 packet_size; + size_t pkt_size; int count = 0; int error; u8 pkt_buf[PKT_BUF_SIZE]; /* Address and length should be 4 bytes aligned */ - if ((address & 0x3) != 0 || (length & 0x3) != 0) { + if ((addr & 0x3) != 0 || (len & 0x3) != 0) { dev_err(&client->dev, - "addr & len must be 4 bytes aligned %x, %x\n", - address, length); + "addr & len must be 4 bytes aligned %x, %zu\n", + addr, len); return -EINVAL; } - while (length) { - packet_size = min(length, PACKET_SIZE); + while (len) { + pkt_size = min_t(size_t, len, PACKET_SIZE); pkt_buf[CMD_REPORT_ID_OFFSET] = VND_REQ_WRITE; pkt_buf[CMD_TYPE_OFFSET] = VND_SET_DATA; - put_unaligned_le16(packet_size, &pkt_buf[CMD_INDEX_OFFSET]); - put_unaligned_le32(address, &pkt_buf[CMD_LENGTH_OFFSET]); - memcpy(&pkt_buf[CMD_DATA_OFFSET], data, packet_size); + put_unaligned_le16(pkt_size, &pkt_buf[CMD_INDEX_OFFSET]); + put_unaligned_le32(addr, &pkt_buf[CMD_LENGTH_OFFSET]); + memcpy(&pkt_buf[CMD_DATA_OFFSET], data, pkt_size); error = wdt87xx_set_feature(client, pkt_buf, sizeof(pkt_buf)); if (error) return error; - length -= packet_size; - data += packet_size; - address += packet_size; + len -= pkt_size; + data += pkt_size; + addr += pkt_size; /* Wait for the controller to finish the write */ mdelay(WDT_FLASH_WRITE_DELAY_MS); @@ -606,61 +1080,30 @@ static int wdt87xx_write_data(struct i2c_client *client, const char *data, return 0; } -static u16 misr(u16 cur_value, u8 new_value) -{ - u32 a, b; - u32 bit0; - u32 y; +static int wdt87xx_checksum_check(struct i2c_client *client, const char *data, + u32 addr, size_t len) - a = cur_value; - b = new_value; - bit0 = a ^ (b & 1); - bit0 ^= a >> 1; - bit0 ^= a >> 2; - bit0 ^= a >> 4; - bit0 ^= a >> 5; - bit0 ^= a >> 7; - bit0 ^= a >> 11; - bit0 ^= a >> 15; - y = (a << 1) ^ b; - y = (y & ~1) | (bit0 & 1); - - return (u16)y; -} - -static u16 wdt87xx_calculate_checksum(const u8 *data, size_t length) -{ - u16 checksum = 0; - size_t i; - - for (i = 0; i < length; i++) - checksum = misr(checksum, data[i]); - - return checksum; -} - -static int wdt87xx_get_checksum(struct i2c_client *client, u16 *checksum, - u32 address, int length) { int error; int time_delay; u8 pkt_buf[PKT_BUF_SIZE]; u8 cmd_buf[CMD_BUF_SIZE]; + u16 dev_chksum, fw_chksum; - error = wdt87xx_send_command(client, VND_SET_CHECKSUM_LENGTH, length); + error = wdt87xx_send_command(client, VND_SET_CHECKSUM_LENGTH, len); if (error) { dev_err(&client->dev, "failed to set checksum length\n"); return error; } - error = wdt87xx_send_command(client, VND_SET_CHECKSUM_CALC, address); + error = wdt87xx_send_command(client, VND_SET_CHECKSUM_CALC, addr); if (error) { dev_err(&client->dev, "failed to set checksum address\n"); return error; } /* Wait the operation to complete */ - time_delay = DIV_ROUND_UP(length, 1024); + time_delay = DIV_ROUND_UP(len, 1024); msleep(time_delay * 30); memset(cmd_buf, 0, sizeof(cmd_buf)); @@ -680,82 +1123,81 @@ static int wdt87xx_get_checksum(struct i2c_client *client, u16 *checksum, return error; } - *checksum = get_unaligned_le16(&pkt_buf[CMD_DATA_OFFSET]); - return 0; + dev_chksum = get_unaligned_le16(&pkt_buf[CMD_DATA_OFFSET]); + + /* Calculate the checksum in bytes */ + fw_chksum = wdt87xx_calculate_checksum(data, len, 1); + if (dev_chksum == fw_chksum) + return 0; + + dev_err(&client->dev, + "checksum fail: %d vs %d\n", dev_chksum, fw_chksum); + return -EAGAIN; } static int wdt87xx_write_firmware(struct i2c_client *client, const void *chunk) { - u32 start_addr = get_unaligned_le32(chunk + FW_CHUNK_TGT_START_OFFSET); - u32 size = get_unaligned_le32(chunk + FW_CHUNK_PAYLOAD_LEN_OFFSET); + u32 st_addr = get_unaligned_le32(chunk + FW_CHUNK_TGT_START_OFFSET); + size_t len = get_unaligned_le32(chunk + FW_CHUNK_PAYLOAD_LEN_OFFSET); const void *data = chunk + FW_CHUNK_PAYLOAD_OFFSET; int error; int err1; - int page_size; + int pg_size; int retry = 0; - u16 device_checksum, firmware_checksum; + struct wdt87xx_data *wdt = i2c_get_clientdata(client); dev_dbg(&client->dev, "start 4k page program\n"); - error = wdt87xx_send_command(client, VND_CMD_STOP, MODE_STOP); + error = wdt->send_cmd(client, VND_CMD_STOP, MODE_STOP); if (error) { - dev_err(&client->dev, "stop report mode failed\n"); + dev_err(&client->dev, "failed to stop report\n"); return error; } - error = wdt87xx_send_command(client, VND_CMD_SFUNL, 0); + error = wdt->send_cmd(client, VND_CMD_SFUNL, 0); if (error) { - dev_err(&client->dev, "unlock failed\n"); + dev_err(&client->dev, "failed to unlock flash\n"); goto out_enable_reporting; } - mdelay(10); + msleep(20); - while (size) { - dev_dbg(&client->dev, "%s: %x, %x\n", __func__, - start_addr, size); + while (len) { + dev_dbg(&client->dev, "%s: %x, %zu\n", __func__, st_addr, len); - page_size = min_t(u32, size, PG_SIZE); - size -= page_size; + pg_size = min_t(size_t, len, PG_SIZE); for (retry = 0; retry < MAX_RETRIES; retry++) { - error = wdt87xx_send_command(client, VND_CMD_ERASE, - start_addr); + error = wdt->send_cmd(client, VND_CMD_ERASE, st_addr); if (error) { dev_err(&client->dev, - "erase failed at %#08x\n", start_addr); + "erase failed at %#08x\n", st_addr); break; } - msleep(WDT_FLASH_ERASE_DELAY_MS); - - error = wdt87xx_write_data(client, data, start_addr, - page_size); + error = wdt->delay(client, W8752_ERASE4K_DELAY_MS); if (error) { dev_err(&client->dev, - "write failed at %#08x (%d bytes)\n", - start_addr, page_size); + "delay failed at %#08x\n", st_addr); break; } - error = wdt87xx_get_checksum(client, &device_checksum, - start_addr, page_size); + error = wdt->write_flash(client, data, st_addr, + pg_size); if (error) { dev_err(&client->dev, - "failed to retrieve checksum for %#08x (len: %d)\n", - start_addr, page_size); + "write failed at %#08x (%d bytes)\n", + st_addr, pg_size); break; } - firmware_checksum = - wdt87xx_calculate_checksum(data, page_size); - - if (device_checksum == firmware_checksum) + error = wdt->chksum_check(client, data, st_addr, + pg_size); + if (error != -EAGAIN) break; - dev_err(&client->dev, - "checksum fail: %d vs %d, retry %d\n", - device_checksum, firmware_checksum, retry); + dev_err(&client->dev, "checksum retry (%d) at 0x%x\n", + retry, st_addr); } if (retry == MAX_RETRIES) { @@ -763,22 +1205,22 @@ static int wdt87xx_write_firmware(struct i2c_client *client, const void *chunk) error = -EIO; goto out_lock_device; } - - start_addr = start_addr + page_size; - data = data + page_size; + len -= pg_size; + st_addr += pg_size; + data += pg_size; } out_lock_device: - err1 = wdt87xx_send_command(client, VND_CMD_SFLCK, 0); + err1 = wdt->send_cmd(client, VND_CMD_SFLCK, 0); if (err1) - dev_err(&client->dev, "lock failed\n"); + dev_err(&client->dev, "failed to lock flash\n"); - mdelay(10); + msleep(20); out_enable_reporting: - err1 = wdt87xx_send_command(client, VND_CMD_START, 0); + err1 = wdt->send_cmd(client, VND_CMD_START, 0); if (err1) - dev_err(&client->dev, "start to report failed\n"); + dev_err(&client->dev, "failed to restart to report\n"); return error ? error : err1; } @@ -796,7 +1238,7 @@ static int wdt87xx_load_chunk(struct i2c_client *client, return -EINVAL; } - error = wdt87xx_validate_fw_chunk(chunk, ck_id); + error = wdt87xx_validate_fw_chunk(client, chunk, ck_id); if (error) { dev_err(&client->dev, "invalid chunk (type %d): %d\n", ck_id, error); @@ -814,6 +1256,70 @@ static int wdt87xx_load_chunk(struct i2c_client *client, return 0; } +static int wdt87xx_get_param(struct wdt87xx_data *wdt) +{ + u8 buf[PKT_READ_SIZE]; + int error; + struct i2c_client *client = wdt->client; + struct wdt87xx_param *param = &wdt->param; + u16 param_key; + + buf[CMD_REPORT_ID_OFFSET] = VND_REQ_CTRLER_INFO; + error = wdt87xx_get_feature(client, buf, PACKET_SIZE); + if (error) + dev_err(&client->dev, "failed to get i2c cfg\n"); + + param_key = get_unaligned_le16(buf + W8752_PARAM_KEY_OFFSET); + if (buf[CMD_REPORT_ID_OFFSET] == VND_REQ_CTRLER_INFO && + param_key == W8752_PARAM_KEY) { + param->plat_id = buf[W8752_PLAT_ID_OFFSET]; + wdt87xx_parse_param(wdt, buf + W8752_PARAM_OFFSET, + get_unaligned_le16(buf + + W8752_PARAM_LEN_OFFSET)); + wdt->plt_id = PLT_WDT8752; + wdt->send_cmd = wdt8752_send_command; + wdt->write_flash = wdt8752_write_data; + wdt->delay = wdt8752_delay; + wdt->chksum_check = wdt8752_checksum_check; + error = wdt87xx_get_param_hid(wdt); + } else { + wdt->send_cmd = wdt87xx_send_command; + wdt->write_flash = wdt87xx_write_data; + wdt->delay = wdt87xx_delay; + wdt->chksum_check = wdt87xx_checksum_check; + error = wdt87xx_get_param_private(wdt); + } + + if (error < 0) + return error; + + dev_info(&client->dev, "pid: %04x, vid: %04x, w: %d, h: %d, i_sz: %d\n", + param->vendor_id, param->product_id, param->phy_w, + param->phy_h, wdt->hid_desc.max_input_length); + + buf[CMD_REPORT_ID_OFFSET] = VND_REQ_FW_INFO; + error = wdt87xx_get_feature(client, buf, 16); + if (error) { + dev_err(&client->dev, "failed to get firmware id\n"); + return error; + } + + if (buf[CMD_REPORT_ID_OFFSET] != VND_REQ_FW_INFO) { + dev_err(&client->dev, "wrong id of fw response: 0x%x\n", + buf[CMD_REPORT_ID_OFFSET]); + return -EINVAL; + } + + param->fw_id = get_unaligned_le16(&buf[1]); + + dev_info(&client->dev, + "fw_id: 0x%x, i2c_cfg: 0x%x, xml_id1: %04x, xml_id2: %04x\n", + param->fw_id, param->i2c_cfg, + param->xmls_id1, param->xmls_id2); + + return 0; +} + static int wdt87xx_do_update_firmware(struct i2c_client *client, const struct firmware *fw, unsigned int chunk_id) @@ -846,10 +1352,10 @@ static int wdt87xx_do_update_firmware(struct i2c_client *client, } /* Refresh the parameters */ - error = wdt87xx_get_sysparam(client, &wdt->param); + error = wdt87xx_get_param(wdt); if (error) dev_err(&client->dev, - "failed to refresh system parameters: %d\n", error); + "failed to refresh parameters: %d\n", error); out: enable_irq(client->irq); mutex_unlock(&wdt->fw_mutex); @@ -857,8 +1363,8 @@ out: return error ? error : 0; } -static int wdt87xx_update_firmware(struct device *dev, - const char *fw_name, unsigned int chunk_id) +static int wdt87xx_update_firmware(struct device *dev, const char *fw_name, + unsigned int chunk_id) { struct i2c_client *client = to_i2c_client(dev); const struct firmware *fw; @@ -950,30 +1456,29 @@ static const struct attribute_group wdt87xx_attr_group = { .attrs = wdt87xx_attrs, }; -static void wdt87xx_report_contact(struct input_dev *input, - struct wdt87xx_sys_param *param, - u8 *buf) +static void wdt87xx_report_contact(struct wdt87xx_data *wdt, + struct wdt87xx_param *param, u8 *buf) { + struct input_dev *input = wdt->input; int finger_id; u32 x, y, w; u8 p; - finger_id = (buf[FINGER_EV_V1_OFFSET_ID] >> 3) - 1; + finger_id = (buf[FINGER_EV_OFFSET_ID] >> 3) - 1; if (finger_id < 0) return; - /* Check if this is an active contact */ - if (!(buf[FINGER_EV_V1_OFFSET_ID] & 0x1)) + if (!(buf[FINGER_EV_OFFSET_ID] & 0x1)) return; - w = buf[FINGER_EV_V1_OFFSET_W]; + w = buf[FINGER_EV_OFFSET_W]; w *= param->scaling_factor; - p = buf[FINGER_EV_V1_OFFSET_P]; + p = buf[FINGER_EV_OFFSET_P]; - x = get_unaligned_le16(buf + FINGER_EV_V1_OFFSET_X); + x = get_unaligned_le16(buf + FINGER_EV_OFFSET_X); - y = get_unaligned_le16(buf + FINGER_EV_V1_OFFSET_Y); + y = get_unaligned_le16(buf + FINGER_EV_OFFSET_Y); y = DIV_ROUND_CLOSEST(y * param->phy_h, param->phy_w); /* Refuse incorrect coordinates */ @@ -997,23 +1502,30 @@ static irqreturn_t wdt87xx_ts_interrupt(int irq, void *dev_id) struct i2c_client *client = wdt->client; int i, fingers; int error; - u8 raw_buf[WDT_V1_RAW_BUF_COUNT] = {0}; + int offset = 0; + u8 raw_buf[WDT_RAW_BUF_COUNT] = {0}; + + if (wdt->hid_desc.max_input_length) { + offset = 2; + error = i2c_master_recv(client, raw_buf, + wdt->hid_desc.max_input_length); + } else { + error = i2c_master_recv(client, raw_buf, WDT_RAW_BUF_COUNT); + } - error = i2c_master_recv(client, raw_buf, WDT_V1_RAW_BUF_COUNT); if (error < 0) { - dev_err(&client->dev, "read v1 raw data failed: %d\n", error); + dev_err(&client->dev, "read raw data failed: %d\n", error); goto irq_exit; } - fingers = raw_buf[TOUCH_PK_V1_OFFSET_FNGR_NUM]; + fingers = raw_buf[offset + TOUCH_PK_OFFSET_FNGR_NUM]; if (!fingers) goto irq_exit; for (i = 0; i < WDT_MAX_FINGER; i++) - wdt87xx_report_contact(wdt->input, - &wdt->param, - &raw_buf[TOUCH_PK_V1_OFFSET_EVENT + - i * FINGER_EV_V1_SIZE]); + wdt87xx_report_contact(wdt, &wdt->param, + &raw_buf[offset + TOUCH_PK_OFFSET_EVENT + + i * FINGER_EV_SIZE]); input_mt_sync_frame(wdt->input); input_sync(wdt->input); @@ -1030,6 +1542,7 @@ static int wdt87xx_ts_create_input_device(struct wdt87xx_data *wdt) int error; input = devm_input_allocate_device(dev); + if (!input) { dev_err(dev, "failed to allocate input device\n"); return -ENOMEM; @@ -1089,7 +1602,7 @@ static int wdt87xx_ts_probe(struct i2c_client *client, snprintf(wdt->phys, sizeof(wdt->phys), "i2c-%u-%04x/input0", client->adapter->nr, client->addr); - error = wdt87xx_get_sysparam(client, &wdt->param); + error = wdt87xx_get_param(wdt); if (error) return error; @@ -1106,6 +1619,12 @@ static int wdt87xx_ts_probe(struct i2c_client *client, return error; } + error = device_init_wakeup(&client->dev, true); + if (error) { + dev_err(&client->dev, "inii wakeup failed: %d\n", error); + return error; + } + error = sysfs_create_group(&client->dev.kobj, &wdt87xx_attr_group); if (error) { dev_err(&client->dev, "create sysfs failed: %d\n", error); @@ -1125,16 +1644,22 @@ static int wdt87xx_ts_remove(struct i2c_client *client) static int __maybe_unused wdt87xx_suspend(struct device *dev) { struct i2c_client *client = to_i2c_client(dev); + struct wdt87xx_data *wdt = i2c_get_clientdata(client); int error; + int mode = MODE_IDLE; disable_irq(client->irq); - error = wdt87xx_send_command(client, VND_CMD_STOP, MODE_IDLE); + if (device_may_wakeup(dev)) + wdt->wake_irq_enabled = (enable_irq_wake(client->irq) == 0); + else if (wdt->plt_id == PLT_WDT8752) + mode = MODE_SLEEP; + + error = wdt->send_cmd(client, VND_CMD_STOP, mode); if (error) { enable_irq(client->irq); dev_err(&client->dev, - "failed to stop device when suspending: %d\n", - error); + "failed to stop device when suspending: %d\n", error); return error; } @@ -1144,19 +1669,28 @@ static int __maybe_unused wdt87xx_suspend(struct device *dev) static int __maybe_unused wdt87xx_resume(struct device *dev) { struct i2c_client *client = to_i2c_client(dev); + struct wdt87xx_data *wdt = i2c_get_clientdata(client); + u8 raw_buf[3] = {0}; int error; + if (device_may_wakeup(dev)) { + if (wdt->wake_irq_enabled) + disable_irq_wake(client->irq); + } else { + /* WDT8752 should wakeup device by read operation first */ + if (wdt->plt_id == PLT_WDT8752) + i2c_master_recv(client, raw_buf, 3); + } /* * The chip may have been reset while system is resuming, * give it some time to settle. */ mdelay(100); - error = wdt87xx_send_command(client, VND_CMD_START, 0); + error = wdt->send_cmd(client, VND_CMD_START, 0); if (error) dev_err(&client->dev, - "failed to start device when resuming: %d\n", - error); + "failed to start device when resuming: %d\n", error); enable_irq(client->irq); @@ -1169,6 +1703,7 @@ static const struct i2c_device_id wdt87xx_dev_id[] = { { WDT87XX_NAME, 0 }, { } }; + MODULE_DEVICE_TABLE(i2c, wdt87xx_dev_id); static const struct acpi_device_id wdt87xx_acpi_id[] = { -- 1.9.1 -- To unsubscribe from this list: send the line "unsubscribe linux-input" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html