mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2024-12-29 04:50:03 +01:00
870 lines
28 KiB
C
870 lines
28 KiB
C
/*
|
|
* Copyright (C) 2018 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_esp32
|
|
* @ingroup drivers_periph_i2c
|
|
* @{
|
|
*
|
|
* @file
|
|
* @brief Low-level I2C driver implementation for ESP32 SDK
|
|
*
|
|
* @note The hardware implementation seems to be very poor and faulty.
|
|
* I2C commands in the I2C command pipeline are not executed
|
|
* sporadically. A number of ACK errors and timeouts caused by
|
|
* protocol errors are the result. You should use the hardware
|
|
* implementation only if they can be tolerated.
|
|
*
|
|
* @author Gunar Schorcht <gunar@schorcht.net>
|
|
*
|
|
* @}
|
|
*/
|
|
|
|
#if defined(MODULE_ESP_I2C_HW) /* hardware implementation used */
|
|
|
|
/**
|
|
* PLEASE NOTE:
|
|
*
|
|
* Some parts of the implementation were inspired by the Espressif IoT
|
|
* Development Framework [ESP-IDF](https://github.com/espressif/esp-idf.git)
|
|
* implementation of I2C. These partes are marked with an according copyright
|
|
* notice.
|
|
*/
|
|
|
|
#define ENABLE_DEBUG (0)
|
|
#include "debug.h"
|
|
|
|
#include <errno.h>
|
|
#include <stdbool.h>
|
|
#include <string.h>
|
|
|
|
#include "cpu.h"
|
|
#include "log.h"
|
|
#include "mutex.h"
|
|
#include "periph_conf.h"
|
|
#include "periph/gpio.h"
|
|
#include "periph/i2c.h"
|
|
#include "thread_flags.h"
|
|
|
|
#include "esp_common.h"
|
|
#include "gpio_arch.h"
|
|
#include "driver/periph_ctrl.h"
|
|
#include "irq_arch.h"
|
|
#include "rom/ets_sys.h"
|
|
#include "soc/gpio_reg.h"
|
|
#include "soc/gpio_sig_map.h"
|
|
#include "soc/gpio_struct.h"
|
|
#include "soc/i2c_reg.h"
|
|
#include "soc/i2c_struct.h"
|
|
#include "soc/rtc.h"
|
|
#include "soc/soc.h"
|
|
#include "syscalls.h"
|
|
#include "xtensa/xtensa_api.h"
|
|
|
|
#if defined(I2C0_SPEED) || defined(I2C1_SPEED)
|
|
|
|
#undef I2C_CLK_FREQ
|
|
#define I2C_CLK_FREQ rtc_clk_apb_freq_get() /* APB_CLK is used */
|
|
|
|
/* operation codes used for commands */
|
|
#define I2C_CMD_RSTART 0
|
|
#define I2C_CMD_WRITE 1
|
|
#define I2C_CMD_READ 2
|
|
#define I2C_CMD_STOP 3
|
|
#define I2C_CMD_END 4
|
|
|
|
/* maximum number of data that can be written / read in one transfer */
|
|
#define I2C_MAX_DATA 30
|
|
|
|
#define I2C_FIFO_USED 1
|
|
|
|
struct i2c_hw_t {
|
|
i2c_dev_t* regs; /* pointer to register data struct of the I2C device */
|
|
uint8_t mod; /* peripheral hardware module of the I2C interface */
|
|
uint8_t int_src; /* peripheral interrupt source used by the I2C device */
|
|
uint8_t pin_scl; /* SCL pin */
|
|
uint8_t pin_sda; /* SDA pin */
|
|
uint8_t signal_scl_in; /* SCL signal to the controller */
|
|
uint8_t signal_scl_out; /* SCL signal from the controller */
|
|
uint8_t signal_sda_in; /* SDA signal to the controller */
|
|
uint8_t signal_sda_out; /* SDA signal from the controller */
|
|
};
|
|
|
|
static const struct i2c_hw_t _i2c_hw[] = {
|
|
#if defined(I2C0_SCL) && defined(I2C0_SDA) && defined(I2C0_SPEED)
|
|
{
|
|
.regs = &I2C0,
|
|
.mod = PERIPH_I2C0_MODULE,
|
|
.int_src = ETS_I2C_EXT0_INTR_SOURCE,
|
|
.pin_scl = I2C0_SCL,
|
|
.pin_sda = I2C0_SDA,
|
|
.signal_scl_in = I2CEXT0_SCL_IN_IDX,
|
|
.signal_scl_out = I2CEXT0_SCL_OUT_IDX,
|
|
.signal_sda_in = I2CEXT0_SDA_IN_IDX,
|
|
.signal_sda_out = I2CEXT0_SDA_OUT_IDX,
|
|
},
|
|
#endif
|
|
#if defined(I2C1_SCL) && defined(I2C1_SDA) && defined(I2C1_SPEED)
|
|
{
|
|
.regs = &I2C1,
|
|
.mod = PERIPH_I2C1_MODULE,
|
|
.int_src = ETS_I2C_EXT1_INTR_SOURCE,
|
|
.pin_scl = I2C1_SCL,
|
|
.pin_sda = I2C1_SDA,
|
|
.signal_scl_in = I2CEXT1_SCL_IN_IDX,
|
|
.signal_scl_out = I2CEXT1_SCL_OUT_IDX,
|
|
.signal_sda_in = I2CEXT1_SDA_IN_IDX,
|
|
.signal_sda_out = I2CEXT1_SDA_OUT_IDX,
|
|
}
|
|
#endif
|
|
};
|
|
|
|
struct _i2c_bus_t
|
|
{
|
|
i2c_speed_t speed; /* bus speed */
|
|
uint8_t cmd; /* command index */
|
|
uint8_t data; /* index in RAM for data */
|
|
mutex_t lock; /* mutex lock */
|
|
kernel_pid_t pid; /* PID of thread that triggered a transfer */
|
|
uint32_t results; /* results of a transfer */
|
|
};
|
|
|
|
static struct _i2c_bus_t _i2c_bus[] =
|
|
{
|
|
#if defined(I2C0_SCL) && defined(I2C0_SDA) && defined(I2C0_SPEED)
|
|
{
|
|
.speed = I2C0_SPEED,
|
|
.cmd = 0,
|
|
.data = 0,
|
|
.lock = MUTEX_INIT
|
|
},
|
|
#endif
|
|
#if defined(I2C1_SCL) && defined(I2C1_SDA) && defined(I2C1_SPEED)
|
|
{
|
|
.speed = I2C1_SPEED,
|
|
.cmd = 0,
|
|
.data = 0,
|
|
.lock = MUTEX_INIT
|
|
},
|
|
#endif
|
|
};
|
|
|
|
/* the number of I2C bus devices used */
|
|
const unsigned i2c_bus_num = sizeof(_i2c_bus) / sizeof(_i2c_bus[0]);
|
|
|
|
/* forward declaration of internal functions */
|
|
|
|
static int _i2c_init_pins (i2c_t dev);
|
|
static void _i2c_start_cmd (i2c_t dev);
|
|
static void _i2c_stop_cmd (i2c_t dev);
|
|
static void _i2c_end_cmd (i2c_t dev);
|
|
static void _i2c_write_cmd (i2c_t dev, const uint8_t* data, uint8_t len);
|
|
static void _i2c_read_cmd (i2c_t dev, uint8_t* data, uint8_t len, bool last);
|
|
static void _i2c_transfer (i2c_t dev);
|
|
static void _i2c_reset_hw (i2c_t dev);
|
|
static void _i2c_clear_bus (i2c_t dev);
|
|
static void _i2c_intr_handler (void *arg);
|
|
static inline void _i2c_delay (uint32_t delay);
|
|
|
|
/* implementation of i2c interface */
|
|
|
|
void i2c_init(i2c_t dev)
|
|
{
|
|
CHECK_PARAM (dev < i2c_bus_num)
|
|
|
|
if (_i2c_bus[dev].speed == I2C_SPEED_FAST_PLUS ||
|
|
_i2c_bus[dev].speed == I2C_SPEED_HIGH) {
|
|
LOG_TAG_INFO("i2c", "I2C_SPEED_FAST_PLUS and I2C_SPEED_HIGH "
|
|
"are not supported\n");
|
|
return;
|
|
}
|
|
|
|
i2c_acquire (dev);
|
|
|
|
_i2c_bus[dev].cmd = 0;
|
|
_i2c_bus[dev].data = 0;
|
|
|
|
DEBUG ("%s scl=%d sda=%d speed=%d\n", __func__,
|
|
_i2c_hw[dev].pin_scl, _i2c_hw[dev].pin_sda, _i2c_bus[dev].speed);
|
|
|
|
/* enable (power on) the according I2C module */
|
|
periph_module_enable(_i2c_hw[dev].mod);
|
|
|
|
/* initialize pins */
|
|
if (_i2c_init_pins(dev) != 0) {
|
|
return;
|
|
}
|
|
|
|
/* set master mode */
|
|
_i2c_hw[dev].regs->ctr.ms_mode = 1;
|
|
|
|
/* set bit order to MSB first */
|
|
_i2c_hw[dev].regs->ctr.tx_lsb_first = 0;
|
|
_i2c_hw[dev].regs->ctr.rx_lsb_first = 0;
|
|
|
|
/* determine the half period of clock in APB clock cycles */
|
|
uint32_t half_period = 0;
|
|
|
|
switch (_i2c_bus[dev].speed) {
|
|
case I2C_SPEED_LOW:
|
|
/* 10 kbps (period 100 us) */
|
|
half_period = (I2C_CLK_FREQ / 10000) >> 1;
|
|
break;
|
|
|
|
case I2C_SPEED_NORMAL:
|
|
/* 100 kbps (period 10 us) */
|
|
half_period = (I2C_CLK_FREQ / 100000) >> 1;
|
|
half_period = half_period * 95 / 100; /* correction factor */
|
|
break;
|
|
|
|
case I2C_SPEED_FAST:
|
|
/* 400 kbps (period 2.5 us) */
|
|
half_period = (I2C_CLK_FREQ / 400000) >> 1;
|
|
half_period = half_period * 82 / 100; /* correction factor */
|
|
break;
|
|
|
|
case I2C_SPEED_FAST_PLUS:
|
|
/* 1 Mbps (period 1 us) not working */
|
|
half_period = (I2C_CLK_FREQ / 1000000) >> 1;
|
|
break;
|
|
|
|
case I2C_SPEED_HIGH:
|
|
/* 3.4 Mbps (period 0.3 us) not working */
|
|
half_period = (I2C_CLK_FREQ / 3400000) >> 1;
|
|
break;
|
|
|
|
default:
|
|
LOG_TAG_ERROR("i2c", "Invalid speed value in %s\n", __func__);
|
|
return;
|
|
}
|
|
|
|
/* set an timeout which is at least 16 times of half cycle */
|
|
_i2c_hw[dev].regs->timeout.tout = half_period << 4;
|
|
|
|
/* timing for SCL (low and high time in APB clock cycles) */
|
|
_i2c_hw[dev].regs->scl_low_period.period = half_period;
|
|
_i2c_hw[dev].regs->scl_high_period.period = half_period;
|
|
|
|
/* timing for SDA (sample time after rising edge and hold time after falling edge) */
|
|
_i2c_hw[dev].regs->sda_sample.time = half_period >> 1;
|
|
_i2c_hw[dev].regs->sda_hold.time = half_period >> 1;
|
|
|
|
/* timing for START condition (START hold and repeated START setup time) */
|
|
_i2c_hw[dev].regs->scl_start_hold.time = half_period >> 1;
|
|
_i2c_hw[dev].regs->scl_rstart_setup.time = half_period >> 1;
|
|
|
|
/* timing for STOP condition (STOP hold and STOP setup time) */
|
|
_i2c_hw[dev].regs->scl_stop_hold.time = half_period >> 1;
|
|
_i2c_hw[dev].regs->scl_stop_setup.time = half_period >> 1;
|
|
|
|
/* configure open drain outputs */
|
|
_i2c_hw[dev].regs->ctr.scl_force_out = 1;
|
|
_i2c_hw[dev].regs->ctr.sda_force_out = 1;
|
|
|
|
/* sample data during high level */
|
|
_i2c_hw[dev].regs->ctr.sample_scl_level = 0;
|
|
|
|
/* enable non FIFO access and disable slave FIFO address offset */
|
|
#if I2C_FIFO_USED
|
|
_i2c_hw[dev].regs->fifo_conf.nonfifo_en = 0;
|
|
#else
|
|
_i2c_hw[dev].regs->fifo_conf.nonfifo_en = 1;
|
|
_i2c_hw[dev].regs->fifo_conf.nonfifo_rx_thres = 0;
|
|
_i2c_hw[dev].regs->fifo_conf.nonfifo_tx_thres = 0;
|
|
_i2c_hw[dev].regs->fifo_conf.rx_fifo_full_thrhd = 0;
|
|
_i2c_hw[dev].regs->fifo_conf.tx_fifo_empty_thrhd = 0;
|
|
|
|
#endif
|
|
_i2c_hw[dev].regs->fifo_conf.fifo_addr_cfg_en = 0;
|
|
|
|
/* route all I2C interrupt sources to same the CPU interrupt */
|
|
intr_matrix_set(PRO_CPU_NUM, _i2c_hw[dev].int_src, CPU_INUM_I2C);
|
|
|
|
/* set the interrupt handler and enable the interrupt */
|
|
xt_set_interrupt_handler(CPU_INUM_I2C, _i2c_intr_handler, NULL);
|
|
xt_ints_on(BIT(CPU_INUM_I2C));
|
|
|
|
i2c_release (dev);
|
|
|
|
return;
|
|
}
|
|
|
|
int i2c_acquire(i2c_t dev)
|
|
{
|
|
DEBUG ("%s\n", __func__);
|
|
|
|
CHECK_PARAM_RET (dev < i2c_bus_num, -1)
|
|
|
|
mutex_lock(&_i2c_bus[dev].lock);
|
|
_i2c_reset_hw(dev);
|
|
return 0;
|
|
}
|
|
|
|
int i2c_release(i2c_t dev)
|
|
{
|
|
DEBUG ("%s\n", __func__);
|
|
|
|
CHECK_PARAM_RET (dev < i2c_bus_num, -1)
|
|
|
|
_i2c_reset_hw (dev);
|
|
mutex_unlock(&_i2c_bus[dev].lock);
|
|
return 0;
|
|
}
|
|
|
|
#define _i2c_return_on_error(dev) \
|
|
if (_i2c_bus[dev].results & I2C_ARBITRATION_LOST_INT_ENA) { \
|
|
LOG_TAG_ERROR("i2c", "arbitration lost dev=%u\n", dev); \
|
|
_i2c_reset_hw (dev); \
|
|
__asm__ volatile ("isync"); \
|
|
return -EAGAIN; \
|
|
} \
|
|
else if (_i2c_bus[dev].results & I2C_ACK_ERR_INT_ENA) { \
|
|
LOG_TAG_ERROR("i2c", "ack error dev=%u\n", dev); \
|
|
_i2c_reset_hw (dev); \
|
|
__asm__ volatile ("isync"); \
|
|
return -EIO; \
|
|
} \
|
|
else if (_i2c_bus[dev].results & I2C_TIME_OUT_INT_ENA) { \
|
|
LOG_TAG_ERROR("i2c", "bus timeout dev=%u\n", dev); \
|
|
_i2c_reset_hw (dev); \
|
|
__asm__ volatile ("isync"); \
|
|
return -ETIMEDOUT; \
|
|
}
|
|
|
|
int 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);
|
|
|
|
CHECK_PARAM_RET (dev < i2c_bus_num, -EINVAL);
|
|
CHECK_PARAM_RET (len > 0, -EINVAL);
|
|
CHECK_PARAM_RET (data != NULL, -EINVAL);
|
|
|
|
/* if I2C_NOSTART is not set, START condition and ADDR is used */
|
|
if (!(flags & I2C_NOSTART)) {
|
|
|
|
/* send START condition */
|
|
_i2c_start_cmd (dev);
|
|
|
|
/* address handling */
|
|
if (flags & I2C_ADDR10) {
|
|
/* prepare 10 bit address bytes */
|
|
uint8_t addr10[2];
|
|
addr10[0] = 0xf0 | (addr & 0x0300) >> 7 | I2C_READ;
|
|
addr10[1] = addr & 0xff;
|
|
/* send ADDR with read flag */
|
|
_i2c_write_cmd (dev, addr10, 2);
|
|
}
|
|
else {
|
|
/* send ADDR with read flag */
|
|
uint8_t addr7 = (addr << 1 | I2C_READ);
|
|
_i2c_write_cmd (dev, &addr7, 1);
|
|
}
|
|
}
|
|
|
|
/* read data bytes in blocks of I2C_MAX_DATA bytes */
|
|
|
|
uint32_t off = 0;
|
|
|
|
/* if len > I2C_MAX_DATA read blocks I2C_MAX_DATA bytes at a time */
|
|
while (len > I2C_MAX_DATA) {
|
|
|
|
/* read one block of data bytes command */
|
|
_i2c_read_cmd (dev, data, I2C_MAX_DATA, false);
|
|
_i2c_end_cmd (dev);
|
|
_i2c_transfer (dev);
|
|
_i2c_return_on_error (dev);
|
|
|
|
/* if transfer was successful, fetch the data from I2C RAM */
|
|
for (unsigned i = 0; i < I2C_MAX_DATA; i++) {
|
|
#if I2C_FIFO_USED
|
|
((uint8_t*)data)[i + off] = _i2c_hw[dev].regs->fifo_data.data;
|
|
#else
|
|
((uint8_t*)data)[i + off] = _i2c_hw[dev].regs->ram_data[i];
|
|
#endif
|
|
}
|
|
|
|
len -= I2C_MAX_DATA;
|
|
off += I2C_MAX_DATA;
|
|
}
|
|
|
|
/* read remaining data bytes command with a final NAK */
|
|
_i2c_read_cmd (dev, data, len, true);
|
|
|
|
/* if I2C_NOSTOP flag is not set, send STOP condition is used */
|
|
if (!(flags & I2C_NOSTOP)) {
|
|
/* send STOP condition */
|
|
_i2c_stop_cmd (dev);
|
|
}
|
|
else {
|
|
/* otherwise place end command in pipeline */
|
|
_i2c_end_cmd (dev);
|
|
}
|
|
|
|
/* finish operation by executing the command pipeline */
|
|
_i2c_transfer (dev);
|
|
_i2c_return_on_error (dev);
|
|
|
|
/* if transfer was successful, fetch data from I2C RAM */
|
|
for (unsigned i = 0; i < len; i++) {
|
|
#if I2C_FIFO_USED
|
|
((uint8_t*)data)[i + off] = _i2c_hw[dev].regs->fifo_data.data;
|
|
#else
|
|
((uint8_t*)data)[i + off] = _i2c_hw[dev].regs->ram_data[i];
|
|
#endif
|
|
}
|
|
|
|
/* return 0 on success */
|
|
return 0;
|
|
}
|
|
|
|
int 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);
|
|
|
|
CHECK_PARAM_RET (dev < i2c_bus_num, -EINVAL);
|
|
CHECK_PARAM_RET (len > 0, -EINVAL);
|
|
CHECK_PARAM_RET (data != NULL, -EINVAL);
|
|
|
|
/* if I2C_NOSTART is not set, START condition and ADDR is used */
|
|
if (!(flags & I2C_NOSTART)) {
|
|
|
|
/* send START condition */
|
|
_i2c_start_cmd (dev);
|
|
|
|
/* address handling */
|
|
if (flags & I2C_ADDR10) {
|
|
/* prepare 10 bit address bytes */
|
|
uint8_t addr10[2];
|
|
addr10[0] = 0xf0 | (addr & 0x0300) >> 7;
|
|
addr10[1] = addr & 0xff;
|
|
/* send ADDR without read flag */
|
|
_i2c_write_cmd (dev, addr10, 2);
|
|
}
|
|
else {
|
|
/* send ADDR without read flag */
|
|
uint8_t addr7 = addr << 1;
|
|
_i2c_write_cmd (dev, &addr7, 1);
|
|
}
|
|
}
|
|
|
|
/* send data bytes in blocks of I2C_MAX_DATA bytes */
|
|
|
|
uint32_t off = 0;
|
|
|
|
/* if len > I2C_MAX_DATA write blocks I2C_MAX_DATA bytes at a time */
|
|
while (len > I2C_MAX_DATA) {
|
|
|
|
/* send on block of data bytes */
|
|
_i2c_write_cmd (dev, ((uint8_t*)data) + off, I2C_MAX_DATA);
|
|
_i2c_end_cmd (dev);
|
|
_i2c_transfer (dev);
|
|
_i2c_return_on_error (dev);
|
|
|
|
len -= I2C_MAX_DATA;
|
|
off += I2C_MAX_DATA;
|
|
}
|
|
|
|
/* write remaining data bytes command */
|
|
_i2c_write_cmd (dev, ((uint8_t*)data), len);
|
|
|
|
/* if I2C_NOSTOP flag is not set, send STOP condition is used */
|
|
if (!(flags & I2C_NOSTOP)) {
|
|
/* send STOP condition */
|
|
_i2c_stop_cmd (dev);
|
|
}
|
|
else {
|
|
/* otherwise place end command in pipeline */
|
|
_i2c_end_cmd (dev);
|
|
}
|
|
|
|
/* finish operation by executing the command pipeline */
|
|
_i2c_transfer (dev);
|
|
_i2c_return_on_error (dev);
|
|
|
|
/* return 0 on success */
|
|
return 0;
|
|
}
|
|
|
|
/* internal functions */
|
|
|
|
static int _i2c_init_pins(i2c_t dev)
|
|
{
|
|
/*
|
|
* reset GPIO usage type if the pins were used already for I2C before to
|
|
* make it possible to reinitialize I2C
|
|
*/
|
|
if (gpio_get_pin_usage(_i2c_hw[dev].pin_scl) == _I2C) {
|
|
gpio_set_pin_usage(_i2c_hw[dev].pin_scl, _GPIO);
|
|
}
|
|
if (gpio_get_pin_usage(_i2c_hw[dev].pin_sda) == _I2C) {
|
|
gpio_set_pin_usage(_i2c_hw[dev].pin_sda, _GPIO);
|
|
}
|
|
|
|
/* try to configure SDA and SCL pin as GPIO in open-drain mode with enabled pull-ups */
|
|
if (gpio_init (_i2c_hw[dev].pin_scl, GPIO_IN_OD_PU) ||
|
|
gpio_init (_i2c_hw[dev].pin_sda, GPIO_IN_OD_PU)) {
|
|
return -ENODEV;
|
|
}
|
|
|
|
/* bring signals to high */
|
|
gpio_set(_i2c_hw[dev].pin_scl);
|
|
gpio_set(_i2c_hw[dev].pin_sda);
|
|
|
|
/* store the usage type in GPIO table */
|
|
gpio_set_pin_usage(_i2c_hw[dev].pin_scl, _I2C);
|
|
gpio_set_pin_usage(_i2c_hw[dev].pin_sda, _I2C);
|
|
|
|
/* connect SCL and SDA pins to output signals through the GPIO matrix */
|
|
GPIO.func_out_sel_cfg[_i2c_hw[dev].pin_scl].func_sel = _i2c_hw[dev].signal_scl_out;
|
|
GPIO.func_out_sel_cfg[_i2c_hw[dev].pin_sda].func_sel = _i2c_hw[dev].signal_sda_out;
|
|
|
|
/* connect SCL and SDA input signals to pins through the GPIO matrix */
|
|
GPIO.func_in_sel_cfg[_i2c_hw[dev].signal_scl_in].sig_in_sel = 1;
|
|
GPIO.func_in_sel_cfg[_i2c_hw[dev].signal_scl_in].sig_in_inv = 0;
|
|
GPIO.func_in_sel_cfg[_i2c_hw[dev].signal_scl_in].func_sel = _i2c_hw[dev].pin_scl;
|
|
GPIO.func_in_sel_cfg[_i2c_hw[dev].signal_sda_in].sig_in_sel = 1;
|
|
GPIO.func_in_sel_cfg[_i2c_hw[dev].signal_sda_in].sig_in_inv = 0;
|
|
GPIO.func_in_sel_cfg[_i2c_hw[dev].signal_sda_in].func_sel = _i2c_hw[dev].pin_sda;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void _i2c_start_cmd(i2c_t dev)
|
|
{
|
|
DEBUG ("%s\n", __func__);
|
|
|
|
/* place START condition command in command queue */
|
|
_i2c_hw[dev].regs->command[_i2c_bus[dev].cmd].val = 0;
|
|
_i2c_hw[dev].regs->command[_i2c_bus[dev].cmd].op_code = I2C_CMD_RSTART;
|
|
|
|
/* increment the command counter */
|
|
_i2c_bus[dev].cmd++;
|
|
}
|
|
|
|
static void _i2c_stop_cmd (i2c_t dev)
|
|
{
|
|
DEBUG ("%s\n", __func__);
|
|
|
|
/* place STOP condition command in command queue */
|
|
_i2c_hw[dev].regs->command[_i2c_bus[dev].cmd].val = 0;
|
|
_i2c_hw[dev].regs->command[_i2c_bus[dev].cmd].op_code = I2C_CMD_STOP;
|
|
|
|
/* increment the command counter */
|
|
_i2c_bus[dev].cmd++;
|
|
}
|
|
|
|
static void _i2c_end_cmd (i2c_t dev)
|
|
{
|
|
DEBUG ("%s\n", __func__);
|
|
|
|
/* place END command for continues data transmission in command queue */
|
|
_i2c_hw[dev].regs->command[_i2c_bus[dev].cmd].val = 0;
|
|
_i2c_hw[dev].regs->command[_i2c_bus[dev].cmd].op_code = I2C_CMD_END;
|
|
|
|
/* increment the command counter */
|
|
_i2c_bus[dev].cmd++;
|
|
}
|
|
|
|
static void _i2c_write_cmd (i2c_t dev, const uint8_t* data, uint8_t len)
|
|
{
|
|
DEBUG ("%s dev=%u data=%p len=%d\n", __func__, dev, data, len);
|
|
|
|
if (_i2c_bus[dev].data + len > I2C_MAX_DATA) {
|
|
LOG_TAG_ERROR("i2c", "Maximum number of bytes (32 bytes) that can be "
|
|
"sent with on transfer reached\n");
|
|
return;
|
|
}
|
|
|
|
/* store the byte in RAM of I2C controller and increment the data counter */
|
|
for (int i = 0; i < len; i++) {
|
|
#if I2C_FIFO_USED
|
|
WRITE_PERI_REG(I2C_DATA_APB_REG(dev), data[i]);
|
|
#else
|
|
_i2c_hw[dev].regs->ram_data[_i2c_bus[dev].data++] = (uint32_t)data[i];
|
|
#endif
|
|
}
|
|
|
|
/* place WRITE command for multiple bytes in command queue */
|
|
_i2c_hw[dev].regs->command[_i2c_bus[dev].cmd].val = 0;
|
|
_i2c_hw[dev].regs->command[_i2c_bus[dev].cmd].byte_num = len;
|
|
_i2c_hw[dev].regs->command[_i2c_bus[dev].cmd].ack_en = 1;
|
|
_i2c_hw[dev].regs->command[_i2c_bus[dev].cmd].ack_exp = 0;
|
|
_i2c_hw[dev].regs->command[_i2c_bus[dev].cmd].ack_val = 0;
|
|
_i2c_hw[dev].regs->command[_i2c_bus[dev].cmd].op_code = I2C_CMD_WRITE;
|
|
|
|
/* increment the command counter */
|
|
_i2c_bus[dev].cmd++;
|
|
}
|
|
|
|
static void _i2c_read_cmd (i2c_t dev, uint8_t* data, uint8_t len, bool last)
|
|
{
|
|
DEBUG ("%s dev=%u data=%p len=%d\n", __func__, dev, data, len);
|
|
|
|
if (len < 1 || len > I2C_MAX_DATA) {
|
|
/* at least one byte has to be read */
|
|
LOG_TAG_ERROR("i2c", "At least one byte has to be read\n");
|
|
return;
|
|
}
|
|
|
|
if (len > 1)
|
|
{
|
|
/* place READ command for len-1 bytes with positive ack in command queue*/
|
|
_i2c_hw[dev].regs->command[_i2c_bus[dev].cmd].val = 0;
|
|
_i2c_hw[dev].regs->command[_i2c_bus[dev].cmd].byte_num = len-1;
|
|
_i2c_hw[dev].regs->command[_i2c_bus[dev].cmd].ack_en = 0;
|
|
_i2c_hw[dev].regs->command[_i2c_bus[dev].cmd].ack_exp = 0;
|
|
_i2c_hw[dev].regs->command[_i2c_bus[dev].cmd].ack_val = 0;
|
|
_i2c_hw[dev].regs->command[_i2c_bus[dev].cmd].op_code = I2C_CMD_READ;
|
|
|
|
/* increment the command counter */
|
|
_i2c_bus[dev].cmd++;
|
|
}
|
|
|
|
/* place READ command for last byte with negative ack in last segment in command queue*/
|
|
_i2c_hw[dev].regs->command[_i2c_bus[dev].cmd].val = 0;
|
|
_i2c_hw[dev].regs->command[_i2c_bus[dev].cmd].byte_num = 1;
|
|
_i2c_hw[dev].regs->command[_i2c_bus[dev].cmd].ack_en = 0;
|
|
_i2c_hw[dev].regs->command[_i2c_bus[dev].cmd].ack_exp = 0;
|
|
_i2c_hw[dev].regs->command[_i2c_bus[dev].cmd].ack_val = last ? 1 : 0;
|
|
_i2c_hw[dev].regs->command[_i2c_bus[dev].cmd].op_code = I2C_CMD_READ;
|
|
|
|
/* increment the command counter */
|
|
_i2c_bus[dev].cmd++;
|
|
}
|
|
|
|
static inline void _i2c_delay (uint32_t cycles)
|
|
{
|
|
/* produces a delay of 0,0625 us per cycle for -O2 compile option */
|
|
/* 1 us = ca. 16 cycles (80 MHz) / 1 us = 32 cycles (160 MHz) */
|
|
|
|
if (cycles) {
|
|
__asm__ volatile ("1: _addi.n %0, %0, -1 \n"
|
|
" bnez %0, 1b \n" : "=r" (cycles) : "0" (cycles));
|
|
}
|
|
}
|
|
|
|
/* transfer related interrupts handled by the driver */
|
|
static const uint32_t transfer_int_mask = I2C_TRANS_COMPLETE_INT_ENA
|
|
| I2C_END_DETECT_INT_ENA
|
|
| I2C_ACK_ERR_INT_ENA
|
|
| I2C_ARBITRATION_LOST_INT_ENA
|
|
| I2C_TIME_OUT_INT_ENA;
|
|
|
|
/* at I2C_SPEED_NORMAL a transfer takes at most 33 byte * 9 clock cycles * 1/100000 s */
|
|
#define I2C_TRANSFER_TIMEOUT 3000
|
|
|
|
#define I2C_THREAD_FLAG BIT (0)
|
|
|
|
#include "xtimer.h"
|
|
|
|
void _i2c_transfer_timeout (void *arg)
|
|
{
|
|
i2c_t dev = (i2c_t)arg;
|
|
|
|
/* reset the hardware if it I2C got stucked */
|
|
_i2c_reset_hw(dev);
|
|
|
|
/* set result to timeout */
|
|
_i2c_bus[dev].results |= I2C_TIME_OUT_INT_ST;
|
|
|
|
/* wake up the thread that is waiting for the results */
|
|
thread_flags_set((thread_t*)thread_get(_i2c_bus[dev].pid), I2C_THREAD_FLAG);
|
|
}
|
|
|
|
/* Transfer of commands in I2C controller command pipeline */
|
|
static void _i2c_transfer (i2c_t dev)
|
|
{
|
|
DEBUG("%s cmd=%d\n", __func__, _i2c_bus[dev].cmd);
|
|
|
|
#if FIFO_USED
|
|
/* reset RX FIFO queue */
|
|
_i2c_hw[dev].regs->fifo_conf.rx_fifo_rst = 1;
|
|
_i2c_hw[dev].regs->fifo_conf.rx_fifo_rst = 0;
|
|
#endif
|
|
|
|
/* disable and enable all transmission interrupts and clear current status */
|
|
_i2c_hw[dev].regs->int_ena.val &= ~transfer_int_mask;
|
|
_i2c_hw[dev].regs->int_ena.val |= transfer_int_mask;
|
|
_i2c_hw[dev].regs->int_clr.val = transfer_int_mask;
|
|
|
|
/* set a timer for the case the I2C hardware gets stuck */
|
|
xtimer_t i2c_timeout = {};
|
|
i2c_timeout.callback = _i2c_transfer_timeout;
|
|
i2c_timeout.arg = (void*)dev;
|
|
xtimer_set(&i2c_timeout, I2C_TRANSFER_TIMEOUT);
|
|
|
|
/* start execution of commands in command pipeline registers */
|
|
_i2c_bus[dev].pid = thread_getpid();
|
|
_i2c_bus[dev].results = 0;
|
|
_i2c_hw[dev].regs->ctr.trans_start = 0;
|
|
_i2c_hw[dev].regs->ctr.trans_start = 1;
|
|
|
|
/* wait for transfer results and remove timeout timer*/
|
|
thread_flags_wait_one(I2C_THREAD_FLAG);
|
|
xtimer_remove(&i2c_timeout);
|
|
|
|
/* returned from transmission */
|
|
DEBUG("%s results=%08x\n", __func__, _i2c_bus[dev].results);
|
|
|
|
#if FIFO_USED
|
|
/* reset TX FIFO queue */
|
|
_i2c_hw[dev].regs->fifo_conf.tx_fifo_rst = 1;
|
|
_i2c_hw[dev].regs->fifo_conf.tx_fifo_rst = 0;
|
|
#endif
|
|
|
|
/* reset command and data index */
|
|
_i2c_bus[dev].cmd = 0;
|
|
_i2c_bus[dev].data = 0;
|
|
}
|
|
|
|
static void IRAM_ATTR _i2c_intr_handler (void *arg)
|
|
{
|
|
/* to satisfy the compiler */
|
|
(void)arg;
|
|
|
|
irq_isr_enter ();
|
|
|
|
/* all I2C peripheral interrupt sources are routed to the same interrupt,
|
|
so we have to use the status register to distinguish interruptees */
|
|
for (unsigned dev = 0; dev < i2c_bus_num; dev++) {
|
|
/* test for transfer related interrupts */
|
|
if (_i2c_hw[dev].regs->int_status.val & transfer_int_mask) {
|
|
/* set transfer result */
|
|
_i2c_bus[dev].results |= _i2c_hw[dev].regs->int_status.val;
|
|
/* disable all interrupts and clear them and left them disabled */
|
|
_i2c_hw[dev].regs->int_ena.val &= ~transfer_int_mask;
|
|
_i2c_hw[dev].regs->int_clr.val = transfer_int_mask;
|
|
/* wake up the thread that is waiting for the results */
|
|
thread_flags_set((thread_t*)thread_get(_i2c_bus[dev].pid), I2C_THREAD_FLAG);
|
|
}
|
|
else if (_i2c_hw[dev].regs->int_status.val) {
|
|
/* if there are any other interrupts, clear them */
|
|
_i2c_hw[dev].regs->int_clr.val = ~0x0U;
|
|
}
|
|
}
|
|
|
|
irq_isr_exit ();
|
|
}
|
|
|
|
#if 1 /* TODO */
|
|
/* Some slave devices will die by accident and keep the SDA in low level,
|
|
* in this case, master should send several clock to make the slave release
|
|
* the bus.
|
|
*/
|
|
static void _i2c_clear_bus(i2c_t dev)
|
|
{
|
|
/* reset the usage type in GPIO table */
|
|
gpio_set_pin_usage(_i2c_hw[dev].pin_scl, _GPIO);
|
|
gpio_set_pin_usage(_i2c_hw[dev].pin_sda, _GPIO);
|
|
|
|
/* configure SDA and SCL pin as GPIO in open-drain mode temporarily */
|
|
gpio_init (_i2c_hw[dev].pin_scl, GPIO_IN_OD_PU);
|
|
gpio_init (_i2c_hw[dev].pin_sda, GPIO_IN_OD_PU);
|
|
|
|
/* master send some clock pulses to make the slave release the bus */
|
|
gpio_set (_i2c_hw[dev].pin_scl);
|
|
gpio_set (_i2c_hw[dev].pin_sda);
|
|
gpio_clear (_i2c_hw[dev].pin_sda);
|
|
for (int i = 0; i < 20; i++) {
|
|
gpio_toggle(_i2c_hw[dev].pin_scl);
|
|
}
|
|
gpio_set(_i2c_hw[dev].pin_sda);
|
|
|
|
/* store the usage type in GPIO table */
|
|
gpio_set_pin_usage(_i2c_hw[dev].pin_scl, _I2C);
|
|
gpio_set_pin_usage(_i2c_hw[dev].pin_sda, _I2C);
|
|
|
|
/* connect SCL and SDA pins to output signals through the GPIO matrix */
|
|
GPIO.func_out_sel_cfg[_i2c_hw[dev].pin_scl].func_sel = _i2c_hw[dev].signal_scl_out;
|
|
GPIO.func_out_sel_cfg[_i2c_hw[dev].pin_sda].func_sel = _i2c_hw[dev].signal_sda_out;
|
|
|
|
/* connect SCL and SDA input signals to pins through the GPIO matrix */
|
|
GPIO.func_in_sel_cfg[_i2c_hw[dev].signal_scl_in].sig_in_sel = 1;
|
|
GPIO.func_in_sel_cfg[_i2c_hw[dev].signal_scl_in].sig_in_inv = 0;
|
|
GPIO.func_in_sel_cfg[_i2c_hw[dev].signal_scl_in].func_sel = _i2c_hw[dev].pin_scl;
|
|
GPIO.func_in_sel_cfg[_i2c_hw[dev].signal_sda_in].sig_in_sel = 1;
|
|
GPIO.func_in_sel_cfg[_i2c_hw[dev].signal_sda_in].sig_in_inv = 0;
|
|
GPIO.func_in_sel_cfg[_i2c_hw[dev].signal_sda_in].func_sel = _i2c_hw[dev].pin_sda;
|
|
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* PLEASE NOTE: Following function is from the ESP-IDF and is licensed
|
|
* under the Apache License, Version 2.0 (the "License").
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
|
|
*/
|
|
static void _i2c_reset_hw (i2c_t dev)
|
|
{
|
|
/* save current configuration */
|
|
uint32_t ctr = _i2c_hw[dev].regs->ctr.val;
|
|
uint32_t fifo_conf = _i2c_hw[dev].regs->fifo_conf.val;
|
|
uint32_t scl_low_period = _i2c_hw[dev].regs->scl_low_period.val;
|
|
uint32_t scl_high_period = _i2c_hw[dev].regs->scl_high_period.val;
|
|
uint32_t scl_start_hold = _i2c_hw[dev].regs->scl_start_hold.val;
|
|
uint32_t scl_rstart_setup = _i2c_hw[dev].regs->scl_rstart_setup.val;
|
|
uint32_t scl_stop_hold = _i2c_hw[dev].regs->scl_stop_hold.val;
|
|
uint32_t scl_stop_setup = _i2c_hw[dev].regs->scl_stop_setup.val;
|
|
uint32_t sda_hold = _i2c_hw[dev].regs->sda_hold.val;
|
|
uint32_t sda_sample = _i2c_hw[dev].regs->sda_sample.val;
|
|
uint32_t timeout = _i2c_hw[dev].regs->timeout.val;
|
|
uint32_t scl_filter_cfg = _i2c_hw[dev].regs->scl_filter_cfg.val;
|
|
uint32_t sda_filter_cfg = _i2c_hw[dev].regs->sda_filter_cfg.val;
|
|
|
|
/* reset hardware mpdule */
|
|
periph_module_disable(_i2c_hw[dev].mod);
|
|
_i2c_clear_bus(dev);
|
|
periph_module_enable(_i2c_hw[dev].mod);
|
|
|
|
/* restore configuration */
|
|
_i2c_hw[dev].regs->int_ena.val = 0;
|
|
_i2c_hw[dev].regs->ctr.val = ctr & (~I2C_TRANS_START_M);
|
|
_i2c_hw[dev].regs->fifo_conf.val = fifo_conf;
|
|
_i2c_hw[dev].regs->scl_low_period.val = scl_low_period;
|
|
_i2c_hw[dev].regs->scl_high_period.val = scl_high_period;
|
|
_i2c_hw[dev].regs->scl_start_hold.val = scl_start_hold;
|
|
_i2c_hw[dev].regs->scl_rstart_setup.val = scl_rstart_setup;
|
|
_i2c_hw[dev].regs->scl_stop_hold.val = scl_stop_hold;
|
|
_i2c_hw[dev].regs->scl_stop_setup.val = scl_stop_setup;
|
|
_i2c_hw[dev].regs->sda_hold.val = sda_hold;
|
|
_i2c_hw[dev].regs->sda_sample.val = sda_sample;
|
|
_i2c_hw[dev].regs->timeout.val = timeout;
|
|
_i2c_hw[dev].regs->scl_filter_cfg.val = scl_filter_cfg;
|
|
_i2c_hw[dev].regs->sda_filter_cfg.val = sda_filter_cfg;
|
|
|
|
/* disable and clear all interrupt sources */
|
|
_i2c_hw[dev].regs->int_ena.val = 0;
|
|
_i2c_hw[dev].regs->int_clr.val = ~0x0U;
|
|
|
|
return;
|
|
}
|
|
|
|
void i2c_print_config(void)
|
|
{
|
|
for (unsigned bus = 0; bus < i2c_bus_num; bus++) {
|
|
ets_printf("\tI2C_DEV(%d)\tscl=%d sda=%d\n",
|
|
bus, _i2c_hw[bus].pin_scl, _i2c_hw[bus].pin_sda);
|
|
}
|
|
}
|
|
|
|
#else /* defined(I2C0_SPEED) || defined(I2C1_SPEED) */
|
|
|
|
void i2c_print_config(void)
|
|
{
|
|
LOG_TAG_INFO("i2c", "no I2C devices\n");
|
|
}
|
|
|
|
#endif /* defined(I2C0_SPEED) || defined(I2C1_SPEED) */
|
|
|
|
#endif /* MODULE_ESP_I2C_HW */
|