mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2024-12-29 04:50:03 +01:00
658 lines
20 KiB
C
658 lines
20 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>
|
|
*
|
|
* @}
|
|
*/
|
|
|
|
/*
|
|
* The I2C peripheral driver uses a mixture of the high level functions and
|
|
* the HAL driver. While complex functions like the configuration of the I2C
|
|
* interface use the high level interface, the HAL interface is used by data
|
|
* transfer functions for better performance and better control.
|
|
*/
|
|
#include <errno.h>
|
|
|
|
#include "gpio_arch.h"
|
|
#include "irq_arch.h"
|
|
#include "log.h"
|
|
#include "macros/units.h"
|
|
#include "mutex.h"
|
|
#include "periph_conf.h"
|
|
#include "periph/i2c.h"
|
|
#include "ztimer.h"
|
|
|
|
#include "esp_attr.h"
|
|
#include "driver/i2c.h"
|
|
#include "hal/i2c_hal.h"
|
|
#include "hal/interrupt_controller_types.h"
|
|
#include "hal/interrupt_controller_ll.h"
|
|
#include "rom/ets_sys.h"
|
|
#include "soc/i2c_reg.h"
|
|
|
|
#define ENABLE_DEBUG 0
|
|
#include "debug.h"
|
|
|
|
/* Ensure that the I2Cn_* symbols define I2C_DEV(n) */
|
|
#if !defined(I2C0_SPEED) && defined(I2C1_SPEED)
|
|
#error "I2C_DEV(1) is used but I2C_DEV(0) is not defined"
|
|
#endif
|
|
|
|
typedef struct {
|
|
i2c_speed_t clk_freq; /* clock freuency in Hz according to bus speed */
|
|
uint8_t cmd; /* command index of a transfer*/
|
|
uint8_t cmd_op; /* main command op code of a transfer */
|
|
uint8_t len; /* data length of a transfer */
|
|
uint32_t status; /* results of a transfer */
|
|
mutex_t cmd_lock; /* command execution locking */
|
|
mutex_t dev_lock; /* device locking */
|
|
} _i2c_bus_t;
|
|
|
|
static i2c_hal_context_t _i2c_hw[] = {
|
|
#if defined(I2C0_SPEED)
|
|
{ .dev = I2C_LL_GET_HW(0) },
|
|
#endif
|
|
#if defined(I2C1_SPEED)
|
|
{ .dev = I2C_LL_GET_HW(1) },
|
|
#endif
|
|
};
|
|
|
|
_Static_assert(I2C_NUMOF == ARRAY_SIZE(_i2c_hw),
|
|
"Size of bus descriptor table doesn't match I2C_NUMOF");
|
|
_Static_assert(I2C_NUMOF <= I2C_NUMOF_MAX,
|
|
"Number of defined I2C devices is greater than the number of supported devices");
|
|
|
|
static _i2c_bus_t _i2c_bus[I2C_NUMOF] = {
|
|
#if defined(I2C0_SPEED)
|
|
{
|
|
.cmd_lock = MUTEX_INIT_LOCKED,
|
|
.dev_lock = MUTEX_INIT,
|
|
},
|
|
#endif
|
|
#if defined(I2C1_SPEED)
|
|
{
|
|
.cmd_lock = MUTEX_INIT_LOCKED,
|
|
.dev_lock = MUTEX_INIT,
|
|
},
|
|
#endif
|
|
};
|
|
|
|
/* functions used from ESP-IDF driver that are not exposed in API */
|
|
extern esp_err_t i2c_hw_fsm_reset(i2c_port_t i2c_num);
|
|
|
|
/* forward declaration of internal functions */
|
|
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 len, bool last);
|
|
static void _i2c_transfer(i2c_t dev);
|
|
static void _i2c_intr_handler(void *arg);
|
|
static int _i2c_status_to_errno(i2c_t dev);
|
|
|
|
void i2c_init(i2c_t dev)
|
|
{
|
|
_Static_assert(I2C_NUMOF <= I2C_NUMOF_MAX, "Too many I2C devices defined");
|
|
_Static_assert(I2C_NUMOF == ARRAY_SIZE(_i2c_hw),
|
|
"Number of hardware descriptors doesn't match the I2C_NUMOF");
|
|
|
|
assert(dev < I2C_NUMOF);
|
|
|
|
/* According to the Technical Reference Manual, only FAST mode is supported,
|
|
* but FAST PLUS mode seems to work also. */
|
|
assert(i2c_config[dev].speed <= I2C_SPEED_FAST_PLUS);
|
|
|
|
/* GPIOs for SCL and SDA signals must not already be used for peripherals */
|
|
if (((gpio_get_pin_usage(i2c_config[dev].scl) != _I2C) &&
|
|
(gpio_get_pin_usage(i2c_config[dev].scl) != _GPIO)) ||
|
|
((gpio_get_pin_usage(i2c_config[dev].sda) != _I2C) &&
|
|
(gpio_get_pin_usage(i2c_config[dev].sda) != _GPIO))) {
|
|
LOG_TAG_ERROR("i2c", "GPIO%u and/or GPIO%u are used for %s/%s and "
|
|
"cannot be used as I2C interface\n",
|
|
i2c_config[dev].scl, i2c_config[dev].sda,
|
|
gpio_get_pin_usage_str(i2c_config[dev].scl),
|
|
gpio_get_pin_usage_str(i2c_config[dev].sda));
|
|
assert(0);
|
|
}
|
|
|
|
_i2c_bus[dev].cmd = 0;
|
|
_i2c_bus[dev].len = 0;
|
|
|
|
i2c_acquire(dev);
|
|
|
|
i2c_config_t cfg = {};
|
|
|
|
cfg.mode = I2C_MODE_MASTER;
|
|
cfg.sda_io_num = i2c_config[dev].sda;
|
|
cfg.scl_io_num = i2c_config[dev].scl;
|
|
cfg.sda_pullup_en = i2c_config[dev].sda_pullup;
|
|
cfg.scl_pullup_en = i2c_config[dev].scl_pullup;
|
|
#if defined(SOC_I2C_SUPPORT_RTC) && !defined(CPU_FAM_ESP32S3)
|
|
cfg.clk_flags = I2C_SCLK_SRC_FLAG_LIGHT_SLEEP;
|
|
#endif
|
|
|
|
switch (i2c_config[dev].speed) {
|
|
case I2C_SPEED_LOW:
|
|
cfg.master.clk_speed = 10 * KHZ(1);
|
|
break;
|
|
case I2C_SPEED_NORMAL:
|
|
cfg.master.clk_speed = 100 * KHZ(1);
|
|
break;
|
|
case I2C_SPEED_FAST:
|
|
cfg.master.clk_speed = 400 * KHZ(1);
|
|
break;
|
|
case I2C_SPEED_FAST_PLUS:
|
|
cfg.master.clk_speed = 1000 * KHZ(1);
|
|
break;
|
|
case I2C_SPEED_HIGH:
|
|
cfg.master.clk_speed = 3400 * KHZ(1);
|
|
break;
|
|
default:
|
|
LOG_TAG_ERROR("i2c", "Invalid speed value in %s\n", __func__);
|
|
assert(0);
|
|
}
|
|
|
|
_i2c_bus[dev].clk_freq = cfg.master.clk_speed;
|
|
|
|
/* configures the GPIOs, sets the bus timing and enables the periphery */
|
|
i2c_param_config(dev, &cfg);
|
|
|
|
#if defined(SOC_I2C_SUPPORT_APB)
|
|
/* If I2C clock is derived from APB clock, the bus timing parameters
|
|
* have to be corrected if the APB clock is less than 80 MHz */
|
|
extern uint32_t rtc_clk_apb_freq_get(void);
|
|
uint32_t apb_clk = rtc_clk_apb_freq_get();
|
|
|
|
if (apb_clk < MHZ(80)) {
|
|
i2c_clk_cal_t clk_cfg;
|
|
i2c_ll_cal_bus_clk(apb_clk, cfg.master.clk_speed, &clk_cfg);
|
|
i2c_ll_set_bus_timing(_i2c_hw[dev].dev, &clk_cfg);
|
|
}
|
|
#endif
|
|
|
|
/* store the usage type in GPIO table */
|
|
gpio_set_pin_usage(i2c_config[dev].scl, _I2C);
|
|
gpio_set_pin_usage(i2c_config[dev].sda, _I2C);
|
|
|
|
/* route all I2C interrupt sources to same the CPU interrupt */
|
|
intr_matrix_set(PRO_CPU_NUM, i2c_periph_signal[dev].irq, CPU_INUM_I2C);
|
|
|
|
/* set interrupt handler and enable the CPU interrupt */
|
|
intr_cntrl_ll_set_int_handler(CPU_INUM_I2C, _i2c_intr_handler, NULL);
|
|
intr_cntrl_ll_enable_interrupts(BIT(CPU_INUM_I2C));
|
|
|
|
i2c_release(dev);
|
|
}
|
|
|
|
void i2c_acquire(i2c_t dev)
|
|
{
|
|
DEBUG ("%s\n", __func__);
|
|
assert(dev < I2C_NUMOF);
|
|
|
|
mutex_lock(&_i2c_bus[dev].dev_lock);
|
|
}
|
|
|
|
void i2c_release(i2c_t dev)
|
|
{
|
|
DEBUG ("%s\n", __func__);
|
|
assert(dev < I2C_NUMOF);
|
|
|
|
mutex_unlock(&_i2c_bus[dev].dev_lock);
|
|
}
|
|
|
|
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);
|
|
|
|
assert(dev < I2C_NUMOF);
|
|
assert(len > 0);
|
|
assert(data != NULL);
|
|
|
|
int res;
|
|
|
|
_i2c_bus[dev].cmd_op = I2C_LL_CMD_READ;
|
|
_i2c_bus[dev].cmd = 0;
|
|
|
|
/* reset TX/RX FIFO queue */
|
|
i2c_hal_txfifo_rst(&_i2c_hw[dev]);
|
|
i2c_hal_rxfifo_rst(&_i2c_hw[dev]);
|
|
|
|
/* 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_MASTER_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 SOC_I2C_FIFO_LEN bytes */
|
|
uint32_t off = 0;
|
|
|
|
/* if len > SOC_I2C_FIFO_LEN read SOC_I2C_FIFO_LEN bytes at a time */
|
|
while (len > SOC_I2C_FIFO_LEN) {
|
|
/* read one block of data bytes command */
|
|
_i2c_bus[dev].len = SOC_I2C_FIFO_LEN;
|
|
_i2c_read_cmd(dev, SOC_I2C_FIFO_LEN, false);
|
|
_i2c_end_cmd(dev);
|
|
_i2c_transfer(dev);
|
|
|
|
res = _i2c_status_to_errno(dev);
|
|
if (res) {
|
|
return res;
|
|
}
|
|
|
|
/* if transfer was successful, fetch the data from I2C RAM */
|
|
i2c_hal_read_rxfifo(&_i2c_hw[dev], data + off, len);
|
|
|
|
/* reset RX FIFO queue */
|
|
i2c_hal_rxfifo_rst(&_i2c_hw[dev]);
|
|
|
|
len -= SOC_I2C_FIFO_LEN;
|
|
off += SOC_I2C_FIFO_LEN;
|
|
}
|
|
|
|
/* read remaining data bytes command with a final NAK */
|
|
_i2c_bus[dev].len = len;
|
|
_i2c_read_cmd(dev, 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);
|
|
|
|
if ((res = _i2c_status_to_errno(dev))) {
|
|
return res;
|
|
}
|
|
|
|
/* fetch the data from RX FIFO */
|
|
i2c_hal_read_rxfifo(&_i2c_hw[dev], data + off, len);
|
|
|
|
/* 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);
|
|
|
|
assert(dev < I2C_NUMOF);
|
|
assert(len > 0);
|
|
assert(data != NULL);
|
|
|
|
int res;
|
|
|
|
_i2c_bus[dev].cmd_op = I2C_LL_CMD_WRITE;
|
|
_i2c_bus[dev].cmd = 0;
|
|
|
|
/* reset TX FIFO queue */
|
|
i2c_hal_txfifo_rst(&_i2c_hw[dev]);
|
|
|
|
/* 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_MASTER_WRITE;
|
|
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 SOC_I2C_FIFO_LEN bytes */
|
|
|
|
uint32_t off = 0;
|
|
uint32_t tx_fifo_free;
|
|
|
|
/* get available TX FIFO space */
|
|
i2c_hal_get_txfifo_cnt(&_i2c_hw[dev], &tx_fifo_free);
|
|
|
|
/* if len > SOC_I2C_FIFO_LEN write SOC_I2C_FIFO_LEN bytes at a time */
|
|
while (len > tx_fifo_free) {
|
|
/* send one block of data bytes */
|
|
_i2c_bus[dev].len = tx_fifo_free;
|
|
_i2c_write_cmd(dev, (uint8_t*)data + off, tx_fifo_free);
|
|
_i2c_end_cmd(dev);
|
|
_i2c_transfer(dev);
|
|
res = _i2c_status_to_errno(dev);
|
|
|
|
if (res) {
|
|
return res;
|
|
}
|
|
|
|
len -= tx_fifo_free;
|
|
off += tx_fifo_free;
|
|
|
|
/* reset TX FIFO queue */
|
|
i2c_hal_txfifo_rst(&_i2c_hw[dev]);
|
|
|
|
/* update available TX FIFO space */
|
|
i2c_hal_get_txfifo_cnt(&_i2c_hw[dev], &tx_fifo_free);
|
|
}
|
|
|
|
/* write remaining data bytes command */
|
|
_i2c_bus[dev].len = len;
|
|
_i2c_write_cmd(dev, (uint8_t*)data + off, 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);
|
|
|
|
return _i2c_status_to_errno(dev);
|
|
}
|
|
|
|
#if defined(CPU_FAM_ESP32)
|
|
#define I2C_NACK_INT_ENA_M I2C_ACK_ERR_INT_ENA_M
|
|
#endif
|
|
|
|
/*
|
|
* @note:
|
|
* It is a known problem that the ESP32x I2C hardware implementation checks
|
|
* the ACK when the next command in the pipeline is executed. That is,
|
|
* for a segmented write operation of type `START ADDR DATA0 STOP` the
|
|
* `I2C_END_DETECT_INT` and the `I2C_NACK_INT` are triggered simultaneously
|
|
* in case of acknowledge errors. In this case it is not clear from the
|
|
* interrupt status whether the acknolegdement error has occurred for the
|
|
* ADDR or the DATA0 byte. The only way to find out is to check the remaining
|
|
* TX FIFO length. If the number of bytes in the TX-FIFO is still higher
|
|
* (for two-byte addresses) or equal (for one-byte addresses) to the length of
|
|
* the data, the acknolegdement error occurred for the ADDR field.
|
|
*/
|
|
static int _i2c_status_to_errno(i2c_t dev)
|
|
{
|
|
if (_i2c_bus[dev].status & I2C_ARBITRATION_LOST_INT_ENA_M) {
|
|
LOG_TAG_DEBUG("i2c", "arbitration lost dev=%u\n", dev);
|
|
return -EAGAIN;
|
|
}
|
|
|
|
if (_i2c_bus[dev].status & I2C_NACK_INT_ENA_M) {
|
|
LOG_TAG_DEBUG("i2c", "ack error dev=%u\n", dev);
|
|
if (_i2c_bus[dev].cmd_op == I2C_LL_CMD_WRITE) {
|
|
/*
|
|
* @note: In a write transfer, an ACK error can happen for the
|
|
* address field as well as for data. If the FIFO still contains
|
|
* all data bytes, the ACK error happened in address field and we
|
|
* have to return -ENXIO. Otherwise, the ACK error happened in data
|
|
* field and we have to return -EIO.
|
|
*/
|
|
uint32_t cnt;
|
|
|
|
i2c_hal_get_txfifo_cnt(&_i2c_hw[dev], &cnt);
|
|
return ((SOC_I2C_FIFO_LEN - cnt) >= _i2c_bus[dev].len) ? -ENXIO : -EIO;
|
|
}
|
|
else {
|
|
/*
|
|
* @note: In a read transfer, an ACK is only expected for the
|
|
* address field. Thus, an ACK error can only happen for the address
|
|
* field. Therefore, we always return -ENXIO in case of an ACK
|
|
* error.
|
|
*/
|
|
return -ENXIO;
|
|
}
|
|
}
|
|
|
|
if (_i2c_bus[dev].status & I2C_TIME_OUT_INT_ENA_M) {
|
|
LOG_TAG_DEBUG("i2c", "bus timeout dev=%u\n", dev);
|
|
i2c_hw_fsm_reset(dev);
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void _i2c_start_cmd(i2c_t dev)
|
|
{
|
|
DEBUG ("%s\n", __func__);
|
|
|
|
/* place START condition command in command queue */
|
|
i2c_hw_cmd_t cmd = { .op_code = I2C_LL_CMD_RESTART };
|
|
i2c_hal_write_cmd_reg(&_i2c_hw[dev], cmd, _i2c_bus[dev].cmd);
|
|
|
|
/* 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_cmd_t cmd = { .op_code = I2C_LL_CMD_STOP };
|
|
i2c_hal_write_cmd_reg(&_i2c_hw[dev], cmd, _i2c_bus[dev].cmd);
|
|
|
|
/* 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_cmd_t cmd = { .op_code = I2C_LL_CMD_END };
|
|
i2c_hal_write_cmd_reg(&_i2c_hw[dev], cmd, _i2c_bus[dev].cmd);
|
|
|
|
/* 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 (len > SOC_I2C_FIFO_LEN) {
|
|
LOG_TAG_ERROR("i2c", "Maximum number of bytes (32 bytes) that can be "
|
|
"sent with on transfer reached\n");
|
|
return;
|
|
}
|
|
|
|
/* store the data in TX FIFO */
|
|
i2c_hal_write_txfifo(&_i2c_hw[dev], (uint8_t *)data, len);
|
|
|
|
/* place WRITE command for multiple bytes in command queue */
|
|
i2c_hw_cmd_t cmd = { .op_code = I2C_LL_CMD_WRITE,
|
|
.byte_num = len,
|
|
.ack_en = 1,
|
|
.ack_exp = 0,
|
|
.ack_val = 0 };
|
|
i2c_hal_write_cmd_reg(&_i2c_hw[dev], cmd, _i2c_bus[dev].cmd);
|
|
|
|
/* increment the command counter */
|
|
_i2c_bus[dev].cmd++;
|
|
}
|
|
|
|
static void _i2c_read_cmd(i2c_t dev, uint8_t len, bool last)
|
|
{
|
|
DEBUG ("%s dev=%u len=%d\n", __func__, dev, len);
|
|
|
|
if (len < 1 || len > SOC_I2C_FIFO_LEN) {
|
|
/* 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_cmd_t cmd = { .op_code = I2C_LL_CMD_READ,
|
|
.byte_num = len-1,
|
|
.ack_en = 0,
|
|
.ack_exp = 0,
|
|
.ack_val = 0 };
|
|
i2c_hal_write_cmd_reg(&_i2c_hw[dev], cmd, _i2c_bus[dev].cmd);
|
|
|
|
/* 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_cmd_t cmd = { .op_code = I2C_LL_CMD_READ,
|
|
.byte_num = 1,
|
|
.ack_en = 0,
|
|
.ack_exp = 0,
|
|
.ack_val = last ? 1 : 0 };
|
|
i2c_hal_write_cmd_reg(&_i2c_hw[dev], cmd, _i2c_bus[dev].cmd);
|
|
|
|
/* increment the command counter */
|
|
_i2c_bus[dev].cmd++;
|
|
}
|
|
|
|
/* a maximum transfer 33 byte * 9 clock cycles * 1/100000 s takes at
|
|
* I2C_SPEED_NORMAL about 450 us, at I2C_SPEED_NORMAL_LOW about 4.5 ms */
|
|
#define I2C_TRANSFER_TIMEOUT 640
|
|
|
|
/* combination of all transfer interrupts */
|
|
#define I2C_LL_MASTER_INT (I2C_LL_MASTER_TX_INT | I2C_LL_MASTER_RX_INT)
|
|
|
|
void _i2c_transfer_timeout(void *arg)
|
|
{
|
|
i2c_t dev = (i2c_t)(uintptr_t)arg;
|
|
|
|
/* reset the hardware if I2C got stuck */
|
|
i2c_hw_fsm_reset(dev);
|
|
|
|
/* set result to timeout */
|
|
_i2c_bus[dev].status = I2C_TIME_OUT_INT_ENA_M;
|
|
|
|
/* wake up the thread that is waiting for the results */
|
|
mutex_unlock(&_i2c_bus[dev].cmd_lock);
|
|
}
|
|
|
|
/* 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);
|
|
|
|
/* disable and enable all transmission interrupts and clear current status */
|
|
i2c_hal_clr_intsts_mask(&_i2c_hw[dev], I2C_LL_MASTER_INT);
|
|
i2c_hal_enable_intr_mask(&_i2c_hw[dev], I2C_LL_MASTER_INT);
|
|
|
|
/* set a timer for the case the I2C hardware gets stuck */
|
|
#if defined(MODULE_ZTIMER_MSEC)
|
|
uint32_t timeout = ((I2C_TRANSFER_TIMEOUT * KHZ(1)) / _i2c_bus[dev].clk_freq) + 1;
|
|
ztimer_t timer = { .callback = _i2c_transfer_timeout,
|
|
.arg = (void*)(uintptr_t)dev };
|
|
ztimer_set(ZTIMER_MSEC, &timer, timeout);
|
|
#endif
|
|
|
|
/* start the execution of commands in command pipeline */
|
|
_i2c_bus[dev].status = 0;
|
|
|
|
i2c_hal_update_config(&_i2c_hw[dev]);
|
|
i2c_hal_trans_start(&_i2c_hw[dev]);
|
|
|
|
/* wait for transfer results and remove timeout timer*/
|
|
mutex_lock(&_i2c_bus[dev].cmd_lock);
|
|
|
|
#if defined(MODULE_ZTIMER_MSEC)
|
|
ztimer_remove(ZTIMER_MSEC, &timer);
|
|
#endif
|
|
|
|
/* returned from transmission */
|
|
DEBUG("%s status=%08"PRIx32"\n", __func__, _i2c_bus[dev].status);
|
|
|
|
/* reset command and data index */
|
|
_i2c_bus[dev].cmd = 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_NUMOF; dev++) {
|
|
uint32_t mask = i2c_ll_get_intsts_mask(_i2c_hw[dev].dev);
|
|
/* test for transfer related interrupts */
|
|
if (mask) {
|
|
_i2c_bus[dev].status = mask;
|
|
/* disable all interrupts and clear pending interrupts */
|
|
i2c_hal_clr_intsts_mask(&_i2c_hw[dev], I2C_LL_MASTER_INT);
|
|
i2c_hal_disable_intr_mask(&_i2c_hw[dev], I2C_LL_MASTER_INT);
|
|
|
|
/* wake up the thread that is waiting for the results */
|
|
mutex_unlock(&_i2c_bus[dev].cmd_lock);
|
|
}
|
|
}
|
|
|
|
irq_isr_exit();
|
|
}
|
|
|
|
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");
|
|
}
|
|
}
|