/* Copyright 2019 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. */ #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) 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; } 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