diff options
-rw-r--r-- | board/link/board.h | 1 | ||||
-rw-r--r-- | common/i2c_common.c | 160 | ||||
-rw-r--r-- | include/ec_commands.h | 43 | ||||
-rw-r--r-- | util/ectool.c | 121 |
4 files changed, 323 insertions, 2 deletions
diff --git a/board/link/board.h b/board/link/board.h index f923d45a46..9552e62b5b 100644 --- a/board/link/board.h +++ b/board/link/board.h @@ -24,6 +24,7 @@ #endif #define CONFIG_CUSTOM_KEYSCAN #define CONFIG_EXTPOWER_GPIO +#define CONFIG_I2C_PASSTHRU_RESTRICTED #ifdef HAS_TASK_KEYPROTO #define CONFIG_KEYBOARD_PROTOCOL_8042 #endif diff --git a/common/i2c_common.c b/common/i2c_common.c index e3f4293a6a..6ee923fadd 100644 --- a/common/i2c_common.c +++ b/common/i2c_common.c @@ -14,6 +14,9 @@ #include "util.h" #include "watchdog.h" +#define CPUTS(outstr) cputs(CC_I2C, outstr) +#define CPRINTF(format, args...) cprintf(CC_I2C, format, ## args) + extern const struct i2c_port_t i2c_ports[I2C_PORTS_USED]; static struct mutex port_mutex[I2C_PORT_COUNT]; @@ -123,8 +126,10 @@ static int i2c_command_read(struct host_cmd_handler_args *args) struct ec_response_i2c_read *r = args->response; int data, rv = -1; +#ifdef CONFIG_I2C_PASSTHRU_RESTRICTED if (system_is_locked()) return EC_RES_ACCESS_DENIED; +#endif if (p->read_size == 16) rv = i2c_read16(p->port, p->addr, p->offset, &data); @@ -145,8 +150,10 @@ static int i2c_command_write(struct host_cmd_handler_args *args) const struct ec_params_i2c_write *p = args->params; int rv = -1; +#ifdef CONFIG_I2C_PASSTHRU_RESTRICTED if (system_is_locked()) return EC_RES_ACCESS_DENIED; +#endif if (p->write_size == 16) rv = i2c_write16(p->port, p->addr, p->offset, p->data); @@ -160,6 +167,159 @@ static int i2c_command_write(struct host_cmd_handler_args *args) } DECLARE_HOST_COMMAND(EC_CMD_I2C_WRITE, i2c_command_write, EC_VER_MASK(0)); +/* TODO: remove temporary extra debugging for help host-side debugging */ +#ifdef CONFIG_I2C_DEBUG_PASSTHRU +#define PTHRUPRINTF(format, args...) cprintf(CC_I2C, format, ## args) +#else +#define PTHRUPRINTF(format, args...) +#endif + +/** + * Perform the voluminous checking required for this message + * + * @param args Arguments + * @return 0 if OK, EC_RES_INVALID_PARAM on error + */ +static int check_i2c_params(const struct host_cmd_handler_args *args) +{ + const struct ec_params_i2c_passthru *params = args->params; + const struct ec_params_i2c_passthru_msg *msg; + int read_len = 0, write_len = 0; + unsigned int size; + int msgnum; + + if (args->params_size < sizeof(*params)) { + PTHRUPRINTF("[%T i2c passthru no params, params_size=%d, need at least %d]\n", + args->params_size, sizeof(*params)); + return EC_RES_INVALID_PARAM; + } + size = sizeof(*params) + params->num_msgs * sizeof(*msg); + if (args->params_size < size) { + PTHRUPRINTF("[%T i2c passthru params_size=%d, need at least %d]\n", + args->params_size, size); + return EC_RES_INVALID_PARAM; + } + + if (params->port >= I2C_PORT_COUNT) { + PTHRUPRINTF("[%T i2c passthru invalid port %d]\n", + params->port); + return EC_RES_INVALID_PARAM; + } + + /* Loop and process messages */; + for (msgnum = 0, msg = params->msg; msgnum < params->num_msgs; + msgnum++, msg++) { + unsigned int addr_flags = msg->addr_flags; + + /* Parse slave address if necessary */ + if (addr_flags & EC_I2C_FLAG_10BIT) { + /* 10-bit addressing not supported yet */ + PTHRUPRINTF("[%T i2c passthru no 10-bit addressing]\n"); + return EC_RES_INVALID_PARAM; + } + + PTHRUPRINTF("[%T i2c passthru port=%d, %s, addr=0x%02x, len=0x%02x]\n", + params->port, + addr_flags & EC_I2C_FLAG_READ ? "read" : "write", + addr_flags & EC_I2C_ADDR_MASK, + msg->len); + + if (addr_flags & EC_I2C_FLAG_READ) + read_len += msg->len; + else + write_len += msg->len; + } + + /* Check there is room for the data */ + if (args->response_max < + sizeof(struct ec_response_i2c_passthru) + read_len) { + PTHRUPRINTF("[%T i2c passthru overflow1]\n"); + return EC_RES_INVALID_PARAM; + } + + /* Must have bytes to write */ + if (args->params_size < size + write_len) { + PTHRUPRINTF("[%T i2c passthru overflow2]\n"); + return EC_RES_INVALID_PARAM; + } + + return EC_RES_SUCCESS; +} + +static int i2c_command_passthru(struct host_cmd_handler_args *args) +{ + const struct ec_params_i2c_passthru *params = args->params; + const struct ec_params_i2c_passthru_msg *msg; + struct ec_response_i2c_passthru *resp = args->response; + const uint8_t *out; + int in_len; + int ret; + +#ifdef CONFIG_I2C_PASSTHRU_RESTRICTED + if (system_is_locked()) + return EC_RES_ACCESS_DENIED; +#endif + + ret = check_i2c_params(args); + if (ret) + return ret; + + /* Loop and process messages */ + resp->i2c_status = 0; + out = args->params + sizeof(*params) + params->num_msgs * sizeof(*msg); + in_len = 0; + + i2c_lock(params->port, 1); + + for (resp->num_msgs = 0, msg = params->msg; + resp->num_msgs < params->num_msgs; + resp->num_msgs++, msg++) { + /* EC uses 8-bit slave address */ + unsigned int addr = (msg->addr_flags & EC_I2C_ADDR_MASK) << 1; + int xferflags = I2C_XFER_START; + int read_len = 0, write_len = 0; + int rv; + + if (msg->addr_flags & EC_I2C_FLAG_READ) + read_len = msg->len; + else + write_len = msg->len; + + /* Set stop bit for last message */ + if (resp->num_msgs == params->num_msgs - 1) + xferflags |= I2C_XFER_STOP; + + /* Transfer next message */ + PTHRUPRINTF("[%T i2c passthru xfer port=%x, addr=%x, out=%p, write_len=%x, data=%p, read_len=%x, xferflags=%x]\n", + params->port, addr, out, write_len, + &resp->data[in_len], read_len, xferflags); + rv = i2c_xfer(params->port, addr, out, write_len, + &resp->data[in_len], read_len, xferflags); + if (rv) { + /* Driver will have sent a stop bit here */ + if (rv == EC_ERROR_TIMEOUT) + resp->i2c_status = EC_I2C_STATUS_TIMEOUT; + else + resp->i2c_status = EC_I2C_STATUS_NAK; + break; + } + + in_len += read_len; + out += write_len; + } + args->response_size = sizeof(*resp) + in_len; + + /* Unlock port */ + i2c_lock(params->port, 0); + + /* + * Return success even if transfer failed so response is sent. Host + * will check message status to determine the transfer result. + */ + return EC_RES_SUCCESS; +} +DECLARE_HOST_COMMAND(EC_CMD_I2C_PASSTHRU, i2c_command_passthru, EC_VER_MASK(0)); + /*****************************************************************************/ /* Console commands */ diff --git a/include/ec_commands.h b/include/ec_commands.h index a8944cb719..56df6291d4 100644 --- a/include/ec_commands.h +++ b/include/ec_commands.h @@ -1140,7 +1140,7 @@ struct ec_response_gpio_get { #define EC_CMD_I2C_READ 0x94 struct ec_params_i2c_read { - uint16_t addr; + uint16_t addr; /* 8-bit address (7-bit shifted << 1) */ uint8_t read_size; /* Either 8 or 16. */ uint8_t port; uint8_t offset; @@ -1154,7 +1154,7 @@ struct ec_response_i2c_read { struct ec_params_i2c_write { uint16_t data; - uint16_t addr; + uint16_t addr; /* 8-bit address (7-bit shifted << 1) */ uint8_t write_size; /* Either 8 or 16. */ uint8_t port; uint8_t offset; @@ -1254,6 +1254,45 @@ struct ec_response_power_info { } __packed; /*****************************************************************************/ +/* I2C passthru command */ + +#define EC_CMD_I2C_PASSTHRU 0x9e + +/* Slave address is 10 (not 7) bit */ +#define EC_I2C_FLAG_10BIT (1 << 16) + +/* Read data; if not present, message is a write */ +#define EC_I2C_FLAG_READ (1 << 15) + +/* Mask for address */ +#define EC_I2C_ADDR_MASK 0x3ff + +#define EC_I2C_STATUS_NAK (1 << 0) /* Transfer was not acknowledged */ +#define EC_I2C_STATUS_TIMEOUT (1 << 1) /* Timeout during transfer */ + +/* Any error */ +#define EC_I2C_STATUS_ERROR (EC_I2C_STATUS_NAK | EC_I2C_STATUS_TIMEOUT) + +struct ec_params_i2c_passthru_msg { + uint16_t addr_flags; /* I2C slave address (7 or 10 bits) and flags */ + uint16_t len; /* Number of bytes to write*/ +} __packed; + +struct ec_params_i2c_passthru { + uint8_t port; /* I2C port number */ + uint8_t num_msgs; /* Number of messages */ + struct ec_params_i2c_passthru_msg msg[]; + /* Data for all messages is concatenated here */ +} __packed; + +struct ec_response_i2c_passthru { + uint8_t i2c_status; /* Status flags (EC_I2C_STATUS_...) */ + uint8_t num_msgs; /* Number of messages processed */ + uint8_t data[]; /* Data for all messages concatenated here */ +} __packed; + + +/*****************************************************************************/ /* Temporary debug commands. TODO: remove this crosbug.com/p/13849 */ /* diff --git a/util/ectool.c b/util/ectool.c index 0315b6dbb1..52b4df94d1 100644 --- a/util/ectool.c +++ b/util/ectool.c @@ -94,6 +94,8 @@ const char help_str[] = " Read I2C bus\n" " i2cwrite\n" " Write I2C bus\n" + " i2cxfer <port> <slave_addr> <read_count> [write bytes...]\n" + " Perform I2C transfer on EC's I2C bus\n" " keyscan <beat_us> <filename>\n" " Test low-level key scanning\n" " lightbar [CMDS]\n" @@ -1933,6 +1935,10 @@ int cmd_i2c_read(int argc, char *argv[]) return -1; } + /* + * TODO: use I2C_XFER command if supported, then fall back to I2C_READ + */ + rv = ec_command(EC_CMD_I2C_READ, 0, &p, sizeof(p), &r, sizeof(r)); if (rv < 0) @@ -1987,6 +1993,10 @@ int cmd_i2c_write(int argc, char *argv[]) return -1; } + /* + * TODO: use I2C_XFER command if supported, then fall back to I2C_WRITE + */ + rv = ec_command(EC_CMD_I2C_WRITE, 0, &p, sizeof(p), NULL, 0); if (rv < 0) @@ -1998,6 +2008,116 @@ int cmd_i2c_write(int argc, char *argv[]) } +int cmd_i2c_xfer(int argc, char *argv[]) +{ + union { + struct ec_params_i2c_passthru p; + uint8_t outbuf[EC_HOST_PARAM_SIZE]; + } params; + union { + struct ec_response_i2c_passthru r; + uint8_t inbuf[EC_HOST_PARAM_SIZE]; + } response; + struct ec_params_i2c_passthru *p = ¶ms.p; + struct ec_response_i2c_passthru *r = &response.r; + struct ec_params_i2c_passthru_msg *msg = p->msg; + unsigned int addr; + uint8_t *pdata; + char *e; + int read_len, write_len; + int size; + int rv, i; + + if (argc < 4) { + fprintf(stderr, + "Usage: %s <port> <slave_addr> <read_count> " + "[write bytes...]\n", argv[0]); + return -1; + } + + p->port = strtol(argv[1], &e, 0); + if (e && *e) { + fprintf(stderr, "Bad port.\n"); + return -1; + } + + addr = strtol(argv[2], &e, 0) & 0x7f; + if (e && *e) { + fprintf(stderr, "Bad slave address.\n"); + return -1; + } + + read_len = strtol(argv[3], &e, 0); + if (e && *e) { + fprintf(stderr, "Bad read length.\n"); + return -1; + } + + /* Skip over params to bytes to write */ + argc -= 4; + argv += 4; + write_len = argc; + p->num_msgs = (read_len != 0) + (write_len != 0); + + size = sizeof(*p) + p->num_msgs * sizeof(*msg); + if (size + write_len > sizeof(params)) { + fprintf(stderr, "Params too large for buffer\n"); + return -1; + } + if (sizeof(*r) + read_len > sizeof(response)) { + fprintf(stderr, "Read length too big for buffer\n"); + return -1; + } + + pdata = (uint8_t *)p + size; + if (write_len) { + msg->addr_flags = addr; + msg->len = write_len; + + for (i = 0; i < write_len; i++) { + pdata[i] = strtol(argv[i], &e, 0); + if (e && *e) { + fprintf(stderr, "Bad write byte %d\n", i); + return -1; + } + } + msg++; + } + + if (read_len) { + msg->addr_flags = addr | EC_I2C_FLAG_READ; + msg->len = read_len; + } + + rv = ec_command(EC_CMD_I2C_PASSTHRU, 0, p, size + write_len, + r, sizeof(*r) + read_len); + if (rv < 0) + return rv; + + /* Parse response */ + if (r->i2c_status & (EC_I2C_STATUS_NAK | EC_I2C_STATUS_TIMEOUT)) { + fprintf(stderr, "Transfer failed with status=0x%x\n", + r->i2c_status); + return -1; + } + + if (rv < sizeof(*r) + read_len) { + fprintf(stderr, "Truncated read response\n"); + return -1; + } + + if (read_len) { + printf("Read bytes:"); + for (i = 0; i < read_len; i++) + printf(" %#02x", r->data[i]); + printf("\n"); + } else { + printf("Write successful.\n"); + } + + return 0; +} + int cmd_lcd_backlight(int argc, char *argv[]) { struct ec_params_switch_enable_backlight p; @@ -2787,6 +2907,7 @@ const struct command commands[] = { {"kbpress", cmd_kbpress}, {"i2cread", cmd_i2c_read}, {"i2cwrite", cmd_i2c_write}, + {"i2cxfer", cmd_i2c_xfer}, {"lightbar", cmd_lightbar}, {"keyconfig", cmd_keyconfig}, {"keyscan", cmd_keyscan}, |