Input: cyapa - add support for smbus protocol
This patch adds support for the Cypress APA Smbus Trackpad type, which uses a modified register map that fits within the limitations of the smbus protocol. Devices that use this protocol include: CYTRA-116001-00 - Samsung Series 5 550 Chromebook trackpad CYTRA-103002-00 - Acer C7 Chromebook trackpad CYTRA-101003-00 - HP Pavilion 14 Chromebook trackpad Signed-off-by: Dudley Du <dudl@cypress.com> Signed-off-by: Benson Leung <bleung@chromium.org> Reviewed-by: Daniel Kurtz <djkurtz@chromium.org> Reviewed-by: Henrik Rydberg <rydberg@euromail.se> Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
This commit is contained in:
parent
48064bdcd6
commit
6ddaf744c9
1 changed files with 174 additions and 5 deletions
|
@ -208,6 +208,7 @@ struct cyapa {
|
||||||
char phys[32]; /* device physical location */
|
char phys[32]; /* device physical location */
|
||||||
int irq;
|
int irq;
|
||||||
bool irq_wake; /* irq wake is enabled */
|
bool irq_wake; /* irq wake is enabled */
|
||||||
|
bool smbus;
|
||||||
|
|
||||||
/* read from query data region. */
|
/* read from query data region. */
|
||||||
char product_id[16];
|
char product_id[16];
|
||||||
|
@ -229,6 +230,60 @@ struct cyapa_cmd_len {
|
||||||
u8 len;
|
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[] = {
|
static const struct cyapa_cmd_len cyapa_i2c_cmds[] = {
|
||||||
{ CYAPA_OFFSET_SOFT_RESET, 1 },
|
{ CYAPA_OFFSET_SOFT_RESET, 1 },
|
||||||
{ REG_OFFSET_COMMAND_BASE + 1, 1 },
|
{ REG_OFFSET_COMMAND_BASE + 1, 1 },
|
||||||
|
@ -245,6 +300,22 @@ static const struct cyapa_cmd_len cyapa_i2c_cmds[] = {
|
||||||
{ REG_OFFSET_DATA_BASE, 32 }
|
{ REG_OFFSET_DATA_BASE, 32 }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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 ssize_t cyapa_i2c_reg_read_block(struct cyapa *cyapa, u8 reg, size_t len,
|
static ssize_t cyapa_i2c_reg_read_block(struct cyapa *cyapa, u8 reg, size_t len,
|
||||||
u8 *values)
|
u8 *values)
|
||||||
{
|
{
|
||||||
|
@ -257,26 +328,92 @@ static ssize_t cyapa_i2c_reg_write_block(struct cyapa *cyapa, u8 reg,
|
||||||
return i2c_smbus_write_i2c_block_data(cyapa->client, reg, len, values);
|
return i2c_smbus_write_i2c_block_data(cyapa->client, reg, len, values);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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)
|
||||||
|
{
|
||||||
|
ssize_t ret;
|
||||||
|
u8 index;
|
||||||
|
u8 smbus_cmd;
|
||||||
|
u8 *buf;
|
||||||
|
struct i2c_client *client = cyapa->client;
|
||||||
|
|
||||||
|
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 = 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
out:
|
||||||
|
return ret > 0 ? len : ret;
|
||||||
|
}
|
||||||
|
|
||||||
static s32 cyapa_read_byte(struct cyapa *cyapa, u8 cmd_idx)
|
static s32 cyapa_read_byte(struct cyapa *cyapa, u8 cmd_idx)
|
||||||
{
|
{
|
||||||
u8 cmd = cyapa_i2c_cmds[cmd_idx].cmd;
|
u8 cmd;
|
||||||
|
|
||||||
|
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);
|
return i2c_smbus_read_byte_data(cyapa->client, cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
static s32 cyapa_write_byte(struct cyapa *cyapa, u8 cmd_idx, u8 value)
|
static s32 cyapa_write_byte(struct cyapa *cyapa, u8 cmd_idx, u8 value)
|
||||||
{
|
{
|
||||||
u8 cmd = cyapa_i2c_cmds[cmd_idx].cmd;
|
u8 cmd;
|
||||||
|
|
||||||
|
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);
|
return i2c_smbus_write_byte_data(cyapa->client, cmd, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
static ssize_t cyapa_read_block(struct cyapa *cyapa, u8 cmd_idx, u8 *values)
|
static ssize_t cyapa_read_block(struct cyapa *cyapa, u8 cmd_idx, u8 *values)
|
||||||
{
|
{
|
||||||
u8 cmd = cyapa_i2c_cmds[cmd_idx].cmd;
|
u8 cmd;
|
||||||
size_t len = cyapa_i2c_cmds[cmd_idx].len;
|
size_t len;
|
||||||
|
|
||||||
return cyapa_i2c_reg_read_block(cyapa, cmd, len, values);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -298,6 +435,15 @@ static int cyapa_get_state(struct cyapa *cyapa)
|
||||||
*/
|
*/
|
||||||
ret = cyapa_i2c_reg_read_block(cyapa, BL_HEAD_OFFSET, BL_STATUS_SIZE,
|
ret = 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 && (ret == -ETIMEDOUT || ret == -ENXIO))
|
||||||
|
ret = cyapa_read_block(cyapa, CYAPA_CMD_BL_STATUS, status);
|
||||||
|
|
||||||
if (ret != BL_STATUS_SIZE)
|
if (ret != BL_STATUS_SIZE)
|
||||||
goto error;
|
goto error;
|
||||||
|
|
||||||
|
@ -594,6 +740,19 @@ out:
|
||||||
return IRQ_HANDLED;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
static int cyapa_create_input_dev(struct cyapa *cyapa)
|
static int cyapa_create_input_dev(struct cyapa *cyapa)
|
||||||
{
|
{
|
||||||
struct device *dev = &cyapa->client->dev;
|
struct device *dev = &cyapa->client->dev;
|
||||||
|
@ -668,9 +827,16 @@ static int cyapa_probe(struct i2c_client *client,
|
||||||
const struct i2c_device_id *dev_id)
|
const struct i2c_device_id *dev_id)
|
||||||
{
|
{
|
||||||
int ret;
|
int ret;
|
||||||
|
u8 adapter_func;
|
||||||
struct cyapa *cyapa;
|
struct cyapa *cyapa;
|
||||||
struct device *dev = &client->dev;
|
struct device *dev = &client->dev;
|
||||||
|
|
||||||
|
adapter_func = cyapa_check_adapter_functionality(client);
|
||||||
|
if (adapter_func == CYAPA_ADAPTER_FUNC_NONE) {
|
||||||
|
dev_err(dev, "not a supported I2C/SMBus adapter\n");
|
||||||
|
return -EIO;
|
||||||
|
}
|
||||||
|
|
||||||
cyapa = kzalloc(sizeof(struct cyapa), GFP_KERNEL);
|
cyapa = kzalloc(sizeof(struct cyapa), GFP_KERNEL);
|
||||||
if (!cyapa) {
|
if (!cyapa) {
|
||||||
dev_err(dev, "allocate memory for cyapa failed\n");
|
dev_err(dev, "allocate memory for cyapa failed\n");
|
||||||
|
@ -683,6 +849,9 @@ static int cyapa_probe(struct i2c_client *client,
|
||||||
sprintf(cyapa->phys, "i2c-%d-%04x/input0", client->adapter->nr,
|
sprintf(cyapa->phys, "i2c-%d-%04x/input0", client->adapter->nr,
|
||||||
client->addr);
|
client->addr);
|
||||||
|
|
||||||
|
/* i2c isn't supported, use smbus */
|
||||||
|
if (adapter_func == CYAPA_ADAPTER_FUNC_SMBUS)
|
||||||
|
cyapa->smbus = true;
|
||||||
cyapa->state = CYAPA_STATE_NO_DEVICE;
|
cyapa->state = CYAPA_STATE_NO_DEVICE;
|
||||||
ret = cyapa_check_is_operational(cyapa);
|
ret = cyapa_check_is_operational(cyapa);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
|
|
Loading…
Add table
Reference in a new issue