From 56625cda88802e5d10987a4886497916f97754da Mon Sep 17 00:00:00 2001 From: "C.S.M" Date: Fri, 8 Nov 2024 17:59:10 +0800 Subject: [PATCH] feat(i2c_slave): refactor i2c slave api to version 2 inorder to solve some existing problem --- components/esp_driver_i2c/CMakeLists.txt | 6 +- components/esp_driver_i2c/Kconfig | 6 + components/esp_driver_i2c/i2c_private.h | 19 + components/esp_driver_i2c/i2c_slave_v2.c | 433 ++++++++++++++++++ .../esp_driver_i2c/include/driver/i2c_slave.h | 141 ++++-- .../esp_driver_i2c/include/driver/i2c_types.h | 25 +- components/hal/esp32/include/hal/i2c_ll.h | 26 +- components/hal/esp32c3/include/hal/i2c_ll.h | 24 + components/hal/esp32c5/include/hal/i2c_ll.h | 24 + components/hal/esp32c6/include/hal/i2c_ll.h | 24 + components/hal/esp32c61/include/hal/i2c_ll.h | 24 + components/hal/esp32h2/include/hal/i2c_ll.h | 24 + components/hal/esp32p4/include/hal/i2c_ll.h | 54 +++ components/hal/esp32s2/include/hal/i2c_ll.h | 43 +- components/hal/esp32s3/include/hal/i2c_ll.h | 24 + components/hal/include/hal/i2c_types.h | 5 + .../esp32p4/include/soc/Kconfig.soc_caps.in | 4 + components/soc/esp32p4/include/soc/soc_caps.h | 1 + .../esp32s2/include/soc/Kconfig.soc_caps.in | 4 + components/soc/esp32s2/include/soc/soc_caps.h | 1 + .../esp32s3/include/soc/Kconfig.soc_caps.in | 4 + components/soc/esp32s3/include/soc/soc_caps.h | 1 + 22 files changed, 873 insertions(+), 44 deletions(-) create mode 100644 components/esp_driver_i2c/i2c_slave_v2.c diff --git a/components/esp_driver_i2c/CMakeLists.txt b/components/esp_driver_i2c/CMakeLists.txt index 699cf0b5d3..0a6f42a8f3 100644 --- a/components/esp_driver_i2c/CMakeLists.txt +++ b/components/esp_driver_i2c/CMakeLists.txt @@ -10,7 +10,11 @@ if(CONFIG_SOC_I2C_SUPPORTED) "i2c_common.c" ) if(CONFIG_SOC_I2C_SUPPORT_SLAVE) - list(APPEND srcs "i2c_slave.c") + if(CONFIG_I2C_ENABLE_SLAVE_DRIVER_VERSION_2) + list(APPEND srcs "i2c_slave_v2.c") + else() + list(APPEND srcs "i2c_slave.c") + endif() endif() endif() diff --git a/components/esp_driver_i2c/Kconfig b/components/esp_driver_i2c/Kconfig index d8156fddf3..294aa05977 100644 --- a/components/esp_driver_i2c/Kconfig +++ b/components/esp_driver_i2c/Kconfig @@ -17,4 +17,10 @@ menu "ESP-Driver:I2C Configurations" note: This cannot be used in the I2C legacy driver. + config I2C_ENABLE_SLAVE_DRIVER_VERSION_2 + bool "Enable I2C slave driver version 2" + default n + help + I2C slave version 2 solves some existing known issues. Such as write/read workflow, stretch handling, etc. + endmenu # I2C Configurations diff --git a/components/esp_driver_i2c/i2c_private.h b/components/esp_driver_i2c/i2c_private.h index b5be2a800b..5dd06fa259 100644 --- a/components/esp_driver_i2c/i2c_private.h +++ b/components/esp_driver_i2c/i2c_private.h @@ -19,6 +19,7 @@ #include "driver/i2c_slave.h" #include "esp_private/periph_ctrl.h" #include "esp_pm.h" +#include "sdkconfig.h" #ifdef __cplusplus extern "C" { @@ -196,6 +197,8 @@ typedef struct { uint32_t rcv_fifo_cnt; // receive fifo count. } i2c_slave_receive_t; +#if !CONFIG_I2C_ENABLE_SLAVE_DRIVER_VERSION_2 + struct i2c_slave_dev_t { i2c_bus_t *base; // bus base class SemaphoreHandle_t slv_rx_mux; // Mutex for slave rx direction @@ -213,6 +216,22 @@ struct i2c_slave_dev_t { uint32_t already_receive_len; // Data length already received in ISR. }; +#else // CONFIG_I2C_ENABLE_SLAVE_DRIVER_VERSION_2 + +struct i2c_slave_dev_t { + i2c_bus_t *base; // bus base class + SemaphoreHandle_t operation_mux; // Mux for i2c slave operation + i2c_slave_request_callback_t request_callback; // i2c slave request callback + i2c_slave_received_callback_t receive_callback; // i2c_slave receive callback + void *user_ctx; // Callback user context + RingbufHandle_t rx_ring_buf; // receive ringbuffer + RingbufHandle_t tx_ring_buf; // transmit ringbuffer + uint32_t rx_data_count; // receive data count + i2c_slave_receive_t receive_desc; // slave receive descriptor +}; + +#endif // CONFIG_I2C_ENABLE_SLAVE_DRIVER_VERSION_2 + /** * @brief Acquire I2C bus handle * diff --git a/components/esp_driver_i2c/i2c_slave_v2.c b/components/esp_driver_i2c/i2c_slave_v2.c new file mode 100644 index 0000000000..773a468c03 --- /dev/null +++ b/components/esp_driver_i2c/i2c_slave_v2.c @@ -0,0 +1,433 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include "sdkconfig.h" +#include "soc/soc_caps.h" +#include "esp_attr.h" +#include "esp_rom_gpio.h" +#include "driver/gpio.h" +#include "hal/gpio_ll.h" +#include "esp_err.h" +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" +#include "freertos/ringbuf.h" +#include "esp_intr_alloc.h" +#include "hal/i2c_ll.h" +#include "i2c_private.h" +#include "driver/i2c_slave.h" +#include "esp_memory_utils.h" +#if CONFIG_I2C_ENABLE_DEBUG_LOG +// The local log level must be defined before including esp_log.h +// Set the maximum log level for this source file +#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG +#endif +#include "esp_log.h" +#include "esp_check.h" + +static const char *TAG = "i2c.slave"; + +IRAM_ATTR static bool i2c_slave_read_rx(i2c_slave_dev_t *i2c_slave, uint8_t *data, size_t len, size_t *read_len) +{ + BaseType_t xTaskWoken = pdFALSE; + size_t read_size = len; + size_t actual_size = 0; + size_t available_size = 0; + size_t get_size = 0; + uint8_t *rx_data = NULL; + + vRingbufferGetInfo(i2c_slave->rx_ring_buf, NULL, NULL, NULL, NULL, &available_size); + if (available_size < read_size) { + read_size = available_size; + } + + while (read_size) { + actual_size = 0; + rx_data = (uint8_t *)xRingbufferReceiveUpToFromISR(i2c_slave->rx_ring_buf, &actual_size, read_size); + if (rx_data != NULL && actual_size != 0) { + memcpy(data + get_size, rx_data, actual_size); + vRingbufferReturnItemFromISR(i2c_slave->rx_ring_buf, rx_data, &xTaskWoken); + get_size += actual_size; + read_size -= actual_size; + } else { + break; + } + } + *read_len = get_size; + return xTaskWoken; +} + +IRAM_ATTR static bool i2c_slave_handle_tx_fifo(i2c_slave_dev_t *i2c_slave) +{ + BaseType_t xTaskWoken = pdFALSE; + i2c_hal_context_t *hal = &i2c_slave->base->hal; + uint8_t *data; + uint32_t fifo_len = 0; + xSemaphoreTakeFromISR(i2c_slave->operation_mux, &xTaskWoken); + i2c_ll_get_txfifo_len(hal->dev, &fifo_len); + size_t actual_get_len = 0; + while (fifo_len > 0) { + data = xRingbufferReceiveUpToFromISR(i2c_slave->tx_ring_buf, &actual_get_len, fifo_len); + if (data) { + portENTER_CRITICAL_ISR(&i2c_slave->base->spinlock); + i2c_ll_write_txfifo(hal->dev, data, actual_get_len); + fifo_len -= actual_get_len; + portEXIT_CRITICAL_ISR(&i2c_slave->base->spinlock); + vRingbufferReturnItemFromISR(i2c_slave->tx_ring_buf, data, &xTaskWoken); + } else { + // No data in ringbuffer, so disable the tx interrupt. + i2c_ll_slave_disable_tx_it(hal->dev); + break; + } + } + xSemaphoreGiveFromISR(i2c_slave->operation_mux, &xTaskWoken); + return xTaskWoken; +} + +IRAM_ATTR static bool i2c_slave_handle_rx_fifo(i2c_slave_dev_t *i2c_slave, uint32_t len) +{ + i2c_hal_context_t *hal = &i2c_slave->base->hal; + uint8_t data[SOC_I2C_FIFO_LEN]; + BaseType_t xTaskWoken = pdFALSE; + xSemaphoreTakeFromISR(i2c_slave->operation_mux, &xTaskWoken); + if (len) { + portENTER_CRITICAL_ISR(&i2c_slave->base->spinlock); + i2c_ll_read_rxfifo(hal->dev, data, len); + portEXIT_CRITICAL_ISR(&i2c_slave->base->spinlock); + BaseType_t res = xRingbufferSendFromISR(i2c_slave->rx_ring_buf, (void *)data, len, &xTaskWoken); + if (res == pdTRUE) { + i2c_slave->rx_data_count += len; + } + } + xSemaphoreTakeFromISR(i2c_slave->operation_mux, &xTaskWoken); + return xTaskWoken; +} + +#if SOC_I2C_SLAVE_CAN_GET_STRETCH_CAUSE +IRAM_ATTR static bool i2c_slave_handle_stretch_event(i2c_slave_dev_t *i2c_slave, uint32_t rx_fifo_exist_len) +{ + i2c_slave_stretch_cause_t cause; + BaseType_t xTaskWoken = pdFALSE; + i2c_hal_context_t *hal = &i2c_slave->base->hal; + i2c_ll_slave_get_stretch_cause(hal->dev, &cause); + if (cause == I2C_SLAVE_STRETCH_CAUSE_ADDRESS_MATCH) { + if (rx_fifo_exist_len) { + xTaskWoken |= i2c_slave_handle_rx_fifo(i2c_slave, rx_fifo_exist_len); + } + if (i2c_slave->rx_data_count) { + uint32_t len = i2c_slave->rx_data_count; + size_t read_len; + xTaskWoken |= i2c_slave_read_rx(i2c_slave, i2c_slave->receive_desc.buffer, len, &read_len); + i2c_slave_rx_done_event_data_t edata = {}; + edata.buffer = i2c_slave->receive_desc.buffer; + edata.length = read_len; + if (i2c_slave->receive_callback) { + xTaskWoken |= i2c_slave->receive_callback(i2c_slave, &edata, i2c_slave->user_ctx); + } + i2c_slave->rx_data_count = 0; + } + i2c_slave_request_event_data_t evt_data = {}; + if (i2c_slave->request_callback) { + xTaskWoken |= i2c_slave->request_callback(i2c_slave, &evt_data, i2c_slave->user_ctx); + } + //will clear after request callback + } else if (cause == I2C_SLAVE_STRETCH_CAUSE_TX_EMPTY) { + xTaskWoken |= i2c_slave_handle_tx_fifo(i2c_slave); + i2c_ll_slave_clear_stretch(hal->dev); + } else if (cause == I2C_SLAVE_STRETCH_CAUSE_RX_FULL) { + xTaskWoken |= i2c_slave_handle_rx_fifo(i2c_slave, rx_fifo_exist_len); + i2c_ll_slave_clear_stretch(hal->dev); + } + return xTaskWoken; +} +#endif + +IRAM_ATTR static void i2c_slave_isr_handler(void *arg) +{ + BaseType_t pxHigherPriorityTaskWoken = false; + i2c_slave_dev_t *i2c_slave = (i2c_slave_dev_t *)arg; + + i2c_hal_context_t *hal = &i2c_slave->base->hal; + uint32_t int_mask = 0; + i2c_ll_get_intr_mask(hal->dev, &int_mask); + i2c_ll_clear_intr_mask(hal->dev, int_mask); + uint32_t rx_fifo_exist_len = 0; + i2c_ll_get_rxfifo_cnt(hal->dev, &rx_fifo_exist_len); + i2c_slave_read_write_status_t slave_rw = i2c_ll_slave_get_read_write_status(hal->dev); + + if (int_mask & I2C_INTR_SLV_RXFIFO_WM) { + pxHigherPriorityTaskWoken |= i2c_slave_handle_rx_fifo(i2c_slave, rx_fifo_exist_len); + } + + if (int_mask & I2C_INTR_SLV_COMPLETE) { + if (rx_fifo_exist_len) { + pxHigherPriorityTaskWoken |= i2c_slave_handle_rx_fifo(i2c_slave, rx_fifo_exist_len); + } + if (i2c_slave->rx_data_count) { + uint32_t len = i2c_slave->rx_data_count; + size_t read_len; + pxHigherPriorityTaskWoken |= i2c_slave_read_rx(i2c_slave, i2c_slave->receive_desc.buffer, len, &read_len); + i2c_slave_rx_done_event_data_t edata = {}; + edata.buffer = i2c_slave->receive_desc.buffer; + edata.length = read_len; + if (i2c_slave->receive_callback) { + pxHigherPriorityTaskWoken |= i2c_slave->receive_callback(i2c_slave, &edata, i2c_slave->user_ctx); + } + i2c_slave->rx_data_count = 0; + } + if (slave_rw == I2C_SLAVE_READ_BY_MASTER) { +#if !SOC_I2C_SLAVE_CAN_GET_STRETCH_CAUSE + i2c_slave_request_event_data_t evt_data = {}; + if (i2c_slave->request_callback) { + pxHigherPriorityTaskWoken |= i2c_slave->request_callback(i2c_slave, &evt_data, i2c_slave->user_ctx); + } +#endif + } + } + +#if SOC_I2C_SLAVE_CAN_GET_STRETCH_CAUSE + if (int_mask & I2C_INTR_STRETCH) { // STRETCH + pxHigherPriorityTaskWoken |= i2c_slave_handle_stretch_event(i2c_slave, rx_fifo_exist_len); + } +#endif + + if (int_mask & I2C_INTR_SLV_TXFIFO_WM) { // TX FiFo Empty + pxHigherPriorityTaskWoken |= i2c_slave_handle_tx_fifo(i2c_slave); + } + + if (pxHigherPriorityTaskWoken) { + portYIELD_FROM_ISR(); + } +} + +static esp_err_t i2c_slave_device_destroy(i2c_slave_dev_handle_t i2c_slave) +{ + i2c_ll_disable_intr_mask(i2c_slave->base->hal.dev, I2C_LL_SLAVE_EVENT_INTR); + if (i2c_slave->rx_ring_buf) { + vRingbufferDeleteWithCaps(i2c_slave->rx_ring_buf); + i2c_slave->rx_ring_buf = NULL; + } + if (i2c_slave->tx_ring_buf) { + vRingbufferDeleteWithCaps(i2c_slave->tx_ring_buf); + i2c_slave->tx_ring_buf = NULL; + } + if (i2c_slave->operation_mux) { + vSemaphoreDeleteWithCaps(i2c_slave->operation_mux); + i2c_slave->operation_mux = NULL; + } + if (i2c_slave->receive_desc.buffer) { + free(i2c_slave->receive_desc.buffer); + } + esp_err_t ret = i2c_release_bus_handle(i2c_slave->base); + + free(i2c_slave); + return ret; +} + +esp_err_t i2c_new_slave_device(const i2c_slave_config_t *slave_config, i2c_slave_dev_handle_t *ret_handle) +{ +#if CONFIG_I2C_ENABLE_DEBUG_LOG + esp_log_level_set(TAG, ESP_LOG_DEBUG); +#endif + esp_err_t ret = ESP_OK; + i2c_slave_dev_t *i2c_slave = NULL; + ESP_RETURN_ON_FALSE(slave_config && ret_handle, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + ESP_RETURN_ON_FALSE(GPIO_IS_VALID_GPIO(slave_config->sda_io_num) && GPIO_IS_VALID_GPIO(slave_config->scl_io_num), ESP_ERR_INVALID_ARG, TAG, "invalid SDA/SCL pin number"); +#if SOC_LP_I2C_SUPPORTED + ESP_RETURN_ON_FALSE(slave_config->i2c_port != (SOC_I2C_NUM - 1), ESP_ERR_NOT_SUPPORTED, TAG, "LP i2c is not supported in I2C slave"); +#endif + ESP_RETURN_ON_FALSE(slave_config->i2c_port < SOC_HP_I2C_NUM || slave_config->i2c_port == -1, ESP_ERR_INVALID_ARG, TAG, "invalid i2c port number"); +#if SOC_I2C_SLAVE_SUPPORT_BROADCAST + ESP_RETURN_ON_FALSE(((slave_config->addr_bit_len != I2C_ADDR_BIT_LEN_10) || (!slave_config->flags.broadcast_en)), ESP_ERR_INVALID_STATE, TAG, "10bits address cannot used together with broadcast"); +#endif + + i2c_slave = heap_caps_calloc(1, sizeof(i2c_slave_dev_t), I2C_MEM_ALLOC_CAPS); + ESP_RETURN_ON_FALSE(i2c_slave, ESP_ERR_NO_MEM, TAG, "no memory for i2c slave bus"); + + ESP_GOTO_ON_ERROR(i2c_acquire_bus_handle(slave_config->i2c_port, &i2c_slave->base, I2C_BUS_MODE_SLAVE), err, TAG, "I2C bus acquire failed"); + + i2c_hal_context_t *hal = &i2c_slave->base->hal; + i2c_slave->base->scl_num = slave_config->scl_io_num; + i2c_slave->base->sda_num = slave_config->sda_io_num; + i2c_slave->base->pull_up_enable = slave_config->flags.enable_internal_pullup; + i2c_slave->rx_data_count = 0; + int i2c_port_num = slave_config->i2c_port; + ESP_GOTO_ON_ERROR(i2c_common_set_pins(i2c_slave->base), err, TAG, "i2c slave set pins failed"); + + i2c_slave->rx_ring_buf = xRingbufferCreateWithCaps(slave_config->receive_buf_depth, RINGBUF_TYPE_BYTEBUF, I2C_MEM_ALLOC_CAPS); + ESP_GOTO_ON_FALSE(i2c_slave->rx_ring_buf != NULL, ESP_ERR_INVALID_STATE, err, TAG, "ringbuffer create failed"); + + i2c_slave->operation_mux = xSemaphoreCreateBinaryWithCaps(I2C_MEM_ALLOC_CAPS); + ESP_GOTO_ON_FALSE(i2c_slave->operation_mux, ESP_ERR_NO_MEM, err, TAG, "No memory for binary semaphore"); + xSemaphoreGive(i2c_slave->operation_mux); + + uint8_t *rcv_buffer = heap_caps_calloc(1, slave_config->receive_buf_depth, I2C_MEM_ALLOC_CAPS); + ESP_RETURN_ON_FALSE(rcv_buffer, ESP_ERR_NO_MEM, TAG, "no memory for i2c slave receive internal buffer"); + + i2c_slave->receive_desc.buffer = rcv_buffer; + i2c_slave->receive_desc.rcv_fifo_cnt = slave_config->receive_buf_depth; + + i2c_slave->tx_ring_buf = xRingbufferCreateWithCaps(slave_config->send_buf_depth, RINGBUF_TYPE_BYTEBUF, I2C_MEM_ALLOC_CAPS); + ESP_RETURN_ON_FALSE(i2c_slave->tx_ring_buf, ESP_ERR_NO_MEM, TAG, "no memory for i2c slave transmit ringbuffer"); + +#if I2C_USE_RETENTION_LINK + if (slave_config->flags.allow_pd != 0) { + i2c_create_retention_module(i2c_slave->base); + } +#endif // I2C_USE_RETENTION_LINK + + int isr_flags = I2C_INTR_ALLOC_FLAG; + if (slave_config->intr_priority) { + isr_flags |= 1 << (slave_config->intr_priority); + } + ret = esp_intr_alloc_intrstatus(i2c_periph_signal[i2c_port_num].irq, isr_flags, (uint32_t)i2c_ll_get_interrupt_status_reg(hal->dev), I2C_LL_SLAVE_EVENT_INTR, i2c_slave_isr_handler, i2c_slave, &i2c_slave->base->intr_handle); + ESP_GOTO_ON_ERROR(ret, err, TAG, "install i2c slave interrupt failed"); + + portENTER_CRITICAL(&i2c_slave->base->spinlock); + i2c_hal_slave_init(hal); + i2c_ll_slave_set_fifo_mode(hal->dev, true); + i2c_ll_set_slave_addr(hal->dev, slave_config->slave_addr, false); + i2c_ll_set_tout(hal->dev, I2C_LL_MAX_TIMEOUT); + + I2C_CLOCK_SRC_ATOMIC() { + i2c_ll_set_source_clk(hal->dev, slave_config->clk_source); + } + bool addr_10bit_en = slave_config->addr_bit_len != I2C_ADDR_BIT_LEN_7; + i2c_ll_set_slave_addr(hal->dev, slave_config->slave_addr, addr_10bit_en); + +#if SOC_I2C_SLAVE_SUPPORT_BROADCAST + i2c_ll_slave_broadcast_enable(hal->dev, slave_config->flags.broadcast_en); +#endif + + i2c_ll_set_txfifo_empty_thr(hal->dev, SOC_I2C_FIFO_LEN / 2); + i2c_ll_set_rxfifo_full_thr(hal->dev, SOC_I2C_FIFO_LEN / 2); + i2c_ll_set_sda_timing(hal->dev, 10, 10); + + i2c_ll_disable_intr_mask(hal->dev, I2C_LL_INTR_MASK); + i2c_ll_clear_intr_mask(hal->dev, I2C_LL_INTR_MASK); + + i2c_ll_enable_intr_mask(hal->dev, I2C_LL_SLAVE_RX_EVENT_INTR); + + // Configure stretch + i2c_ll_slave_set_stretch_protect_num(hal->dev, I2C_LL_STRETCH_PROTECT_TIME); + i2c_ll_slave_enable_scl_stretch(hal->dev, true); + i2c_ll_slave_clear_stretch(hal->dev); + + i2c_ll_update(hal->dev); + portEXIT_CRITICAL(&i2c_slave->base->spinlock); + + *ret_handle = i2c_slave; + return ret; + +err: + if (i2c_slave) { + i2c_slave_device_destroy(i2c_slave); + } + return ret; +} + +esp_err_t i2c_del_slave_device(i2c_slave_dev_handle_t i2c_slave) +{ + ESP_RETURN_ON_FALSE(i2c_slave, ESP_ERR_INVALID_ARG, TAG, "i2c slave not initialized"); + int port_id = i2c_slave->base->port_num; + ESP_LOGD(TAG, "del i2c bus(%d)", port_id); + ESP_RETURN_ON_ERROR(i2c_slave_device_destroy(i2c_slave), TAG, "destroy i2c bus failed"); + return ESP_OK; +} + +esp_err_t i2c_slave_write(i2c_slave_dev_handle_t i2c_slave, const uint8_t *data, uint32_t len, uint32_t *write_len, int timeout_ms) +{ + ESP_RETURN_ON_FALSE(i2c_slave, ESP_ERR_INVALID_ARG, TAG, "i2c slave not initialized"); + ESP_RETURN_ON_FALSE(data, ESP_ERR_INVALID_ARG, TAG, "invalid data buffer"); + ESP_RETURN_ON_FALSE(write_len, ESP_ERR_INVALID_ARG, TAG, "invalid write length pointer"); + uint32_t free_fifo_len = 0; + uint32_t write_ringbuffer_len = 0; + uint32_t actual_write_fifo_size = 0; + uint8_t *existing_data = NULL; + size_t existing_size = 0; + i2c_hal_context_t *hal = &i2c_slave->base->hal; + TickType_t wait_ticks = (timeout_ms == -1) ? portMAX_DELAY : pdMS_TO_TICKS(timeout_ms); + + xSemaphoreTake(i2c_slave->operation_mux, wait_ticks); + + portENTER_CRITICAL(&i2c_slave->base->spinlock); +#if !SOC_I2C_SLAVE_CAN_GET_STRETCH_CAUSE + i2c_ll_slave_disable_tx_it(hal->dev); + uint32_t txfifo_len = 0; + i2c_ll_get_txfifo_len(hal->dev, &txfifo_len); + if (txfifo_len < SOC_I2C_FIFO_LEN) { + // For the target (esp32) cannot stretch, reset the fifo when there is any dirty data in fifo. + i2c_ll_txfifo_rst(hal->dev); + } +#endif + i2c_ll_get_txfifo_len(hal->dev, &free_fifo_len); + portEXIT_CRITICAL(&i2c_slave->base->spinlock); + + // Check if there is any data in the ringbuffer in last transaction + existing_data = xRingbufferReceiveUpTo(i2c_slave->tx_ring_buf, &existing_size, 0, free_fifo_len); + if (existing_data) { + // has data, fill to the fifo + i2c_ll_write_txfifo(hal->dev, existing_data, existing_size); + free_fifo_len -= existing_size; + vRingbufferReturnItem(i2c_slave->tx_ring_buf, existing_data); + } + + // Write data. + if (free_fifo_len) { + portENTER_CRITICAL(&i2c_slave->base->spinlock); + if (len < free_fifo_len) { + actual_write_fifo_size = len; + } + i2c_ll_write_txfifo(hal->dev, (uint8_t *)data, actual_write_fifo_size); + data += actual_write_fifo_size; + len -= actual_write_fifo_size; + portEXIT_CRITICAL(&i2c_slave->base->spinlock); + //write the rest of the bytes to the ringbuffer + } + + if (len) { + write_ringbuffer_len = xRingbufferGetCurFreeSize(i2c_slave->tx_ring_buf); + if (len < write_ringbuffer_len) { + write_ringbuffer_len = len; + } + + if (xRingbufferSend(i2c_slave->tx_ring_buf, data, write_ringbuffer_len, wait_ticks) != pdTRUE) { + write_ringbuffer_len = 0; + } + } + + *write_len = write_ringbuffer_len + actual_write_fifo_size; + i2c_ll_slave_enable_tx_it(hal->dev); + i2c_ll_slave_clear_stretch(hal->dev); + + xSemaphoreGive(i2c_slave->operation_mux); + + return ESP_OK; +} + +esp_err_t i2c_slave_register_event_callbacks(i2c_slave_dev_handle_t i2c_slave, const i2c_slave_event_callbacks_t *cbs, void *user_data) +{ + ESP_RETURN_ON_FALSE(i2c_slave != NULL, ESP_ERR_INVALID_ARG, TAG, "i2c slave handle not initialized"); + ESP_RETURN_ON_FALSE(cbs, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); +#if CONFIG_I2C_ISR_IRAM_SAFE + if (cbs->on_request) { + ESP_RETURN_ON_FALSE(esp_ptr_in_iram(cbs->on_request), ESP_ERR_INVALID_ARG, TAG, "i2c request occur callback not in IRAM"); + } + if (cbs->on_receive) { + ESP_RETURN_ON_FALSE(esp_ptr_in_iram(cbs->on_receive), ESP_ERR_INVALID_ARG, TAG, "i2c receive occur callback not in IRAM"); + } + if (user_data) { + ESP_RETURN_ON_FALSE(esp_ptr_internal(user_data), ESP_ERR_INVALID_ARG, TAG, "user context not in internal RAM"); + } +#endif + + i2c_slave->user_ctx = user_data; + i2c_slave->request_callback = cbs->on_request; + i2c_slave->receive_callback = cbs->on_receive; + return ESP_OK; +} diff --git a/components/esp_driver_i2c/include/driver/i2c_slave.h b/components/esp_driver_i2c/include/driver/i2c_slave.h index 5a443a174c..094f0e9094 100644 --- a/components/esp_driver_i2c/include/driver/i2c_slave.h +++ b/components/esp_driver_i2c/include/driver/i2c_slave.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -10,11 +10,14 @@ #include "driver/i2c_types.h" #include "hal/gpio_types.h" #include "soc/soc_caps.h" +#include "sdkconfig.h" #ifdef __cplusplus extern "C" { #endif +#if !CONFIG_I2C_ENABLE_SLAVE_DRIVER_VERSION_2 + /** * @brief I2C slave specific configurations */ @@ -59,28 +62,6 @@ typedef struct { i2c_slave_received_callback_t on_recv_done; /*!< I2C slave receive done callback */ } i2c_slave_event_callbacks_t; -/** - * @brief Initialize an I2C slave device - * - * @param[in] slave_config I2C slave device configurations - * @param[out] ret_handle Return a generic I2C device handle - * @return - * - ESP_OK: I2C slave device initialized successfully - * - ESP_ERR_INVALID_ARG: I2C device initialization failed because of invalid argument. - * - ESP_ERR_NO_MEM: Create I2C device failed because of out of memory. - */ -esp_err_t i2c_new_slave_device(const i2c_slave_config_t *slave_config, i2c_slave_dev_handle_t *ret_handle); - -/** - * @brief Deinitialize the I2C slave device - * - * @param[in] i2c_slave I2C slave device handle that created by `i2c_new_slave_device`. - * @return - * - ESP_OK: Delete I2C device successfully. - * - ESP_ERR_INVALID_ARG: I2C device initialization failed because of invalid argument. - */ -esp_err_t i2c_del_slave_device(i2c_slave_dev_handle_t i2c_slave); - /** * @brief Read bytes from I2C internal buffer. Start a job to receive I2C data. * @@ -116,23 +97,6 @@ esp_err_t i2c_slave_receive(i2c_slave_dev_handle_t i2c_slave, uint8_t *data, siz */ esp_err_t i2c_slave_transmit(i2c_slave_dev_handle_t i2c_slave, const uint8_t *data, int size, int xfer_timeout_ms); -/** - * @brief Set I2C slave event callbacks for I2C slave channel. - * - * @note User can deregister a previously registered callback by calling this function and setting the callback member in the `cbs` structure to NULL. - * @note When CONFIG_I2C_ISR_IRAM_SAFE is enabled, the callback itself and functions called by it should be placed in IRAM. - * The variables used in the function should be in the SRAM as well. The `user_data` should also reside in SRAM. - * - * @param[in] i2c_slave I2C slave device handle that created by `i2c_new_slave_device`. - * @param[in] cbs Group of callback functions - * @param[in] user_data User data, which will be passed to callback functions directly - * @return - * - ESP_OK: Set I2C transaction callbacks successfully - * - ESP_ERR_INVALID_ARG: Set I2C transaction callbacks failed because of invalid argument - * - ESP_FAIL: Set I2C transaction callbacks failed because of other error - */ -esp_err_t i2c_slave_register_event_callbacks(i2c_slave_dev_handle_t i2c_slave, const i2c_slave_event_callbacks_t *cbs, void *user_data); - #if SOC_I2C_SLAVE_SUPPORT_I2CRAM_ACCESS /** * @brief Read bytes from I2C internal ram. This can be only used when `access_ram_en` in configuration structure set to true. @@ -164,6 +128,103 @@ esp_err_t i2c_slave_read_ram(i2c_slave_dev_handle_t i2c_slave, uint8_t ram_addre esp_err_t i2c_slave_write_ram(i2c_slave_dev_handle_t i2c_slave, uint8_t ram_address, const uint8_t *data, size_t size); #endif + +#elif CONFIG_I2C_ENABLE_SLAVE_DRIVER_VERSION_2 || __DOXYGEN__ +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////// I2C SLAVE VERSION TWO ///////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * @brief I2C slave specific configurations + */ +typedef struct { + i2c_port_num_t i2c_port; /*!< I2C port number, `-1` for auto selecting */ + gpio_num_t sda_io_num; /*!< SDA IO number used by I2C bus */ + gpio_num_t scl_io_num; /*!< SCL IO number used by I2C bus */ + i2c_clock_source_t clk_source; /*!< Clock source of I2C bus. */ + uint32_t send_buf_depth; /*!< Depth of internal transfer ringbuffer */ + uint32_t receive_buf_depth; /*!< Depth of receive internal software buffer */ + uint16_t slave_addr; /*!< I2C slave address */ + i2c_addr_bit_len_t addr_bit_len; /*!< I2C slave address in bit length */ + int intr_priority; /*!< I2C interrupt priority, if set to 0, driver will select the default priority (1,2,3). */ + struct { + uint32_t allow_pd: 1; /*!< If set, the driver will backup/restore the I2C registers before/after entering/exist sleep mode. + By this approach, the system can power off I2C's power domain. + This can save power, but at the expense of more RAM being consumed */ + uint32_t enable_internal_pullup: 1; /*!< Enable internal pullups. Note: This is not strong enough to pullup buses under high-speed frequency. Recommend proper external pull-up if possible */ +#if SOC_I2C_SLAVE_SUPPORT_BROADCAST + uint32_t broadcast_en: 1; /*!< I2C slave enable broadcast, able to respond to broadcast address */ +#endif + } flags; /*!< I2C slave config flags */ +} i2c_slave_config_t; + +/** + * @brief Group of I2C slave callbacks. Take care of potential concurrency issues. + * @note The callbacks are all running under ISR context + * @note When CONFIG_I2C_ISR_IRAM_SAFE is enabled, the callback itself and functions called by it should be placed in IRAM. + * The variables used in the function should be in the SRAM as well. + */ +typedef struct { + i2c_slave_request_callback_t on_request; /*!< Callback for when a master requests data from the slave */ + i2c_slave_received_callback_t on_receive; /*!< Callback for when the slave receives data from the master */ +} i2c_slave_event_callbacks_t; + +/** + * @brief Write buffer to hardware fifo. If write length is larger than hardware fifo, then restore in software buffer. + * + * @param[in] i2c_slave I2C slave device handle that created by `i2c_new_slave_device`. + * @param[in] data Buffer to write to slave fifo, can pickup by master. + * @param[in] len In bytes, of `data` buffer. + * @param[out] write_len In bytes, actually write length. + * @param[in] timeout_ms Wait timeout, in ms. Note: -1 means wait forever. + * @return + * - ESP_OK: I2C slave write success. + * - ESP_ERR_INVALID_ARG: I2C slave write parameter invalid. + * - ESP_ERR_TIMEOUT: Operation timeout(larger than xfer_timeout_ms) because the device is busy or hardware crash. + */ +esp_err_t i2c_slave_write(i2c_slave_dev_handle_t i2c_slave, const uint8_t *data, uint32_t len, uint32_t *write_len, int timeout_ms); + +#endif // CONFIG_I2C_ENABLE_SLAVE_DRIVER_VERSION_2 + +/** + * @brief Initialize an I2C slave device + * + * @param[in] slave_config I2C slave device configurations + * @param[out] ret_handle Return a generic I2C device handle + * @return + * - ESP_OK: I2C slave device initialized successfully + * - ESP_ERR_INVALID_ARG: I2C device initialization failed because of invalid argument. + * - ESP_ERR_NO_MEM: Create I2C device failed because of out of memory. + */ +esp_err_t i2c_new_slave_device(const i2c_slave_config_t *slave_config, i2c_slave_dev_handle_t *ret_handle); + +/** + * @brief Set I2C slave event callbacks for I2C slave channel. + * + * @note User can deregister a previously registered callback by calling this function and setting the callback member in the `cbs` structure to NULL. + * @note When CONFIG_I2C_ISR_IRAM_SAFE is enabled, the callback itself and functions called by it should be placed in IRAM. + * The variables used in the function should be in the SRAM as well. The `user_data` should also reside in SRAM. + * + * @param[in] i2c_slave I2C slave device handle that created by `i2c_new_slave_device`. + * @param[in] cbs Group of callback functions + * @param[in] user_data User data, which will be passed to callback functions directly + * @return + * - ESP_OK: Set I2C transaction callbacks successfully + * - ESP_ERR_INVALID_ARG: Set I2C transaction callbacks failed because of invalid argument + * - ESP_FAIL: Set I2C transaction callbacks failed because of other error + */ +esp_err_t i2c_slave_register_event_callbacks(i2c_slave_dev_handle_t i2c_slave, const i2c_slave_event_callbacks_t *cbs, void *user_data); + +/** + * @brief Deinitialize the I2C slave device + * + * @param[in] i2c_slave I2C slave device handle that created by `i2c_new_slave_device`. + * @return + * - ESP_OK: Delete I2C device successfully. + * - ESP_ERR_INVALID_ARG: I2C device initialization failed because of invalid argument. + */ +esp_err_t i2c_del_slave_device(i2c_slave_dev_handle_t i2c_slave); + #ifdef __cplusplus } #endif diff --git a/components/esp_driver_i2c/include/driver/i2c_types.h b/components/esp_driver_i2c/include/driver/i2c_types.h index 1dffb25cea..aa80a840af 100644 --- a/components/esp_driver_i2c/include/driver/i2c_types.h +++ b/components/esp_driver_i2c/include/driver/i2c_types.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -10,6 +10,7 @@ #include #include "hal/i2c_types.h" #include "soc/soc_caps.h" +#include "sdkconfig.h" #ifdef __cplusplus extern "C" { @@ -82,6 +83,9 @@ typedef bool (*i2c_master_callback_t)(i2c_master_dev_handle_t i2c_dev, const i2c */ typedef struct { uint8_t *buffer; /**< Pointer for buffer received in callback. */ +#if CONFIG_I2C_ENABLE_SLAVE_DRIVER_VERSION_2 + uint32_t length; /**< Length for buffer received in callback. */ +#endif } i2c_slave_rx_done_event_data_t; /** @@ -117,6 +121,25 @@ typedef bool (*i2c_slave_stretch_callback_t)(i2c_slave_dev_handle_t i2c_slave, c #endif +/** + * @brief Event structure used in I2C slave request. + */ +typedef struct { + +} i2c_slave_request_event_data_t; + +/** + * @brief Callback signature for I2C slave request event. When this callback is triggered that means master want to read data + * from slave while there is no data in slave fifo. So user should write data to fifo via `i2c_slave_write` + * + * @param[in] i2c_slave Handle for I2C slave. + * @param[out] evt_data I2C receive event data, fed by driver + * @param[in] arg User data, set in `i2c_slave_register_event_callbacks()` + * + * @return Whether a high priority task has been waken up by this function + */ +typedef bool (*i2c_slave_request_callback_t)(i2c_slave_dev_handle_t i2c_slave, const i2c_slave_request_event_data_t *evt_data, void *arg); + #ifdef __cplusplus } #endif diff --git a/components/hal/esp32/include/hal/i2c_ll.h b/components/hal/esp32/include/hal/i2c_ll.h index 7e5d352337..cc47434dee 100644 --- a/components/hal/esp32/include/hal/i2c_ll.h +++ b/components/hal/esp32/include/hal/i2c_ll.h @@ -67,11 +67,13 @@ typedef enum { // Get the I2C hardware instance #define I2C_LL_GET_HW(i2c_num) (((i2c_num) == 0) ? &I2C0 : &I2C1) #define I2C_LL_MASTER_EVENT_INTR (I2C_ACK_ERR_INT_ENA_M|I2C_TIME_OUT_INT_ENA_M|I2C_TRANS_COMPLETE_INT_ENA_M|I2C_ARBITRATION_LOST_INT_ENA_M|I2C_END_DETECT_INT_ENA_M) -#define I2C_LL_SLAVE_EVENT_INTR (I2C_TRANS_COMPLETE_INT_ENA_M|I2C_TXFIFO_EMPTY_INT_ENA_M|I2C_RX_REC_FULL_INT_ST_M) +#define I2C_LL_SLAVE_EVENT_INTR (I2C_TRANS_COMPLETE_INT_ENA_M|I2C_TXFIFO_EMPTY_INT_ENA_M|I2C_RX_REC_FULL_INT_ST_M|I2C_RXFIFO_FULL_INT_ENA) #define I2C_LL_SLAVE_RX_EVENT_INTR (I2C_TRANS_COMPLETE_INT_ENA_M|I2C_RX_REC_FULL_INT_ST_M) #define I2C_LL_SLAVE_TX_EVENT_INTR (I2C_TXFIFO_EMPTY_INT_ENA_M) #define I2C_LL_SCL_WAIT_US_VAL_DEFAULT (2000) // 2000 is not default value on esp32, but 0 is not good to be default +#define I2C_LL_STRETCH_PROTECT_TIME (0x3ff) // Not supported on esp32, keep consistent with other chips. + /** * @brief Calculate I2C bus frequency * @@ -808,6 +810,16 @@ static inline void i2c_ll_slave_clear_stretch(i2c_dev_t *dev) // Not supported on esp32 } +/** + * @brief Set I2C clock stretch protect num + * + * @param dev Beginning address of the peripheral registers + */ +static inline void i2c_ll_slave_set_stretch_protect_num(i2c_dev_t *dev, uint32_t protect_num) +{ + // Not supported on esp32 +} + /** * @brief Check if i2c command is done. * @@ -835,6 +847,18 @@ static inline uint32_t i2c_ll_calculate_timeout_us_to_reg_val(uint32_t src_clk_h return clk_cycle_num_per_us * timeout_us; } +/** + * @brief Get status of i2c slave + * + * @param Beginning address of the peripheral registers + * @return i2c slave working status + */ +__attribute__((always_inline)) +static inline i2c_slave_read_write_status_t i2c_ll_slave_get_read_write_status(i2c_dev_t *hw) +{ + return (hw->status_reg.slave_rw == 0) ? I2C_SLAVE_WRITE_BY_MASTER : I2C_SLAVE_READ_BY_MASTER; +} + //////////////////////////////////////////Deprecated Functions////////////////////////////////////////////////////////// /////////////////////////////The following functions are only used by the legacy driver///////////////////////////////// /////////////////////////////They might be removed in the next major release (ESP-IDF 6.0)////////////////////////////// diff --git a/components/hal/esp32c3/include/hal/i2c_ll.h b/components/hal/esp32c3/include/hal/i2c_ll.h index c74f591c01..326ab5471e 100644 --- a/components/hal/esp32c3/include/hal/i2c_ll.h +++ b/components/hal/esp32c3/include/hal/i2c_ll.h @@ -76,6 +76,8 @@ typedef enum { #define I2C_LL_RESET_SLV_SCL_PULSE_NUM_DEFAULT (9) #define I2C_LL_SCL_WAIT_US_VAL_DEFAULT (2000) // Approximate value for SCL timeout regs (in us). +#define I2C_LL_STRETCH_PROTECT_TIME (0x3ff) + /** * @brief Calculate I2C bus frequency * Note that the clock accuracy is affected by the external pull-up resistor, @@ -916,6 +918,16 @@ static inline void i2c_ll_slave_clear_stretch(i2c_dev_t *dev) dev->scl_stretch_conf.slave_scl_stretch_clr = 1; } +/** + * @brief Set I2C clock stretch protect num + * + * @param dev Beginning address of the peripheral registers + */ +static inline void i2c_ll_slave_set_stretch_protect_num(i2c_dev_t *dev, uint32_t protect_num) +{ + dev->scl_stretch_conf.stretch_protect_num = protect_num; +} + /** * @brief Check if i2c command is done. * @@ -944,6 +956,18 @@ static inline uint32_t i2c_ll_calculate_timeout_us_to_reg_val(uint32_t src_clk_h return 32 - __builtin_clz(clk_cycle_num_per_us * timeout_us); } +/** + * @brief Get status of i2c slave + * + * @param Beginning address of the peripheral registers + * @return i2c slave working status + */ +__attribute__((always_inline)) +static inline i2c_slave_read_write_status_t i2c_ll_slave_get_read_write_status(i2c_dev_t *hw) +{ + return (hw->sr.slave_rw == 0) ? I2C_SLAVE_WRITE_BY_MASTER : I2C_SLAVE_READ_BY_MASTER; +} + //////////////////////////////////////////Deprecated Functions////////////////////////////////////////////////////////// /////////////////////////////The following functions are only used by the legacy driver///////////////////////////////// /////////////////////////////They might be removed in the next major release (ESP-IDF 6.0)////////////////////////////// diff --git a/components/hal/esp32c5/include/hal/i2c_ll.h b/components/hal/esp32c5/include/hal/i2c_ll.h index ba2dd6610c..1d86684dbe 100644 --- a/components/hal/esp32c5/include/hal/i2c_ll.h +++ b/components/hal/esp32c5/include/hal/i2c_ll.h @@ -78,6 +78,8 @@ typedef enum { #define I2C_LL_RESET_SLV_SCL_PULSE_NUM_DEFAULT (9) #define I2C_LL_SCL_WAIT_US_VAL_DEFAULT (2500) // Approximate value for SCL timeout regs (in us). +#define I2C_LL_STRETCH_PROTECT_TIME (0x3ff) + // Record for Pins usage logs #define LP_I2C_SCL_PIN_ERR_LOG "SCL pin can only be configured as GPIO#7" @@ -954,6 +956,16 @@ static inline void i2c_ll_slave_clear_stretch(i2c_dev_t *dev) dev->scl_stretch_conf.slave_scl_stretch_clr = 1; } +/** + * @brief Set I2C clock stretch protect num + * + * @param dev Beginning address of the peripheral registers + */ +static inline void i2c_ll_slave_set_stretch_protect_num(i2c_dev_t *dev, uint32_t protect_num) +{ + dev->scl_stretch_conf.stretch_protect_num = protect_num; +} + /** * @brief Check if i2c command is done. * @@ -982,6 +994,18 @@ static inline uint32_t i2c_ll_calculate_timeout_us_to_reg_val(uint32_t src_clk_h return 32 - __builtin_clz(clk_cycle_num_per_us * timeout_us); } +/** + * @brief Get status of i2c slave + * + * @param Beginning address of the peripheral registers + * @return i2c slave working status + */ +__attribute__((always_inline)) +static inline i2c_slave_read_write_status_t i2c_ll_slave_get_read_write_status(i2c_dev_t *hw) +{ + return (hw->sr.slave_rw == 0) ? I2C_SLAVE_WRITE_BY_MASTER : I2C_SLAVE_READ_BY_MASTER; +} + //////////////////////////////////////////Deprecated Functions////////////////////////////////////////////////////////// /////////////////////////////The following functions are only used by the legacy driver///////////////////////////////// /////////////////////////////They might be removed in the next major release (ESP-IDF 6.0)////////////////////////////// diff --git a/components/hal/esp32c6/include/hal/i2c_ll.h b/components/hal/esp32c6/include/hal/i2c_ll.h index c50ad9c136..393909065f 100644 --- a/components/hal/esp32c6/include/hal/i2c_ll.h +++ b/components/hal/esp32c6/include/hal/i2c_ll.h @@ -77,6 +77,8 @@ typedef enum { #define I2C_LL_RESET_SLV_SCL_PULSE_NUM_DEFAULT (9) #define I2C_LL_SCL_WAIT_US_VAL_DEFAULT (2000) // Approximate value for SCL timeout regs (in us). +#define I2C_LL_STRETCH_PROTECT_TIME (0x3ff) + // Record for Pins usage logs #define LP_I2C_SCL_PIN_ERR_LOG "SCL pin can only be configured as GPIO#7" @@ -956,6 +958,16 @@ static inline void i2c_ll_slave_clear_stretch(i2c_dev_t *dev) dev->scl_stretch_conf.slave_scl_stretch_clr = 1; } +/** + * @brief Set I2C clock stretch protect num + * + * @param dev Beginning address of the peripheral registers + */ +static inline void i2c_ll_slave_set_stretch_protect_num(i2c_dev_t *dev, uint32_t protect_num) +{ + dev->scl_stretch_conf.stretch_protect_num = protect_num; +} + /** * @brief Check if i2c command is done. * @@ -984,6 +996,18 @@ static inline uint32_t i2c_ll_calculate_timeout_us_to_reg_val(uint32_t src_clk_h return 32 - __builtin_clz(clk_cycle_num_per_us * timeout_us); } +/** + * @brief Get status of i2c slave + * + * @param Beginning address of the peripheral registers + * @return i2c slave working status + */ +__attribute__((always_inline)) +static inline i2c_slave_read_write_status_t i2c_ll_slave_get_read_write_status(i2c_dev_t *hw) +{ + return (hw->sr.slave_rw == 0) ? I2C_SLAVE_WRITE_BY_MASTER : I2C_SLAVE_READ_BY_MASTER; +} + //////////////////////////////////////////Deprecated Functions////////////////////////////////////////////////////////// /////////////////////////////The following functions are only used by the legacy driver///////////////////////////////// /////////////////////////////They might be removed in the next major release (ESP-IDF 6.0)////////////////////////////// diff --git a/components/hal/esp32c61/include/hal/i2c_ll.h b/components/hal/esp32c61/include/hal/i2c_ll.h index c5dc44348d..7393fe8343 100644 --- a/components/hal/esp32c61/include/hal/i2c_ll.h +++ b/components/hal/esp32c61/include/hal/i2c_ll.h @@ -78,6 +78,8 @@ typedef enum { #define I2C_LL_RESET_SLV_SCL_PULSE_NUM_DEFAULT (9) #define I2C_LL_SCL_WAIT_US_VAL_DEFAULT (2500) // Approximate value for SCL timeout regs (in us). +#define I2C_LL_STRETCH_PROTECT_TIME (0x3ff) + /** * @brief Calculate I2C bus frequency * Note that the clock accuracy is affected by the external pull-up resistor, @@ -880,6 +882,16 @@ static inline void i2c_ll_slave_clear_stretch(i2c_dev_t *dev) dev->scl_stretch_conf.slave_scl_stretch_clr = 1; } +/** + * @brief Set I2C clock stretch protect num + * + * @param dev Beginning address of the peripheral registers + */ +static inline void i2c_ll_slave_set_stretch_protect_num(i2c_dev_t *dev, uint32_t protect_num) +{ + dev->scl_stretch_conf.stretch_protect_num = protect_num; +} + /** * @brief Check if i2c command is done. * @@ -908,6 +920,18 @@ static inline uint32_t i2c_ll_calculate_timeout_us_to_reg_val(uint32_t src_clk_h return 32 - __builtin_clz(clk_cycle_num_per_us * timeout_us); } +/** + * @brief Get status of i2c slave + * + * @param Beginning address of the peripheral registers + * @return i2c slave working status + */ +__attribute__((always_inline)) +static inline i2c_slave_read_write_status_t i2c_ll_slave_get_read_write_status(i2c_dev_t *hw) +{ + return (hw->sr.slave_rw == 0) ? I2C_SLAVE_WRITE_BY_MASTER : I2C_SLAVE_READ_BY_MASTER; +} + //////////////////////////////////////////Deprecated Functions////////////////////////////////////////////////////////// /////////////////////////////The following functions are only used by the legacy driver///////////////////////////////// /////////////////////////////They might be removed in the next major release (ESP-IDF 6.0)////////////////////////////// diff --git a/components/hal/esp32h2/include/hal/i2c_ll.h b/components/hal/esp32h2/include/hal/i2c_ll.h index 83d8816f73..3a625302ce 100644 --- a/components/hal/esp32h2/include/hal/i2c_ll.h +++ b/components/hal/esp32h2/include/hal/i2c_ll.h @@ -76,6 +76,8 @@ typedef enum { #define I2C_LL_RESET_SLV_SCL_PULSE_NUM_DEFAULT (9) #define I2C_LL_SCL_WAIT_US_VAL_DEFAULT (2500) // Approximate value for SCL timeout regs (in us). +#define I2C_LL_STRETCH_PROTECT_TIME (0x3ff) + /** * @brief Calculate I2C bus frequency * Note that the clock accuracy is affected by the external pull-up resistor, @@ -863,6 +865,16 @@ static inline void i2c_ll_slave_clear_stretch(i2c_dev_t *dev) dev->scl_stretch_conf.slave_scl_stretch_clr = 1; } +/** + * @brief Set I2C clock stretch protect num + * + * @param dev Beginning address of the peripheral registers + */ +static inline void i2c_ll_slave_set_stretch_protect_num(i2c_dev_t *dev, uint32_t protect_num) +{ + dev->scl_stretch_conf.stretch_protect_num = protect_num; +} + /** * @brief Check if i2c command is done. * @@ -891,6 +903,18 @@ static inline uint32_t i2c_ll_calculate_timeout_us_to_reg_val(uint32_t src_clk_h return 32 - __builtin_clz(clk_cycle_num_per_us * timeout_us); } +/** + * @brief Get status of i2c slave + * + * @param Beginning address of the peripheral registers + * @return i2c slave working status + */ +__attribute__((always_inline)) +static inline i2c_slave_read_write_status_t i2c_ll_slave_get_read_write_status(i2c_dev_t *hw) +{ + return (hw->sr.slave_rw == 0) ? I2C_SLAVE_WRITE_BY_MASTER : I2C_SLAVE_READ_BY_MASTER; +} + //////////////////////////////////////////Deprecated Functions////////////////////////////////////////////////////////// /////////////////////////////The following functions are only used by the legacy driver///////////////////////////////// /////////////////////////////They might be removed in the next major release (ESP-IDF 6.0)////////////////////////////// diff --git a/components/hal/esp32p4/include/hal/i2c_ll.h b/components/hal/esp32p4/include/hal/i2c_ll.h index 7471583274..1de981b347 100644 --- a/components/hal/esp32p4/include/hal/i2c_ll.h +++ b/components/hal/esp32p4/include/hal/i2c_ll.h @@ -82,6 +82,8 @@ typedef enum { #define I2C_LL_RESET_SLV_SCL_PULSE_NUM_DEFAULT (9) #define I2C_LL_SCL_WAIT_US_VAL_DEFAULT (2000) // Approximate value for SCL timeout regs (in us). +#define I2C_LL_STRETCH_PROTECT_TIME (0x3ff) + /** * @brief Calculate I2C bus frequency * Note that the clock accuracy is affected by the external pull-up resistor, @@ -353,6 +355,36 @@ static inline void i2c_ll_slave_broadcast_enable(i2c_dev_t *hw, bool broadcast_e hw->ctr.addr_broadcasting_en = broadcast_en; } +/** + * @brief Get the cause of SCL clock stretching in slave mode + * + * @param hw Beginning address of the peripheral registers + * @param stretch_cause Pointer to stretch cause in the slave mode. + * + * @return None + */ +__attribute__((always_inline)) +static inline void i2c_ll_slave_get_stretch_cause(i2c_dev_t *hw, i2c_slave_stretch_cause_t *stretch_cause) +{ + switch (hw->sr.stretch_cause) { + case 0: + *stretch_cause = I2C_SLAVE_STRETCH_CAUSE_ADDRESS_MATCH; + break; + case 1: + *stretch_cause = I2C_SLAVE_STRETCH_CAUSE_TX_EMPTY; + break; + case 2: + *stretch_cause = I2C_SLAVE_STRETCH_CAUSE_RX_FULL; + break; + case 3: + *stretch_cause = I2C_SLAVE_STRETCH_CAUSE_SENDING_ACK; + break; + default: + HAL_ASSERT(false); + break; + } +} + /** * @brief Configure I2C slave address * @@ -963,6 +995,16 @@ static inline void i2c_ll_slave_clear_stretch(i2c_dev_t *dev) dev->scl_stretch_conf.slave_scl_stretch_clr = 1; } +/** + * @brief Set I2C clock stretch protect num + * + * @param dev Beginning address of the peripheral registers + */ +static inline void i2c_ll_slave_set_stretch_protect_num(i2c_dev_t *dev, uint32_t protect_num) +{ + dev->scl_stretch_conf.stretch_protect_num = protect_num; +} + /** * @brief Check if i2c command is done. * @@ -991,6 +1033,18 @@ static inline uint32_t i2c_ll_calculate_timeout_us_to_reg_val(uint32_t src_clk_h return 32 - __builtin_clz(clk_cycle_num_per_us * timeout_us); } +/** + * @brief Get status of i2c slave + * + * @param Beginning address of the peripheral registers + * @return i2c slave working status + */ +__attribute__((always_inline)) +static inline i2c_slave_read_write_status_t i2c_ll_slave_get_read_write_status(i2c_dev_t *hw) +{ + return (hw->sr.slave_rw == 0) ? I2C_SLAVE_WRITE_BY_MASTER : I2C_SLAVE_READ_BY_MASTER; +} + //////////////////////////////////////////Deprecated Functions////////////////////////////////////////////////////////// /////////////////////////////The following functions are only used by the legacy driver///////////////////////////////// /////////////////////////////They might be removed in the next major release (ESP-IDF 6.0)////////////////////////////// diff --git a/components/hal/esp32s2/include/hal/i2c_ll.h b/components/hal/esp32s2/include/hal/i2c_ll.h index 9391b32b27..840937e4e8 100644 --- a/components/hal/esp32s2/include/hal/i2c_ll.h +++ b/components/hal/esp32s2/include/hal/i2c_ll.h @@ -15,6 +15,7 @@ #include "hal/i2c_types.h" #include "esp_attr.h" #include "hal/misc.h" +#include "hal/assert.h" #ifdef __cplusplus extern "C" { @@ -72,6 +73,8 @@ typedef enum { #define I2C_LL_RESET_SLV_SCL_PULSE_NUM_DEFAULT (9) #define I2C_LL_SCL_WAIT_US_VAL_DEFAULT (2000) // 2000 is not default value on esp32s2, but 0 is not good to be default +#define I2C_LL_STRETCH_PROTECT_TIME (0x3ff) + /** * @brief Calculate I2C bus frequency * @@ -309,7 +312,23 @@ static inline void i2c_ll_slave_broadcast_enable(i2c_dev_t *hw, bool broadcast_e __attribute__((always_inline)) static inline void i2c_ll_slave_get_stretch_cause(i2c_dev_t *hw, i2c_slave_stretch_cause_t *stretch_cause) { - // Not supported on esp32s2 + switch (hw->status_reg.stretch_cause) { + case 0: + *stretch_cause = I2C_SLAVE_STRETCH_CAUSE_ADDRESS_MATCH; + break; + case 1: + *stretch_cause = I2C_SLAVE_STRETCH_CAUSE_TX_EMPTY; + break; + case 2: + *stretch_cause = I2C_SLAVE_STRETCH_CAUSE_RX_FULL; + break; + case 3: + *stretch_cause = I2C_SLAVE_STRETCH_CAUSE_SENDING_ACK; + break; + default: + HAL_ASSERT(false); + break; + } } /** @@ -858,6 +877,16 @@ static inline void i2c_ll_slave_clear_stretch(i2c_dev_t *dev) dev->scl_stretch_conf.slave_scl_stretch_clr = 1; } +/** + * @brief Set I2C clock stretch protect num + * + * @param dev Beginning address of the peripheral registers + */ +static inline void i2c_ll_slave_set_stretch_protect_num(i2c_dev_t *dev, uint32_t protect_num) +{ + dev->scl_stretch_conf.stretch_protect_num = protect_num; +} + /** * @brief Check if i2c command is done. * @@ -885,6 +914,18 @@ static inline uint32_t i2c_ll_calculate_timeout_us_to_reg_val(uint32_t src_clk_h return clk_cycle_num_per_us * timeout_us; } +/** + * @brief Get status of i2c slave + * + * @param Beginning address of the peripheral registers + * @return i2c slave working status + */ +__attribute__((always_inline)) +static inline i2c_slave_read_write_status_t i2c_ll_slave_get_read_write_status(i2c_dev_t *hw) +{ + return (hw->status_reg.slave_rw == 0) ? I2C_SLAVE_WRITE_BY_MASTER : I2C_SLAVE_READ_BY_MASTER; +} + //////////////////////////////////////////Deprecated Functions////////////////////////////////////////////////////////// /////////////////////////////The following functions are only used by the legacy driver///////////////////////////////// /////////////////////////////They might be removed in the next major release (ESP-IDF 6.0)////////////////////////////// diff --git a/components/hal/esp32s3/include/hal/i2c_ll.h b/components/hal/esp32s3/include/hal/i2c_ll.h index 50dfb6eaf7..9883b92488 100644 --- a/components/hal/esp32s3/include/hal/i2c_ll.h +++ b/components/hal/esp32s3/include/hal/i2c_ll.h @@ -76,6 +76,8 @@ typedef enum { #define I2C_LL_RESET_SLV_SCL_PULSE_NUM_DEFAULT (9) #define I2C_LL_SCL_WAIT_US_VAL_DEFAULT (2000) // Approximate value for SCL timeout regs (in us). +#define I2C_LL_STRETCH_PROTECT_TIME (0x3ff) + /** * @brief Calculate I2C bus frequency * Note that the clock accuracy is affected by the external pull-up resistor, @@ -920,6 +922,16 @@ static inline void i2c_ll_slave_clear_stretch(i2c_dev_t *dev) dev->scl_stretch_conf.slave_scl_stretch_clr = 1; } +/** + * @brief Set I2C clock stretch protect num + * + * @param dev Beginning address of the peripheral registers + */ +static inline void i2c_ll_slave_set_stretch_protect_num(i2c_dev_t *dev, uint32_t protect_num) +{ + dev->scl_stretch_conf.stretch_protect_num = protect_num; +} + /** * @brief Check if i2c command is done. * @@ -948,6 +960,18 @@ static inline uint32_t i2c_ll_calculate_timeout_us_to_reg_val(uint32_t src_clk_h return 32 - __builtin_clz(clk_cycle_num_per_us * timeout_us); } +/** + * @brief Get status of i2c slave + * + * @param Beginning address of the peripheral registers + * @return i2c slave working status + */ +__attribute__((always_inline)) +static inline i2c_slave_read_write_status_t i2c_ll_slave_get_read_write_status(i2c_dev_t *hw) +{ + return (hw->sr.slave_rw == 0) ? I2C_SLAVE_WRITE_BY_MASTER : I2C_SLAVE_READ_BY_MASTER; +} + //////////////////////////////////////////Deprecated Functions////////////////////////////////////////////////////////// /////////////////////////////The following functions are only used by the legacy driver///////////////////////////////// /////////////////////////////They might be removed in the next major release (ESP-IDF 6.0)////////////////////////////// diff --git a/components/hal/include/hal/i2c_types.h b/components/hal/include/hal/i2c_types.h index 6a0d685ac7..57cf5a6b9f 100644 --- a/components/hal/include/hal/i2c_types.h +++ b/components/hal/include/hal/i2c_types.h @@ -98,6 +98,11 @@ typedef enum { I2C_SLAVE_STRETCH_CAUSE_SENDING_ACK = 3, /*!< Stretching SCL low when slave sending ACK */ } i2c_slave_stretch_cause_t; +typedef enum { + I2C_SLAVE_WRITE_BY_MASTER = 0, + I2C_SLAVE_READ_BY_MASTER = 1, +} i2c_slave_read_write_status_t; + #if SOC_I2C_SUPPORTED /** * @brief I2C group clock source diff --git a/components/soc/esp32p4/include/soc/Kconfig.soc_caps.in b/components/soc/esp32p4/include/soc/Kconfig.soc_caps.in index deea07d8a7..cf207d4f9b 100644 --- a/components/soc/esp32p4/include/soc/Kconfig.soc_caps.in +++ b/components/soc/esp32p4/include/soc/Kconfig.soc_caps.in @@ -779,6 +779,10 @@ config SOC_I2C_SLAVE_SUPPORT_BROADCAST bool default y +config SOC_I2C_SLAVE_CAN_GET_STRETCH_CAUSE + bool + default y + config SOC_I2C_SLAVE_SUPPORT_I2CRAM_ACCESS bool default y diff --git a/components/soc/esp32p4/include/soc/soc_caps.h b/components/soc/esp32p4/include/soc/soc_caps.h index 41847de00d..3e9020c11e 100644 --- a/components/soc/esp32p4/include/soc/soc_caps.h +++ b/components/soc/esp32p4/include/soc/soc_caps.h @@ -303,6 +303,7 @@ #define SOC_I2C_SUPPORT_RTC (1) #define SOC_I2C_SUPPORT_10BIT_ADDR (1) #define SOC_I2C_SLAVE_SUPPORT_BROADCAST (1) +#define SOC_I2C_SLAVE_CAN_GET_STRETCH_CAUSE (1) #define SOC_I2C_SLAVE_SUPPORT_I2CRAM_ACCESS (1) #define SOC_I2C_SLAVE_SUPPORT_SLAVE_UNMATCH (1) diff --git a/components/soc/esp32s2/include/soc/Kconfig.soc_caps.in b/components/soc/esp32s2/include/soc/Kconfig.soc_caps.in index 686747c9f8..33623a9edb 100644 --- a/components/soc/esp32s2/include/soc/Kconfig.soc_caps.in +++ b/components/soc/esp32s2/include/soc/Kconfig.soc_caps.in @@ -451,6 +451,10 @@ config SOC_I2C_SUPPORT_SLAVE bool default y +config SOC_I2C_SLAVE_CAN_GET_STRETCH_CAUSE + bool + default y + config SOC_I2C_SUPPORT_HW_CLR_BUS bool default y diff --git a/components/soc/esp32s2/include/soc/soc_caps.h b/components/soc/esp32s2/include/soc/soc_caps.h index 3e50662f73..94811dd937 100644 --- a/components/soc/esp32s2/include/soc/soc_caps.h +++ b/components/soc/esp32s2/include/soc/soc_caps.h @@ -200,6 +200,7 @@ #define SOC_I2C_FIFO_LEN (32) /*!< I2C hardware FIFO depth */ #define SOC_I2C_CMD_REG_NUM (16) /*!< Number of I2C command registers */ #define SOC_I2C_SUPPORT_SLAVE (1) +#define SOC_I2C_SLAVE_CAN_GET_STRETCH_CAUSE (1) // FSM_RST only resets the FSM, not using it. So SOC_I2C_SUPPORT_HW_FSM_RST not defined. //ESP32-S2 support hardware clear bus diff --git a/components/soc/esp32s3/include/soc/Kconfig.soc_caps.in b/components/soc/esp32s3/include/soc/Kconfig.soc_caps.in index 0c228dc841..c93e95122b 100644 --- a/components/soc/esp32s3/include/soc/Kconfig.soc_caps.in +++ b/components/soc/esp32s3/include/soc/Kconfig.soc_caps.in @@ -551,6 +551,10 @@ config SOC_I2C_SLAVE_SUPPORT_I2CRAM_ACCESS bool default y +config SOC_I2C_SLAVE_CAN_GET_STRETCH_CAUSE + bool + default y + config SOC_I2S_NUM int default 2 diff --git a/components/soc/esp32s3/include/soc/soc_caps.h b/components/soc/esp32s3/include/soc/soc_caps.h index 6e8194fbf9..0a2c08e439 100644 --- a/components/soc/esp32s3/include/soc/soc_caps.h +++ b/components/soc/esp32s3/include/soc/soc_caps.h @@ -219,6 +219,7 @@ #define SOC_I2C_SUPPORT_10BIT_ADDR (1) #define SOC_I2C_SLAVE_SUPPORT_BROADCAST (1) #define SOC_I2C_SLAVE_SUPPORT_I2CRAM_ACCESS (1) +#define SOC_I2C_SLAVE_CAN_GET_STRETCH_CAUSE (1) /*-------------------------- I2S CAPS ----------------------------------------*/ #define SOC_I2S_NUM (2U)