342 lines
7.7 KiB
C
342 lines
7.7 KiB
C
/* Copyright 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");
|
|
|