/* 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"); 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");