| /* Copyright 2019 The ChromiumOS Authors |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #include "console.h" |
| #include "gpio.h" |
| #include "i2c_bitbang.h" |
| #include "task.h" |
| #include "timer.h" |
| #include "util.h" |
| |
| #define CPUTS(str) cputs(CC_I2C, str) |
| #define CPRINTS(format, args...) cprints(CC_I2C, format, ##args) |
| |
| static int started; |
| |
| /* TODO: respect i2c_port->kbps setting */ |
| static void i2c_delay(void) |
| { |
| udelay(5); |
| } |
| |
| /* Number of attempts to unwedge each pin. */ |
| #define UNWEDGE_SCL_ATTEMPTS 10 |
| #define UNWEDGE_SDA_ATTEMPTS 3 |
| |
| static void i2c_bitbang_unwedge(const struct i2c_port_t *i2c_port) |
| { |
| int i, j; |
| |
| gpio_set_level(i2c_port->scl, 1); |
| /* |
| * If clock is low, wait for a while in case of clock stretched |
| * by a peripheral. |
| */ |
| if (!gpio_get_level(i2c_port->scl)) { |
| for (i = 0;; i++) { |
| if (i >= UNWEDGE_SCL_ATTEMPTS) { |
| /* |
| * If we get here, a peripheral is holding the |
| * clock low and there is nothing we can do. |
| */ |
| CPUTS("I2C unwedge failed, SCL is held low\n"); |
| return; |
| } |
| i2c_delay(); |
| if (gpio_get_level(i2c_port->scl)) |
| break; |
| } |
| } |
| |
| if (gpio_get_level(i2c_port->sda)) |
| return; |
| |
| CPUTS("I2C unwedge called with SDA held low\n"); |
| |
| /* 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. */ |
| gpio_set_level(i2c_port->scl, 0); |
| i2c_delay(); |
| |
| /* |
| * Clock through the problem by clocking out 9 bits. If |
| * peripheral releases the SDA line, then we can stop clocking |
| * bits and send a STOP. |
| */ |
| for (j = 0; j < 9; j++) { |
| if (gpio_get_level(i2c_port->sda)) |
| break; |
| |
| gpio_set_level(i2c_port->scl, 0); |
| i2c_delay(); |
| gpio_set_level(i2c_port->scl, 1); |
| i2c_delay(); |
| } |
| |
| /* Take control of SDA line and issue a STOP command. */ |
| gpio_set_level(i2c_port->sda, 0); |
| i2c_delay(); |
| gpio_set_level(i2c_port->sda, 1); |
| i2c_delay(); |
| |
| /* Check if the bus is unwedged. */ |
| if (gpio_get_level(i2c_port->sda) && |
| gpio_get_level(i2c_port->scl)) |
| break; |
| } |
| |
| if (!gpio_get_level(i2c_port->sda)) |
| CPUTS("I2C unwedge failed, SDA still low\n"); |
| if (!gpio_get_level(i2c_port->scl)) |
| CPUTS("I2C unwedge failed, SCL still low\n"); |
| } |
| |
| static void i2c_stop_cond(const struct i2c_port_t *i2c_port) |
| { |
| int i; |
| |
| if (!started) |
| return; |
| |
| gpio_set_level(i2c_port->sda, 0); |
| i2c_delay(); |
| |
| gpio_set_level(i2c_port->scl, 1); |
| |
| /* |
| * SMBus 3.0, 4.2.5 |
| * |
| * the recommendation is that if SMBDAT is still low tTIMEOUT,MAX after |
| * SMBCLK has gone high at the end of a transaction the controller |
| * should hold SMBCLK low for at least tTIMEOUT,MAX in an attempt to |
| * reset the SMBus interface of all of the devices on the bus. |
| */ |
| for (i = 0; i < 7000; i++) { |
| if (gpio_get_level(i2c_port->scl)) |
| break; |
| i2c_delay(); |
| } |
| i2c_delay(); |
| |
| /* SCL is high, set SDA from 0 to 1 */ |
| gpio_set_level(i2c_port->sda, 1); |
| i2c_delay(); |
| |
| started = 0; |
| } |
| |
| static int clock_stretching(const struct i2c_port_t *i2c_port) |
| { |
| int i; |
| |
| i2c_delay(); |
| /* 5us * 7000 iterations ~= 35ms */ |
| for (i = 0; i < 7000; i++) { |
| if (gpio_get_level(i2c_port->scl)) |
| return 0; |
| i2c_delay(); |
| } |
| |
| /* |
| * SMBus 3.0, Note 3 |
| * Devices participating in a transfer can abort the transfer in |
| * progress and release the bus when any single clock low interval |
| * exceeds the value of tTIMEOUT,MIN(=25ms). |
| * After the controller in a transaction detects this condition, it must |
| * generate a stop condition within or after the current data byte in |
| * the transfer process. |
| */ |
| i2c_stop_cond(i2c_port); |
| CPUTS("clock low timeout\n"); |
| |
| return EC_ERROR_TIMEOUT; |
| } |
| |
| static int i2c_start_cond(const struct i2c_port_t *i2c_port) |
| { |
| int err; |
| |
| if (started) { |
| gpio_set_level(i2c_port->sda, 1); |
| i2c_delay(); |
| |
| gpio_set_level(i2c_port->scl, 1); |
| err = clock_stretching(i2c_port); |
| if (err) |
| return err; |
| i2c_delay(); |
| |
| if (gpio_get_level(i2c_port->sda) == 0) { |
| CPUTS("start_cond: arbitration lost\n"); |
| started = 0; |
| return EC_ERROR_UNKNOWN; |
| } |
| } |
| |
| /* check if bus is idle before starting */ |
| if (gpio_get_level(i2c_port->scl) == 0 || |
| gpio_get_level(i2c_port->sda) == 0) |
| return EC_ERROR_UNKNOWN; |
| |
| gpio_set_level(i2c_port->sda, 0); |
| i2c_delay(); |
| |
| gpio_set_level(i2c_port->scl, 0); |
| started = 1; |
| |
| return 0; |
| } |
| |
| static int i2c_write_bit(const struct i2c_port_t *i2c_port, int bit) |
| { |
| int err; |
| |
| gpio_set_level(i2c_port->sda, !!bit); |
| i2c_delay(); |
| |
| gpio_set_level(i2c_port->scl, 1); |
| err = clock_stretching(i2c_port); |
| if (err) |
| return err; |
| i2c_delay(); |
| |
| if (bit && gpio_get_level(i2c_port->sda) == 0) { |
| CPUTS("write_bit: arbitration lost\n"); |
| started = 0; |
| return EC_ERROR_UNKNOWN; |
| } |
| |
| gpio_set_level(i2c_port->scl, 0); |
| |
| return 0; |
| } |
| |
| static int i2c_read_bit(const struct i2c_port_t *i2c_port, int *bit) |
| { |
| int err; |
| |
| gpio_set_level(i2c_port->sda, 1); |
| i2c_delay(); |
| |
| gpio_set_level(i2c_port->scl, 1); |
| err = clock_stretching(i2c_port); |
| if (err) |
| return err; |
| i2c_delay(); |
| *bit = gpio_get_level(i2c_port->sda); |
| |
| gpio_set_level(i2c_port->scl, 0); |
| |
| return 0; |
| } |
| |
| static int i2c_write_byte(const struct i2c_port_t *i2c_port, uint8_t byte) |
| { |
| int i, nack, err; |
| |
| for (i = 7; i >= 0; i--) { |
| err = i2c_write_bit(i2c_port, byte & (1 << i)); |
| if (err) |
| return err; |
| } |
| |
| err = i2c_read_bit(i2c_port, &nack); |
| if (err) |
| return err; |
| |
| if (nack) { |
| /* |
| * The peripheral device detects an invalid command or invalid |
| * data. In this case the peripheral device must NACK the |
| * received byte. The controller upon detection of this |
| * condition must generate a STOP condition and retry the |
| * transaction |
| */ |
| i2c_stop_cond(i2c_port); |
| /* return EC_ERROR_BUSY to indicate i2c_xfer() to retry */ |
| return EC_ERROR_BUSY; |
| } |
| return 0; |
| } |
| |
| static int i2c_read_byte(const struct i2c_port_t *i2c_port, uint8_t *byte, |
| int nack) |
| { |
| int i; |
| |
| *byte = 0; |
| for (i = 0; i < 8; i++) { |
| int bit = 0, err; |
| |
| err = i2c_read_bit(i2c_port, &bit); |
| if (err) |
| return err; |
| *byte = (*byte << 1) | bit; |
| } |
| |
| return i2c_write_bit(i2c_port, nack); |
| } |
| |
| static int i2c_bitbang_xfer(const struct i2c_port_t *i2c_port, |
| const uint16_t addr_flags, const uint8_t *out, |
| int out_size, uint8_t *in, int in_size, int flags) |
| { |
| uint16_t addr_8bit = addr_flags << 1, err = EC_SUCCESS; |
| int i = 0; |
| |
| if (i2c_port->kbps != 100) |
| CPUTS("warning: bitbang driver only supports 100kbps\n"); |
| |
| if (out_size) { |
| if (flags & I2C_XFER_START) { |
| err = i2c_start_cond(i2c_port); |
| if (err) |
| goto exit; |
| err = i2c_write_byte(i2c_port, addr_8bit); |
| if (err) |
| goto exit; |
| } |
| |
| for (i = 0; i < out_size; i++) { |
| err = i2c_write_byte(i2c_port, out[i]); |
| if (err) |
| goto exit; |
| } |
| } |
| |
| if (in_size) { |
| if (flags & I2C_XFER_START) { |
| err = i2c_start_cond(i2c_port); |
| if (err) |
| goto exit; |
| err = i2c_write_byte(i2c_port, addr_8bit | 1); |
| if (err) |
| goto exit; |
| } |
| |
| for (i = 0; i < in_size; i++) { |
| err = i2c_read_byte(i2c_port, &in[i], |
| (flags & I2C_XFER_STOP) && |
| (i == in_size - 1)); |
| if (err) |
| goto exit; |
| } |
| } |
| |
| if (flags & I2C_XFER_STOP) |
| i2c_stop_cond(i2c_port); |
| |
| exit: |
| if (err) { |
| i2c_bitbang_unwedge(i2c_port); |
| started = 0; |
| } |
| return err; |
| } |
| |
| void enable_i2c_raw_mode(bool enable) |
| { |
| int i; |
| |
| for (i = 0; i < i2c_bitbang_ports_used; i++) { |
| if (i2c_raw_mode(i2c_bitbang_ports[i].port, enable)) |
| CPRINTS("I2C p%d: Failed to %s raw mode", |
| i2c_bitbang_ports[i].port, |
| enable ? "enable" : "disable"); |
| } |
| } |
| |
| __overridable void board_pre_task_i2c_peripheral_init(void) |
| { |
| } |
| |
| const struct i2c_drv bitbang_drv = { .xfer = &i2c_bitbang_xfer }; |
| |
| #ifdef TEST_BUILD |
| int bitbang_start_cond(const struct i2c_port_t *i2c_port) |
| { |
| return i2c_start_cond(i2c_port); |
| } |
| |
| void bitbang_stop_cond(const struct i2c_port_t *i2c_port) |
| { |
| i2c_stop_cond(i2c_port); |
| } |
| |
| int bitbang_write_byte(const struct i2c_port_t *i2c_port, uint8_t byte) |
| { |
| return i2c_write_byte(i2c_port, byte); |
| } |
| |
| void bitbang_set_started(int val) |
| { |
| started = val; |
| } |
| #endif |