diff options
Diffstat (limited to 'drivers/input/mouse/cyapa.c')
-rw-r--r-- | drivers/input/mouse/cyapa.c | 1076 |
1 files changed, 411 insertions, 665 deletions
diff --git a/drivers/input/mouse/cyapa.c b/drivers/input/mouse/cyapa.c index 1bece8cad46f..36c64335662c 100644 --- a/drivers/input/mouse/cyapa.c +++ b/drivers/input/mouse/cyapa.c @@ -20,408 +20,127 @@ #include <linux/input/mt.h> #include <linux/interrupt.h> #include <linux/module.h> +#include <linux/mutex.h> #include <linux/slab.h> +#include <linux/uaccess.h> +#include "cyapa.h" -/* APA trackpad firmware generation */ -#define CYAPA_GEN3 0x03 /* support MT-protocol B with tracking ID. */ - -#define CYAPA_NAME "Cypress APA Trackpad (cyapa)" - -/* commands for read/write registers of Cypress trackpad */ -#define CYAPA_CMD_SOFT_RESET 0x00 -#define CYAPA_CMD_POWER_MODE 0x01 -#define CYAPA_CMD_DEV_STATUS 0x02 -#define CYAPA_CMD_GROUP_DATA 0x03 -#define CYAPA_CMD_GROUP_CMD 0x04 -#define CYAPA_CMD_GROUP_QUERY 0x05 -#define CYAPA_CMD_BL_STATUS 0x06 -#define CYAPA_CMD_BL_HEAD 0x07 -#define CYAPA_CMD_BL_CMD 0x08 -#define CYAPA_CMD_BL_DATA 0x09 -#define CYAPA_CMD_BL_ALL 0x0a -#define CYAPA_CMD_BLK_PRODUCT_ID 0x0b -#define CYAPA_CMD_BLK_HEAD 0x0c - -/* report data start reg offset address. */ -#define DATA_REG_START_OFFSET 0x0000 - -#define BL_HEAD_OFFSET 0x00 -#define BL_DATA_OFFSET 0x10 - -/* - * Operational Device Status Register - * - * bit 7: Valid interrupt source - * bit 6 - 4: Reserved - * bit 3 - 2: Power status - * bit 1 - 0: Device status - */ -#define REG_OP_STATUS 0x00 -#define OP_STATUS_SRC 0x80 -#define OP_STATUS_POWER 0x0c -#define OP_STATUS_DEV 0x03 -#define OP_STATUS_MASK (OP_STATUS_SRC | OP_STATUS_POWER | OP_STATUS_DEV) - -/* - * Operational Finger Count/Button Flags Register - * - * bit 7 - 4: Number of touched finger - * bit 3: Valid data - * bit 2: Middle Physical Button - * bit 1: Right Physical Button - * bit 0: Left physical Button - */ -#define REG_OP_DATA1 0x01 -#define OP_DATA_VALID 0x08 -#define OP_DATA_MIDDLE_BTN 0x04 -#define OP_DATA_RIGHT_BTN 0x02 -#define OP_DATA_LEFT_BTN 0x01 -#define OP_DATA_BTN_MASK (OP_DATA_MIDDLE_BTN | OP_DATA_RIGHT_BTN | \ - OP_DATA_LEFT_BTN) - -/* - * Bootloader Status Register - * - * bit 7: Busy - * bit 6 - 5: Reserved - * bit 4: Bootloader running - * bit 3 - 1: Reserved - * bit 0: Checksum valid - */ -#define REG_BL_STATUS 0x01 -#define BL_STATUS_BUSY 0x80 -#define BL_STATUS_RUNNING 0x10 -#define BL_STATUS_DATA_VALID 0x08 -#define BL_STATUS_CSUM_VALID 0x01 - -/* - * Bootloader Error Register - * - * bit 7: Invalid - * bit 6: Invalid security key - * bit 5: Bootloading - * bit 4: Command checksum - * bit 3: Flash protection error - * bit 2: Flash checksum error - * bit 1 - 0: Reserved - */ -#define REG_BL_ERROR 0x02 -#define BL_ERROR_INVALID 0x80 -#define BL_ERROR_INVALID_KEY 0x40 -#define BL_ERROR_BOOTLOADING 0x20 -#define BL_ERROR_CMD_CSUM 0x10 -#define BL_ERROR_FLASH_PROT 0x08 -#define BL_ERROR_FLASH_CSUM 0x04 - -#define BL_STATUS_SIZE 3 /* length of bootloader status registers */ -#define BLK_HEAD_BYTES 32 - -#define PRODUCT_ID_SIZE 16 -#define QUERY_DATA_SIZE 27 -#define REG_PROTOCOL_GEN_QUERY_OFFSET 20 - -#define REG_OFFSET_DATA_BASE 0x0000 -#define REG_OFFSET_COMMAND_BASE 0x0028 -#define REG_OFFSET_QUERY_BASE 0x002a - -#define CAPABILITY_LEFT_BTN_MASK (0x01 << 3) -#define CAPABILITY_RIGHT_BTN_MASK (0x01 << 4) -#define CAPABILITY_MIDDLE_BTN_MASK (0x01 << 5) -#define CAPABILITY_BTN_MASK (CAPABILITY_LEFT_BTN_MASK | \ - CAPABILITY_RIGHT_BTN_MASK | \ - CAPABILITY_MIDDLE_BTN_MASK) - -#define CYAPA_OFFSET_SOFT_RESET REG_OFFSET_COMMAND_BASE - -#define REG_OFFSET_POWER_MODE (REG_OFFSET_COMMAND_BASE + 1) - -#define PWR_MODE_MASK 0xfc -#define PWR_MODE_FULL_ACTIVE (0x3f << 2) -#define PWR_MODE_IDLE (0x05 << 2) /* default sleep time is 50 ms. */ -#define PWR_MODE_OFF (0x00 << 2) - -#define PWR_STATUS_MASK 0x0c -#define PWR_STATUS_ACTIVE (0x03 << 2) -#define PWR_STATUS_IDLE (0x02 << 2) -#define PWR_STATUS_OFF (0x00 << 2) - -/* - * CYAPA trackpad device states. - * Used in register 0x00, bit1-0, DeviceStatus field. - * Other values indicate device is in an abnormal state and must be reset. - */ -#define CYAPA_DEV_NORMAL 0x03 -#define CYAPA_DEV_BUSY 0x01 - -enum cyapa_state { - CYAPA_STATE_OP, - CYAPA_STATE_BL_IDLE, - CYAPA_STATE_BL_ACTIVE, - CYAPA_STATE_BL_BUSY, - CYAPA_STATE_NO_DEVICE, -}; - - -struct cyapa_touch { - /* - * high bits or x/y position value - * bit 7 - 4: high 4 bits of x position value - * bit 3 - 0: high 4 bits of y position value - */ - u8 xy_hi; - u8 x_lo; /* low 8 bits of x position value. */ - u8 y_lo; /* low 8 bits of y position value. */ - u8 pressure; - /* id range is 1 - 15. It is incremented with every new touch. */ - u8 id; -} __packed; - -/* The touch.id is used as the MT slot id, thus max MT slot is 15 */ -#define CYAPA_MAX_MT_SLOTS 15 - -struct cyapa_reg_data { - /* - * bit 0 - 1: device status - * bit 3 - 2: power mode - * bit 6 - 4: reserved - * bit 7: interrupt valid bit - */ - u8 device_status; - /* - * bit 7 - 4: number of fingers currently touching pad - * bit 3: valid data check bit - * bit 2: middle mechanism button state if exists - * bit 1: right mechanism button state if exists - * bit 0: left mechanism button state if exists - */ - u8 finger_btn; - /* CYAPA reports up to 5 touches per packet. */ - struct cyapa_touch touches[5]; -} __packed; - -/* The main device structure */ -struct cyapa { - enum cyapa_state state; - - struct i2c_client *client; - struct input_dev *input; - char phys[32]; /* device physical location */ - bool irq_wake; /* irq wake is enabled */ - bool smbus; - - /* read from query data region. */ - char product_id[16]; - u8 btn_capability; - u8 gen; - int max_abs_x; - int max_abs_y; - int physical_size_x; - int physical_size_y; -}; - -static const u8 bl_deactivate[] = { 0x00, 0xff, 0x3b, 0x00, 0x01, 0x02, 0x03, - 0x04, 0x05, 0x06, 0x07 }; -static const u8 bl_exit[] = { 0x00, 0xff, 0xa5, 0x00, 0x01, 0x02, 0x03, 0x04, - 0x05, 0x06, 0x07 }; - -struct cyapa_cmd_len { - u8 cmd; - u8 len; -}; #define CYAPA_ADAPTER_FUNC_NONE 0 #define CYAPA_ADAPTER_FUNC_I2C 1 #define CYAPA_ADAPTER_FUNC_SMBUS 2 #define CYAPA_ADAPTER_FUNC_BOTH 3 -/* - * macros for SMBus communication - */ -#define SMBUS_READ 0x01 -#define SMBUS_WRITE 0x00 -#define SMBUS_ENCODE_IDX(cmd, idx) ((cmd) | (((idx) & 0x03) << 1)) -#define SMBUS_ENCODE_RW(cmd, rw) ((cmd) | ((rw) & 0x01)) -#define SMBUS_BYTE_BLOCK_CMD_MASK 0x80 -#define SMBUS_GROUP_BLOCK_CMD_MASK 0x40 - - /* for byte read/write command */ -#define CMD_RESET 0 -#define CMD_POWER_MODE 1 -#define CMD_DEV_STATUS 2 -#define SMBUS_BYTE_CMD(cmd) (((cmd) & 0x3f) << 1) -#define CYAPA_SMBUS_RESET SMBUS_BYTE_CMD(CMD_RESET) -#define CYAPA_SMBUS_POWER_MODE SMBUS_BYTE_CMD(CMD_POWER_MODE) -#define CYAPA_SMBUS_DEV_STATUS SMBUS_BYTE_CMD(CMD_DEV_STATUS) - - /* for group registers read/write command */ -#define REG_GROUP_DATA 0 -#define REG_GROUP_CMD 2 -#define REG_GROUP_QUERY 3 -#define SMBUS_GROUP_CMD(grp) (0x80 | (((grp) & 0x07) << 3)) -#define CYAPA_SMBUS_GROUP_DATA SMBUS_GROUP_CMD(REG_GROUP_DATA) -#define CYAPA_SMBUS_GROUP_CMD SMBUS_GROUP_CMD(REG_GROUP_CMD) -#define CYAPA_SMBUS_GROUP_QUERY SMBUS_GROUP_CMD(REG_GROUP_QUERY) - - /* for register block read/write command */ -#define CMD_BL_STATUS 0 -#define CMD_BL_HEAD 1 -#define CMD_BL_CMD 2 -#define CMD_BL_DATA 3 -#define CMD_BL_ALL 4 -#define CMD_BLK_PRODUCT_ID 5 -#define CMD_BLK_HEAD 6 -#define SMBUS_BLOCK_CMD(cmd) (0xc0 | (((cmd) & 0x1f) << 1)) - -/* register block read/write command in bootloader mode */ -#define CYAPA_SMBUS_BL_STATUS SMBUS_BLOCK_CMD(CMD_BL_STATUS) -#define CYAPA_SMBUS_BL_HEAD SMBUS_BLOCK_CMD(CMD_BL_HEAD) -#define CYAPA_SMBUS_BL_CMD SMBUS_BLOCK_CMD(CMD_BL_CMD) -#define CYAPA_SMBUS_BL_DATA SMBUS_BLOCK_CMD(CMD_BL_DATA) -#define CYAPA_SMBUS_BL_ALL SMBUS_BLOCK_CMD(CMD_BL_ALL) - -/* register block read/write command in operational mode */ -#define CYAPA_SMBUS_BLK_PRODUCT_ID SMBUS_BLOCK_CMD(CMD_BLK_PRODUCT_ID) -#define CYAPA_SMBUS_BLK_HEAD SMBUS_BLOCK_CMD(CMD_BLK_HEAD) - -static const struct cyapa_cmd_len cyapa_i2c_cmds[] = { - { CYAPA_OFFSET_SOFT_RESET, 1 }, - { REG_OFFSET_COMMAND_BASE + 1, 1 }, - { REG_OFFSET_DATA_BASE, 1 }, - { REG_OFFSET_DATA_BASE, sizeof(struct cyapa_reg_data) }, - { REG_OFFSET_COMMAND_BASE, 0 }, - { REG_OFFSET_QUERY_BASE, QUERY_DATA_SIZE }, - { BL_HEAD_OFFSET, 3 }, - { BL_HEAD_OFFSET, 16 }, - { BL_HEAD_OFFSET, 16 }, - { BL_DATA_OFFSET, 16 }, - { BL_HEAD_OFFSET, 32 }, - { REG_OFFSET_QUERY_BASE, PRODUCT_ID_SIZE }, - { REG_OFFSET_DATA_BASE, 32 } -}; +const char product_id[] = "CYTRA"; -static const struct cyapa_cmd_len cyapa_smbus_cmds[] = { - { CYAPA_SMBUS_RESET, 1 }, - { CYAPA_SMBUS_POWER_MODE, 1 }, - { CYAPA_SMBUS_DEV_STATUS, 1 }, - { CYAPA_SMBUS_GROUP_DATA, sizeof(struct cyapa_reg_data) }, - { CYAPA_SMBUS_GROUP_CMD, 2 }, - { CYAPA_SMBUS_GROUP_QUERY, QUERY_DATA_SIZE }, - { CYAPA_SMBUS_BL_STATUS, 3 }, - { CYAPA_SMBUS_BL_HEAD, 16 }, - { CYAPA_SMBUS_BL_CMD, 16 }, - { CYAPA_SMBUS_BL_DATA, 16 }, - { CYAPA_SMBUS_BL_ALL, 32 }, - { CYAPA_SMBUS_BLK_PRODUCT_ID, PRODUCT_ID_SIZE }, - { CYAPA_SMBUS_BLK_HEAD, 16 }, -}; +static int cyapa_reinitialize(struct cyapa *cyapa); -static ssize_t cyapa_i2c_reg_read_block(struct cyapa *cyapa, u8 reg, size_t len, - u8 *values) +static inline bool cyapa_is_bootloader_mode(struct cyapa *cyapa) { - return i2c_smbus_read_i2c_block_data(cyapa->client, reg, len, values); + if (cyapa->gen == CYAPA_GEN5 && cyapa->state == CYAPA_STATE_GEN5_BL) + return true; + + if (cyapa->gen == CYAPA_GEN3 && + cyapa->state >= CYAPA_STATE_BL_BUSY && + cyapa->state <= CYAPA_STATE_BL_ACTIVE) + return true; + + return false; } -static ssize_t cyapa_i2c_reg_write_block(struct cyapa *cyapa, u8 reg, - size_t len, const u8 *values) +static inline bool cyapa_is_operational_mode(struct cyapa *cyapa) { - return i2c_smbus_write_i2c_block_data(cyapa->client, reg, len, values); + if (cyapa->gen == CYAPA_GEN5 && cyapa->state == CYAPA_STATE_GEN5_APP) + return true; + + if (cyapa->gen == CYAPA_GEN3 && cyapa->state == CYAPA_STATE_OP) + return true; + + return false; } -/* - * cyapa_smbus_read_block - perform smbus block read command - * @cyapa - private data structure of the driver - * @cmd - the properly encoded smbus command - * @len - expected length of smbus command result - * @values - buffer to store smbus command result - * - * Returns negative errno, else the number of bytes written. - * - * Note: - * In trackpad device, the memory block allocated for I2C register map - * is 256 bytes, so the max read block for I2C bus is 256 bytes. - */ -static ssize_t cyapa_smbus_read_block(struct cyapa *cyapa, u8 cmd, size_t len, - u8 *values) +/* Returns 0 on success, else negative errno on failure. */ +static ssize_t cyapa_i2c_read(struct cyapa *cyapa, u8 reg, size_t len, + u8 *values) { - ssize_t ret; - u8 index; - u8 smbus_cmd; - u8 *buf; struct i2c_client *client = cyapa->client; + struct i2c_msg msgs[] = { + { + .addr = client->addr, + .flags = 0, + .len = 1, + .buf = ®, + }, + { + .addr = client->addr, + .flags = I2C_M_RD, + .len = len, + .buf = values, + }, + }; + int ret; - if (!(SMBUS_BYTE_BLOCK_CMD_MASK & cmd)) - return -EINVAL; - - if (SMBUS_GROUP_BLOCK_CMD_MASK & cmd) { - /* read specific block registers command. */ - smbus_cmd = SMBUS_ENCODE_RW(cmd, SMBUS_READ); - ret = i2c_smbus_read_block_data(client, smbus_cmd, values); - goto out; - } + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); - ret = 0; - for (index = 0; index * I2C_SMBUS_BLOCK_MAX < len; index++) { - smbus_cmd = SMBUS_ENCODE_IDX(cmd, index); - smbus_cmd = SMBUS_ENCODE_RW(smbus_cmd, SMBUS_READ); - buf = values + I2C_SMBUS_BLOCK_MAX * index; - ret = i2c_smbus_read_block_data(client, smbus_cmd, buf); - if (ret < 0) - goto out; - } + if (ret != ARRAY_SIZE(msgs)) + return ret < 0 ? ret : -EIO; -out: - return ret > 0 ? len : ret; + return 0; } -static s32 cyapa_read_byte(struct cyapa *cyapa, u8 cmd_idx) +/** + * cyapa_i2c_write - Execute i2c block data write operation + * @cyapa: Handle to this driver + * @ret: Offset of the data to written in the register map + * @len: number of bytes to write + * @values: Data to be written + * + * Return negative errno code on error; return zero when success. + */ +static int cyapa_i2c_write(struct cyapa *cyapa, u8 reg, + size_t len, const void *values) { - u8 cmd; + struct i2c_client *client = cyapa->client; + char buf[32]; + int ret; - if (cyapa->smbus) { - cmd = cyapa_smbus_cmds[cmd_idx].cmd; - cmd = SMBUS_ENCODE_RW(cmd, SMBUS_READ); - } else { - cmd = cyapa_i2c_cmds[cmd_idx].cmd; - } - return i2c_smbus_read_byte_data(cyapa->client, cmd); -} + if (len > sizeof(buf) - 1) + return -ENOMEM; -static s32 cyapa_write_byte(struct cyapa *cyapa, u8 cmd_idx, u8 value) -{ - u8 cmd; + buf[0] = reg; + memcpy(&buf[1], values, len); - if (cyapa->smbus) { - cmd = cyapa_smbus_cmds[cmd_idx].cmd; - cmd = SMBUS_ENCODE_RW(cmd, SMBUS_WRITE); - } else { - cmd = cyapa_i2c_cmds[cmd_idx].cmd; - } - return i2c_smbus_write_byte_data(cyapa->client, cmd, value); + ret = i2c_master_send(client, buf, len + 1); + if (ret != len + 1) + return ret < 0 ? ret : -EIO; + + return 0; } -static ssize_t cyapa_read_block(struct cyapa *cyapa, u8 cmd_idx, u8 *values) +static u8 cyapa_check_adapter_functionality(struct i2c_client *client) { - u8 cmd; - size_t len; + u8 ret = CYAPA_ADAPTER_FUNC_NONE; - if (cyapa->smbus) { - cmd = cyapa_smbus_cmds[cmd_idx].cmd; - len = cyapa_smbus_cmds[cmd_idx].len; - return cyapa_smbus_read_block(cyapa, cmd, len, values); - } else { - cmd = cyapa_i2c_cmds[cmd_idx].cmd; - len = cyapa_i2c_cmds[cmd_idx].len; - return cyapa_i2c_reg_read_block(cyapa, cmd, len, values); - } + if (i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) + ret |= CYAPA_ADAPTER_FUNC_I2C; + if (i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA | + I2C_FUNC_SMBUS_BLOCK_DATA | + I2C_FUNC_SMBUS_I2C_BLOCK)) + ret |= CYAPA_ADAPTER_FUNC_SMBUS; + return ret; } /* * Query device for its current operating state. - * */ static int cyapa_get_state(struct cyapa *cyapa) { u8 status[BL_STATUS_SIZE]; + u8 cmd[32]; + /* The i2c address of gen4 and gen5 trackpad device must be even. */ + bool even_addr = ((cyapa->client->addr & 0x0001) == 0); + bool smbus = false; + int retries = 2; int error; cyapa->state = CYAPA_STATE_NO_DEVICE; @@ -433,39 +152,66 @@ static int cyapa_get_state(struct cyapa *cyapa) * */ error = cyapa_i2c_reg_read_block(cyapa, BL_HEAD_OFFSET, BL_STATUS_SIZE, - status); + status); /* * On smbus systems in OP mode, the i2c_reg_read will fail with * -ETIMEDOUT. In this case, try again using the smbus equivalent * command. This should return a BL_HEAD indicating CYAPA_STATE_OP. */ - if (cyapa->smbus && (error == -ETIMEDOUT || error == -ENXIO)) - error = cyapa_read_block(cyapa, CYAPA_CMD_BL_STATUS, status); + if (cyapa->smbus && (error == -ETIMEDOUT || error == -ENXIO)) { + if (!even_addr) + error = cyapa_read_block(cyapa, + CYAPA_CMD_BL_STATUS, status); + smbus = true; + } if (error != BL_STATUS_SIZE) goto error; - if ((status[REG_OP_STATUS] & OP_STATUS_SRC) == OP_STATUS_SRC) { - switch (status[REG_OP_STATUS] & OP_STATUS_DEV) { - case CYAPA_DEV_NORMAL: - case CYAPA_DEV_BUSY: - cyapa->state = CYAPA_STATE_OP; - break; - default: - error = -EAGAIN; - goto error; + /* + * Detect trackpad protocol based on characteristic registers and bits. + */ + do { + cyapa->status[REG_OP_STATUS] = status[REG_OP_STATUS]; + cyapa->status[REG_BL_STATUS] = status[REG_BL_STATUS]; + cyapa->status[REG_BL_ERROR] = status[REG_BL_ERROR]; + + if (cyapa->gen == CYAPA_GEN_UNKNOWN || + cyapa->gen == CYAPA_GEN3) { + error = cyapa_gen3_ops.state_parse(cyapa, + status, BL_STATUS_SIZE); + if (!error) + goto out_detected; } - } else { - if (status[REG_BL_STATUS] & BL_STATUS_BUSY) - cyapa->state = CYAPA_STATE_BL_BUSY; - else if (status[REG_BL_ERROR] & BL_ERROR_BOOTLOADING) - cyapa->state = CYAPA_STATE_BL_ACTIVE; - else - cyapa->state = CYAPA_STATE_BL_IDLE; - } + /* + * Write 0x00 0x00 to trackpad device to force update its + * status, then redo the detection again. + */ + if (!smbus) { + cmd[0] = 0x00; + cmd[1] = 0x00; + error = cyapa_i2c_write(cyapa, 0, 2, cmd); + if (error) + goto error; + + msleep(50); + + error = cyapa_i2c_read(cyapa, BL_HEAD_OFFSET, + BL_STATUS_SIZE, status); + if (error) + goto error; + } + } while (--retries > 0 && !smbus); + + goto error; + +out_detected: + if (cyapa->state <= CYAPA_STATE_BL_BUSY) + return -EAGAIN; return 0; + error: return (error < 0) ? error : -EAGAIN; } @@ -482,143 +228,23 @@ error: * Returns: * 0 when the device eventually responds with a valid non-busy state. * -ETIMEDOUT if device never responds (too many -EAGAIN) - * < 0 other errors + * -EAGAIN if bootload is busy, or unknown state. + * < 0 other errors */ -static int cyapa_poll_state(struct cyapa *cyapa, unsigned int timeout) +int cyapa_poll_state(struct cyapa *cyapa, unsigned int timeout) { int error; int tries = timeout / 100; - error = cyapa_get_state(cyapa); - while ((error || cyapa->state >= CYAPA_STATE_BL_BUSY) && tries--) { - msleep(100); + do { error = cyapa_get_state(cyapa); - } - return (error == -EAGAIN || error == -ETIMEDOUT) ? -ETIMEDOUT : error; -} + if (!error && cyapa->state > CYAPA_STATE_BL_BUSY) + return 0; -static int cyapa_bl_deactivate(struct cyapa *cyapa) -{ - int error; - - error = cyapa_i2c_reg_write_block(cyapa, 0, sizeof(bl_deactivate), - bl_deactivate); - if (error) - return error; - - /* wait for bootloader to switch to idle state; should take < 100ms */ - msleep(100); - error = cyapa_poll_state(cyapa, 500); - if (error) - return error; - if (cyapa->state != CYAPA_STATE_BL_IDLE) - return -EAGAIN; - return 0; -} - -/* - * Exit bootloader - * - * Send bl_exit command, then wait 50 - 100 ms to let device transition to - * operational mode. If this is the first time the device's firmware is - * running, it can take up to 2 seconds to calibrate its sensors. So, poll - * the device's new state for up to 2 seconds. - * - * Returns: - * -EIO failure while reading from device - * -EAGAIN device is stuck in bootloader, b/c it has invalid firmware - * 0 device is supported and in operational mode - */ -static int cyapa_bl_exit(struct cyapa *cyapa) -{ - int error; - - error = cyapa_i2c_reg_write_block(cyapa, 0, sizeof(bl_exit), bl_exit); - if (error) - return error; - - /* - * Wait for bootloader to exit, and operation mode to start. - * Normally, this takes at least 50 ms. - */ - usleep_range(50000, 100000); - /* - * In addition, when a device boots for the first time after being - * updated to new firmware, it must first calibrate its sensors, which - * can take up to an additional 2 seconds. - */ - error = cyapa_poll_state(cyapa, 2000); - if (error < 0) - return error; - if (cyapa->state != CYAPA_STATE_OP) - return -EAGAIN; - - return 0; -} - -/* - * Set device power mode - * - */ -static int cyapa_set_power_mode(struct cyapa *cyapa, u8 power_mode) -{ - struct device *dev = &cyapa->client->dev; - int ret; - u8 power; - - if (cyapa->state != CYAPA_STATE_OP) - return 0; - - ret = cyapa_read_byte(cyapa, CYAPA_CMD_POWER_MODE); - if (ret < 0) - return ret; - - power = ret & ~PWR_MODE_MASK; - power |= power_mode & PWR_MODE_MASK; - ret = cyapa_write_byte(cyapa, CYAPA_CMD_POWER_MODE, power); - if (ret < 0) { - dev_err(dev, "failed to set power_mode 0x%02x err = %d\n", - power_mode, ret); - return ret; - } - - return 0; -} - -static int cyapa_get_query_data(struct cyapa *cyapa) -{ - u8 query_data[QUERY_DATA_SIZE]; - int ret; - - if (cyapa->state != CYAPA_STATE_OP) - return -EBUSY; - - ret = cyapa_read_block(cyapa, CYAPA_CMD_GROUP_QUERY, query_data); - if (ret < 0) - return ret; - if (ret != QUERY_DATA_SIZE) - return -EIO; - - memcpy(&cyapa->product_id[0], &query_data[0], 5); - cyapa->product_id[5] = '-'; - memcpy(&cyapa->product_id[6], &query_data[5], 6); - cyapa->product_id[12] = '-'; - memcpy(&cyapa->product_id[13], &query_data[11], 2); - cyapa->product_id[15] = '\0'; - - cyapa->btn_capability = query_data[19] & CAPABILITY_BTN_MASK; - - cyapa->gen = query_data[20] & 0x0f; - - cyapa->max_abs_x = ((query_data[21] & 0xf0) << 4) | query_data[22]; - cyapa->max_abs_y = ((query_data[21] & 0x0f) << 8) | query_data[23]; - - cyapa->physical_size_x = - ((query_data[24] & 0xf0) << 4) | query_data[25]; - cyapa->physical_size_y = - ((query_data[24] & 0x0f) << 8) | query_data[26]; + msleep(100); + } while (tries--); - return 0; + return (error == -EAGAIN || error == -ETIMEDOUT) ? -ETIMEDOUT : error; } /* @@ -628,8 +254,10 @@ static int cyapa_get_query_data(struct cyapa *cyapa) * firmware supported by this driver. * * Returns: + * -ENODEV no device * -EBUSY no device or in bootloader * -EIO failure while reading from device + * -ETIMEDOUT timeout failure for bus idle or bus no response * -EAGAIN device is still in bootloader * if ->state = CYAPA_STATE_BL_IDLE, device has invalid firmware * -EINVAL device is in operational mode, but not supported by this driver @@ -637,122 +265,53 @@ static int cyapa_get_query_data(struct cyapa *cyapa) */ static int cyapa_check_is_operational(struct cyapa *cyapa) { - struct device *dev = &cyapa->client->dev; - static const char unique_str[] = "CYTRA"; int error; - error = cyapa_poll_state(cyapa, 2000); + error = cyapa_poll_state(cyapa, 4000); if (error) return error; - switch (cyapa->state) { - case CYAPA_STATE_BL_ACTIVE: - error = cyapa_bl_deactivate(cyapa); - if (error) - return error; - - /* Fallthrough state */ - case CYAPA_STATE_BL_IDLE: - error = cyapa_bl_exit(cyapa); - if (error) - return error; - - /* Fallthrough state */ - case CYAPA_STATE_OP: - error = cyapa_get_query_data(cyapa); - if (error) - return error; - - /* only support firmware protocol gen3 */ - if (cyapa->gen != CYAPA_GEN3) { - dev_err(dev, "unsupported protocol version (%d)", - cyapa->gen); - return -EINVAL; - } - - /* only support product ID starting with CYTRA */ - if (memcmp(cyapa->product_id, unique_str, - sizeof(unique_str) - 1) != 0) { - dev_err(dev, "unsupported product ID (%s)\n", - cyapa->product_id); - return -EINVAL; - } - return 0; + switch (cyapa->gen) { + case CYAPA_GEN3: + cyapa->ops = &cyapa_gen3_ops; + break; default: - return -EIO; + return -ENODEV; } - return 0; + + error = cyapa->ops->operational_check(cyapa); + if (!error && cyapa_is_operational_mode(cyapa)) + cyapa->operational = true; + else + cyapa->operational = false; + + return error; } -static irqreturn_t cyapa_irq(int irq, void *dev_id) + +/* + * Returns 0 on device detected, negative errno on no device detected. + * And when the device is detected and opertaional, it will be reset to + * full power active mode automatically. + */ +static int cyapa_detect(struct cyapa *cyapa) { - struct cyapa *cyapa = dev_id; struct device *dev = &cyapa->client->dev; - struct input_dev *input = cyapa->input; - struct cyapa_reg_data data; - int i; - int ret; - int num_fingers; - - if (device_may_wakeup(dev)) - pm_wakeup_event(dev, 0); - - ret = cyapa_read_block(cyapa, CYAPA_CMD_GROUP_DATA, (u8 *)&data); - if (ret != sizeof(data)) - goto out; + int error; - if ((data.device_status & OP_STATUS_SRC) != OP_STATUS_SRC || - (data.device_status & OP_STATUS_DEV) != CYAPA_DEV_NORMAL || - (data.finger_btn & OP_DATA_VALID) != OP_DATA_VALID) { - goto out; - } + error = cyapa_check_is_operational(cyapa); + if (error) { + if (error != -ETIMEDOUT && error != -ENODEV && + cyapa_is_bootloader_mode(cyapa)) { + dev_warn(dev, "device detected but not operational\n"); + return 0; + } - num_fingers = (data.finger_btn >> 4) & 0x0f; - for (i = 0; i < num_fingers; i++) { - const struct cyapa_touch *touch = &data.touches[i]; - /* Note: touch->id range is 1 to 15; slots are 0 to 14. */ - int slot = touch->id - 1; - - input_mt_slot(input, slot); - input_mt_report_slot_state(input, MT_TOOL_FINGER, true); - input_report_abs(input, ABS_MT_POSITION_X, - ((touch->xy_hi & 0xf0) << 4) | touch->x_lo); - input_report_abs(input, ABS_MT_POSITION_Y, - ((touch->xy_hi & 0x0f) << 8) | touch->y_lo); - input_report_abs(input, ABS_MT_PRESSURE, touch->pressure); + dev_err(dev, "no device detected: %d\n", error); + return error; } - input_mt_sync_frame(input); - - if (cyapa->btn_capability & CAPABILITY_LEFT_BTN_MASK) - input_report_key(input, BTN_LEFT, - data.finger_btn & OP_DATA_LEFT_BTN); - - if (cyapa->btn_capability & CAPABILITY_MIDDLE_BTN_MASK) - input_report_key(input, BTN_MIDDLE, - data.finger_btn & OP_DATA_MIDDLE_BTN); - - if (cyapa->btn_capability & CAPABILITY_RIGHT_BTN_MASK) - input_report_key(input, BTN_RIGHT, - data.finger_btn & OP_DATA_RIGHT_BTN); - - input_sync(input); - -out: - return IRQ_HANDLED; -} - -static u8 cyapa_check_adapter_functionality(struct i2c_client *client) -{ - u8 ret = CYAPA_ADAPTER_FUNC_NONE; - - if (i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) - ret |= CYAPA_ADAPTER_FUNC_I2C; - if (i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA | - I2C_FUNC_SMBUS_BLOCK_DATA | - I2C_FUNC_SMBUS_I2C_BLOCK)) - ret |= CYAPA_ADAPTER_FUNC_SMBUS; - return ret; + return 0; } static int cyapa_open(struct input_dev *input) @@ -761,22 +320,49 @@ static int cyapa_open(struct input_dev *input) struct i2c_client *client = cyapa->client; int error; - error = cyapa_set_power_mode(cyapa, PWR_MODE_FULL_ACTIVE); - if (error) { - dev_err(&client->dev, "set active power failed: %d\n", error); + error = mutex_lock_interruptible(&cyapa->state_sync_lock); + if (error) return error; + + if (cyapa->operational) { + /* + * though failed to set active power mode, + * but still may be able to work in lower scan rate + * when in operational mode. + */ + error = cyapa->ops->set_power_mode(cyapa, + PWR_MODE_FULL_ACTIVE, 0); + if (error) { + dev_warn(&client->dev, + "set active power failed: %d\n", error); + goto out; + } + } else { + error = cyapa_reinitialize(cyapa); + if (error || !cyapa->operational) { + error = error ? error : -EAGAIN; + goto out; + } } enable_irq(client->irq); - return 0; +out: + mutex_unlock(&cyapa->state_sync_lock); + return error; } static void cyapa_close(struct input_dev *input) { struct cyapa *cyapa = input_get_drvdata(input); + struct i2c_client *client = cyapa->client; + + mutex_lock(&cyapa->state_sync_lock); - disable_irq(cyapa->client->irq); - cyapa_set_power_mode(cyapa, PWR_MODE_OFF); + disable_irq(client->irq); + if (cyapa->operational) + cyapa->ops->set_power_mode(cyapa, PWR_MODE_OFF, 0); + + mutex_unlock(&cyapa->state_sync_lock); } static int cyapa_create_input_dev(struct cyapa *cyapa) @@ -813,7 +399,28 @@ static int cyapa_create_input_dev(struct cyapa *cyapa) 0); input_set_abs_params(input, ABS_MT_POSITION_Y, 0, cyapa->max_abs_y, 0, 0); - input_set_abs_params(input, ABS_MT_PRESSURE, 0, 255, 0, 0); + input_set_abs_params(input, ABS_MT_PRESSURE, 0, cyapa->max_z, 0, 0); + if (cyapa->gen > CYAPA_GEN3) { + input_set_abs_params(input, ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0); + input_set_abs_params(input, ABS_MT_TOUCH_MINOR, 0, 255, 0, 0); + /* + * Orientation is the angle between the vertical axis and + * the major axis of the contact ellipse. + * The range is -127 to 127. + * the positive direction is clockwise form the vertical axis. + * If the ellipse of contact degenerates into a circle, + * orientation is reported as 0. + * + * Also, for Gen5 trackpad the accurate of this orientation + * value is value + (-30 ~ 30). + */ + input_set_abs_params(input, ABS_MT_ORIENTATION, + -127, 127, 0, 0); + } + if (cyapa->gen >= CYAPA_GEN5) { + input_set_abs_params(input, ABS_MT_WIDTH_MAJOR, 0, 255, 0, 0); + input_set_abs_params(input, ABS_MT_WIDTH_MINOR, 0, 255, 0, 0); + } input_abs_set_res(input, ABS_MT_POSITION_X, cyapa->max_abs_x / cyapa->physical_size_x); @@ -838,16 +445,159 @@ static int cyapa_create_input_dev(struct cyapa *cyapa) return error; } + /* Register the device in input subsystem */ + error = input_register_device(input); + if (error) { + dev_err(dev, "failed to register input device: %d\n", error); + return error; + } + cyapa->input = input; return 0; } +/* + * cyapa_sleep_time_to_pwr_cmd and cyapa_pwr_cmd_to_sleep_time + * + * These are helper functions that convert to and from integer idle + * times and register settings to write to the PowerMode register. + * The trackpad supports between 20ms to 1000ms scan intervals. + * The time will be increased in increments of 10ms from 20ms to 100ms. + * From 100ms to 1000ms, time will be increased in increments of 20ms. + * + * When Idle_Time < 100, the format to convert Idle_Time to Idle_Command is: + * Idle_Command = Idle Time / 10; + * When Idle_Time >= 100, the format to convert Idle_Time to Idle_Command is: + * Idle_Command = Idle Time / 20 + 5; + */ +u8 cyapa_sleep_time_to_pwr_cmd(u16 sleep_time) +{ + u16 encoded_time; + + sleep_time = clamp_val(sleep_time, 20, 1000); + encoded_time = sleep_time < 100 ? sleep_time / 10 : sleep_time / 20 + 5; + return (encoded_time << 2) & PWR_MODE_MASK; +} + +u16 cyapa_pwr_cmd_to_sleep_time(u8 pwr_mode) +{ + u8 encoded_time = pwr_mode >> 2; + + return (encoded_time < 10) ? encoded_time * 10 + : (encoded_time - 5) * 20; +} + +/* 0 on driver initialize and detected successfully, negative on failure. */ +static int cyapa_initialize(struct cyapa *cyapa) +{ + int error = 0; + + cyapa->state = CYAPA_STATE_NO_DEVICE; + cyapa->gen = CYAPA_GEN_UNKNOWN; + mutex_init(&cyapa->state_sync_lock); + + /* + * Set to hard code default, they will be updated with trackpad set + * default values after probe and initialized. + */ + cyapa->suspend_power_mode = PWR_MODE_SLEEP; + cyapa->suspend_sleep_time = + cyapa_pwr_cmd_to_sleep_time(cyapa->suspend_power_mode); + + /* ops.initialize() is aimed to prepare for module communications. */ + error = cyapa_gen3_ops.initialize(cyapa); + if (error) + return error; + + error = cyapa_detect(cyapa); + if (error) + return error; + + /* Power down the device until we need it. */ + if (cyapa->operational) + cyapa->ops->set_power_mode(cyapa, PWR_MODE_OFF, 0); + + return 0; +} + +static int cyapa_reinitialize(struct cyapa *cyapa) +{ + struct device *dev = &cyapa->client->dev; + struct input_dev *input = cyapa->input; + int error; + + /* Avoid command failures when TP was in OFF state. */ + if (cyapa->operational) + cyapa->ops->set_power_mode(cyapa, PWR_MODE_FULL_ACTIVE, 0); + + error = cyapa_detect(cyapa); + if (error) + goto out; + + if (!input && cyapa->operational) { + error = cyapa_create_input_dev(cyapa); + if (error) { + dev_err(dev, "create input_dev instance failed: %d\n", + error); + goto out; + } + } + +out: + if (!input || !input->users) { + /* Reset to power OFF state to save power when no user open. */ + if (cyapa->operational) + cyapa->ops->set_power_mode(cyapa, PWR_MODE_OFF, 0); + } + + return error; +} + +static irqreturn_t cyapa_irq(int irq, void *dev_id) +{ + struct cyapa *cyapa = dev_id; + struct device *dev = &cyapa->client->dev; + + if (device_may_wakeup(dev)) + pm_wakeup_event(dev, 0); + + /* Interrupt event maybe cuased by host command to trackpad device. */ + if (cyapa->ops->irq_cmd_handler(cyapa)) { + /* + * Interrupt event maybe from trackpad device input reporting. + */ + if (!cyapa->input) { + /* + * Still in probling or in firware image + * udpating or reading. + */ + cyapa->ops->sort_empty_output_data(cyapa, + NULL, NULL, NULL); + goto out; + } + + if (!cyapa->operational || cyapa->ops->irq_handler(cyapa)) { + if (!mutex_trylock(&cyapa->state_sync_lock)) { + cyapa->ops->sort_empty_output_data(cyapa, + NULL, NULL, NULL); + goto out; + } + cyapa_reinitialize(cyapa); + mutex_unlock(&cyapa->state_sync_lock); + } + } + +out: + return IRQ_HANDLED; +} + static int cyapa_probe(struct i2c_client *client, const struct i2c_device_id *dev_id) { struct device *dev = &client->dev; struct cyapa *cyapa; u8 adapter_func; + union i2c_smbus_data dummy; int error; adapter_func = cyapa_check_adapter_functionality(client); @@ -856,39 +606,30 @@ static int cyapa_probe(struct i2c_client *client, return -EIO; } + /* Make sure there is something at this address */ + if (i2c_smbus_xfer(client->adapter, client->addr, 0, + I2C_SMBUS_READ, 0, I2C_SMBUS_BYTE, &dummy) < 0) + return -ENODEV; + cyapa = devm_kzalloc(dev, sizeof(struct cyapa), GFP_KERNEL); if (!cyapa) return -ENOMEM; - cyapa->gen = CYAPA_GEN3; - cyapa->client = client; - i2c_set_clientdata(client, cyapa); - sprintf(cyapa->phys, "i2c-%d-%04x/input0", client->adapter->nr, - client->addr); - /* i2c isn't supported, use smbus */ if (adapter_func == CYAPA_ADAPTER_FUNC_SMBUS) cyapa->smbus = true; - cyapa->state = CYAPA_STATE_NO_DEVICE; - - error = cyapa_check_is_operational(cyapa); - if (error) { - dev_err(dev, "device not operational, %d\n", error); - return error; - } + cyapa->client = client; + i2c_set_clientdata(client, cyapa); + sprintf(cyapa->phys, "i2c-%d-%04x/input0", client->adapter->nr, + client->addr); - /* Power down the device until we need it */ - error = cyapa_set_power_mode(cyapa, PWR_MODE_OFF); + error = cyapa_initialize(cyapa); if (error) { - dev_err(dev, "failed to quiesce the device: %d\n", error); + dev_err(dev, "failed to detect and initialize tp device.\n"); return error; } - error = cyapa_create_input_dev(cyapa); - if (error) - return error; - error = devm_request_threaded_irq(dev, client->irq, NULL, cyapa_irq, IRQF_TRIGGER_FALLING | IRQF_ONESHOT, @@ -901,11 +642,18 @@ static int cyapa_probe(struct i2c_client *client, /* Disable IRQ until the device is opened */ disable_irq(client->irq); - /* Register the device in input subsystem */ - error = input_register_device(cyapa->input); - if (error) { - dev_err(dev, "failed to register input device: %d\n", error); - return error; + /* + * Register the device in the input subsystem when it's operational. + * Otherwise, keep in this driver, so it can be be recovered or updated + * through the sysfs mode and update_fw interfaces by user or apps. + */ + if (cyapa->operational) { + error = cyapa_create_input_dev(cyapa); + if (error) { + dev_err(dev, "create input_dev instance failed: %d\n", + error); + return error; + } } return 0; @@ -915,11 +663,10 @@ static int __maybe_unused cyapa_suspend(struct device *dev) { struct i2c_client *client = to_i2c_client(dev); struct cyapa *cyapa = i2c_get_clientdata(client); - struct input_dev *input = cyapa->input; u8 power_mode; int error; - error = mutex_lock_interruptible(&input->mutex); + error = mutex_lock_interruptible(&cyapa->state_sync_lock); if (error) return error; @@ -929,18 +676,20 @@ static int __maybe_unused cyapa_suspend(struct device *dev) * Set trackpad device to idle mode if wakeup is allowed, * otherwise turn off. */ - power_mode = device_may_wakeup(dev) ? PWR_MODE_IDLE - : PWR_MODE_OFF; - error = cyapa_set_power_mode(cyapa, power_mode); - if (error) - dev_err(dev, "resume: set power mode to %d failed: %d\n", - power_mode, error); + if (cyapa->operational) { + power_mode = device_may_wakeup(dev) ? cyapa->suspend_power_mode + : PWR_MODE_OFF; + error = cyapa->ops->set_power_mode(cyapa, power_mode, + cyapa->suspend_sleep_time); + if (error) + dev_err(dev, "suspend set power mode failed: %d\n", + error); + } if (device_may_wakeup(dev)) cyapa->irq_wake = (enable_irq_wake(client->irq) == 0); - mutex_unlock(&input->mutex); - + mutex_unlock(&cyapa->state_sync_lock); return 0; } @@ -948,25 +697,22 @@ static int __maybe_unused cyapa_resume(struct device *dev) { struct i2c_client *client = to_i2c_client(dev); struct cyapa *cyapa = i2c_get_clientdata(client); - struct input_dev *input = cyapa->input; - u8 power_mode; int error; - mutex_lock(&input->mutex); + mutex_lock(&cyapa->state_sync_lock); - if (device_may_wakeup(dev) && cyapa->irq_wake) + if (device_may_wakeup(dev) && cyapa->irq_wake) { disable_irq_wake(client->irq); + cyapa->irq_wake = false; + } - power_mode = input->users ? PWR_MODE_FULL_ACTIVE : PWR_MODE_OFF; - error = cyapa_set_power_mode(cyapa, PWR_MODE_FULL_ACTIVE); + error = cyapa_reinitialize(cyapa); if (error) - dev_warn(dev, "resume: set power mode to %d failed: %d\n", - power_mode, error); + dev_warn(dev, "failed to reinitialize TP device: %d\n", error); enable_irq(client->irq); - mutex_unlock(&input->mutex); - + mutex_unlock(&cyapa->state_sync_lock); return 0; } |