summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHung-ying Tyan <tyanh@chromium.org>2013-08-16 16:20:20 +0800
committerchrome-internal-fetch <chrome-internal-fetch@google.com>2014-03-06 21:33:09 +0000
commit6bdc69940188cd4f17ecde547afbbc3ad8335367 (patch)
tree84202d049ff7e0f3d9dd65a648f9d8ba24f67b8e
parent6ab4ad5f95ea86156d0e47b806c7a6bcfbae67d8 (diff)
downloadchrome-ec-6bdc69940188cd4f17ecde547afbbc3ad8335367.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>
-rw-r--r--common/build.mk1
-rw-r--r--common/i2c_wedge.c343
2 files changed, 344 insertions, 0 deletions
diff --git a/common/build.mk b/common/build.mk
index fc28dcc0fe..8e92e6f328 100644
--- a/common/build.mk
+++ b/common/build.mk
@@ -27,6 +27,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);
+