/**************************************************************************** * arch/xtensa/src/esp32s3/esp32s3_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 #ifdef CONFIG_ESP32S3_I2C #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "esp32s3_gpio.h" #include "esp32s3_i2c.h" #include "esp32s3_irq.h" #include "xtensa.h" #include "hardware/esp32s3_gpio_sigmap.h" #include "hardware/esp32s3_i2c.h" #include "hardware/esp32s3_soc.h" #include "hardware/esp32s3_system.h" /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ /* Command format */ #define I2C_BASE_CMD(_cmd, _check_ack) (((_cmd) << 11) | \ ((_check_ack) << 8)) #define I2C_SEND_CMD(_cmd, _check_ack, _bytes) (((_cmd) << 11) | \ ((_check_ack) << 8) | \ (_bytes)) #define I2C_RECV_CMD(_cmd, _ack_val, _bytes) (((_cmd) << 11) | \ ((_ack_val) << 10) | \ (_bytes)) /* Helper */ #ifdef CONFIG_I2C_POLLED #define TIMESPEC_TO_US(sec, nano) ((sec) * USEC_PER_SEC + (nano) / NSEC_PER_USEC) #endif #define ESP32S3_I2CTIMEOTICKS \ (SEC2TICK(CONFIG_ESP32S3_I2CTIMEOSEC) + MSEC2TICK(CONFIG_ESP32S3_I2CTIMEOMS)) /* Default option */ #define I2C_FIFO_SIZE (32) #define I2C_FILTER_CYC_NUM_DEF (7) #define I2C_CLK_FREQ_DEF (100 * 1000) #define I2C_INT_ERR_MASK (I2C_NACK_INT_ENA | \ I2C_TIME_OUT_INT_ENA | \ I2C_ARBITRATION_LOST_INT_ENA) #define I2C_SCL_CYC_NUM_DEF 9 /* 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 i2c_tracereset(p) # define i2c_tracenew(p,s) # define i2c_traceevent(p,e,a,s) # define i2c_tracedump(p) #endif #ifndef CONFIG_I2C_NTRACE # define CONFIG_I2C_NTRACE 32 #endif /**************************************************************************** * Private Types ****************************************************************************/ /* I2C state */ enum esp32s3_i2cstate_e { I2CSTATE_IDLE = 0, I2CSTATE_PROC, I2CSTATE_STOP, #ifndef CONFIG_I2C_POLLED I2CSTATE_FINISH, #endif I2CSTATE_ERROR }; /* I2C hardware command */ enum 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 esp32s3_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 esp32s3_trace_s { uint32_t status; /* I2C 32-bit SR status */ uint32_t count; /* Interrupt count when status change */ enum esp32s3_trace_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 esp32s3_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 esp32s3_i2c_priv_s { const struct i2c_ops_s *ops; /* Standard I2C operations */ uint32_t id; /* I2C instance */ /* Port configuration */ const struct esp32s3_i2c_config_s *config; int refs; /* Reference count */ sem_t sem_excl; /* Mutual exclusion semaphore */ #ifndef CONFIG_I2C_POLLED sem_t sem_isr; /* Interrupt wait semaphore */ int cpuint; /* CPU interrupt assigned to this I2C */ uint8_t cpu; /* CPU ID */ #endif /* I2C work state (see enum esp32s3_i2cstate_e) */ volatile enum esp32s3_i2cstate_e i2cstate; struct i2c_msg_s *msgv; /* Message list */ uint8_t msgid; /* Current message ID */ ssize_t bytes; /* Processed data bytes */ uint32_t error; /* I2C transform error */ bool ready_read; /* If I2C has read data */ uint32_t clk_freq; /* Current I2C Clock frequency */ spinlock_t lock; /* Device specific lock */ #ifdef CONFIG_I2C_TRACE /* I2C trace support */ int tndx; /* Trace array index */ clock_t start_time; /* Time when the trace was started */ /* The actual trace data */ struct esp32s3_trace_s trace[CONFIG_I2C_NTRACE]; #endif }; /**************************************************************************** * Private Function Prototypes ****************************************************************************/ static void i2c_init_clock(struct esp32s3_i2c_priv_s *priv, uint32_t clock); static void i2c_init(struct esp32s3_i2c_priv_s *priv); static void i2c_deinit(struct esp32s3_i2c_priv_s *priv); static int i2c_transfer(struct i2c_master_s *dev, struct i2c_msg_s *msgs, int count); static inline void i2c_process(struct esp32s3_i2c_priv_s *priv, uint32_t status); static void i2c_sem_init(struct esp32s3_i2c_priv_s *priv); static void i2c_sem_destroy(struct esp32s3_i2c_priv_s *priv); static void i2c_sem_post(struct esp32s3_i2c_priv_s *priv); static int i2c_sem_wait(struct esp32s3_i2c_priv_s *priv); #ifndef CONFIG_I2C_POLLED static int i2c_sem_waitdone(struct esp32s3_i2c_priv_s *priv); #endif #ifdef CONFIG_I2C_POLLED static int i2c_polling_waitdone(struct esp32s3_i2c_priv_s *priv); #endif static void i2c_clear_bus(struct esp32s3_i2c_priv_s *priv); static void i2c_reset_fsmc(struct esp32s3_i2c_priv_s *priv); #ifdef CONFIG_I2C_RESET static int i2c_reset(struct i2c_master_s *dev); #endif #ifdef CONFIG_I2C_TRACE static void i2c_tracereset(struct esp32s3_i2c_priv_s *priv); static void i2c_tracenew(struct esp32s3_i2c_priv_s *priv, uint32_t status); static void i2c_traceevent(struct esp32s3_i2c_priv_s *priv, enum esp32s3_trace_e event, uint32_t parm, uint32_t status); static void i2c_tracedump(struct esp32s3_i2c_priv_s *priv); #endif /* CONFIG_I2C_TRACE */ /**************************************************************************** * Private Data ****************************************************************************/ /* I2C interface */ static const struct i2c_ops_s g_esp32s3_i2c_ops = { .transfer = i2c_transfer #ifdef CONFIG_I2C_RESET , .reset = i2c_reset #endif }; /* I2C device structures */ #ifdef CONFIG_ESP32S3_I2C0 static const struct esp32s3_i2c_config_s g_esp32s3_i2c0_config = { .clk_freq = I2C_CLK_FREQ_DEF, .scl_pin = CONFIG_ESP32S3_I2C0_SCLPIN, .sda_pin = CONFIG_ESP32S3_I2C0_SDAPIN, #ifndef CONFIG_I2C_POLLED .periph = ESP32S3_PERIPH_I2C_EXT0, .irq = ESP32S3_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 esp32s3_i2c_priv_s g_esp32s3_i2c0_priv = { .ops = &g_esp32s3_i2c_ops, .id = ESP32S3_I2C0, .config = &g_esp32s3_i2c0_config, .refs = 0, .i2cstate = I2CSTATE_IDLE, .msgv = NULL, .msgid = 0, .bytes = 0, .ready_read = false }; #endif /* CONFIG_ESP32S3_I2C0 */ #ifdef CONFIG_ESP32S3_I2C1 static const struct esp32s3_i2c_config_s g_esp32s3_i2c1_config = { .clk_freq = I2C_CLK_FREQ_DEF, .scl_pin = CONFIG_ESP32S3_I2C1_SCLPIN, .sda_pin = CONFIG_ESP32S3_I2C1_SDAPIN, #ifndef CONFIG_I2C_POLLED .periph = ESP32S3_PERIPH_I2C_EXT1, .irq = ESP32S3_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 esp32s3_i2c_priv_s g_esp32s3_i2c1_priv = { .ops = &g_esp32s3_i2c_ops, .id = ESP32S3_I2C1, .config = &g_esp32s3_i2c1_config, .refs = 0, .i2cstate = I2CSTATE_IDLE, .msgv = NULL, .msgid = 0, .bytes = 0, .ready_read = false }; #endif /* CONFIG_ESP32S3_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: i2c_reset_fifo * * Description: * Reset I2C RX and TX hardware FIFO. * * Parameters: * priv - Pointer to the internal driver state structure. * ****************************************************************************/ static void i2c_reset_fifo(struct esp32s3_i2c_priv_s *priv) { uint32_t bits = I2C_TX_FIFO_RST | I2C_RX_FIFO_RST; modifyreg32(I2C_FIFO_CONF_REG(priv->id), 0, bits); modifyreg32(I2C_FIFO_CONF_REG(priv->id), bits, 0); } /**************************************************************************** * Name: i2c_intr_enable * * Description: * Enable I2C interrupts. * * Parameters: * priv - Pointer to the internal driver state structure. * ****************************************************************************/ static void i2c_intr_enable(struct esp32s3_i2c_priv_s *priv) { putreg32(UINT32_MAX, I2C_INT_CLR_REG(priv->id)); putreg32(I2C_TRANS_COMPLETE_INT_ENA | I2C_END_DETECT_INT_ENA | I2C_INT_ERR_MASK, I2C_INT_ENA_REG(priv->id)); } /**************************************************************************** * Name: i2c_intr_disable * * Description: * Disable I2C interrupts. * * Parameters: * priv - Pointer to the internal driver state structure. * ****************************************************************************/ static void i2c_intr_disable(struct esp32s3_i2c_priv_s *priv) { putreg32(0, I2C_INT_ENA_REG(priv->id)); putreg32(UINT32_MAX, I2C_INT_CLR_REG(priv->id)); } /**************************************************************************** * Name: i2c_sendstart * * Description: * Send I2C start signal. * * Parameters: * priv - Pointer to the internal driver state structure. * ****************************************************************************/ static void i2c_sendstart(struct esp32s3_i2c_priv_s *priv) { struct i2c_msg_s *msg = &priv->msgv[priv->msgid]; /* Write I2C command registers */ putreg32(I2C_BASE_CMD(I2C_CMD_RESTART, 0), I2C_COMD0_REG(priv->id)); putreg32(I2C_SEND_CMD(I2C_CMD_WRITE, 1, 1), I2C_COMD1_REG(priv->id)); putreg32(I2C_BASE_CMD(I2C_CMD_END, 0), I2C_COMD2_REG(priv->id)); /* Write data to FIFO register */ if ((msg->flags & I2C_M_READ) == 0) { putreg32(I2C_WRITEADDR8(msg->addr), I2C_DATA_REG(priv->id)); } else { putreg32(I2C_READADDR8(msg->addr), I2C_DATA_REG(priv->id)); } /* Enable I2C master TX interrupt */ i2c_intr_enable(priv); /* Update I2C configuration */ modifyreg32(I2C_CTR_REG(priv->id), 0, I2C_CONF_UPGATE); /* Configure the I2C to trigger a transaction */ modifyreg32(I2C_CTR_REG(priv->id), 0, I2C_TRANS_START); } /**************************************************************************** * Name: i2c_senddata * * Description: * Send I2C data. * * Parameters: * priv - Pointer to the internal driver state structure. * ****************************************************************************/ static void i2c_senddata(struct esp32s3_i2c_priv_s *priv) { struct i2c_msg_s *msg = &priv->msgv[priv->msgid]; int n = msg->length - priv->bytes; n = n < I2C_FIFO_SIZE ? n : I2C_FIFO_SIZE; putreg32(I2C_SEND_CMD(I2C_CMD_WRITE, 1, n), I2C_COMD0_REG(priv->id)); putreg32(I2C_BASE_CMD(I2C_CMD_END, 0), I2C_COMD1_REG(priv->id)); for (int i = 0; i < n; i++) { putreg32(msg->buffer[priv->bytes + i], I2C_DATA_REG(priv->id)); } priv->bytes += n; /* Enable I2C master TX interrupt */ i2c_intr_enable(priv); /* Update I2C configuration */ modifyreg32(I2C_CTR_REG(priv->id), 0, I2C_CONF_UPGATE); /* Configure the I2C to trigger a transaction */ modifyreg32(I2C_CTR_REG(priv->id), 0, I2C_TRANS_START); } /**************************************************************************** * Name: i2c_recvdata * * Description: * Transfer data from the FIFO to the driver buffer. * * Parameters: * priv - Pointer to the internal driver state structure. * ****************************************************************************/ static void i2c_recvdata(struct esp32s3_i2c_priv_s *priv) { struct i2c_msg_s *msg = &priv->msgv[priv->msgid]; uint32_t cmd = getreg32(I2C_COMD0_REG(priv->id)); uint8_t n = cmd & 0xff; uint32_t data = 0; for (int i = 0; i < n; i++) { data = getreg32(I2C_DATA_REG(priv->id)); msg->buffer[priv->bytes + i] = data & 0xff; } priv->bytes += n; } /**************************************************************************** * Name: 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 i2c_startrecv(struct esp32s3_i2c_priv_s *priv) { int ack_value; struct i2c_msg_s *msg = &priv->msgv[priv->msgid]; int n = msg->length - priv->bytes; if (n > 1) { n -= 1; n = n < I2C_FIFO_SIZE ? n : I2C_FIFO_SIZE; ack_value = 0; } else { ack_value = 1; } putreg32(I2C_RECV_CMD(I2C_CMD_READ, ack_value, n), I2C_COMD0_REG(priv->id)); putreg32(I2C_BASE_CMD(I2C_CMD_END, 0), I2C_COMD1_REG(priv->id)); /* Enable I2C master RX interrupt */ i2c_intr_enable(priv); /* Update I2C configuration */ modifyreg32(I2C_CTR_REG(priv->id), 0, I2C_CONF_UPGATE); /* Configure the I2C to trigger a transaction */ modifyreg32(I2C_CTR_REG(priv->id), 0, I2C_TRANS_START); } /**************************************************************************** * Name: i2c_sendstop * * Description: * Send I2C STOP signal. * * Parameters: * priv - Pointer to the internal driver state structure. * ****************************************************************************/ static void i2c_sendstop(struct esp32s3_i2c_priv_s *priv) { putreg32(I2C_BASE_CMD(I2C_CMD_STOP, 0), I2C_COMD0_REG(priv->id)); /* Enable I2C master TX interrupt */ i2c_intr_enable(priv); /* Update I2C configuration */ modifyreg32(I2C_CTR_REG(priv->id), 0, I2C_CONF_UPGATE); /* Configure the I2C to trigger a transaction */ modifyreg32(I2C_CTR_REG(priv->id), 0, I2C_TRANS_START); } /**************************************************************************** * Name: 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 i2c_init_clock(struct esp32s3_i2c_priv_s *priv, uint32_t bus_freq) { if (bus_freq == priv->clk_freq) { return; } uint32_t reg_value = 0; uint32_t scl_low = 0; uint32_t scl_high = 0; uint32_t scl_wait_high = 0; uint32_t sda_hold = 0; uint32_t sda_sample = 0; uint32_t setup = 0; uint32_t hold = 0; uint32_t timeout = 0; uint32_t source_clk = XTAL_CLK_FREQ; uint32_t clkm_div = source_clk / (bus_freq * 1024) + 1; uint32_t sclk_freq = source_clk / clkm_div; uint32_t half_cycle = sclk_freq / bus_freq / 2; modifyreg32(I2C_CLK_CONF_REG(priv->id), I2C_SCLK_DIV_NUM_M, VALUE_TO_FIELD((clkm_div - 1), I2C_SCLK_DIV_NUM)); /* According to the Technical Reference Manual, the following timings must * be subtracted by 1. * Moreover, the frequency calculation also shows that we must subtract 3 * to the total SCL. */ scl_low = half_cycle; putreg32(scl_low - 1 - 2, I2C_SCL_LOW_PERIOD_REG(priv->id)); /* By default, scl_wait_high must be less than scl_high. * A time compensation is needed for when the bus frequency is higher * than 50K. */ scl_high = (bus_freq <= 50000) ? half_cycle : (half_cycle / 5 * 4 + 4); scl_wait_high = half_cycle - scl_high; reg_value = VALUE_TO_FIELD(scl_high - 1 - 1, I2C_SCL_HIGH_PERIOD); reg_value |= VALUE_TO_FIELD(scl_wait_high - 1 - 1, I2C_SCL_WAIT_HIGH_PERIOD); putreg32(reg_value, I2C_SCL_HIGH_PERIOD_REG(priv->id)); sda_hold = half_cycle / 2; putreg32(sda_hold - 1, I2C_SDA_HOLD_REG(priv->id)); /* scl_wait_high < sda_sample <= scl_high */ sda_sample = half_cycle / 2; putreg32(sda_sample - 1, I2C_SDA_SAMPLE_REG(priv->id)); setup = half_cycle; putreg32(setup - 1, I2C_SCL_RSTART_SETUP_REG(priv->id)); putreg32(setup - 1, I2C_SCL_STOP_SETUP_REG(priv->id)); hold = half_cycle; putreg32(hold - 1, I2C_SCL_START_HOLD_REG(priv->id)); putreg32(hold - 1, I2C_SCL_STOP_HOLD_REG(priv->id)); /* By default, we set the timeout value to about 10 bus cycles * log(20*half_cycle)/log(2) = log(half_cycle)/log(2) + log(20)/log(2) */ timeout = sizeof(half_cycle) * 8; timeout -= __builtin_clz(5 * half_cycle); timeout += 2; reg_value = I2C_TIME_OUT_EN; reg_value |= VALUE_TO_FIELD(timeout, I2C_TIME_OUT_VALUE); putreg32(reg_value, I2C_TO_REG(priv->id)); priv->clk_freq = bus_freq; } /**************************************************************************** * Name: i2c_init * * Description: * Initialize I2C hardware. * * Parameters: * priv - Pointer to the internal driver state structure. * ****************************************************************************/ static void i2c_init(struct esp32s3_i2c_priv_s *priv) { const struct esp32s3_i2c_config_s *config = priv->config; esp32s3_gpiowrite(config->scl_pin, 1); esp32s3_configgpio(config->scl_pin, INPUT_PULLUP | OUTPUT_OPEN_DRAIN); esp32s3_gpio_matrix_out(config->scl_pin, config->scl_outsig, 0, 0); esp32s3_gpio_matrix_in(config->scl_pin, config->scl_insig, 0); esp32s3_gpiowrite(config->sda_pin, 1); esp32s3_configgpio(config->sda_pin, INPUT_PULLUP | OUTPUT_OPEN_DRAIN); esp32s3_gpio_matrix_out(config->sda_pin, config->sda_outsig, 0, 0); esp32s3_gpio_matrix_in(config->sda_pin, config->sda_insig, 0); /* Enable I2C hardware */ modifyreg32(SYSTEM_PERIP_CLK_EN0_REG, 0, config->clk_bit); modifyreg32(SYSTEM_PERIP_RST_EN0_REG, config->rst_bit, 0); /* Disable I2C interrupts */ i2c_intr_disable(priv); /* Initialize I2C Master */ putreg32(I2C_MS_MODE | I2C_CLK_EN | I2C_SCL_FORCE_OUT | I2C_SDA_FORCE_OUT, I2C_CTR_REG(priv->id)); /* Set FIFO mode */ modifyreg32(I2C_FIFO_CONF_REG(priv->id), I2C_NONFIFO_EN, 0); /* Ensure I2C data mode is set to MSB */ modifyreg32(I2C_CTR_REG(priv->id), I2C_TX_LSB_FIRST | I2C_RX_LSB_FIRST, 0); i2c_reset_fifo(priv); /* Configure the hardware filter function */ putreg32(I2C_SCL_FILTER_EN | I2C_SDA_FILTER_EN | VALUE_TO_FIELD(I2C_FILTER_CYC_NUM_DEF, I2C_SCL_FILTER_THRES) | VALUE_TO_FIELD(I2C_FILTER_CYC_NUM_DEF, I2C_SDA_FILTER_THRES), I2C_FILTER_CFG_REG(priv->id)); /* Set I2C source clock */ modifyreg32(I2C_CLK_CONF_REG(priv->id), I2C_SCLK_SEL, 0); /* Configure I2C bus frequency */ i2c_init_clock(priv, config->clk_freq); /* Update I2C configuration */ modifyreg32(I2C_CTR_REG(priv->id), 0, I2C_CONF_UPGATE); } /**************************************************************************** * Name: i2c_deinit * * Description: * Disable I2C hardware. * * Parameters: * priv - Pointer to the internal driver state structure. * ****************************************************************************/ static void i2c_deinit(struct esp32s3_i2c_priv_s *priv) { const struct esp32s3_i2c_config_s *config = priv->config; priv->clk_freq = 0; modifyreg32(SYSTEM_PERIP_RST_EN0_REG, 0, config->rst_bit); modifyreg32(SYSTEM_PERIP_CLK_EN0_REG, config->clk_bit, 0); } /**************************************************************************** * Name: i2c_reset_fsmc * * Description: * Reset I2C hardware state machine and registers. * * Parameters: * priv - Pointer to the internal driver state structure. * ****************************************************************************/ static void i2c_reset_fsmc(struct esp32s3_i2c_priv_s *priv) { /* Reset FSM machine */ modifyreg32(I2C_CTR_REG(priv->id), 0, I2C_FSM_RST); i2c_clear_bus(priv); } /**************************************************************************** * Name: 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 i2c_sem_waitdone(struct esp32s3_i2c_priv_s *priv) { /* Wait on ISR semaphore */ return nxsem_tickwait_uninterruptible(&priv->sem_isr, ESP32S3_I2CTIMEOTICKS); } #endif /**************************************************************************** * Name: 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 i2c_polling_waitdone(struct esp32s3_i2c_priv_s *priv) { int ret; clock_t current; clock_t timeout; uint32_t status = 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 ((current < timeout) && (priv->error == 0)) { /* Check if any interrupt triggered, clear them * process the operation. */ status = getreg32(I2C_INT_STATUS_REG(priv->id)); if (status != 0) { /* 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 ((status & I2C_TRANS_COMPLETE_INT_ST) != 0) { putreg32(status, I2C_INT_CLR_REG(priv->id)); break; } putreg32(status, I2C_INT_CLR_REG(priv->id)); 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 != 0) { ret = ERROR; } else { ret = OK; } /* Disable all interrupts */ i2c_intr_disable(priv); return ret; } #endif /**************************************************************************** * Name: i2c_sem_wait * * Description: * Take the exclusive access, waiting as necessary. * * 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. * ****************************************************************************/ static int i2c_sem_wait(struct esp32s3_i2c_priv_s *priv) { return nxsem_wait_uninterruptible(&priv->sem_excl); } /**************************************************************************** * Name: i2c_sem_post * * Description: * Release the mutual exclusion semaphore. * * Parameters: * priv - Pointer to the internal driver state structure. * ****************************************************************************/ static void i2c_sem_post(struct esp32s3_i2c_priv_s *priv) { nxsem_post(&priv->sem_excl); } /**************************************************************************** * Name: i2c_sem_destroy * * Description: * Destroy semaphores. * * Parameters: * priv - Pointer to the internal driver state structure. * ****************************************************************************/ static void i2c_sem_destroy(struct esp32s3_i2c_priv_s *priv) { nxsem_destroy(&priv->sem_excl); #ifndef CONFIG_I2C_POLLED nxsem_destroy(&priv->sem_isr); #endif } /**************************************************************************** * Name: i2c_sem_init * * Description: * Initialize semaphores. * * Parameters: * priv - Pointer to the internal driver state structure. * ****************************************************************************/ static void i2c_sem_init(struct esp32s3_i2c_priv_s *priv) { nxsem_init(&priv->sem_excl, 0, 1); /* This semaphore is used for signaling and, hence, should not have * priority inheritance enabled. */ #ifndef CONFIG_I2C_POLLED nxsem_init(&priv->sem_isr, 0, 0); nxsem_set_protocol(&priv->sem_isr, SEM_PRIO_NONE); #endif } /**************************************************************************** * Device Driver Operations ****************************************************************************/ /**************************************************************************** * Name: 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 i2c_transfer(struct i2c_master_s *dev, struct i2c_msg_s *msgs, int count) { int ret = OK; struct esp32s3_i2c_priv_s *priv = (struct esp32s3_i2c_priv_s *)dev; DEBUGASSERT(count > 0); ret = i2c_sem_wait(priv); if (ret < 0) { return ret; } /* If previous state is different than idle, * reset the FSMC to the idle state. */ if (priv->i2cstate != I2CSTATE_IDLE) { i2c_reset_fsmc(priv); priv->i2cstate = I2CSTATE_IDLE; } /* Transfer the messages to the internal struct * and loop count times to make all transfers. */ priv->msgv = msgs; for (int i = 0; i < count; i++) { /* Clear TX and RX FIFOs. */ i2c_reset_fifo(priv); priv->bytes = 0; priv->msgid = i; priv->ready_read = false; priv->error = 0; priv->i2cstate = I2CSTATE_PROC; /* Set the SCLK frequency for the current msg. */ i2c_init_clock(priv, msgs[i].frequency); if ((msgs[i].flags & I2C_M_NOSTART) != 0) { i2c_traceevent(priv, I2CEVENT_SENDBYTE, priv->bytes, getreg32(I2C_SR_REG(priv->id))); i2c_senddata(priv); if (priv->bytes == msgs[i].length) { if ((msgs[i].flags & I2C_M_NOSTOP) == 0) { priv->i2cstate = I2CSTATE_STOP; } #ifndef CONFIG_I2C_POLLED else { priv->i2cstate = I2CSTATE_FINISH; } #endif } } else { /* Reset I2C trace logic */ i2c_tracereset(priv); i2c_traceevent(priv, I2CEVENT_SENDADDR, msgs[i].addr, getreg32(I2C_SR_REG(priv->id))); i2c_sendstart(priv); } #ifndef CONFIG_I2C_POLLED if (i2c_sem_waitdone(priv) < 0) { /* Timed out - transfer was not completed within the timeout */ i2cerr("Message %" PRIu8 " timed out.\n", priv->msgid); ret = -ETIMEDOUT; break; } else { if (priv->error != 0) { /* An error occurred */ i2cerr("Transfer error %" PRIu32 "\n", priv->error); ret = -EIO; break; } else { /* Successful transfer, update the I2C state to idle */ priv->i2cstate = I2CSTATE_IDLE; ret = OK; } } #else ret = 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 */ i2c_tracedump(priv); i2c_sem_post(priv); return ret; } /**************************************************************************** * Name: 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. * ****************************************************************************/ static void i2c_clear_bus(struct esp32s3_i2c_priv_s *priv) { modifyreg32(I2C_SCL_SP_CONF_REG(priv->id), I2C_SCL_RST_SLV_EN | I2C_SCL_RST_SLV_NUM_M, VALUE_TO_FIELD(I2C_SCL_CYC_NUM_DEF, I2C_SCL_RST_SLV_NUM)); modifyreg32(I2C_CTR_REG(priv->id), 0, I2C_CONF_UPGATE); modifyreg32(I2C_SCL_SP_CONF_REG(priv->id), 0, I2C_SCL_RST_SLV_EN); } /**************************************************************************** * Name: i2c_reset * * Description: * Perform an I2C bus reset in an attempt to break loose stuck I2C devices. * * Input Parameters: * dev - Device-specific state data * * Returned Value: * Zero (OK) on success; a negated errno value on failure. * ****************************************************************************/ #ifdef CONFIG_I2C_RESET static int i2c_reset(struct i2c_master_s *dev) { irqstate_t flags; struct esp32s3_i2c_priv_s *priv = (struct esp32s3_i2c_priv_s *)dev; DEBUGASSERT(dev != NULL); DEBUGASSERT(priv->refs > 0); flags = spin_lock_irqsave(&priv->lock); i2c_reset_fsmc(priv); /* Clear bus */ i2c_clear_bus(priv); priv->i2cstate = I2CSTATE_IDLE; priv->msgid = 0; priv->bytes = 0; priv->ready_read = false; spin_unlock_irqrestore(&priv->lock, flags); return OK; } #endif /**************************************************************************** * Name: 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 i2c_traceclear(struct esp32s3_i2c_priv_s *priv) { struct esp32s3_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: 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 i2c_tracereset(struct esp32s3_i2c_priv_s *priv) { priv->tndx = 0; priv->start_time = clock_systime_ticks(); i2c_traceclear(priv); } #endif /* CONFIG_I2C_TRACE */ /**************************************************************************** * Name: 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 i2c_tracenew(struct esp32s3_i2c_priv_s *priv, uint32_t status) { struct esp32s3_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 */ 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: 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 i2c_traceevent(struct esp32s3_i2c_priv_s *priv, enum esp32s3_trace_e event, uint32_t parm, uint32_t status) { /* Check for new trace setup */ i2c_tracenew(priv, status); if (event != I2CEVENT_NONE) { struct esp32s3_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++; i2c_traceclear(priv); } } #endif /* CONFIG_I2C_TRACE */ /**************************************************************************** * Name: i2c_tracedump * * Description: * Dump the trace results. * * Parameters: * priv - Pointer to the internal driver state structure. * ****************************************************************************/ #ifdef CONFIG_I2C_TRACE static void i2c_tracedump(struct esp32s3_i2c_priv_s *priv) { syslog(LOG_DEBUG, "Elapsed time: %" PRIu32 "\n", clock_systime_ticks() - priv->start_time); for (int i = 0; i < priv->tndx; i++) { struct esp32s3_trace_s *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: 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 i2c_irq(int cpuint, void *context, void *arg) { struct esp32s3_i2c_priv_s *priv = (struct esp32s3_i2c_priv_s *)arg; /* Get the interrupt status and clear the interrupts that * triggered. */ uint32_t irq_status = getreg32(I2C_INT_STATUS_REG(priv->id)); putreg32(irq_status, I2C_INT_CLR_REG(priv->id)); i2c_process(priv, irq_status); return OK; } #endif /**************************************************************************** * Name: 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 void i2c_process(struct esp32s3_i2c_priv_s *priv, uint32_t status) { struct i2c_msg_s *msg = &priv->msgv[priv->msgid]; /* Check for any errors */ if ((I2C_INT_ERR_MASK & status) != 0) { /* Save the errors, register the error event, disable interrupts * and release the semaphore to conclude the transfer. */ priv->error = status & I2C_INT_ERR_MASK; priv->i2cstate = I2CSTATE_ERROR; i2c_traceevent(priv, I2CEVENT_ERROR, priv->error, getreg32(I2C_SR_REG(priv->id))); i2c_intr_disable(priv); #ifndef CONFIG_I2C_POLLED nxsem_post(&priv->sem_isr); #endif } else /* If no error */ { /* If a transfer has just initialized */ if (priv->i2cstate == I2CSTATE_PROC) { /* Check the flags to perform a read or write operation */ if ((msg->flags & I2C_M_READ) != 0) { /* RX FIFO has available data */ if (priv->ready_read) { i2c_traceevent(priv, I2CEVENT_RCVBYTE, priv->bytes, getreg32(I2C_SR_REG(priv->id))); i2c_recvdata(priv); priv->ready_read = false; } /* Received all data. Finish the transaction * and update the I2C state. */ if (priv->bytes == msg->length) { i2c_traceevent(priv, I2CEVENT_STOP, msg->length, getreg32(I2C_SR_REG(priv->id))); i2c_sendstop(priv); #ifndef CONFIG_I2C_POLLED priv->i2cstate = I2CSTATE_FINISH; #endif } else /* Start a receive operation */ { i2c_traceevent(priv, I2CEVENT_RCVMODEEN, 0, getreg32(I2C_SR_REG(priv->id))); i2c_startrecv(priv); priv->ready_read = true; } } else /* Write operation */ { i2c_traceevent(priv, I2CEVENT_SENDBYTE, priv->bytes, getreg32(I2C_SR_REG(priv->id))); i2c_senddata(priv); /* Finally sent the entire message. Update the I2C state to * send a stop signal in the next interrupt. */ if (priv->bytes == msg->length) { if ((msg->flags & I2C_M_NOSTOP) == 0) { priv->i2cstate = I2CSTATE_STOP; } #ifndef CONFIG_I2C_POLLED else { priv->i2cstate = I2CSTATE_FINISH; } #endif } } } else if (priv->i2cstate == I2CSTATE_STOP) { /* Transmitted all data. Finish the transaction sending a stop * and update the I2C state. */ i2c_traceevent(priv, I2CEVENT_STOP, msg->length, getreg32(I2C_SR_REG(priv->id))); i2c_sendstop(priv); #ifndef CONFIG_I2C_POLLED priv->i2cstate = I2CSTATE_FINISH; #endif } #ifndef CONFIG_I2C_POLLED else if (priv->i2cstate == I2CSTATE_FINISH) { /* Disable all interrupts and release the semaphore */ i2c_intr_disable(priv); nxsem_post(&priv->sem_isr); } #endif } } /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** * Name: esp32s3_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 *esp32s3_i2cbus_initialize(int port) { irqstate_t flags; struct esp32s3_i2c_priv_s *priv; #ifndef CONFIG_I2C_POLLED const struct esp32s3_i2c_config_s *config; int ret; #endif switch (port) { #ifdef CONFIG_ESP32S3_I2C0 case ESP32S3_I2C0: priv = &g_esp32s3_i2c0_priv; break; #endif #ifdef CONFIG_ESP32S3_I2C1 case ESP32S3_I2C1: priv = &g_esp32s3_i2c1_priv; break; #endif default: return NULL; } flags = spin_lock_irqsave(&priv->lock); if (priv->refs++ != 0) { spin_unlock_irqrestore(&priv->lock, flags); return (struct i2c_master_s *)priv; } #ifndef CONFIG_I2C_POLLED config = priv->config; /* Set up to receive peripheral interrupts on the current CPU */ priv->cpu = up_cpu_index(); priv->cpuint = esp32s3_setup_irq(priv->cpu, config->periph, 1, ESP32S3_CPUINT_LEVEL); if (priv->cpuint < 0) { /* Failed to allocate a CPU interrupt of this type */ spin_unlock_irqrestore(&priv->lock, flags); return NULL; } ret = irq_attach(config->irq, i2c_irq, priv); if (ret != OK) { esp32s3_teardown_irq(priv->cpu, config->periph, priv->cpuint); spin_unlock_irqrestore(&priv->lock, flags); return NULL; } up_enable_irq(config->irq); #endif i2c_sem_init(priv); i2c_init(priv); spin_unlock_irqrestore(&priv->lock, flags); return (struct i2c_master_s *)priv; } /**************************************************************************** * Name: esp32s3_i2cbus_uninitialize * * Description: * De-initialize the selected I2C port and power down the device. * * Parameters: * dev - Device structure as returned by * esp32s3_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 esp32s3_i2cbus_uninitialize(struct i2c_master_s *dev) { irqstate_t flags; struct esp32s3_i2c_priv_s *priv = (struct esp32s3_i2c_priv_s *)dev; DEBUGASSERT(dev != NULL); if (priv->refs == 0) { return ERROR; } flags = spin_lock_irqsave(&priv->lock); if (--priv->refs) { spin_unlock_irqrestore(&priv->lock, flags); return OK; } spin_unlock_irqrestore(&priv->lock, flags); #ifndef CONFIG_I2C_POLLED up_disable_irq(priv->config->irq); esp32s3_teardown_irq(priv->cpu, priv->config->periph, priv->cpuint); #endif i2c_deinit(priv); i2c_sem_destroy(priv); return OK; } #endif /* CONFIG_ESP32S3_I2C */