1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00
RIOT/cpu/esp_common/periph/i2c_sw.c
2024-02-18 20:46:09 +01:00

858 lines
24 KiB
C

/*
* Copyright (C) 2019 Gunar Schorcht
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @ingroup cpu_esp_common
* @ingroup drivers_periph_i2c
* @{
*
* @file
* @brief Low-level I2C driver software implementation for ESP SoCs
*
* @author Gunar Schorcht <gunar@schorcht.net>
*
* @}
*/
/*
PLEASE NOTE:
The implementation bases on the bit-banging I2C master implementation as
described in [wikipedia](https://en.wikipedia.org/wiki/I%C2%B2C#Example_of_bit-banging_the_I%C2%B2C_master_protocol).
*/
#define ENABLE_DEBUG 0
#include "debug.h"
#include <assert.h>
#include <errno.h>
#include <stdbool.h>
#include "cpu.h"
#include "log.h"
#include "macros/units.h"
#include "mutex.h"
#include "periph_conf.h"
#include "periph/gpio.h"
#include "periph/i2c.h"
#include "esp_attr.h"
#include "esp_common.h"
#if !defined(CPU_ESP8266)
#include "esp_private/esp_clk.h"
#endif
#include "gpio_arch.h"
#include "rom/ets_sys.h"
#ifndef CPU_ESP8266
#include "hal/cpu_hal.h"
#include "soc/gpio_reg.h"
#include "soc/gpio_struct.h"
/* max clock stretching counter */
#define I2C_CLOCK_STRETCH 200
/* gpio access macros */
#if defined(CPU_FAM_ESP32) || defined(CPU_FAM_ESP32S2) || defined(CPU_FAM_ESP32S3)
#define GPIO_SET(lo, hi, b) if (b < 32) { GPIO.lo = BIT(b); } else { GPIO.hi.val = BIT(b-32); }
#define GPIO_GET(lo, hi, b) ((b < 32) ? GPIO.lo & BIT(b) : GPIO.hi.val & BIT(b-32))
#elif defined(CPU_FAM_ESP32C3)
#define GPIO_SET(lo, hi, b) GPIO.lo.val = BIT(b)
#define GPIO_GET(lo, hi, b) GPIO.lo.val & BIT(b)
#else
#error "Platform implementation is missing"
#endif
#else /* CPU_ESP8266 */
#include "esp/gpio_regs.h"
#include "sdk/ets.h"
/**
* @brief Clock stretching counter.
*
* Set to 0 to disable clock stretching. This will cause SCL to be always driven
* instead of open-drain, which allows using GPIO15 (normally pulled down to
* boot) to be used as SCL.
*/
#ifndef I2C_CLOCK_STRETCH
/* max clock stretching counter (ca. 10 ms) */
#define I2C_CLOCK_STRETCH 40000
#endif /* I2C_CLOCK_STRETCH */
/* following functions have to be declared as extern since it is not possible */
/* to include user_interface.h due to conflicts with gpio_init */
extern uint8_t system_get_cpu_freq(void);
extern bool system_update_cpu_freq(uint8_t freq);
#endif /* CPU_ESP8266 */
typedef struct {
i2c_speed_t speed;
i2c_t dev;
bool started;
gpio_t scl;
gpio_t sda;
uint32_t scl_bit; /* gpio bit mask for faster access */
uint32_t sda_bit; /* gpio bit mask for faster access */
uint32_t delay;
mutex_t lock;
} _i2c_bus_t;
static _i2c_bus_t _i2c_bus[I2C_NUMOF] = {};
/* to ensure that I2C is always optimized with -O2 to use the defined delays */
#pragma GCC optimize ("O2")
#if defined(CPU_FAM_ESP32)
#define I2C_CLK_CAL 62 /* clock calibration offset */
#elif defined(CPU_FAM_ESP32C3)
#define I2C_CLK_CAL 32 /* clock calibration offset */
#elif defined(CPU_FAM_ESP32S2)
#define I2C_CLK_CAL 82 /* clock calibration offset */
#elif defined(CPU_FAM_ESP32S3)
#define I2C_CLK_CAL 82 /* clock calibration offset */
#elif defined(CPU_ESP8266)
#define I2C_CLK_CAL 47 /* clock calibration offset */
#else
#error "Platform implementation is missing"
#endif
static const uint32_t _i2c_clocks[] = {
10 * KHZ(1), /* I2C_SPEED_LOW */
100 * KHZ(1), /* I2C_SPEED_NORMAL */
400 * KHZ(1), /* I2C_SPEED_FAST */
1000 * KHZ(1), /* I2C_SPEED_FAST_PLUS */
3400 * KHZ(1), /* I2C_SPEED_HIGH */
};
/* forward declaration of internal functions */
static inline void _i2c_delay(_i2c_bus_t* bus);
static inline bool _i2c_scl_read(_i2c_bus_t* bus);
static inline bool _i2c_sda_read(_i2c_bus_t* bus);
static inline void _i2c_scl_high(_i2c_bus_t* bus);
static inline void _i2c_scl_low(_i2c_bus_t* bus);
static inline void _i2c_sda_high(_i2c_bus_t* bus);
static inline void _i2c_sda_low(_i2c_bus_t* bus);
static int _i2c_start_cond(_i2c_bus_t* bus);
static int _i2c_stop_cond(_i2c_bus_t* bus);
static int _i2c_write_bit(_i2c_bus_t* bus, bool bit);
static int _i2c_read_bit(_i2c_bus_t* bus, bool* bit);
static int _i2c_write_byte(_i2c_bus_t* bus, uint8_t byte);
static int _i2c_read_byte(_i2c_bus_t* bus, uint8_t* byte, bool ack);
static int _i2c_arbitration_lost(_i2c_bus_t* bus, const char* func);
static void _i2c_abort(_i2c_bus_t* bus, const char* func);
static void _i2c_clear(_i2c_bus_t* bus);
#if defined(CPU_ESP8266)
static inline int esp_clk_cpu_freq(void)
{
return ets_get_cpu_frequency() * MHZ(1);
}
#endif
/* implementation of i2c interface */
void i2c_init(i2c_t dev)
{
assert(dev < I2C_NUMOF_MAX);
assert(dev < I2C_NUMOF);
/* clock speeds up to 1 MHz are supported */
assert(i2c_config[dev].speed <= I2C_SPEED_FAST_PLUS);
mutex_init(&_i2c_bus[dev].lock);
_i2c_bus[dev].scl = i2c_config[dev].scl;
_i2c_bus[dev].sda = i2c_config[dev].sda;
_i2c_bus[dev].speed = i2c_config[dev].speed;
_i2c_bus[dev].dev = dev;
_i2c_bus[dev].scl_bit = BIT(_i2c_bus[dev].scl); /* store bit mask for faster access */
_i2c_bus[dev].sda_bit = BIT(_i2c_bus[dev].sda); /* store bit mask for faster access */
_i2c_bus[dev].started = false; /* for handling of repeated start condition */
uint32_t delay;
delay = esp_clk_cpu_freq() / _i2c_clocks[_i2c_bus[dev].speed] / 2;
delay = (delay > I2C_CLK_CAL) ? delay - I2C_CLK_CAL : 0;
_i2c_bus[dev].delay = delay;
DEBUG("%s: scl=%d sda=%d speed=%d\n", __func__,
_i2c_bus[dev].scl, _i2c_bus[dev].sda, _i2c_bus[dev].speed);
/* reset the GPIO usage if the pins were used for I2C before */
if (gpio_get_pin_usage(_i2c_bus[dev].scl) == _I2C) {
gpio_set_pin_usage(_i2c_bus[dev].scl, _GPIO);
}
if (gpio_get_pin_usage(_i2c_bus[dev].sda) == _I2C) {
gpio_set_pin_usage(_i2c_bus[dev].sda, _GPIO);
}
/* Configure and initialize SDA and SCL pin. */
#ifndef CPU_ESP8266
/*
* ESP32 pins are used in input/output mode with open-drain output driver.
* Signal levels are then realized as following:
*
* - HIGH: Output value 1 lets the pin floating and is pulled-up to high.
* - LOW : Output value 0 actively drives the pin to low.
*/
if (gpio_init(_i2c_bus[dev].scl, GPIO_IN_OD_PU) ||
gpio_init(_i2c_bus[dev].sda, GPIO_IN_OD_PU)) {
return;
}
/* clear the bus by sending 10 clock pulses while SDA is LOW */
gpio_set(i2c_config[dev].scl);
gpio_set(i2c_config[dev].sda);
gpio_clear(i2c_config[dev].sda);
for (int i = 0; i < 20; i++) {
gpio_toggle(i2c_config[dev].scl);
}
gpio_set(i2c_config[dev].sda);
#else /* !CPU_ESP8266 */
/*
* Due to critical timing required by the I2C software implementation,
* the ESP8266 GPIOs can not be used directly in GPIO_OD_PU mode.
* Instead, the GPIOs are configured in GPIO_IN_PU mode with open-drain
* output driver. Signal levels are then realized as following:
*
* - HIGH: The GPIO is used in the configured GPIO_IN_PU mode. In this
* mode, the output driver is in open-drain mode and pulled-up.
* - LOW : The GPIO is temporarily switched to GPIO_OD_PU mode. In this
* mode, the output value 0, which is written during
* initialization, actively drives the pin to low.
*
* When I2C_CLOCK_STRETCH is 0 (disabled), SCL is instead an always driven
* output GPIO with no pull-up.
*/
if (gpio_init(_i2c_bus[dev].scl, I2C_CLOCK_STRETCH ? GPIO_IN_PU : GPIO_OUT) ||
gpio_init(_i2c_bus[dev].sda, GPIO_IN_PU)) {
return;
}
#endif /* !CPU_ESP8266 */
/* store the usage type in GPIO table */
gpio_set_pin_usage(_i2c_bus[dev].scl, _I2C);
gpio_set_pin_usage(_i2c_bus[dev].sda, _I2C);
/* set SDA and SCL to be floating and pulled-up to high */
_i2c_sda_high(&_i2c_bus[dev]);
_i2c_scl_high(&_i2c_bus[dev]);
/* clear the bus if necessary (SDA is driven permanently low) */
_i2c_clear(&_i2c_bus[dev]);
return;
}
void i2c_acquire(i2c_t dev)
{
assert(dev < I2C_NUMOF);
mutex_lock(&_i2c_bus[dev].lock);
}
void i2c_release(i2c_t dev)
{
assert(dev < I2C_NUMOF);
mutex_unlock(&_i2c_bus[dev].lock);
}
int IRAM_ATTR i2c_read_bytes(i2c_t dev, uint16_t addr, void *data, size_t len, uint8_t flags)
{
DEBUG("%s: dev=%u addr=%02x data=%p len=%d flags=%01x\n",
__func__, dev, addr, data, len, flags);
assert(dev < I2C_NUMOF);
CHECK_PARAM_RET(len > 0, -EINVAL);
CHECK_PARAM_RET(data != NULL, -EINVAL);
_i2c_bus_t* bus = &_i2c_bus[dev];
int res = 0;
/* send START condition and address if I2C_NOSTART is not set */
if (!(flags & I2C_NOSTART)) {
/* START condition */
if ((res = _i2c_start_cond(bus)) != 0) {
return res;
}
/* send 10 bit or 7 bit address */
if (flags & I2C_ADDR10) {
/* prepare 10 bit address bytes */
uint8_t addr1 = 0xf0 | (addr & 0x0300) >> 7 | I2C_READ;
uint8_t addr2 = addr & 0xff;
/* send address bytes with read flag */
if ((res = _i2c_write_byte(bus, addr1)) != 0 ||
(res = _i2c_write_byte(bus, addr2)) != 0) {
/* abort transfer */
_i2c_abort(bus, __func__);
return -ENXIO;
}
}
else {
/* send address byte with read flag */
if ((res = _i2c_write_byte(bus, (addr << 1 | I2C_READ))) != 0) {
/* abort transfer */
_i2c_abort(bus, __func__);
return -ENXIO;
}
}
}
/* receive bytes if send address was successful */
for (unsigned int i = 0; i < len; i++) {
if ((res = _i2c_read_byte(bus, &(((uint8_t*)data)[i]), i < len-1)) != 0) {
/* abort transfer */
_i2c_abort(bus, __func__);
return res;
}
}
/* send STOP condition if I2C_NOSTOP flag is not set */
if (!(flags & I2C_NOSTOP)) {
res = _i2c_stop_cond(bus);
}
return res;
}
int IRAM_ATTR i2c_write_bytes(i2c_t dev, uint16_t addr, const void *data, size_t len, uint8_t flags)
{
DEBUG("%s: dev=%u addr=%02x data=%p len=%d flags=%01x\n",
__func__, dev, addr, data, len, flags);
assert(dev < I2C_NUMOF);
CHECK_PARAM_RET(len > 0, -EINVAL);
CHECK_PARAM_RET(data != NULL, -EINVAL);
_i2c_bus_t* bus = &_i2c_bus[dev];
int res = 0;
/* if I2C_NOSTART is not set, send START condition and ADDR */
if (!(flags & I2C_NOSTART)) {
/* START condition */
if ((res = _i2c_start_cond(bus)) != 0) {
return res;
}
/* send 10 bit or 7 bit address */
if (flags & I2C_ADDR10) {
/* prepare 10 bit address bytes */
uint8_t addr1 = 0xf0 | (addr & 0x0300) >> 7;
uint8_t addr2 = addr & 0xff;
/* send address bytes without read flag */
if ((res = _i2c_write_byte(bus, addr1)) != 0 ||
(res = _i2c_write_byte(bus, addr2)) != 0) {
/* abort transfer */
_i2c_abort(bus, __func__);
return -ENXIO;
}
}
else {
/* send address byte without read flag */
if ((res = _i2c_write_byte(bus, addr << 1)) != 0) {
/* abort transfer */
_i2c_abort(bus, __func__);
return -ENXIO;
}
}
}
/* send bytes if send address was successful */
for (unsigned int i = 0; i < len; i++) {
if ((res = _i2c_write_byte(bus, ((uint8_t*)data)[i])) != 0) {
/* abort transfer */
_i2c_abort(bus, __func__);
return res;
}
}
/* send STOP condition if I2C_NOSTOP flag is not set */
if (!(flags & I2C_NOSTOP)) {
res = _i2c_stop_cond(bus);
}
return res;
}
void i2c_poweron(i2c_t dev)
{
/* since I2C is realized in software there is no device to power on */
/* just return */
}
void i2c_poweroff(i2c_t dev)
{
/* since I2C is realized in software there is no device to power off */
/* just return */
}
/* --- internal functions --- */
static inline void _i2c_delay(_i2c_bus_t* bus)
{
/* produces a delay */
uint32_t cycles = bus->delay;
if (cycles) {
#ifdef CPU_ESP8266
uint32_t ccount;
__asm__ __volatile__("rsr %0,ccount":"=a" (ccount));
uint32_t wait_until = ccount + cycles;
while (ccount < wait_until) {
__asm__ __volatile__("rsr %0,ccount":"=a" (ccount));
}
#else
uint32_t start = cpu_hal_get_cycle_count();
uint32_t wait_until = start + cycles;
while (cpu_hal_get_cycle_count() < wait_until) { }
#endif
}
}
/*
* Please note: SDA and SDL pins are used in GPIO_OD_PU mode
* (open-drain with pull-ups).
*
* Setting a pin which is in open-drain mode leaves the pin floating and
* the signal is pulled up to high. The signal can then be actively driven
* to low by a slave. A read operation returns the current signal at the pin.
*
* Clearing a pin which is in open-drain mode actively drives the signal to
* low.
*/
static inline bool _i2c_scl_read(_i2c_bus_t* bus)
{
/* read SCL status (pin is in open-drain mode and set) */
#ifndef CPU_ESP8266
return GPIO_GET(in, in1, bus->scl);
#else /* !CPU_ESP8266 */
return GPIO.IN & bus->scl_bit;
#endif /* !CPU_ESP8266 */
}
static inline bool _i2c_sda_read(_i2c_bus_t* bus)
{
/* read SDA status (pin is in open-drain mode and set) */
#ifndef CPU_ESP8266
return GPIO_GET(in, in1, bus->sda);
#else /* !CPU_ESP8266 */
return GPIO.IN & bus->sda_bit;
#endif /* !CPU_ESP8266 */
}
static inline void _i2c_scl_high(_i2c_bus_t* bus)
{
#ifndef CPU_ESP8266
/* set SCL signal high (pin is in open-drain mode and pulled-up) */
GPIO_SET(out_w1ts, out1_w1ts, bus->scl);
#else /* !CPU_ESP8266 */
#if I2C_CLOCK_STRETCH > 0
/*
* set SCL signal high (switch back to GPIO_IN_PU mode, that is the pin is
* in open-drain mode and pulled-up to high)
*/
GPIO.ENABLE_OUT_CLEAR = bus->scl_bit;
#else /* I2C_CLOCK_STRETCH > 0 */
/* No clock stretching supported, always drive the SCL pin. */
GPIO.OUT_SET = bus->scl_bit;
#endif /* I2C_CLOCK_STRETCH > 0 */
#endif /* !CPU_ESP8266 */
}
static inline void _i2c_scl_low(_i2c_bus_t* bus)
{
#ifndef CPU_ESP8266
/* set SCL signal low (actively driven to low) */
GPIO_SET(out_w1tc, out1_w1tc, bus->scl);
#else /* !CPU_ESP8266 */
#if I2C_CLOCK_STRETCH > 0
/*
* set SCL signal low (switch temporarily to GPIO_OD_PU where the
* written output value 0 drives the pin actively to low)
*/
GPIO.ENABLE_OUT_SET = bus->scl_bit;
#else /* I2C_CLOCK_STRETCH > 0 */
/* No clock stretching supported, always drive the SCL pin. */
GPIO.OUT_CLEAR = bus->scl_bit;
#endif /* I2C_CLOCK_STRETCH > 0 */
#endif /* !CPU_ESP8266 */
}
static inline void _i2c_sda_high(_i2c_bus_t* bus)
{
#ifndef CPU_ESP8266
/* set SDA signal high (pin is in open-drain mode and pulled-up) */
GPIO_SET(out_w1ts, out1_w1ts, bus->sda);
#else /* !CPU_ESP8266 */
/*
* set SDA signal high (switch back to GPIO_IN_PU mode, that is the pin is
* in open-drain mode and pulled-up to high)
*/
GPIO.ENABLE_OUT_CLEAR = bus->sda_bit;
#endif /* !CPU_ESP8266 */
}
static inline void _i2c_sda_low(_i2c_bus_t* bus)
{
#ifndef CPU_ESP8266
/* set SDA signal low (actively driven to low) */
GPIO_SET(out_w1tc, out1_w1tc, bus->sda);
#else /* !CPU_ESP8266 */
/*
* set SDA signal low (switch temporarily to GPIO_OD_PU where the
* written output value 0 drives the pin actively to low)
*/
GPIO.ENABLE_OUT_SET = bus->sda_bit;
#endif /* !CPU_ESP8266 */
}
static void _i2c_clear(_i2c_bus_t* bus)
{
DEBUG("%s: dev=%u\n", __func__, bus->dev);
/**
* Sometimes a slave blocks and drives the SDA line permanently low.
* Send some clock pulses in that case (10 at maximum)
*/
/*
* If SDA is low while SCL is high for 10 half cycles, it is not an
* arbitration lost but a bus lock.
*/
int count = 10;
while (!_i2c_sda_read(bus) && _i2c_scl_read(bus) && count) {
count--;
_i2c_delay(bus);
}
if (count) {
/* was not a bus lock */
return;
}
/* send 10 clock pulses in case of bus lock */
count = 10;
while (!_i2c_sda_read(bus) && count--) {
_i2c_scl_low(bus);
_i2c_delay(bus);
_i2c_scl_high(bus);
_i2c_delay(bus);
}
}
static void _i2c_abort(_i2c_bus_t* bus, const char* func)
{
DEBUG("%s: dev=%u\n", func, bus->dev);
/* reset SCL and SDA to passive HIGH (floating and pulled-up) */
_i2c_sda_high(bus);
_i2c_scl_high(bus);
/* reset repeated start indicator */
bus->started = false;
/* clear the bus if necessary (SDA is driven permanently low) */
_i2c_clear(bus);
}
static IRAM_ATTR int _i2c_arbitration_lost(_i2c_bus_t* bus, const char* func)
{
DEBUG("%s: arbitration lost dev=%u\n", func, bus->dev);
/* reset SCL and SDA to passive HIGH (floating and pulled-up) */
_i2c_sda_high(bus);
_i2c_scl_high(bus);
/* reset repeated start indicator */
bus->started = false;
/* clear the bus if necessary (SDA is driven permanently low) */
_i2c_clear(bus);
return -EAGAIN;
}
static IRAM_ATTR int _i2c_start_cond(_i2c_bus_t* bus)
{
/*
* send start condition
* on entry: SDA and SCL are set to be floating and pulled-up to high
* on exit : SDA and SCL are actively driven to low
*/
int res = 0;
if (bus->started) {
/* prepare the repeated start condition */
/* SDA = passive HIGH (floating and pulled-up) */
_i2c_sda_high(bus);
/* t_VD;DAT not necessary */
/* _i2c_delay(bus); */
/* SCL = passive HIGH (floating and pulled-up) */
_i2c_scl_high(bus);
/* clock stretching, wait as long as clock is driven to low by the slave */
uint32_t stretch = I2C_CLOCK_STRETCH;
while (stretch && !_i2c_scl_read(bus)) {
stretch--;
}
if (stretch == 0) {
DEBUG("%s: clock stretching timeout dev=%u\n", __func__, bus->dev);
res = -ETIMEDOUT;
}
/* wait t_SU;STA - set-up time for a repeated START condition */
/* min. in us: 4.7 (SM), 0.6 (FM), 0.26 (FPM), 0.16 (HSM); no max. */
_i2c_delay(bus);
}
/* if SDA is low, arbitration is lost and someone else is driving the bus */
if (!_i2c_sda_read(bus)) {
return _i2c_arbitration_lost(bus, __func__);
}
/* begin the START condition: SDA = active LOW */
_i2c_sda_low(bus);
/* wait t_HD;STA - hold time (repeated) START condition, */
/* max none */
/* min 4.0 us (SM), 0.6 us (FM), 0.26 us (FPM), 0.16 us (HSM) */
_i2c_delay(bus);
/* complete the START condition: SCL = active LOW */
_i2c_scl_low(bus);
/* needed for repeated start condition */
bus->started = true;
return res;
}
static IRAM_ATTR int _i2c_stop_cond(_i2c_bus_t* bus)
{
/*
* send stop condition
* on entry: SCL is active low and SDA can be changed
* on exit : SCL and SDA are set to be floating and pulled-up to high
*/
int res = 0;
/* begin the STOP condition: SDA = active LOW */
_i2c_sda_low(bus);
/* wait t_LOW - LOW period of SCL clock */
/* min. in us: 4.7 (SM), 1.3 (FM), 0.5 (FPM), 0.16 (HSM); no max. */
_i2c_delay(bus);
/* SCL = passive HIGH (floating and pulled up) while SDA = active LOW */
_i2c_scl_high(bus);
/* clock stretching, wait as long as clock is driven to low by the slave */
uint32_t stretch = I2C_CLOCK_STRETCH;
while (stretch && !_i2c_scl_read(bus)) {
stretch--;
}
if (stretch == 0) {
DEBUG("%s: clock stretching timeout dev=%u\n", __func__, bus->dev);
res = -ETIMEDOUT;
}
/* wait t_SU;STO - hold time STOP condition, */
/* min. in us: 4.0 (SM), 0.6 (FM), 0.26 (FPM), 0.16 (HSM); no max. */
_i2c_delay(bus);
/* complete the STOP condition: SDA = passive HIGH (floating and pulled up) */
_i2c_sda_high(bus);
/* reset repeated start indicator */
bus->started = false;
/* wait t_BUF - bus free time between a STOP and a START condition */
/* min. in us: 4.7 (SM), 1.3 (FM), 0.5 (FPM), 0.16 (HSM); no max. */
_i2c_delay(bus);
/* one additional delay */
_i2c_delay(bus);
/* if SDA is low, arbitration is lost and someone else is driving the bus */
if (_i2c_sda_read(bus) == 0) {
return _i2c_arbitration_lost(bus, __func__);
}
return res;
}
static IRAM_ATTR int _i2c_write_bit(_i2c_bus_t* bus, bool bit)
{
/*
* send one bit
* on entry: SCL is active low, SDA can be changed
* on exit : SCL is active low, SDA can be changed
*/
int res = 0;
/* SDA = bit */
if (bit) {
_i2c_sda_high(bus);
}
else {
_i2c_sda_low(bus);
}
/* wait t_VD;DAT - data valid time (time until data are valid) */
/* max. in us: 3.45 (SM), 0.9 (FM), 0.45 (FPM); no min */
_i2c_delay(bus);
/* SCL = passive HIGH (floating and pulled-up), SDA value is available */
_i2c_scl_high(bus);
/* wait t_HIGH - time for the slave to read SDA */
/* min. in us: 4 (SM), 0.6 (FM), 0.26 (FPM), 0.09 (HSM); no max. */
_i2c_delay(bus);
/* clock stretching, wait as long as clock is driven low by the slave */
uint32_t stretch = I2C_CLOCK_STRETCH;
while (stretch && !_i2c_scl_read(bus)) {
stretch--;
}
if (stretch == 0) {
DEBUG("%s: clock stretching timeout dev=%u\n", __func__, bus->dev);
res = -ETIMEDOUT;
}
/* if SCL is high, now data is valid */
/* if SDA is high, check that nobody else is driving SDA low */
if (bit && !_i2c_sda_read(bus)) {
return _i2c_arbitration_lost(bus, __func__);
}
/* SCL = active LOW to allow next SDA change */
_i2c_scl_low(bus);
return res;
}
static IRAM_ATTR int _i2c_read_bit(_i2c_bus_t* bus, bool* bit)
{
/* read one bit
* on entry: SCL is active low, SDA can be changed
* on exit : SCL is active low, SDA can be changed
*/
int res = 0;
/* SDA = passive HIGH (floating and pulled-up) to let the slave drive data */
_i2c_sda_high(bus);
/* wait t_VD;DAT - data valid time (time until data are valid) */
/* max. in us: 3.45 (SM), 0.9 (FM), 0.45 (FPM); no min */
_i2c_delay(bus);
/* SCL = passive HIGH (floating and pulled-up), SDA value is available */
_i2c_scl_high(bus);
/* clock stretching, wait as long as clock is driven to low by the slave */
uint32_t stretch = I2C_CLOCK_STRETCH;
while (stretch && !_i2c_scl_read(bus)) {
stretch--;
}
if (stretch == 0) {
DEBUG("%s: clock stretching timeout dev=%u\n", __func__, bus->dev);
res = -ETIMEDOUT;
}
/* wait t_HIGH - time for the slave to read SDA */
/* min. in us: 4 (SM), 0.6 (FM), 0.26 (FPM), 0.09 (HSM); no max. */
_i2c_delay(bus);
/* SCL is high, read out bit */
*bit = _i2c_sda_read(bus);
/* SCL = active LOW to allow next SDA change */
_i2c_scl_low(bus);
return res;
}
static IRAM_ATTR int _i2c_write_byte(_i2c_bus_t* bus, uint8_t byte)
{
/* send one byte and returns 0 in case of ACK from slave */
/* send the byte from MSB to LSB */
for (unsigned i = 0; i < 8; i++) {
int res = _i2c_write_bit(bus, (byte & 0x80) != 0);
if (res != 0) {
return res;
}
byte = byte << 1;
}
/* read acknowledge bit (low) from slave */
bool bit;
int res = _i2c_read_bit(bus, &bit);
if (res != 0) {
return res;
}
return !bit ? 0 : -EIO;
}
static IRAM_ATTR int _i2c_read_byte(_i2c_bus_t* bus, uint8_t *byte, bool ack)
{
bool bit;
/* read the byte */
for (unsigned i = 0; i < 8; i++) {
int res = _i2c_read_bit(bus, &bit);
if (res != 0) {
return res;
}
*byte = (*byte << 1) | (bit ? 1 : 0);
}
/* write acknowledgement flag */
_i2c_write_bit(bus, !ack);
return 0;
}
void i2c_print_config(void)
{
if (I2C_NUMOF) {
for (unsigned dev = 0; dev < I2C_NUMOF; dev++) {
printf("\tI2C_DEV(%u)\tscl=%d sda=%d\n",
dev, i2c_config[dev].scl, i2c_config[dev].sda);
}
}
else {
LOG_TAG_INFO("i2c", "no I2C devices\n");
}
}