blob: 2cf0f64e6702203124bb86816641dc22899b60a4 [file] [log] [blame]
/****************************************************************************
* arch/risc-v/src/common/espressif/esp_i2c.c
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#ifdef CONFIG_ESPRESSIF_I2C
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <assert.h>
#include <errno.h>
#include <debug.h>
#include <time.h>
#include <sys/time.h>
#include <nuttx/arch.h>
#include <nuttx/irq.h>
#include <nuttx/clock.h>
#include <nuttx/mutex.h>
#include <nuttx/semaphore.h>
#include <nuttx/i2c/i2c_master.h>
#include <arch/board/board.h>
#include "esp_i2c.h"
#include "esp_irq.h"
#include "esp_gpio.h"
#include "riscv_internal.h"
#include "periph_ctrl.h"
#include "hal/i2c_hal.h"
#include "hal/i2c_types.h"
#include "hal/i2c_ll.h"
#include "soc/system_reg.h"
#include "soc/gpio_sig_map.h"
#include "soc/i2c_periph.h"
#if defined(CONFIG_ESPRESSIF_ESP32H2) || defined(CONFIG_ESPRESSIF_ESP32C6)
# include "soc/pcr_reg.h"
#endif
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
#ifdef CONFIG_ESPRESSIF_ESP32H2
# define SYSTEM_I2C_EXT0_CLK_EN PCR_I2C0_CLK_EN
# define SYSTEM_I2C_EXT0_RST PCR_I2C0_RST_EN
# define SYSTEM_I2C_EXT1_CLK_EN PCR_I2C1_CLK_EN
# define SYSTEM_I2C_EXT1_RST PCR_I2C1_RST_EN
#endif
#ifdef CONFIG_ESPRESSIF_ESP32C6
# define SYSTEM_I2C_EXT0_CLK_EN PCR_I2C_CLK_EN
# define SYSTEM_I2C_EXT0_RST PCR_I2C_RST_EN
#endif
#define GET_STATUS(hw) hw->sr.val
#define ESPRESSIF_I2CTIMEOTICKS \
(SEC2TICK(CONFIG_ESPRESSIF_I2CTIMEOSEC) + MSEC2TICK(CONFIG_ESPRESSIF_I2CTIMEOMS))
/* I2C hardware FIFO depth */
#define I2C_FIFO_SIZE (32)
/* Number of bus cycles filtered by default */
#define I2C_FILTER_CYC_NUM_DEF (7)
/* Number of bus cycles for the master to generate when the slave is in
* deadlock
*/
#define I2C_SCL_CYC_NUM_DEF (9)
/* I2C default clock frequency */
#define I2C_CLK_FREQ_DEF (100 * 1000)
/* Mask for the interrupt errors */
#define I2C_INT_ERR_MASK (I2C_NACK_INT_ENA_M | \
I2C_TIME_OUT_INT_ENA_M | \
I2C_ARBITRATION_LOST_INT_ENA_M)
/* I2C event trace logic.
* NOTE: trace uses the internal, non-standard, low-level debug interface
* syslog() but does not require that any other debug is enabled.
*/
#ifndef CONFIG_I2C_TRACE
# define esp_i2c_tracereset(p)
# define esp_i2c_tracenew(p,s)
# define esp_i2c_traceevent(p,e,a,s)
# define esp_i2c_tracedump(p)
#endif
#ifndef CONFIG_I2C_NTRACE
# define CONFIG_I2C_NTRACE 32
#endif
/****************************************************************************
* Private Types
****************************************************************************/
/* I2C state */
enum esp_i2cstate_e
{
I2CSTATE_IDLE = 0,
I2CSTATE_PROC,
I2CSTATE_STOP,
#ifndef CONFIG_I2C_POLLED
I2CSTATE_FINISH,
#endif
I2CSTATE_ERROR
};
/* I2C hardware command */
enum esp_i2c_opmode_e
{
I2C_CMD_RESTART = 6, /* I2C restart command */
I2C_CMD_WRITE = 1, /* I2C write command */
I2C_CMD_READ = 3, /* I2C read command */
I2C_CMD_STOP = 2, /* I2C stop command */
I2C_CMD_END = 4 /* I2C end command */
};
#ifdef CONFIG_I2C_TRACE
/* Trace events */
enum esp_trace_e
{
I2CEVENT_NONE = 0, /* No events have occurred with this status */
I2CEVENT_SENDADDR, /* Start/Master bit set and address sent, param = addr */
I2CEVENT_SENDBYTE, /* Send byte, param = bytes */
I2CEVENT_RCVMODEEN, /* Receive mode enabled, param = 0 */
I2CEVENT_RCVBYTE, /* Read more dta, param = bytes */
I2CEVENT_STOP, /* Last byte sten, send stop, param = length */
I2CEVENT_ERROR /* Error occurred, param = 0 */
};
/* Trace data */
struct esp_trace_s
{
uint32_t status; /* I2C 32-bit SR status */
uint32_t count; /* Interrupt count when status change */
enum esp_i2cstate_e event; /* Last event that occurred with this status */
uint32_t parm; /* Parameter associated with the event */
clock_t time; /* First of event or first status */
};
#endif /* CONFIG_I2C_TRACE */
/* I2C Device hardware configuration */
struct esp_i2c_config_s
{
uint32_t clk_freq; /* Clock frequency */
uint8_t scl_pin; /* GPIO configuration for SCL as SCL */
uint8_t sda_pin; /* GPIO configuration for SDA as SDA */
#ifndef CONFIG_I2C_POLLED
uint8_t periph; /* Peripheral ID */
uint8_t irq; /* Interrupt ID */
#endif
uint32_t clk_bit; /* Clock enable bit */
uint32_t rst_bit; /* I2C reset bit */
uint32_t scl_insig; /* I2C SCL input signal index */
uint32_t scl_outsig; /* I2C SCL output signal index */
uint32_t sda_insig; /* I2C SDA input signal index */
uint32_t sda_outsig; /* I2C SDA output signal index */
};
/* I2C Device Private Data */
struct esp_i2c_priv_s
{
const struct i2c_ops_s *ops; /* Standard I2C operations */
uint32_t id; /* I2C instance */
/* Port configuration */
const struct esp_i2c_config_s *config;
int refs; /* Reference count */
mutex_t lock; /* Mutual exclusion mutex */
#ifndef CONFIG_I2C_POLLED
sem_t sem_isr; /* Interrupt wait semaphore */
#endif
/* I2C work state (see enum esp_i2cstate_e) */
volatile enum esp_i2cstate_e i2cstate;
struct i2c_msg_s *msgv; /* Message list */
uint8_t msgid; /* Current message ID */
ssize_t bytes; /* Processed data bytes */
#ifndef CONFIG_I2C_POLLED
int cpuint; /* CPU interrupt assigned to this I2C */
#endif
uint32_t error; /* I2C transform error */
bool ready_read; /* If I2C is ready for receiving data */
uint32_t clk_freq; /* Current I2C Clock frequency */
/* I2C trace support */
#ifdef CONFIG_I2C_TRACE
int tndx; /* Trace array index */
clock_t start_time; /* Time when the trace was started */
/* The actual trace data */
struct esp_trace_s trace[CONFIG_I2C_NTRACE];
#endif
i2c_hal_context_t *ctx; /* Common layer struct */
};
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
static void esp_i2c_reset_fifo(struct esp_i2c_priv_s *priv);
static void esp_i2c_intr_enable(struct esp_i2c_priv_s *priv);
static void esp_i2c_intr_enable(struct esp_i2c_priv_s *priv);
static void esp_i2c_intr_disable(struct esp_i2c_priv_s *priv);
static void esp_i2c_sendstart(struct esp_i2c_priv_s *priv);
static void esp_i2c_senddata(struct esp_i2c_priv_s *priv);
static void esp_i2c_recvdata(struct esp_i2c_priv_s *priv);
static void esp_i2c_startrecv(struct esp_i2c_priv_s *priv);
static void esp_i2c_sendstop(struct esp_i2c_priv_s *priv);
static void esp_i2c_init_clock(struct esp_i2c_priv_s *priv,
uint32_t bus_freq);
static void esp_i2c_init(struct esp_i2c_priv_s *priv);
static void esp_i2c_deinit(struct esp_i2c_priv_s *priv);
static void esp_i2c_reset_fsmc(struct esp_i2c_priv_s *priv);
static int esp_i2c_sem_waitdone(struct esp_i2c_priv_s *priv);
#ifdef CONFIG_I2C_POLLED
static int esp_i2c_polling_waitdone(struct esp_i2c_priv_s *priv);
#endif /* CONFIG_I2C_POLLED */
static int esp_i2c_transfer(struct i2c_master_s *dev,
struct i2c_msg_s *msgs,
int count);
#ifdef CONFIG_I2C_RESET
static void esp_i2c_clear_bus(struct esp_i2c_priv_s *priv);
static int esp_i2c_reset(struct i2c_master_s *dev);
#endif /* CONFIG_I2C_RESET */
#ifdef CONFIG_I2C_TRACE
static void esp_i2c_traceclear(struct esp_i2c_priv_s *priv);
static void esp_i2c_tracereset(struct esp_i2c_priv_s *priv);
static void esp_i2c_tracenew(struct esp_i2c_priv_s *priv,
uint32_t status);
static void esp_i2c_traceevent(struct esp_i2c_priv_s *priv,
enum esp_trace_e event,
uint32_t parm,
uint32_t status);
static void esp_i2c_tracedump(struct esp_i2c_priv_s *priv);
#endif /* CONFIG_I2C_TRACE */
#ifndef CONFIG_I2C_POLLED
static int esp_i2c_irq(int cpuint, void *context, void *arg);
#endif /* CONFIG_I2C_POLLED */
static inline void esp_i2c_process(struct esp_i2c_priv_s *priv,
uint32_t status);
/****************************************************************************
* Private Data
****************************************************************************/
/* I2C interface */
static const struct i2c_ops_s esp_i2c_ops =
{
.transfer = esp_i2c_transfer
#ifdef CONFIG_I2C_RESET
, .reset = esp_i2c_reset
#endif
};
#ifdef CONFIG_ESPRESSIF_I2C0
i2c_hal_context_t i2c0_ctx =
{
0
};
/* I2C device structure */
static const struct esp_i2c_config_s esp_i2c0_config =
{
.clk_freq = I2C_CLK_FREQ_DEF,
.scl_pin = CONFIG_ESPRESSIF_I2C0_SCLPIN,
.sda_pin = CONFIG_ESPRESSIF_I2C0_SDAPIN,
#ifndef CONFIG_I2C_POLLED
.periph = ETS_I2C_EXT0_INTR_SOURCE,
.irq = ESP_IRQ_I2C_EXT0,
#endif
.clk_bit = SYSTEM_I2C_EXT0_CLK_EN,
.rst_bit = SYSTEM_I2C_EXT0_RST,
.scl_insig = I2CEXT0_SCL_IN_IDX,
.scl_outsig = I2CEXT0_SCL_OUT_IDX,
.sda_insig = I2CEXT0_SDA_IN_IDX,
.sda_outsig = I2CEXT0_SDA_OUT_IDX
};
static struct esp_i2c_priv_s esp_i2c0_priv =
{
.ops = &esp_i2c_ops,
.id = 0,
.config = &esp_i2c0_config,
.refs = 0,
.lock = NXMUTEX_INITIALIZER,
#ifndef CONFIG_I2C_POLLED
.sem_isr = SEM_INITIALIZER(0),
#endif
.i2cstate = I2CSTATE_IDLE,
.msgv = NULL,
.msgid = 0,
.bytes = 0,
#ifndef CONFIG_I2C_POLLED
.cpuint = -ENOMEM,
#endif
.error = 0,
.ready_read = false,
.clk_freq = 0,
.ctx = &i2c0_ctx
};
#endif
#ifdef CONFIG_ESPRESSIF_I2C1
i2c_hal_context_t i2c1_ctx =
{
0
};
/* I2C device structure */
static const struct esp_i2c_config_s esp_i2c1_config =
{
.clk_freq = I2C_CLK_FREQ_DEF,
.scl_pin = CONFIG_ESPRESSIF_I2C1_SCLPIN,
.sda_pin = CONFIG_ESPRESSIF_I2C1_SDAPIN,
#ifndef CONFIG_I2C_POLLED
.periph = ETS_I2C_EXT1_INTR_SOURCE,
.irq = ESP_IRQ_I2C_EXT1,
#endif
.clk_bit = SYSTEM_I2C_EXT1_CLK_EN,
.rst_bit = SYSTEM_I2C_EXT1_RST,
.scl_insig = I2CEXT1_SCL_IN_IDX,
.scl_outsig = I2CEXT1_SCL_OUT_IDX,
.sda_insig = I2CEXT1_SDA_IN_IDX,
.sda_outsig = I2CEXT1_SDA_OUT_IDX
};
static struct esp_i2c_priv_s esp_i2c1_priv =
{
.ops = &esp_i2c_ops,
.id = 1,
.config = &esp_i2c1_config,
.refs = 0,
.lock = NXMUTEX_INITIALIZER,
#ifndef CONFIG_I2C_POLLED
.sem_isr = SEM_INITIALIZER(0),
#endif
.i2cstate = I2CSTATE_IDLE,
.msgv = NULL,
.msgid = 0,
.bytes = 0,
#ifndef CONFIG_I2C_POLLED
.cpuint = -ENOMEM,
#endif
.error = 0,
.ready_read = false,
.clk_freq = 0,
.ctx = &i2c1_ctx
};
#endif /* CONFIG_ESPRESSIF_I2C1 */
/* Trace events strings */
#ifdef CONFIG_I2C_TRACE
static const char *g_trace_names[] =
{
"NONE ",
"SENDADDR ",
"SENDBYTE ",
"RCVMODEEN ",
"RCVBYTE ",
"STOP ",
"ERROR "
};
#endif
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: esp_i2c_reset_fifo
*
* Description:
* Reset I2C RX and TX hardware FIFO.
*
* Parameters:
* priv - Pointer to the internal driver state structure.
*
****************************************************************************/
static void esp_i2c_reset_fifo(struct esp_i2c_priv_s *priv)
{
i2c_ll_txfifo_rst(priv->ctx->dev);
i2c_ll_rxfifo_rst(priv->ctx->dev);
}
/****************************************************************************
* Name: esp_i2c_intr_enable
*
* Description:
* Enable I2C interrupts.
*
* Parameters:
* priv - Pointer to the internal driver state structure.
*
****************************************************************************/
static void esp_i2c_intr_enable(struct esp_i2c_priv_s *priv)
{
i2c_ll_clear_intr_mask(priv->ctx->dev, I2C_LL_INTR_MASK);
i2c_ll_enable_intr_mask(priv->ctx->dev, I2C_LL_MASTER_EVENT_INTR);
}
/****************************************************************************
* Name: esp_i2c_intr_disable
*
* Description:
* Disable I2C interrupts.
*
* Parameters:
* priv - Pointer to the internal driver state structure.
*
****************************************************************************/
static void esp_i2c_intr_disable(struct esp_i2c_priv_s *priv)
{
i2c_ll_disable_intr_mask(priv->ctx->dev, I2C_LL_INTR_MASK);
i2c_ll_clear_intr_mask(priv->ctx->dev, I2C_LL_INTR_MASK);
}
/****************************************************************************
* Name: esp_i2c_sendstart
*
* Description:
* Send I2C start signal.
*
* Parameters:
* priv - Pointer to the internal driver state structure.
*
****************************************************************************/
static void esp_i2c_sendstart(struct esp_i2c_priv_s *priv)
{
struct i2c_msg_s *msg = &priv->msgv[priv->msgid];
uint32_t fifo_val = 0;
i2c_ll_hw_cmd_t restart_cmd;
i2c_ll_hw_cmd_t write_cmd;
i2c_ll_hw_cmd_t end_cmd;
/* Write I2C command registers */
restart_cmd.op_code = I2C_LL_CMD_RESTART;
write_cmd.byte_num = 1;
write_cmd.ack_en = 1;
write_cmd.op_code = I2C_LL_CMD_WRITE;
end_cmd.op_code = I2C_LL_CMD_END;
i2c_ll_write_cmd_reg(priv->ctx->dev, restart_cmd, 0);
i2c_ll_write_cmd_reg(priv->ctx->dev, write_cmd, 1);
i2c_ll_write_cmd_reg(priv->ctx->dev, end_cmd, 2);
/* Write data to FIFO register */
fifo_val = (msg->addr << 1) | (msg->flags & I2C_M_READ);
i2c_ll_write_txfifo(priv->ctx->dev, (uint8_t *)&fifo_val, 1);
/* Enable I2C master TX interrupt */
esp_i2c_intr_enable(priv);
/* Update I2C configuration */
/* Configure the I2C to trigger a transaction */
i2c_hal_master_trans_start(priv->ctx);
}
/****************************************************************************
* Name: esp_i2c_senddata
*
* Description:
* Send I2C data.
*
* Parameters:
* priv - Pointer to the internal driver state structure.
*
****************************************************************************/
static void esp_i2c_senddata(struct esp_i2c_priv_s *priv)
{
struct i2c_msg_s *msg = &priv->msgv[priv->msgid];
int n = msg->length - priv->bytes;
i2c_ll_hw_cmd_t write_cmd =
{
.ack_en = 1,
.op_code = I2C_LL_CMD_WRITE
};
i2c_ll_hw_cmd_t end_cmd =
{
.op_code = I2C_LL_CMD_END
};
n = n < I2C_FIFO_SIZE ? n : I2C_FIFO_SIZE;
write_cmd.byte_num = n;
i2c_ll_write_cmd_reg(priv->ctx->dev, write_cmd, 0);
i2c_ll_write_cmd_reg(priv->ctx->dev, end_cmd, 1);
i2c_ll_write_txfifo(priv->ctx->dev, &msg->buffer[priv->bytes], n);
i2c_ll_master_enable_tx_it(priv->ctx->dev);
priv->bytes += n;
/* Enable I2C master TX interrupt */
esp_i2c_intr_enable(priv);
/* Update I2C configuration */
/* Configure the I2C to trigger a transaction */
i2c_hal_master_trans_start(priv->ctx);
}
/****************************************************************************
* Name: esp_i2c_recvdata
*
* Description:
* Transfer data from the FIFO to the driver buffer.
*
* Parameters:
* priv - Pointer to the internal driver state structure.
*
****************************************************************************/
static void esp_i2c_recvdata(struct esp_i2c_priv_s *priv)
{
struct i2c_msg_s *msg = &priv->msgv[priv->msgid];
uint32_t cmd = 0;
uint8_t n = 0;
uint32_t data = 0;
i2c_ll_get_rxfifo_cnt(priv->ctx->dev, &cmd);
n = cmd & 0xff;
i2c_ll_read_rxfifo(priv->ctx->dev, &msg->buffer[priv->bytes], n);
i2c_ll_master_enable_rx_it(priv->ctx->dev);
priv->bytes += n;
}
/****************************************************************************
* Name: esp_i2c_startrecv
*
* Description:
* Configure I2C to prepare receiving data and it will create an interrupt
* to receive real data.
*
* Parameters:
* priv - Pointer to the internal driver state structure.
*
****************************************************************************/
static void esp_i2c_startrecv(struct esp_i2c_priv_s *priv)
{
int ack_value = 0;
struct i2c_msg_s *msg = &priv->msgv[priv->msgid];
int n = msg->length - priv->bytes;
i2c_ll_hw_cmd_t read_cmd;
i2c_ll_hw_cmd_t end_cmd;
if (n > 1)
{
n -= 1;
n = n < I2C_FIFO_SIZE ? n : I2C_FIFO_SIZE;
ack_value = 0;
}
else
{
ack_value = 1;
}
read_cmd.byte_num = n;
read_cmd.ack_val = ack_value;
read_cmd.op_code = I2C_LL_CMD_READ;
i2c_ll_write_cmd_reg(priv->ctx->dev, read_cmd, 0);
end_cmd.op_code = I2C_LL_CMD_END;
i2c_ll_write_cmd_reg(priv->ctx->dev, end_cmd, 1);
/* Enable I2C master RX interrupt */
esp_i2c_intr_enable(priv);
/* Update I2C configuration */
/* Configure the I2C to trigger a transaction */
i2c_hal_master_trans_start(priv->ctx);
}
/****************************************************************************
* Name: esp_i2c_sendstop
*
* Description:
* Send I2C STOP signal.
*
* Parameters:
* priv - Pointer to the internal driver state structure.
*
****************************************************************************/
static void esp_i2c_sendstop(struct esp_i2c_priv_s *priv)
{
i2c_ll_hw_cmd_t stop_cmd =
{
.op_code = I2C_LL_CMD_STOP
};
i2c_ll_write_cmd_reg(priv->ctx->dev, stop_cmd, 0);
/* Enable I2C master TX interrupt */
esp_i2c_intr_enable(priv);
/* Update I2C configuration */
/* Configure the I2C to trigger a transaction */
i2c_hal_master_trans_start(priv->ctx);
}
/****************************************************************************
* Name: esp_i2c_init_clock
*
* Description:
* Initialize I2C hardware clock.
*
* Parameters:
* priv - Pointer to the internal driver state structure.
* bus_freq - Clock frequency of the I2C bus in Hz.
*
****************************************************************************/
static void esp_i2c_init_clock(struct esp_i2c_priv_s *priv,
uint32_t bus_freq)
{
if (bus_freq == priv->clk_freq)
{
return ;
}
i2c_clock_source_t src_clk = I2C_CLK_SRC_DEFAULT;
i2c_hal_set_bus_timing(priv->ctx, priv->config->clk_freq,
src_clk, XTAL_CLK_FREQ);
i2c_ll_update(priv->ctx->dev);
priv->clk_freq = bus_freq;
}
/****************************************************************************
* Name: esp_i2c_init
*
* Description:
* Initialize I2C hardware.
*
* Parameters:
* priv - Pointer to the internal driver state structure.
*
****************************************************************************/
static void esp_i2c_init(struct esp_i2c_priv_s *priv)
{
const struct esp_i2c_config_s *config = priv->config;
/* Configure GPIO signals for I2C SCL and SDA pins */
esp_gpiowrite(config->scl_pin, 1);
esp_gpiowrite(config->sda_pin, 1);
esp_configgpio(config->scl_pin, INPUT_PULLUP | OUTPUT_OPEN_DRAIN);
esp_gpio_matrix_out(config->scl_pin, config->scl_outsig, 0, 0);
esp_gpio_matrix_in(config->scl_pin, config->scl_insig, 0);
esp_configgpio(config->sda_pin, INPUT_PULLUP | OUTPUT_OPEN_DRAIN);
esp_gpio_matrix_out(config->sda_pin, config->sda_outsig, 0, 0);
esp_gpio_matrix_in(config->sda_pin, config->sda_insig, 0);
/* Enable I2C hardware */
periph_module_enable(i2c_periph_signal[priv->id].module);
i2c_hal_init(priv->ctx, priv->id);
/* Disable I2C interrupts */
esp_i2c_intr_disable(priv);
/* Initialize I2C Master */
i2c_hal_master_init(priv->ctx);
/* Configure the hardware filter function */
i2c_ll_set_filter(priv->ctx->dev, I2C_FILTER_CYC_NUM_DEF);
/* Initialize I2C bus clock */
esp_i2c_init_clock(priv, config->clk_freq);
}
/****************************************************************************
* Name: esp_i2c_deinit
*
* Description:
* Disable I2C hardware.
*
* Parameters:
* priv - Pointer to the internal driver state structure.
*
****************************************************************************/
static void esp_i2c_deinit(struct esp_i2c_priv_s *priv)
{
const struct esp_i2c_config_s *config = priv->config;
priv->clk_freq = 0;
i2c_hal_deinit(priv->ctx);
periph_module_disable(i2c_periph_signal[priv->id].module);
}
/****************************************************************************
* Name: esp_i2c_reset_fsmc
*
* Description:
* Reset I2C hardware state machine and registers.
*
* Parameters:
* priv - Pointer to the internal driver state structure.
*
****************************************************************************/
static void esp_i2c_reset_fsmc(struct esp_i2c_priv_s *priv)
{
/* Reset FSM machine */
i2c_hal_master_fsm_rst(priv->ctx);
}
/****************************************************************************
* Name: esp_i2c_sem_waitdone
*
* Description:
* Wait for a transfer to complete.
*
* Parameters:
* priv - Pointer to the internal driver state structure.
*
* Returned Value:
* Zero (OK) is returned on success. A negated errno value is returned on
* failure.
*
****************************************************************************/
#ifndef CONFIG_I2C_POLLED
static int esp_i2c_sem_waitdone(struct esp_i2c_priv_s *priv)
{
return nxsem_tickwait_uninterruptible(&priv->sem_isr,
ESPRESSIF_I2CTIMEOTICKS);
}
#endif
/****************************************************************************
* Name: esp_i2c_polling_waitdone
*
* Description:
* Wait for a transfer to complete by polling status interrupt registers,
* which indicates the status of the I2C operations. This function is only
* used in polling driven mode.
*
* Parameters:
* priv - Pointer to the internal driver state structure.
*
* Returned Values:
* Zero (OK) is returned on successfull transfer. -ETIMEDOUT is returned
* in case a transfer didn't finish within the timeout interval. And ERROR
* is returned in case of any I2C error during the transfer has happened.
*
****************************************************************************/
#ifdef CONFIG_I2C_POLLED
static int esp_i2c_polling_waitdone(struct esp_i2c_priv_s *priv)
{
int ret;
clock_t current;
clock_t timeout;
uint32_t status = 0;
i2c_intr_event_t event = 0;
/* Get the current absolute time and add an offset as timeout.
* Preferable to use monotonic, so in case the time changes,
* the time reference is kept, i.e., current time can't jump
* forward and backwards.
*/
current = clock_systime_ticks();
timeout = current + SEC2TICK(10);
/* Loop while a transfer is in progress
* and an error didn't occur within the timeout
*/
while ((sclock_t)(current - timeout) < 0 && (priv->error == 0))
{
/* Check if any interrupt triggered, clear them
* process the operation.
*/
i2c_ll_get_intr_mask(priv->ctx->dev, &status);
if (status != 0)
{
i2c_ll_master_get_event(priv->ctx->dev, &event);
/* Check if the stop operation ended. Don't use
* I2CSTATE_FINISH, because it is set before the stop
* signal really ends. This works for interrupts because
* the i2c_state is checked in the next interrupt when
* stop signal has concluded. This is not the case of
* polling.
*/
if (event == I2C_INTR_EVENT_TRANS_DONE)
{
i2c_ll_clear_intr_mask(priv->ctx->dev, status);
break;
}
i2c_ll_clear_intr_mask(priv->ctx->dev, status);
esp_i2c_process(priv, status);
}
/* Update current time */
current = clock_systime_ticks();
}
/* Return a negated value in case of timeout, and in the other scenarios
* return a positive value.
* The transfer function will check the status of priv to check the other
* scenarios.
*/
if (current >= timeout)
{
ret = -ETIMEDOUT;
}
else if (priv->error)
{
ret = ERROR;
}
else
{
ret = OK;
}
/* Disable all interrupts */
esp_i2c_intr_disable(priv);
return ret;
}
#endif
/****************************************************************************
* Device Driver Operations
****************************************************************************/
/****************************************************************************
* Name: esp_i2c_transfer
*
* Description:
* Generic I2C transfer function.
*
* Parameters:
* dev - Device-specific state data
* msgs - A pointer to a set of message descriptors
* count - The number of transfers to perform
*
* Returned Value:
* Zero (OK) is returned on success. A negated errno value is returned on
* failure.
*
****************************************************************************/
static int esp_i2c_transfer(struct i2c_master_s *dev,
struct i2c_msg_s *msgs,
int count)
{
int ret = OK;
struct esp_i2c_priv_s *priv = (struct esp_i2c_priv_s *)dev;
#ifdef CONFIG_I2C_TRACE
uint32_t status = 0;
#endif
i2cinfo("Starting transfer request of %d message(s):\n", count);
DEBUGASSERT(count > 0);
ret = nxmutex_lock(&priv->lock);
if (ret < 0)
{
return ret;
}
if (priv->i2cstate != I2CSTATE_IDLE)
{
esp_i2c_reset_fsmc(priv);
priv->i2cstate = I2CSTATE_IDLE;
}
priv->msgv = msgs;
for (int i = 0; i < count; i++)
{
esp_i2c_reset_fifo(priv);
priv->bytes = 0;
priv->msgid = i;
priv->ready_read = false;
priv->error = 0;
priv->i2cstate = I2CSTATE_PROC;
i2cinfo("Sending message %" PRIu8 "...\n", priv->msgid);
esp_i2c_init_clock(priv, msgs[i].frequency);
#ifdef CONFIG_I2C_TRACE
status = GET_STATUS(priv->ctx->dev);
#endif
#ifndef CONFIG_I2C_POLLED
if ((msgs[i].flags & I2C_M_NOSTART) != 0)
{
esp_i2c_traceevent(priv, I2CEVENT_SENDBYTE, priv->bytes, status);
esp_i2c_senddata(priv);
if (priv->bytes == msgs[i].length)
{
priv->i2cstate = I2CSTATE_STOP;
if ((msgs[i].flags & I2C_M_NOSTOP) != 0)
{
priv->i2cstate = I2CSTATE_FINISH;
}
}
}
else
#endif
{
/* Reset I2C trace logic */
esp_i2c_tracereset(priv);
esp_i2c_traceevent(priv, I2CEVENT_SENDADDR, msgs[i].addr, status);
esp_i2c_sendstart(priv);
}
#ifndef CONFIG_I2C_POLLED
if (esp_i2c_sem_waitdone(priv) < 0)
{
i2cerr("Message %" PRIu8 " timed out.\n", priv->msgid);
ret = -ETIMEDOUT;
break;
}
else
{
if (priv->error != 0)
{
i2cerr("Transfer error %" PRIu32 "\n", priv->error);
ret = -EIO;
break;
}
else
{
priv->i2cstate = I2CSTATE_IDLE;
ret = OK;
}
}
#else
ret = esp_i2c_polling_waitdone(priv);
if (ret < 0)
{
if (ret == -ETIMEDOUT)
{
break;
}
else
{
ret = -EIO;
break;
}
}
else
{
/* Successful transfer, update the I2C state to idle */
priv->i2cstate = I2CSTATE_IDLE;
ret = OK;
}
#endif
i2cinfo("Message %" PRIu8 " transfer complete.\n", priv->msgid);
}
/* Dump the trace result */
esp_i2c_tracedump(priv);
nxmutex_unlock(&priv->lock);
return ret;
}
/****************************************************************************
* Name: esp_i2c_clear_bus
*
* Description:
* Clear I2C bus, when the slave is stuck in a deadlock and keeps pulling
* the bus low, master can control the SCL bus to generate 9 CLKs.
*
* Parameters:
* priv - Pointer to the internal driver state structure.
*
****************************************************************************/
#ifdef CONFIG_I2C_RESET
static void esp_i2c_clear_bus(struct esp_i2c_priv_s *priv)
{
i2c_ll_master_clr_bus(priv->ctx->dev);
}
#endif
/****************************************************************************
* Name: esp_i2c_reset
*
* Description:
* Perform an I2C bus reset in an attempt to break loose stuck I2C devices.
*
* Parameters:
* dev - Device-specific state data
*
* Returned Value:
* Zero (OK) is returned on success. A negated errno value is returned on
* failure.
*
****************************************************************************/
#ifdef CONFIG_I2C_RESET
static int esp_i2c_reset(struct i2c_master_s *dev)
{
irqstate_t flags;
struct esp_i2c_priv_s *priv = (struct esp_i2c_priv_s *)dev;
DEBUGASSERT(dev);
DEBUGASSERT(priv->refs > 0);
flags = enter_critical_section();
esp_i2c_reset_fsmc(priv);
/* Clear bus */
esp_i2c_clear_bus(priv);
priv->i2cstate = I2CSTATE_IDLE;
priv->msgid = 0;
priv->bytes = 0;
priv->ready_read = false;
leave_critical_section(flags);
return OK;
}
#endif
/****************************************************************************
* Name: esp_i2c_traceclear
*
* Description:
* Set I2C trace fields to default value.
*
* Parameters:
* priv - Pointer to the internal driver state structure.
*
****************************************************************************/
#ifdef CONFIG_I2C_TRACE
static void esp_i2c_traceclear(struct esp_i2c_priv_s *priv)
{
struct esp_trace_s *trace = &priv->trace[priv->tndx];
trace->status = 0;
trace->count = 0;
trace->event = I2CEVENT_NONE;
trace->parm = 0;
trace->time = 0;
}
#endif /* CONFIG_I2C_TRACE */
/****************************************************************************
* Name: esp_i2c_tracereset
*
* Description:
* Reset the trace info for a new data collection.
*
* Parameters:
* priv - Pointer to the internal driver state structure.
*
****************************************************************************/
#ifdef CONFIG_I2C_TRACE
static void esp_i2c_tracereset(struct esp_i2c_priv_s *priv)
{
priv->tndx = 0;
priv->start_time = clock_systime_ticks();
esp_i2c_traceclear(priv);
}
#endif /* CONFIG_I2C_TRACE */
/****************************************************************************
* Name: esp_i2c_tracenew
*
* Description:
* Create a new trace entry.
*
* Parameters:
* priv - Pointer to the internal driver state structure.
* status - Current value of I2C status register.
*
****************************************************************************/
#ifdef CONFIG_I2C_TRACE
static void esp_i2c_tracenew(struct esp_i2c_priv_s *priv,
uint32_t status)
{
struct esp_trace_s *trace = &priv->trace[priv->tndx];
/* Check if the current entry is already initialized or if its status had
* already changed
*/
if (trace->count == 0 || status != trace->status)
{
/* Check whether the status changed */
if (trace->count != 0)
{
/* Bump up the trace index (unless we are out of trace entries) */
if (priv->tndx >= (CONFIG_I2C_NTRACE - 1))
{
i2cerr("ERROR: Trace table overflow\n");
return;
}
priv->tndx++;
trace = &priv->trace[priv->tndx];
}
/* Initialize the new trace entry */
esp_i2c_traceclear(priv);
trace->status = status;
trace->count = 1;
trace->time = clock_systime_ticks();
}
else
{
/* Just increment the count of times that we have seen this status */
trace->count++;
}
}
#endif /* CONFIG_I2C_TRACE */
/****************************************************************************
* Name: esp_i2c_traceevent
*
* Description:
* Record a new trace event.
*
* Parameters:
* priv - Pointer to the internal driver state structure.
* event - Event to be recorded on the trace.
* parm - Parameter associated with the event.
* status - Current value of I2C status register.
*
****************************************************************************/
#ifdef CONFIG_I2C_TRACE
static void esp_i2c_traceevent(struct esp_i2c_priv_s *priv,
enum esp_trace_e event,
uint32_t parm,
uint32_t status)
{
/* Check for new trace setup */
esp_i2c_tracenew(priv, status);
if (event != I2CEVENT_NONE)
{
struct esp_trace_s *trace = &priv->trace[priv->tndx];
/* Initialize the new trace entry */
trace->event = event;
trace->parm = parm;
/* Bump up the trace index (unless we are out of trace entries) */
if (priv->tndx >= (CONFIG_I2C_NTRACE - 1))
{
i2cerr("ERROR: Trace table overflow\n");
return;
}
priv->tndx++;
esp_i2c_traceclear(priv);
}
}
#endif /* CONFIG_I2C_TRACE */
/****************************************************************************
* Name: esp_i2c_tracedump
*
* Description:
* Dump the trace results.
*
* Parameters:
* priv - Pointer to the internal driver state structure.
*
****************************************************************************/
#ifdef CONFIG_I2C_TRACE
static void esp_i2c_tracedump(struct esp_i2c_priv_s *priv)
{
struct esp_trace_s *trace;
int i;
syslog(LOG_DEBUG, "Elapsed time: %" PRIu32 "\n",
(clock_systime_ticks() - priv->start_time));
for (i = 0; i < priv->tndx; i++)
{
trace = &priv->trace[i];
syslog(LOG_DEBUG,
"%2d. STATUS: %08" PRIx32 " COUNT: %3" PRIu32 " EVENT: %s(%2d)"
" PARM: %08" PRIx32 " TIME: %" PRIu32 "\n",
i + 1, trace->status, trace->count, g_trace_names[trace->event],
trace->event, trace->parm, trace->time - priv->start_time);
}
}
#endif /* CONFIG_I2C_TRACE */
/****************************************************************************
* Name: esp_i2c_irq
*
* Description:
* This is the common I2C interrupt handler. It will be invoked when an
* interrupt is received on the device.
*
* Parameters:
* cpuint - CPU interrupt index
* context - Context data from the ISR
* arg - Opaque pointer to the internal driver state structure.
*
* Returned Value:
* Zero (OK) is returned on success. A negated errno value is returned on
* failure.
*
****************************************************************************/
#ifndef CONFIG_I2C_POLLED
static int esp_i2c_irq(int cpuint, void *context, void *arg)
{
struct esp_i2c_priv_s *priv = (struct esp_i2c_priv_s *)arg;
uint32_t irq_status = 0;
i2c_ll_get_intr_mask(priv->ctx->dev, &irq_status);
i2c_ll_clear_intr_mask(priv->ctx->dev, irq_status);
esp_i2c_process(priv , irq_status);
return OK;
}
#endif
/****************************************************************************
* Name: esp_i2c_process
*
* Description:
* This routine manages the transfer. It's called after some specific
* commands from the I2C controller are executed or in case of errors.
* It's responsible for writing/reading operations and transferring data
* from/to FIFO.
* It's called in the interrupt and polled driven mode.
*
* Parameters:
* priv - Pointer to the internal driver state structure.
* status - The current interrupt status register.
*
****************************************************************************/
static inline void esp_i2c_process(struct esp_i2c_priv_s *priv,
uint32_t irq_status)
{
struct i2c_msg_s *msg = &priv->msgv[priv->msgid];
#ifdef CONFIG_I2C_TRACE
uint32_t status = 0;
status = GET_STATUS(priv->ctx->dev);
#endif
/* Check for any errors */
if (I2C_INT_ERR_MASK & irq_status)
{
priv->error = irq_status & I2C_INT_ERR_MASK;
priv->i2cstate = I2CSTATE_ERROR;
esp_i2c_traceevent(priv, I2CEVENT_ERROR, priv->error, status);
esp_i2c_intr_disable(priv);
#ifndef CONFIG_I2C_POLLED
nxsem_post(&priv->sem_isr);
#endif
}
else
{
if (priv->i2cstate == I2CSTATE_PROC)
{
if (msg->flags & I2C_M_READ)
{
if (priv->ready_read)
{
esp_i2c_traceevent(priv, I2CEVENT_RCVBYTE, priv->bytes,
status);
esp_i2c_recvdata(priv);
priv->ready_read = false;
}
if (priv->bytes == msg->length)
{
esp_i2c_traceevent(priv, I2CEVENT_STOP, msg->length,
status);
esp_i2c_sendstop(priv);
#ifndef CONFIG_I2C_POLLED
priv->i2cstate = I2CSTATE_FINISH;
#endif
}
else
{
esp_i2c_traceevent(priv, I2CEVENT_RCVMODEEN, 0,
status);
esp_i2c_startrecv(priv);
priv->ready_read = true;
}
}
else
{
esp_i2c_traceevent(priv, I2CEVENT_SENDBYTE, priv->bytes,
status);
esp_i2c_senddata(priv);
if (priv->bytes == msg->length)
{
priv->i2cstate = I2CSTATE_STOP;
#ifndef CONFIG_I2C_POLLED
if ((msg->flags & I2C_M_NOSTOP) != 0)
{
priv->i2cstate = I2CSTATE_FINISH;
}
#endif
}
}
}
else if (priv->i2cstate == I2CSTATE_STOP)
{
esp_i2c_traceevent(priv, I2CEVENT_STOP, msg->length,
status);
esp_i2c_sendstop(priv);
#ifndef CONFIG_I2C_POLLED
priv->i2cstate = I2CSTATE_FINISH;
#endif
}
#ifndef CONFIG_I2C_POLLED
else if (priv->i2cstate == I2CSTATE_FINISH)
{
esp_i2c_intr_disable(priv);
nxsem_post(&priv->sem_isr);
}
#endif
}
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: esp_i2cbus_initialize
*
* Description:
* Initialize the selected I2C port. And return a pointer to an unique
* instance of struct i2c_master_s. This function may be called to obtain
* multiple instances of the interface, each of which may be set up with a
* different frequency and slave address.
*
* Parameters:
* port - Port number of the I2C interface to be initialized.
*
* Returned Value:
* Pointer to valid I2C device structure is returned on success.
* A NULL pointer is returned on failure.
*
****************************************************************************/
struct i2c_master_s *esp_i2cbus_initialize(int port)
{
struct esp_i2c_priv_s *priv;
#ifndef CONFIG_I2C_POLLED
const struct esp_i2c_config_s *config;
int ret;
#endif
switch (port)
{
#ifdef CONFIG_ESPRESSIF_I2C0
case ESPRESSIF_I2C0:
priv = &esp_i2c0_priv;
break;
#endif
#ifdef CONFIG_ESPRESSIF_I2C1
case ESPRESSIF_I2C1:
priv = &esp_i2c1_priv;
break;
#endif
default:
return NULL;
}
nxmutex_lock(&priv->lock);
if (priv->refs++ != 0)
{
nxmutex_unlock(&priv->lock);
i2cinfo("Returning previously initialized I2C bus. "
"Handler: %p\n", priv);
return (struct i2c_master_s *)priv;
}
#ifndef CONFIG_I2C_POLLED
config = priv->config;
if (priv->cpuint != -ENOMEM)
{
/* Disable the previous IRQ */
up_disable_irq(config->irq);
esp_teardown_irq(config->periph, priv->cpuint);
}
priv->cpuint = esp_setup_irq(config->periph,
ESP_IRQ_PRIORITY_DEFAULT,
ESP_IRQ_TRIGGER_LEVEL);
if (priv->cpuint < 0)
{
/* Failed to allocate a CPU interrupt of this type. */
priv->refs--;
nxmutex_unlock(&priv->lock);
return NULL;
}
ret = irq_attach(config->irq, esp_i2c_irq, priv);
if (ret != OK)
{
/* Failed to attach IRQ, free the allocated CPU interrupt */
esp_teardown_irq(config->periph, priv->cpuint);
priv->cpuint = -ENOMEM;
priv->refs--;
nxmutex_unlock(&priv->lock);
return NULL;
}
/* Enable the CPU interrupt that is linked to the I2C device. */
up_enable_irq(config->irq);
#endif
esp_i2c_init(priv);
nxmutex_unlock(&priv->lock);
i2cinfo("I2C bus initialized! Handler: %p\n", priv);
return (struct i2c_master_s *)priv;
}
/****************************************************************************
* Name: esp_i2cbus_uninitialize
*
* Description:
* De-initialize the selected I2C port and power down the device.
*
* Parameters:
* dev - Device structure as returned by
* esp_i2cbus_initialize()
*
* Returned Value:
* OK is returned on success. ERROR is returned when internal reference
* count mismatches or dev points to invalid hardware device.
*
****************************************************************************/
int esp_i2cbus_uninitialize(struct i2c_master_s *dev)
{
struct esp_i2c_priv_s *priv = (struct esp_i2c_priv_s *)dev;
DEBUGASSERT(dev);
if (priv->refs == 0)
{
return ERROR;
}
nxmutex_lock(&priv->lock);
if (--priv->refs)
{
nxmutex_unlock(&priv->lock);
return OK;
}
#ifndef CONFIG_I2C_POLLED
up_disable_irq(priv->config->irq);
esp_teardown_irq(priv->config->periph, priv->cpuint);
priv->cpuint = -ENOMEM;
#endif
esp_i2c_deinit(priv);
nxmutex_unlock(&priv->lock);
return OK;
}
#endif /* CONFIG_ESPRESSIF_I2C */