/* Copyright (c) 2013 The Chromium OS Authors. All rights reserved. * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ /* I2C cross-platform code for Chrome EC */ #include "battery.h" #include "clock.h" #include "charge_state.h" #include "console.h" #include "host_command.h" #include "gpio.h" #include "i2c.h" #include "system.h" #include "task.h" #include "util.h" #include "watchdog.h" #include "virtual_battery.h" /* Delay for bitbanging i2c corresponds roughly to 100kHz. */ #define I2C_BITBANG_DELAY_US 5 /* Number of attempts to unwedge each pin. */ #define UNWEDGE_SCL_ATTEMPTS 10 #define UNWEDGE_SDA_ATTEMPTS 3 #define CPUTS(outstr) cputs(CC_I2C, outstr) #define CPRINTS(format, args...) cprints(CC_I2C, format, ## args) /* Only chips with multi-port controllers will define I2C_CONTROLLER_COUNT */ #ifndef I2C_CONTROLLER_COUNT #define I2C_CONTROLLER_COUNT I2C_PORT_COUNT #endif static struct mutex port_mutex[I2C_CONTROLLER_COUNT]; /* A bitmap of the controllers which are currently servicing a request. */ static uint32_t i2c_port_active_list; BUILD_ASSERT(I2C_CONTROLLER_COUNT < 32); static uint8_t port_protected[I2C_CONTROLLER_COUNT]; /** * Non-deterministically test the lock status of the port. If another task * has locked the port and the caller is accessing it illegally, then this test * will incorrectly return true. However, callers which failed to statically * lock the port will fail quickly. */ static int i2c_port_is_locked(int port) { #ifdef CONFIG_I2C_MULTI_PORT_CONTROLLER /* Test the controller, not the port */ port = i2c_port_to_controller(port); #endif return (i2c_port_active_list >> port) & 1; } const struct i2c_port_t *get_i2c_port(int port) { int i; /* Find the matching port in i2c_ports[] table. */ for (i = 0; i < i2c_ports_used; i++) { if (i2c_ports[i].port == port) return &i2c_ports[i]; } return NULL; } static int chip_i2c_xfer_with_notify(int port, int slave_addr, const uint8_t *out, int out_size, uint8_t *in, int in_size, int flags) { int ret; #ifdef CONFIG_I2C_XFER_BOARD_CALLBACK i2c_start_xfer_notify(port, slave_addr); #endif ret = chip_i2c_xfer(port, slave_addr, out, out_size, in, in_size, flags); #ifdef CONFIG_I2C_XFER_BOARD_CALLBACK i2c_end_xfer_notify(port, slave_addr); #endif return ret; } #ifdef CONFIG_I2C_XFER_LARGE_READ /* * Internal function that splits reading into multiple chip_i2c_xfer() calls * if in_size exceeds CONFIG_I2C_CHIP_MAX_READ_SIZE. */ static int i2c_xfer_no_retry(int port, int slave_addr, const uint8_t *out, int out_size, uint8_t *in, int in_size, int flags) { int ret; int out_flags = flags & I2C_XFER_START; int in_chunk_size = MIN(in_size, CONFIG_I2C_CHIP_MAX_READ_SIZE); in_size -= in_chunk_size; out_flags |= !in_size ? (flags & I2C_XFER_STOP) : 0; ret = chip_i2c_xfer_with_notify(port, slave_addr, out, out_size, in, in_chunk_size, out_flags); in += in_chunk_size; while (in_size && ret == EC_SUCCESS) { in_chunk_size = MIN(in_size, CONFIG_I2C_CHIP_MAX_READ_SIZE); in_size -= in_chunk_size; ret = chip_i2c_xfer_with_notify(port, slave_addr, NULL, 0, in, in_chunk_size, !in_size ? (flags & I2C_XFER_STOP) : 0); in += in_chunk_size; } return ret; } #endif /* CONFIG_I2C_XFER_LARGE_READ */ int i2c_xfer_unlocked(int port, int slave_addr, const uint8_t *out, int out_size, uint8_t *in, int in_size, int flags) { int i; int ret = EC_SUCCESS; if (!i2c_port_is_locked(port)) { CPUTS("Access I2C without lock!"); return EC_ERROR_INVAL; } for (i = 0; i <= CONFIG_I2C_NACK_RETRY_COUNT; i++) { #ifdef CONFIG_I2C_XFER_LARGE_READ ret = i2c_xfer_no_retry(port, slave_addr, out, out_size, in, in_size, flags); #else ret = chip_i2c_xfer_with_notify(port, slave_addr, out, out_size, in, in_size, flags); #endif /* CONFIG_I2C_XFER_LARGE_READ */ if (ret != EC_ERROR_BUSY) break; } return ret; } int i2c_xfer(int port, int slave_addr, const uint8_t *out, int out_size, uint8_t *in, int in_size) { int rv; i2c_lock(port, 1); rv = i2c_xfer_unlocked(port, slave_addr, out, out_size, in, in_size, I2C_XFER_SINGLE); i2c_lock(port, 0); return rv; } void i2c_lock(int port, int lock) { #ifdef CONFIG_I2C_MULTI_PORT_CONTROLLER /* Lock the controller, not the port */ port = i2c_port_to_controller(port); #endif if (port < 0) return; if (lock) { mutex_lock(port_mutex + port); /* Disable interrupt during changing counter for preemption. */ interrupt_disable(); i2c_port_active_list |= 1 << port; /* Ec cannot enter sleep if there's any i2c port active. */ disable_sleep(SLEEP_MASK_I2C_MASTER); interrupt_enable(); } else { interrupt_disable(); i2c_port_active_list &= ~(1 << port); /* Once there is no i2c port active, enable sleep bit of i2c. */ if (!i2c_port_active_list) enable_sleep(SLEEP_MASK_I2C_MASTER); interrupt_enable(); mutex_unlock(port_mutex + port); } } void i2c_prepare_sysjump(void) { int i; /* Lock all i2c controllers */ for (i = 0; i < I2C_CONTROLLER_COUNT; ++i) mutex_lock(port_mutex + i); } int i2c_read32(int port, int slave_addr, int offset, int *data) { int rv; uint8_t reg, buf[sizeof(uint32_t)]; reg = offset & 0xff; /* I2C read 32-bit word: transmit 8-bit offset, and read 32bits */ rv = i2c_xfer(port, slave_addr, ®, 1, buf, sizeof(uint32_t)); if (rv) return rv; if (slave_addr & I2C_FLAG_BIG_ENDIAN) *data = ((int)buf[0] << 24) | ((int)buf[1] << 16) | ((int)buf[0] << 8) | buf[1]; else *data = ((int)buf[3] << 24) | ((int)buf[2] << 16) | ((int)buf[1] << 8) | buf[0]; return EC_SUCCESS; } int i2c_write32(int port, int slave_addr, int offset, int data) { uint8_t buf[1 + sizeof(uint32_t)]; buf[0] = offset & 0xff; if (slave_addr & I2C_FLAG_BIG_ENDIAN) { buf[1] = (data >> 24) & 0xff; buf[2] = (data >> 16) & 0xff; buf[3] = (data >> 8) & 0xff; buf[4] = data & 0xff; } else { buf[1] = data & 0xff; buf[2] = (data >> 8) & 0xff; buf[3] = (data >> 16) & 0xff; buf[4] = (data >> 24) & 0xff; } return i2c_xfer(port, slave_addr, buf, sizeof(uint32_t) + 1, NULL, 0); } int i2c_read16(int port, int slave_addr, int offset, int *data) { int rv; uint8_t reg, buf[sizeof(uint16_t)]; reg = offset & 0xff; /* I2C read 16-bit word: transmit 8-bit offset, and read 16bits */ rv = i2c_xfer(port, slave_addr, ®, 1, buf, sizeof(uint16_t)); if (rv) return rv; if (slave_addr & I2C_FLAG_BIG_ENDIAN) *data = ((int)buf[0] << 8) | buf[1]; else *data = ((int)buf[1] << 8) | buf[0]; return EC_SUCCESS; } int i2c_write16(int port, int slave_addr, int offset, int data) { uint8_t buf[1 + sizeof(uint16_t)]; buf[0] = offset & 0xff; if (slave_addr & I2C_FLAG_BIG_ENDIAN) { buf[1] = (data >> 8) & 0xff; buf[2] = data & 0xff; } else { buf[1] = data & 0xff; buf[2] = (data >> 8) & 0xff; } return i2c_xfer(port, slave_addr, buf, 1 + sizeof(uint16_t), NULL, 0); } int i2c_read8(int port, int slave_addr, int offset, int *data) { int rv; uint8_t reg = offset; uint8_t buf; reg = offset; rv = i2c_xfer(port, slave_addr, ®, 1, &buf, 1); if (!rv) *data = buf; return rv; } int i2c_write8(int port, int slave_addr, int offset, int data) { uint8_t buf[2]; buf[0] = offset; buf[1] = data; return i2c_xfer(port, slave_addr, buf, 2, 0, 0); } int i2c_read_string(int port, int slave_addr, int offset, uint8_t *data, int len) { int rv; uint8_t reg, block_length; i2c_lock(port, 1); reg = offset; /* * Send device reg space offset, and read back block length. Keep this * session open without a stop. */ rv = i2c_xfer_unlocked(port, slave_addr, ®, 1, &block_length, 1, I2C_XFER_START); if (rv) goto exit; if (len && block_length > (len - 1)) block_length = len - 1; rv = i2c_xfer_unlocked(port, slave_addr, 0, 0, data, block_length, I2C_XFER_STOP); data[block_length] = 0; exit: i2c_lock(port, 0); return rv; } int i2c_read_block(int port, int slave_addr, int offset, uint8_t *data, int len) { int rv; uint8_t reg_address = offset; rv = i2c_xfer(port, slave_addr, ®_address, 1, data, len); return rv; } int i2c_write_block(int port, int slave_addr, int offset, const uint8_t *data, int len) { int rv; uint8_t reg_address = offset; /* * Split into two transactions to avoid the stack space consumption of * appending the destination address with the data array. */ i2c_lock(port, 1); rv = i2c_xfer_unlocked(port, slave_addr, ®_address, 1, NULL, 0, I2C_XFER_START); if (!rv) { rv = i2c_xfer_unlocked(port, slave_addr, data, len, NULL, 0, I2C_XFER_STOP); } i2c_lock(port, 0); return rv; } int get_sda_from_i2c_port(int port, enum gpio_signal *sda) { const struct i2c_port_t *i2c_port = get_i2c_port(port); /* Crash if the port given is not in the i2c_ports[] table. */ ASSERT(i2c_port); /* Check if the SCL and SDA pins have been defined for this port. */ if (i2c_port->scl == 0 && i2c_port->sda == 0) return EC_ERROR_INVAL; *sda = i2c_port->sda; return EC_SUCCESS; } int get_scl_from_i2c_port(int port, enum gpio_signal *scl) { const struct i2c_port_t *i2c_port = get_i2c_port(port); /* Crash if the port given is not in the i2c_ports[] table. */ ASSERT(i2c_port); /* Check if the SCL and SDA pins have been defined for this port. */ if (i2c_port->scl == 0 && i2c_port->sda == 0) return EC_ERROR_INVAL; *scl = i2c_port->scl; return EC_SUCCESS; } void i2c_raw_set_scl(int port, int level) { enum gpio_signal g; if (get_scl_from_i2c_port(port, &g) == EC_SUCCESS) gpio_set_level(g, level); } void i2c_raw_set_sda(int port, int level) { enum gpio_signal g; if (get_sda_from_i2c_port(port, &g) == EC_SUCCESS) gpio_set_level(g, level); } int i2c_raw_mode(int port, int enable) { enum gpio_signal sda, scl; int ret_sda, ret_scl; /* Get the SDA and SCL pins for this port. If none, then return. */ if (get_sda_from_i2c_port(port, &sda) != EC_SUCCESS) return EC_ERROR_INVAL; if (get_scl_from_i2c_port(port, &scl) != EC_SUCCESS) return EC_ERROR_INVAL; if (enable) { int raw_gpio_mode_flags = GPIO_ODR_HIGH; /* If the CLK line is 1.8V, then ensure we set 1.8V mode */ if ((gpio_list + scl)->flags & GPIO_SEL_1P8V) raw_gpio_mode_flags |= GPIO_SEL_1P8V; /* * To enable raw mode, take out of alternate function mode and * set the flags to open drain output. */ ret_sda = gpio_config_pin(MODULE_I2C, sda, 0); ret_scl = gpio_config_pin(MODULE_I2C, scl, 0); gpio_set_flags(scl, raw_gpio_mode_flags); gpio_set_flags(sda, raw_gpio_mode_flags); } else { /* * Configure the I2C pins to exit raw mode and return * to normal mode. */ ret_sda = gpio_config_pin(MODULE_I2C, sda, 1); ret_scl = gpio_config_pin(MODULE_I2C, scl, 1); } return ret_sda == EC_SUCCESS ? ret_scl : ret_sda; } /* * Unwedge the i2c bus for the given port. * * Some devices on our i2c busses keep power even if we get a reset. That * means that they could be part way through a transaction and could be * driving the bus in a way that makes it hard for us to talk on the bus. * ...or they might listen to the next transaction and interpret it in a * weird way. * * Note that devices could be in one of several states: * - If a device got interrupted in a write transaction it will be watching * for additional data to finish its write. It will probably be looking to * ack the data (drive the data line low) after it gets everything. * - If a device got interrupted while responding to a register read, it will * be watching for clocks and will drive data out when it sees clocks. At * the moment it might be trying to send out a 1 (so both clock and data * may be high) or it might be trying to send out a 0 (so it's driving data * low). * * We attempt to unwedge the bus by doing: * - If SCL is being held low, then a slave is clock extending. The only * thing we can do is try to wait until the slave stops clock extending. * - Otherwise, we will toggle the clock until the slave releases the SDA line. * Once the SDA line is released, try to send a STOP bit. Rinse and repeat * until either the bus is normal, or we run out of attempts. * * Note this should work for most devices, but depending on the slaves i2c * state machine, it may not be possible to unwedge the bus. */ int i2c_unwedge(int port) { int i, j; int ret = EC_SUCCESS; #ifdef CONFIG_I2C_BUS_MAY_BE_UNPOWERED /* * Don't try to unwedge the port if we know it's unpowered; it's futile. */ if (!board_is_i2c_port_powered(port)) { CPRINTS("Skipping i2c unwedge, bus not powered."); return EC_ERROR_NOT_POWERED; } #endif /* CONFIG_I2C_BUS_MAY_BE_UNPOWERED */ /* Try to put port in to raw bit bang mode. */ if (i2c_raw_mode(port, 1) != EC_SUCCESS) return EC_ERROR_UNKNOWN; /* * If clock is low, wait for a while in case of clock stretched * by a slave. */ if (!i2c_raw_get_scl(port)) { for (i = 0;; i++) { if (i >= UNWEDGE_SCL_ATTEMPTS) { /* * If we get here, a slave is holding the clock * low and there is nothing we can do. */ CPRINTS("I2C%d unwedge failed, " "SCL is held low", port); ret = EC_ERROR_UNKNOWN; goto unwedge_done; } udelay(I2C_BITBANG_DELAY_US); if (i2c_raw_get_scl(port)) break; } } if (i2c_raw_get_sda(port)) goto unwedge_done; CPRINTS("I2C%d unwedge called with SDA held low", port); /* Keep trying to unwedge the SDA line until we run out of attempts. */ for (i = 0; i < UNWEDGE_SDA_ATTEMPTS; i++) { /* Drive the clock high. */ i2c_raw_set_scl(port, 1); udelay(I2C_BITBANG_DELAY_US); /* * Clock through the problem by clocking out 9 bits. If slave * releases the SDA line, then we can stop clocking bits and * send a STOP. */ for (j = 0; j < 9; j++) { if (i2c_raw_get_sda(port)) break; i2c_raw_set_scl(port, 0); udelay(I2C_BITBANG_DELAY_US); i2c_raw_set_scl(port, 1); udelay(I2C_BITBANG_DELAY_US); } /* Take control of SDA line and issue a STOP command. */ i2c_raw_set_sda(port, 0); udelay(I2C_BITBANG_DELAY_US); i2c_raw_set_sda(port, 1); udelay(I2C_BITBANG_DELAY_US); /* Check if the bus is unwedged. */ if (i2c_raw_get_sda(port) && i2c_raw_get_scl(port)) break; } if (!i2c_raw_get_sda(port)) { CPRINTS("I2C%d unwedge failed, SDA still low", port); ret = EC_ERROR_UNKNOWN; } if (!i2c_raw_get_scl(port)) { CPRINTS("I2C%d unwedge failed, SCL still low", port); ret = EC_ERROR_UNKNOWN; } unwedge_done: /* Take port out of raw bit bang mode. */ i2c_raw_mode(port, 0); return ret; } /*****************************************************************************/ /* Host commands */ #ifdef CONFIG_I2C_DEBUG_PASSTHRU #define PTHRUPRINTF(format, args...) CPRINTS(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("i2c passthru no params, params_size=%d, " "need at least %d", args->params_size, sizeof(*params)); return EC_RES_INVALID_PARAM; } size = sizeof(*params) + params->num_msgs * sizeof(*msg); if (args->params_size < size) { PTHRUPRINTF("i2c passthru params_size=%d, " "need at least %d", args->params_size, size); 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; PTHRUPRINTF("i2c passthru port=%d, %s, addr=0x%02x, " "len=0x%02x", 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("i2c passthru overflow1"); return EC_RES_INVALID_PARAM; } /* Must have bytes to write */ if (args->params_size < size + write_len) { PTHRUPRINTF("i2c passthru overflow2"); 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 struct i2c_port_t *i2c_port; const uint8_t *out; int in_len; int ret, i; int port_is_locked = 0; #ifdef CONFIG_BATTERY_CUT_OFF /* * Some batteries would wake up after cut-off if we talk to it. */ if (battery_is_cut_off()) return EC_RES_ACCESS_DENIED; #endif i2c_port = get_i2c_port(params->port); if (!i2c_port) return EC_RES_INVALID_PARAM; ret = check_i2c_params(args); if (ret) return ret; if (port_protected[params->port] && i2c_port->passthru_allowed) { for (i = 0; i < params->num_msgs; i++) { if (!i2c_port->passthru_allowed(i2c_port, params->msg[i].addr_flags & EC_I2C_ADDR_MASK)) return EC_RES_ACCESS_DENIED; } } /* Loop and process messages */ resp->i2c_status = 0; out = args->params + sizeof(*params) + params->num_msgs * sizeof(*msg); in_len = 0; 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 = 1; 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; #if defined(VIRTUAL_BATTERY_ADDR) && defined(I2C_PORT_VIRTUAL_BATTERY) if (params->port == I2C_PORT_VIRTUAL_BATTERY && VIRTUAL_BATTERY_ADDR == addr) { if (virtual_battery_handler(resp, in_len, &rv, xferflags, read_len, write_len, out)) break; } #endif /* Transfer next message */ PTHRUPRINTF("i2c passthru xfer port=%x, addr=%x, out=%p, " "write_len=%x, data=%p, read_len=%x, flags=%x", params->port, addr, out, write_len, &resp->data[in_len], read_len, xferflags); if (rv) { #ifdef CONFIG_I2C_PASSTHRU_RESTRICTED if (system_is_locked() && !board_allow_i2c_passthru(params->port)) { if (port_is_locked) i2c_lock(params->port, 0); return EC_RES_ACCESS_DENIED; } #endif if (!port_is_locked) i2c_lock(params->port, (port_is_locked = 1)); rv = i2c_xfer_unlocked(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 */ if (port_is_locked) 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)); static int i2c_command_passthru_protect(struct host_cmd_handler_args *args) { const struct ec_params_i2c_passthru_protect *params = args->params; struct ec_response_i2c_passthru_protect *resp = args->response; if (args->params_size < sizeof(*params)) { PTHRUPRINTF("i2c passthru protect no params, params_size=%d, " "need at least %d", args->params_size, sizeof(*params)); return EC_RES_INVALID_PARAM; } if (!get_i2c_port(params->port)) { PTHRUPRINTF("i2c passthru protect invalid port %d", params->port); return EC_RES_INVALID_PARAM; } if (params->subcmd == EC_CMD_I2C_PASSTHRU_PROTECT_STATUS) { if (args->response_max < sizeof(*resp)) { PTHRUPRINTF("i2c passthru protect no response, " "response_max=%d, need at least %d", args->response_max, sizeof(*resp)); return EC_RES_INVALID_PARAM; } resp->status = port_protected[params->port]; args->response_size = sizeof(*resp); } else if (params->subcmd == EC_CMD_I2C_PASSTHRU_PROTECT_ENABLE) { port_protected[params->port] = 1; } else { return EC_RES_INVALID_COMMAND; } return EC_RES_SUCCESS; } DECLARE_HOST_COMMAND(EC_CMD_I2C_PASSTHRU_PROTECT, i2c_command_passthru_protect, EC_VER_MASK(0)); /*****************************************************************************/ /* Console commands */ #ifdef CONFIG_CMD_I2C_PROTECT static int command_i2cprotect(int argc, char **argv) { if (argc == 1) { int i, port; for (i = 0; i < i2c_ports_used; i++) { port = i2c_ports[i].port; ccprintf("Port %d: %s\n", port, port_protected[port] ? "Protected" : "Unprotected"); } } else if (argc == 2) { int port; char *e; port = strtoi(argv[1], &e, 0); if (*e) return EC_ERROR_PARAM2; if (!get_i2c_port(port)) { ccprintf("i2c passthru protect invalid port %d\n", port); return EC_RES_INVALID_PARAM; } port_protected[port] = 1; } else { return EC_ERROR_PARAM_COUNT; } return EC_RES_SUCCESS; } DECLARE_CONSOLE_COMMAND(i2cprotect, command_i2cprotect, "[port]", "Protect I2C bus"); #endif #ifdef CONFIG_CMD_I2C_SCAN static void scan_bus(int port, const char *desc) { int a; uint8_t tmp; ccprintf("Scanning %d %s", port, desc); i2c_lock(port, 1); /* Don't scan a busy port, since reads will just fail / time out */ a = i2c_get_line_levels(port); if (a != I2C_LINE_IDLE) { ccprintf(": port busy (SDA=%d, SCL=%d)", (a & I2C_LINE_SDA_HIGH) ? 1 : 0, (a & I2C_LINE_SCL_HIGH) ? 1 : 0); goto scan_bus_exit; } for (a = 0; a < 0x100; a += 2) { watchdog_reload(); /* Otherwise a full scan trips watchdog */ ccputs("."); /* Do a single read */ if (!i2c_xfer_unlocked(port, a, NULL, 0, &tmp, 1, I2C_XFER_SINGLE)) ccprintf("\n 0x%02x", a); } scan_bus_exit: i2c_lock(port, 0); ccputs("\n"); } static int command_scan(int argc, char **argv) { int port; char *e; if (argc == 1) { for (port = 0; port < i2c_ports_used; port++) scan_bus(i2c_ports[port].port, i2c_ports[port].name); return EC_SUCCESS; } port = strtoi(argv[1], &e, 0); if ((*e) || (port >= i2c_ports_used)) return EC_ERROR_PARAM2; scan_bus(i2c_ports[port].port, i2c_ports[port].name); return EC_SUCCESS; } DECLARE_CONSOLE_COMMAND(i2cscan, command_scan, "i2cscan [port]", "Scan I2C ports for devices"); #endif #ifdef CONFIG_CMD_I2C_XFER static int command_i2cxfer(int argc, char **argv) { int port, slave_addr; uint8_t offset; int v = 0; uint8_t data[32]; char *e; int rv = 0; if (argc < 5) return EC_ERROR_PARAM_COUNT; port = strtoi(argv[2], &e, 0); if (*e) return EC_ERROR_PARAM2; slave_addr = strtoi(argv[3], &e, 0); if (*e) return EC_ERROR_PARAM3; offset = strtoi(argv[4], &e, 0); if (*e) return EC_ERROR_PARAM4; if (argc >= 6) { v = strtoi(argv[5], &e, 0); if (*e) return EC_ERROR_PARAM5; } if (strcasecmp(argv[1], "r") == 0) { /* 8-bit read */ rv = i2c_read8(port, slave_addr, offset, &v); if (!rv) ccprintf("0x%02x [%d]\n", v, v); } else if (strcasecmp(argv[1], "r16") == 0) { /* 16-bit read */ rv = i2c_read16(port, slave_addr, offset, &v); if (!rv) ccprintf("0x%04x [%d]\n", v, v); } else if (strcasecmp(argv[1], "rlen") == 0) { /* Arbitrary length read; param5 = len */ if (argc < 6 || v < 0 || v > sizeof(data)) return EC_ERROR_PARAM5; rv = i2c_xfer(port, slave_addr, &offset, 1, data, v); if (!rv) ccprintf("Data: %.*h\n", v, data); } else if (strcasecmp(argv[1], "w") == 0) { /* 8-bit write */ if (argc < 6) return EC_ERROR_PARAM5; rv = i2c_write8(port, slave_addr, offset, v); } else if (strcasecmp(argv[1], "w16") == 0) { /* 16-bit write */ if (argc < 6) return EC_ERROR_PARAM5; rv = i2c_write16(port, slave_addr, offset, v); } else { return EC_ERROR_PARAM1; } return rv; } DECLARE_CONSOLE_COMMAND(i2cxfer, command_i2cxfer, "r/r16/rlen/w/w16 port addr offset [value | len]", "Read write I2C"); #endif #ifdef CONFIG_CMD_I2C_STRESS_TEST static void i2c_test_status(struct i2c_test_results *i2c_test, int test_dev) { ccprintf("test_dev=%2d, ", test_dev); ccprintf("r=%5d, rs=%5d, rf=%5d, ", i2c_test->read_success + i2c_test->read_fail, i2c_test->read_success, i2c_test->read_fail); ccprintf("w=%5d, ws=%5d, wf=%5d\n", i2c_test->write_success + i2c_test->write_fail, i2c_test->write_success, i2c_test->write_fail); i2c_test->read_success = 0; i2c_test->read_fail = 0; i2c_test->write_success = 0, i2c_test->write_fail = 0; } #define I2C_STRESS_TEST_DATA_VERIFY_RETRY_COUNT 3 static int command_i2ctest(int argc, char **argv) { char *e; int i, j, rv; uint32_t rand; int data, data_verify; int port, addr; int count = 10000; int udelay = 100; int test_dev = i2c_test_dev_used; struct i2c_stress_test_dev *i2c_s_test = NULL; struct i2c_test_reg_info *reg_s_info; struct i2c_test_results *test_s_results; if (argc > 1) { count = strtoi(argv[1], &e, 0); if (*e) return EC_ERROR_PARAM2; } if (argc > 2) { udelay = strtoi(argv[2], &e, 0); if (*e) return EC_ERROR_PARAM3; } if (argc > 3) { test_dev = strtoi(argv[3], &e, 0); if (*e || test_dev < 1 || test_dev > i2c_test_dev_used) return EC_ERROR_PARAM4; test_dev--; } for (i = 0; i < count; i++) { if (!(i % 1000)) ccprintf("running test %d\n", i); if (argc < 4) { rand = get_time().val; test_dev = rand % i2c_test_dev_used; } port = i2c_stress_tests[test_dev].port; addr = i2c_stress_tests[test_dev].addr; i2c_s_test = i2c_stress_tests[test_dev].i2c_test; reg_s_info = &i2c_s_test->reg_info; test_s_results = &i2c_s_test->test_results; rand = get_time().val; if (rand & 0x1) { /* read */ rv = i2c_s_test->i2c_read ? i2c_s_test->i2c_read(port, addr, reg_s_info->read_reg, &data) : i2c_s_test->i2c_read_dev( reg_s_info->read_reg, &data); if (rv || data != reg_s_info->read_val) test_s_results->read_fail++; else test_s_results->read_success++; } else { /* * Reads are more than writes in the system. * Read and then write same value to ensure we are * not changing any settings. */ /* Read the write register */ rv = i2c_s_test->i2c_read ? i2c_s_test->i2c_read(port, addr, reg_s_info->read_reg, &data) : i2c_s_test->i2c_read_dev( reg_s_info->read_reg, &data); if (rv) { /* Skip writing invalid data */ test_s_results->read_fail++; continue; } else test_s_results->read_success++; j = I2C_STRESS_TEST_DATA_VERIFY_RETRY_COUNT; do { /* Write same value back */ rv = i2c_s_test->i2c_write ? i2c_s_test->i2c_write(port, addr, reg_s_info->write_reg, data) : i2c_s_test->i2c_write_dev( reg_s_info->write_reg, data); i++; if (rv) { /* Skip reading as write failed */ test_s_results->write_fail++; break; } test_s_results->write_success++; /* Read back to verify the data */ rv = i2c_s_test->i2c_read ? i2c_s_test->i2c_read(port, addr, reg_s_info->read_reg, &data_verify) : i2c_s_test->i2c_read_dev( reg_s_info->read_reg, &data_verify); i++; if (rv) { /* Read failed try next time */ test_s_results->read_fail++; break; } else if (!rv && data != data_verify) { /* Either data writes/read is wrong */ j--; } else { j = 0; test_s_results->read_success++; } } while (j); } usleep(udelay); } ccprintf("\n**********final result **********\n"); cflush(); if (argc > 3) { i2c_test_status(&i2c_s_test->test_results, test_dev + 1); } else { for (i = 0; i < i2c_test_dev_used; i++) { i2c_s_test = i2c_stress_tests[i].i2c_test; i2c_test_status(&i2c_s_test->test_results, i + 1); msleep(100); } } cflush(); return EC_SUCCESS; } DECLARE_CONSOLE_COMMAND(i2ctest, command_i2ctest, "i2ctest count|udelay|dev", "I2C stress test"); #endif /* CONFIG_CMD_I2C_STRESS_TEST */