diff options
author | Hung-ying Tyan <tyanh@chromium.org> | 2013-08-16 16:20:20 +0800 |
---|---|---|
committer | chrome-internal-fetch <chrome-internal-fetch@google.com> | 2014-03-07 18:58:29 +0000 |
commit | aed3b68d91b172ea339b8ee0889315c38ab805c9 (patch) | |
tree | 65780d8ac01a0a1ed4d3bffbd3080c3bb7a1511a | |
parent | b563f83c8dc8db4e8ec463817bc9b04a90aa37dc (diff) | |
download | chrome-ec-aed3b68d91b172ea339b8ee0889315c38ab805c9.tar.gz |
i2c: add wedge command
This command wedges the I2C bus by writing part of a byte to or reading part
of the response from the slave device.
To enabled the wedge command you must define CONFIG_CMD_I2CWEDGE and you must
define I2C_PORT_HOST, the i2c port to use the wedge command.
BUG=chrome-os-partner:19286
TEST=Manual test on peach pit, spring, and glimmer. Define config in board.h
to enable the command:
On the EC console, execute the following "i2cwedge" command
i2cwedge 0x90 0 1 (wedge write)
or
i2cwedge 0x90 0 2 (wedge read)
and then "battery". Observe that the command reports an error.
Similarly, execute
i2cwedge 0x90 0 5 (wedge write + reboot)
or
i2cwedge 0x90 0 6 (wedge read + reboot)
on the EC console and observe a reboot. Then execute "battery" and observe
that the command works properly.
BRANCH=none
Change-Id: I10ccb21b047df907a4dfdbd84c0f582cfa2d939a
Signed-off-by: Hung-ying Tyan <tyanh@chromium.org>
Signed-off-by: Doug Anderson <dianders@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/66389
Tested-by: Alec Berg <alecaberg@chromium.org>
Reviewed-by: Randall Spangler <rspangler@chromium.org>
Commit-Queue: Alec Berg <alecaberg@chromium.org>
(cherry picked from commit 6bdc69940188cd4f17ecde547afbbc3ad8335367)
Reviewed-on: https://chromium-review.googlesource.com/189214
-rw-r--r-- | common/build.mk | 1 | ||||
-rw-r--r-- | common/i2c_wedge.c | 343 |
2 files changed, 344 insertions, 0 deletions
diff --git a/common/build.mk b/common/build.mk index a3eca0e983..921a07f20c 100644 --- a/common/build.mk +++ b/common/build.mk @@ -26,6 +26,7 @@ common-$(CONFIG_CHARGER)+=charge_state.o charger.o # TODO(crosbug.com/p/23815): This is really the charge state machine # for ARM, not the charger driver for the tps65090. Rename. common-$(CONFIG_CHARGER_TPS65090)+=pmu_tps65090_charger.o +common-$(CONFIG_CMD_I2CWEDGE)+=i2c_wedge.o common-$(CONFIG_COMMON_PANIC_OUTPUT)+=panic_output.o common-$(CONFIG_COMMON_TIMER)+=timer.o common-$(CONFIG_PMU_POWERINFO)+=pmu_tps65090_powerinfo.o diff --git a/common/i2c_wedge.c b/common/i2c_wedge.c new file mode 100644 index 0000000000..4fda02896c --- /dev/null +++ b/common/i2c_wedge.c @@ -0,0 +1,343 @@ +/* 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. + */ + +/* + * Define CONFIG_CMD_I2CWEDGE and I2C_PORT_HOST to enable the 'i2cwedge' + * console command to allow us to bang the bus into a wedged state. For + * example, include the following lines in board/pit/board.h to enable it on + * pit: + * + * #define CONFIG_CMD_I2CWEDGE + * #define I2C_PORT_HOST I2C_PORT_MASTER + * + */ + +#include "console.h" +#include "gpio.h" +#include "i2c.h" +#include "system.h" +#include "timer.h" +#include "util.h" + +/* + * The implementation is based on Wikipedia. + */ + +int i2c_bang_started; + +static void i2c_bang_delay(void) +{ + udelay(5); +} + +static void i2c_bang_start_cond(void) +{ + /* Restart if needed */ + if (i2c_bang_started) { + /* set SDA to 1 */ + i2c_raw_set_sda(I2C_PORT_HOST, 1); + i2c_bang_delay(); + + /* Clock stretching */ + i2c_raw_set_scl(I2C_PORT_HOST, 1); + while (i2c_raw_get_scl(I2C_PORT_HOST) == 0) + ; /* TODO(crosbug.com/p/26487): TIMEOUT */ + + /* Repeated start setup time, minimum 4.7us */ + i2c_bang_delay(); + } + + i2c_raw_set_sda(I2C_PORT_HOST, 1); + if (i2c_raw_get_sda(I2C_PORT_HOST) == 0) + ; /* TODO(crosbug.com/p/26487): arbitration_lost */ + + /* SCL is high, set SDA from 1 to 0. */ + i2c_raw_set_sda(I2C_PORT_HOST, 0); + i2c_bang_delay(); + i2c_raw_set_scl(I2C_PORT_HOST, 0); + i2c_bang_started = 1; + + ccputs("BITBANG: send start\n"); +} + +static void i2c_bang_stop_cond(void) +{ + /* set SDA to 0 */ + i2c_raw_set_sda(I2C_PORT_HOST, 0); + i2c_bang_delay(); + + /* Clock stretching */ + i2c_raw_set_scl(I2C_PORT_HOST, 1); + while (i2c_raw_get_scl(I2C_PORT_HOST) == 0) + ; /* TODO(crosbug.com/p/26487): TIMEOUT */ + + /* Stop bit setup time, minimum 4us */ + i2c_bang_delay(); + + /* SCL is high, set SDA from 0 to 1 */ + i2c_raw_set_sda(I2C_PORT_HOST, 1); + if (i2c_raw_get_sda(I2C_PORT_HOST) == 0) + ; /* TODO(crosbug.com/p/26487): arbitration_lost */ + + i2c_bang_delay(); + + i2c_bang_started = 0; + ccputs("BITBANG: send stop\n"); +} + +static void i2c_bang_out_bit(int bit) +{ + if (bit) + i2c_raw_set_sda(I2C_PORT_HOST, 1); + else + i2c_raw_set_sda(I2C_PORT_HOST, 0); + + i2c_bang_delay(); + + /* Clock stretching */ + i2c_raw_set_scl(I2C_PORT_HOST, 1); + while (i2c_raw_get_scl(I2C_PORT_HOST) == 0) + ; /* TODO(crosbug.com/p/26487): TIMEOUT */ + + /* + * SCL is high, now data is valid + * If SDA is high, check that nobody else is driving SDA + */ + i2c_raw_set_sda(I2C_PORT_HOST, 1); + if (bit && i2c_raw_get_sda(I2C_PORT_HOST) == 0) + ; /* TODO(crosbug.com/p/26487): arbitration_lost */ + + i2c_bang_delay(); + i2c_raw_set_scl(I2C_PORT_HOST, 0); +} + +static int i2c_bang_in_bit(void) +{ + int bit; + + /* Let the slave drive data */ + i2c_raw_set_sda(I2C_PORT_HOST, 1); + i2c_bang_delay(); + + /* Clock stretching */ + i2c_raw_set_scl(I2C_PORT_HOST, 1); + while (i2c_raw_get_scl(I2C_PORT_HOST) == 0) + ; /* TODO(crosbug.com/p/26487): TIMEOUT */ + + /* SCL is high, now data is valid */ + bit = i2c_raw_get_sda(I2C_PORT_HOST); + i2c_bang_delay(); + i2c_raw_set_scl(I2C_PORT_HOST, 0); + + return bit; +} + +/* Write a byte to I2C bus. Return 0 if ack by the slave. */ +static int i2c_bang_out_byte(int send_start, int send_stop, unsigned char byte) +{ + unsigned bit; + int nack; + int tmp = byte; + + if (send_start) + i2c_bang_start_cond(); + + for (bit = 0; bit < 8; bit++) { + i2c_bang_out_bit((byte & 0x80) != 0); + byte <<= 1; + } + + nack = i2c_bang_in_bit(); + + ccprintf(" write byte: %d ack/nack=%d\n", tmp, nack); + + if (send_stop) + i2c_bang_stop_cond(); + + return nack; +} + +static unsigned char i2c_bang_in_byte(int ack, int send_stop) +{ + unsigned char byte = 0; + int i; + for (i = 0; i < 8; ++i) + byte = (byte << 1) | i2c_bang_in_bit(); + i2c_bang_out_bit(ack != 0); + if (send_stop) + i2c_bang_stop_cond(); + return byte; +} + +static void i2c_bang_init(void) +{ + i2c_bang_started = 0; + + i2c_raw_mode(I2C_PORT_HOST, 1); +} + +static void i2c_bang_xfer(int slave_addr, int reg) +{ + int byte; + + i2c_bang_init(); + + /* State a write command to 'slave_addr' */ + i2c_bang_out_byte(1 /*start*/, 0 /*stop*/, slave_addr); + /* Write 'reg' */ + i2c_bang_out_byte(0 /*start*/, 0 /*stop*/, reg); + + /* Start a read command */ + i2c_bang_out_byte(1 /*start*/, 0 /*stop*/, slave_addr | 1); + + /* Read two bytes */ + byte = i2c_bang_in_byte(0, 0); /* ack and no stop */ + ccprintf(" read byte: %d\n", byte); + byte = i2c_bang_in_byte(1, 1); /* nack and stop */ + ccprintf(" read byte: %d\n", byte); +} + +static void i2c_bang_wedge_write(int slave_addr, int byte, int bit_count, + int reboot) +{ + int i; + + i2c_bang_init(); + + /* State a write command to 'slave_addr' */ + i2c_bang_out_byte(1 /*start*/, 0 /*stop*/, slave_addr); + /* Send a few bits and stop */ + for (i = 0; i < bit_count; ++i) { + i2c_bang_out_bit((byte & 0x80) != 0); + byte <<= 1; + } + ccprintf(" wedged write after %d bits\n", bit_count); + + if (reboot) + system_reset(0); +} + +static void i2c_bang_wedge_read(int slave_addr, int reg, int bit_count, + int reboot) +{ + int i; + + i2c_bang_init(); + + /* State a write command to 'slave_addr' */ + i2c_bang_out_byte(1 /*start*/, 0 /*stop*/, slave_addr); + /* Write 'reg' */ + i2c_bang_out_byte(0 /*start*/, 0 /*stop*/, reg); + + /* Start a read command */ + i2c_bang_out_byte(1 /*start*/, 0 /*stop*/, slave_addr | 1); + + /* Read bit_count bits and stop */ + for (i = 0; i < bit_count; ++i) + i2c_bang_in_bit(); + + ccprintf(" wedged read after %d bits\n", bit_count); + + if (reboot) + system_reset(0); +} + +#define WEDGE_WRITE 1 +#define WEDGE_READ 2 +#define WEDGE_REBOOT 4 + +static int command_i2c_wedge(int argc, char **argv) +{ + int slave_addr, reg, wedge_flag = 0, wedge_bit_count = -1; + char *e; + enum gpio_signal tmp; + + /* Verify that the I2C_PORT_HOST has SDA and SCL pins defined. */ + if (get_sda_from_i2c_port(I2C_PORT_HOST, &tmp) != EC_SUCCESS || + get_scl_from_i2c_port(I2C_PORT_HOST, &tmp) != EC_SUCCESS) { + ccprintf("Cannot wedge bus because no SCL and SDA pins are" + "defined for this port. Check i2c_ports[].\n"); + return EC_SUCCESS; + } + + if (argc < 3) { + ccputs("Usage: i2cwedge slave_addr out_byte " + "[wedge_flag [wedge_bit_count]]\n"); + ccputs(" wedge_flag - (1: wedge out; 2: wedge in;" + " 5: wedge out+reboot; 6: wedge in+reboot)]\n"); + ccputs(" wedge_bit_count - 0 to 8\n"); + return EC_ERROR_UNKNOWN; + } + + slave_addr = strtoi(argv[1], &e, 0); + if (*e) { + ccprintf("Invalid slave_addr %s\n", argv[1]); + return EC_ERROR_INVAL; + } + reg = strtoi(argv[2], &e, 0); + if (*e) { + ccprintf("Invalid out_byte %s\n", argv[2]); + return EC_ERROR_INVAL; + } + if (argc > 3) { + wedge_flag = strtoi(argv[3], &e, 0); + if (*e) { + ccprintf("Invalid wedge_flag %s\n", argv[3]); + return EC_ERROR_INVAL; + } + } + if (argc > 4) { + wedge_bit_count = strtoi(argv[4], &e, 0); + if (*e || wedge_bit_count < 0 || wedge_bit_count > 8) { + ccprintf("Invalid wedge_bit_count %s.\n", argv[4]); + return EC_ERROR_INVAL; + } + } + + i2c_lock(I2C_PORT_HOST, 1); + + if (wedge_flag & WEDGE_WRITE) { + if (wedge_bit_count < 0) + wedge_bit_count = 8; + i2c_bang_wedge_write(slave_addr, reg, wedge_bit_count, + (wedge_flag & WEDGE_REBOOT)); + } else if (wedge_flag & WEDGE_READ) { + if (wedge_bit_count < 0) + wedge_bit_count = 2; + i2c_bang_wedge_read(slave_addr, reg, wedge_bit_count, + (wedge_flag & WEDGE_REBOOT)); + } else { + i2c_bang_xfer(slave_addr, reg); + } + + /* Put it back into normal mode */ + i2c_raw_mode(I2C_PORT_HOST, 0); + + i2c_lock(I2C_PORT_HOST, 0); + + if (wedge_flag & (WEDGE_WRITE | WEDGE_READ)) + ccprintf("I2C bus %d is now wedged. Enjoy.\n", I2C_PORT_HOST); + else + ccprintf("Bit bang xfer complete.\n"); + + return EC_SUCCESS; +} +DECLARE_CONSOLE_COMMAND(i2cwedge, command_i2c_wedge, + "i2cwedge slave_addr out_byte " + "[wedge_flag [wedge_bit_count]]", + "Wedge host I2C bus", + NULL); + +static int command_i2c_unwedge(int argc, char **argv) +{ + i2c_unwedge(I2C_PORT_HOST); + + return EC_SUCCESS; +} +DECLARE_CONSOLE_COMMAND(i2cunwedge, command_i2c_unwedge, + "", + "Unwedge host I2C bus", + NULL); + |