From 4ef94fc0dc1f691fc4bfb2fa3b93dabe52eb5c9a Mon Sep 17 00:00:00 2001 From: Cao Sen Miao Date: Thu, 3 Aug 2023 12:29:44 +0800 Subject: [PATCH] feat(i2c): Add new API and implementation for I2C driver --- components/driver/CMakeLists.txt | 7 +- components/driver/Kconfig | 20 + components/driver/i2c/i2c.c | 44 +- components/driver/i2c/i2c_common.c | 230 ++++ components/driver/i2c/i2c_master.c | 1102 +++++++++++++++++ components/driver/i2c/i2c_private.h | 209 ++++ .../driver/i2c/include/driver/i2c_master.h | 213 ++++ .../driver/i2c/include/driver/i2c_types.h | 74 ++ components/driver/i2c/linker.lf | 8 + .../{i2c => i2c_test_apps}/CMakeLists.txt | 0 .../{i2c => i2c_test_apps}/README.md | 0 .../main/CMakeLists.txt | 0 .../main/test_app_main.c | 0 .../test_apps/i2c_test_apps/main/test_i2c.c | 131 ++ .../test_apps/i2c_test_apps/pytest_i2c.py | 19 + .../sdkconfig.ci.defaults | 0 .../i2c_test_apps/sdkconfig.ci.iram_safe | 6 + .../sdkconfig.ci.release | 0 .../{i2c => i2c_test_apps}/sdkconfig.defaults | 0 .../legacy_i2c_driver/CMakeLists.txt | 22 + .../test_apps/legacy_i2c_driver/README.md | 2 + .../legacy_i2c_driver/main/CMakeLists.txt | 6 + .../legacy_i2c_driver/main/test_app_main.c | 42 + .../main/test_i2c.c | 0 .../pytest_i2c_legacy.py} | 4 +- .../legacy_i2c_driver/sdkconfig.ci.defaults | 2 + .../sdkconfig.ci.iram_safe | 0 .../legacy_i2c_driver/sdkconfig.ci.release | 5 + .../legacy_i2c_driver/sdkconfig.defaults | 2 + components/hal/esp32/include/hal/i2c_ll.h | 213 ++-- components/hal/esp32c2/include/hal/i2c_ll.h | 176 +-- components/hal/esp32c3/include/hal/i2c_ll.h | 274 ++-- components/hal/esp32c6/include/hal/i2c_ll.h | 275 ++-- components/hal/esp32h2/include/hal/i2c_ll.h | 277 +++-- components/hal/esp32s2/include/hal/i2c_ll.h | 220 ++-- components/hal/esp32s3/include/hal/i2c_ll.h | 274 ++-- components/hal/i2c_hal.c | 18 +- components/hal/i2c_hal_iram.c | 5 + components/hal/include/hal/i2c_hal.h | 16 + components/hal/include/hal/i2c_types.h | 27 +- .../soc/esp32/include/soc/Kconfig.soc_caps.in | 4 + components/soc/esp32/include/soc/soc_caps.h | 3 + .../esp32c2/include/soc/Kconfig.soc_caps.in | 4 + components/soc/esp32c2/include/soc/soc_caps.h | 1 + .../esp32c3/include/soc/Kconfig.soc_caps.in | 4 + components/soc/esp32c3/include/soc/soc_caps.h | 1 + .../esp32c6/include/soc/Kconfig.soc_caps.in | 4 + components/soc/esp32c6/include/soc/soc_caps.h | 1 + .../esp32h2/include/soc/Kconfig.soc_caps.in | 4 + components/soc/esp32h2/include/soc/soc_caps.h | 1 + .../esp32s3/include/soc/Kconfig.soc_caps.in | 4 + components/soc/esp32s3/include/soc/soc_caps.h | 2 + 52 files changed, 3351 insertions(+), 605 deletions(-) create mode 100644 components/driver/i2c/i2c_common.c create mode 100644 components/driver/i2c/i2c_master.c create mode 100644 components/driver/i2c/i2c_private.h create mode 100644 components/driver/i2c/include/driver/i2c_master.h create mode 100644 components/driver/i2c/include/driver/i2c_types.h create mode 100644 components/driver/i2c/linker.lf rename components/driver/test_apps/{i2c => i2c_test_apps}/CMakeLists.txt (100%) rename components/driver/test_apps/{i2c => i2c_test_apps}/README.md (100%) rename components/driver/test_apps/{i2c => i2c_test_apps}/main/CMakeLists.txt (100%) rename components/driver/test_apps/{i2c => i2c_test_apps}/main/test_app_main.c (100%) create mode 100644 components/driver/test_apps/i2c_test_apps/main/test_i2c.c create mode 100644 components/driver/test_apps/i2c_test_apps/pytest_i2c.py rename components/driver/test_apps/{i2c => i2c_test_apps}/sdkconfig.ci.defaults (100%) create mode 100644 components/driver/test_apps/i2c_test_apps/sdkconfig.ci.iram_safe rename components/driver/test_apps/{i2c => i2c_test_apps}/sdkconfig.ci.release (100%) rename components/driver/test_apps/{i2c => i2c_test_apps}/sdkconfig.defaults (100%) create mode 100644 components/driver/test_apps/legacy_i2c_driver/CMakeLists.txt create mode 100644 components/driver/test_apps/legacy_i2c_driver/README.md create mode 100644 components/driver/test_apps/legacy_i2c_driver/main/CMakeLists.txt create mode 100644 components/driver/test_apps/legacy_i2c_driver/main/test_app_main.c rename components/driver/test_apps/{i2c => legacy_i2c_driver}/main/test_i2c.c (100%) rename components/driver/test_apps/{i2c/pytest_i2c.py => legacy_i2c_driver/pytest_i2c_legacy.py} (87%) create mode 100644 components/driver/test_apps/legacy_i2c_driver/sdkconfig.ci.defaults rename components/driver/test_apps/{i2c => legacy_i2c_driver}/sdkconfig.ci.iram_safe (100%) create mode 100644 components/driver/test_apps/legacy_i2c_driver/sdkconfig.ci.release create mode 100644 components/driver/test_apps/legacy_i2c_driver/sdkconfig.defaults diff --git a/components/driver/CMakeLists.txt b/components/driver/CMakeLists.txt index c76641075a..1e7d46b897 100644 --- a/components/driver/CMakeLists.txt +++ b/components/driver/CMakeLists.txt @@ -91,7 +91,10 @@ endif() # I2C related source files if(CONFIG_SOC_I2C_SUPPORTED) - list(APPEND srcs "i2c/i2c.c") + list(APPEND srcs "i2c/i2c.c" + "i2c/i2c_master.c" + "i2c/i2c_common.c" + ) endif() # I2S related source files @@ -216,7 +219,7 @@ else() INCLUDE_DIRS ${includes} PRIV_REQUIRES efuse esp_timer REQUIRES esp_pm esp_ringbuf freertos soc hal esp_hw_support - LDFRAGMENTS linker.lf gptimer/linker.lf gpio/linker.lf twai/linker.lf) + LDFRAGMENTS linker.lf gptimer/linker.lf gpio/linker.lf twai/linker.lf i2c/linker.lf) endif() # If system needs to monitor USJ connection status, then usb_serial_jtag_connection_monitor object file has to be linked diff --git a/components/driver/Kconfig b/components/driver/Kconfig index 7c68ec740f..a9a818c4de 100644 --- a/components/driver/Kconfig +++ b/components/driver/Kconfig @@ -486,4 +486,24 @@ menu "Driver Configurations" Enabling this option can improve driver performance as well. endmenu # LEDC Configuration + menu "I2C Configuration" + config I2C_ISR_IRAM_SAFE + bool "I2C ISR IRAM-Safe" + default n + help + Ensure the I2C interrupt is IRAM-Safe by allowing the interrupt handler to be + executable when the cache is disabled (e.g. SPI Flash write). + note: This cannot be used in the I2C legacy driver. + + config I2C_ENABLE_DEBUG_LOG + bool "Enable I2C debug log" + default n + help + Wether to enable the debug log message for I2C driver. + Note that this option only controls the I2C driver log, will not affect other drivers. + + note: This cannot be used in the I2C legacy driver. + + endmenu # I2C Configurations + endmenu # Driver configurations diff --git a/components/driver/i2c/i2c.c b/components/driver/i2c/i2c.c index 830cb55fe3..1d638babb7 100644 --- a/components/driver/i2c/i2c.c +++ b/components/driver/i2c/i2c.c @@ -632,7 +632,7 @@ static esp_err_t i2c_master_clear_bus(i2c_port_t i2c_num) gpio_set_level(sda_io, 1); // STOP, SDA low -> high while SCL is HIGH i2c_set_pin(i2c_num, sda_io, scl_io, 1, 1, I2C_MODE_MASTER); #else - i2c_ll_master_clr_bus(i2c_context[i2c_num].hal.dev); + i2c_ll_master_clr_bus(i2c_context[i2c_num].hal.dev, I2C_CLR_BUS_SCL_NUM); #endif return ESP_OK; } @@ -650,7 +650,7 @@ static esp_err_t i2c_hw_fsm_reset(i2c_port_t i2c_num) uint8_t filter_cfg; i2c_hal_get_timing_config(&i2c_context[i2c_num].hal, &timing_config); - i2c_ll_get_filter(i2c_context[i2c_num].hal.dev, &filter_cfg); + i2c_ll_master_get_filter(i2c_context[i2c_num].hal.dev, &filter_cfg); //to reset the I2C hw module, we need re-enable the hw i2c_hw_disable(i2c_num); @@ -661,7 +661,7 @@ static esp_err_t i2c_hw_fsm_reset(i2c_port_t i2c_num) i2c_ll_disable_intr_mask(i2c_context[i2c_num].hal.dev, I2C_LL_INTR_MASK); i2c_ll_clear_intr_mask(i2c_context[i2c_num].hal.dev, I2C_LL_INTR_MASK); i2c_hal_set_timing_config(&i2c_context[i2c_num].hal, &timing_config); - i2c_ll_set_filter(i2c_context[i2c_num].hal.dev, filter_cfg); + i2c_ll_master_set_filter(i2c_context[i2c_num].hal.dev, filter_cfg); #else i2c_ll_master_fsm_rst(i2c_context[i2c_num].hal.dev); i2c_master_clear_bus(i2c_num); @@ -767,7 +767,7 @@ esp_err_t i2c_param_config(i2c_port_t i2c_num, const i2c_config_t *i2c_conf) { i2c_hal_master_init(&(i2c_context[i2c_num].hal)); //Default, we enable hardware filter - i2c_ll_set_filter(i2c_context[i2c_num].hal.dev, I2C_FILTER_CYC_NUM_DEF); + i2c_ll_master_set_filter(i2c_context[i2c_num].hal.dev, I2C_FILTER_CYC_NUM_DEF); i2c_hal_set_bus_timing(&(i2c_context[i2c_num].hal), i2c_conf->master.clk_speed, src_clk, s_get_src_clk_freq(src_clk)); } i2c_ll_update(i2c_context[i2c_num].hal.dev); @@ -802,7 +802,7 @@ esp_err_t i2c_filter_enable(i2c_port_t i2c_num, uint8_t cyc_num) ESP_RETURN_ON_FALSE(i2c_num < I2C_NUM_MAX, ESP_ERR_INVALID_ARG, I2C_TAG, I2C_NUM_ERROR_STR); ESP_RETURN_ON_FALSE(p_i2c_obj[i2c_num] != NULL, ESP_FAIL, I2C_TAG, I2C_DRIVER_ERR_STR); I2C_ENTER_CRITICAL(&(i2c_context[i2c_num].spinlock)); - i2c_ll_set_filter(i2c_context[i2c_num].hal.dev, cyc_num); + i2c_ll_master_set_filter(i2c_context[i2c_num].hal.dev, cyc_num); i2c_ll_update(i2c_context[i2c_num].hal.dev); I2C_EXIT_CRITICAL(&(i2c_context[i2c_num].spinlock)); return ESP_OK; @@ -812,7 +812,7 @@ esp_err_t i2c_filter_disable(i2c_port_t i2c_num) { ESP_RETURN_ON_FALSE(i2c_num < I2C_NUM_MAX, ESP_ERR_INVALID_ARG, I2C_TAG, I2C_NUM_ERROR_STR); I2C_ENTER_CRITICAL(&(i2c_context[i2c_num].spinlock)); - i2c_ll_set_filter(i2c_context[i2c_num].hal.dev, 0); + i2c_ll_master_set_filter(i2c_context[i2c_num].hal.dev, 0); i2c_ll_update(i2c_context[i2c_num].hal.dev); I2C_EXIT_CRITICAL(&(i2c_context[i2c_num].spinlock)); return ESP_OK; @@ -825,7 +825,7 @@ esp_err_t i2c_set_start_timing(i2c_port_t i2c_num, int setup_time, int hold_time ESP_RETURN_ON_FALSE((setup_time <= I2C_SCL_RSTART_SETUP_TIME_V) && (setup_time > 0), ESP_ERR_INVALID_ARG, I2C_TAG, I2C_TIMING_VAL_ERR_STR); I2C_ENTER_CRITICAL(&(i2c_context[i2c_num].spinlock)); - i2c_ll_set_start_timing(i2c_context[i2c_num].hal.dev, setup_time, hold_time); + i2c_ll_master_set_start_timing(i2c_context[i2c_num].hal.dev, setup_time, hold_time); i2c_ll_update(i2c_context[i2c_num].hal.dev); I2C_EXIT_CRITICAL(&(i2c_context[i2c_num].spinlock)); return ESP_OK; @@ -847,7 +847,7 @@ esp_err_t i2c_set_stop_timing(i2c_port_t i2c_num, int setup_time, int hold_time) ESP_RETURN_ON_FALSE((hold_time <= I2C_SCL_STOP_HOLD_TIME_V) && (hold_time > 0), ESP_ERR_INVALID_ARG, I2C_TAG, I2C_TIMING_VAL_ERR_STR); I2C_ENTER_CRITICAL(&(i2c_context[i2c_num].spinlock)); - i2c_ll_set_stop_timing(i2c_context[i2c_num].hal.dev, setup_time, hold_time); + i2c_ll_master_set_stop_timing(i2c_context[i2c_num].hal.dev, setup_time, hold_time); i2c_ll_update(i2c_context[i2c_num].hal.dev); I2C_EXIT_CRITICAL(&(i2c_context[i2c_num].spinlock)); return ESP_OK; @@ -1411,8 +1411,8 @@ static void IRAM_ATTR i2c_master_cmd_begin_static(i2c_port_t i2c_num, BaseType_t } hw_cmd.byte_num = fifo_fill; i2c_ll_write_txfifo(i2c_context[i2c_num].hal.dev, write_pr, fifo_fill); - i2c_ll_write_cmd_reg(i2c_context[i2c_num].hal.dev, hw_cmd, p_i2c->cmd_idx); - i2c_ll_write_cmd_reg(i2c_context[i2c_num].hal.dev, hw_end_cmd, p_i2c->cmd_idx + 1); + i2c_ll_master_write_cmd_reg(i2c_context[i2c_num].hal.dev, hw_cmd, p_i2c->cmd_idx); + i2c_ll_master_write_cmd_reg(i2c_context[i2c_num].hal.dev, hw_end_cmd, p_i2c->cmd_idx + 1); i2c_ll_master_enable_tx_it(i2c_context[i2c_num].hal.dev); p_i2c->cmd_idx = 0; if (i2c_cmd_is_single_byte(cmd) || cmd->total_bytes == cmd->bytes_used) { @@ -1428,13 +1428,13 @@ static void IRAM_ATTR i2c_master_cmd_begin_static(i2c_port_t i2c_num, BaseType_t fifo_fill = MIN(remaining_bytes, SOC_I2C_FIFO_LEN); p_i2c->rx_cnt = fifo_fill; hw_cmd.byte_num = fifo_fill; - i2c_ll_write_cmd_reg(i2c_context[i2c_num].hal.dev, hw_cmd, p_i2c->cmd_idx); - i2c_ll_write_cmd_reg(i2c_context[i2c_num].hal.dev, hw_end_cmd, p_i2c->cmd_idx + 1); + i2c_ll_master_write_cmd_reg(i2c_context[i2c_num].hal.dev, hw_cmd, p_i2c->cmd_idx); + i2c_ll_master_write_cmd_reg(i2c_context[i2c_num].hal.dev, hw_end_cmd, p_i2c->cmd_idx + 1); i2c_ll_master_enable_rx_it(i2c_context[i2c_num].hal.dev); p_i2c->status = I2C_STATUS_READ; break; } else { - i2c_ll_write_cmd_reg(i2c_context[i2c_num].hal.dev, hw_cmd, p_i2c->cmd_idx); + i2c_ll_master_write_cmd_reg(i2c_context[i2c_num].hal.dev, hw_cmd, p_i2c->cmd_idx); } p_i2c->cmd_idx++; p_i2c->cmd_link.head = p_i2c->cmd_link.head->next; @@ -1444,7 +1444,7 @@ static void IRAM_ATTR i2c_master_cmd_begin_static(i2c_port_t i2c_num, BaseType_t } } i2c_ll_update(i2c_context[i2c_num].hal.dev); - i2c_ll_trans_start(i2c_context[i2c_num].hal.dev); + i2c_ll_master_trans_start(i2c_context[i2c_num].hal.dev); return; } @@ -1643,3 +1643,19 @@ int i2c_slave_read_buffer(i2c_port_t i2c_num, uint8_t *data, size_t max_size, Ti return max_size - size_rem; } #endif + +/** + * @brief This function will be called during start up, to check that this legacy i2c driver is not running along with the new I2C driver + */ +__attribute__((constructor)) +static void check_i2c_driver_conflict(void) +{ + // This function was declared as weak here. The new I2C driver has the implementation. + // So if the new I2C driver is not linked in, then `i2c_acquire_bus_handle()` should be NULL at runtime. + extern __attribute__((weak)) esp_err_t i2c_acquire_bus_handle(int port_num, void *i2c_new_bus, int mode); + if ((void *)i2c_acquire_bus_handle != NULL) { + ESP_EARLY_LOGE(I2C_TAG, "CONFLICT! driver_ng is not allowed to be used with this old driver"); + abort(); + } + ESP_EARLY_LOGW(I2C_TAG, "This driver is an old driver, please migrate your application code to adapt `driver/i2c_master.h`"); +} diff --git a/components/driver/i2c/i2c_common.c b/components/driver/i2c/i2c_common.c new file mode 100644 index 0000000000..6823bcc33d --- /dev/null +++ b/components/driver/i2c/i2c_common.c @@ -0,0 +1,230 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include "sdkconfig.h" +#include "esp_types.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" +#include "esp_pm.h" +#include "freertos/FreeRTOS.h" +#include "hal/i2c_hal.h" +#include "hal/gpio_hal.h" +#include "esp_private/periph_ctrl.h" +#include "esp_rom_gpio.h" +#include "i2c_private.h" +#include "driver/gpio.h" +#include "soc/clk_tree_defs.h" +#include "soc/i2c_periph.h" +#include "esp_clk_tree.h" +#include "clk_ctrl_os.h" + +static const char *TAG = "i2c.common"; + +typedef struct i2c_platform_t { + _lock_t mutex; // platform level mutex lock. + i2c_bus_handle_t buses[SOC_I2C_NUM]; // array of I2C bus instances. + uint32_t count[SOC_I2C_NUM]; // reference count used to protect group install/uninstall. +} i2c_platform_t; + +static i2c_platform_t s_i2c_platform = {}; // singleton platform + +esp_err_t i2c_acquire_bus_handle(i2c_port_num_t port_num, i2c_bus_handle_t *i2c_new_bus, i2c_bus_mode_t mode) +{ +#if CONFIG_I2C_ENABLE_DEBUG_LOG + esp_log_level_set(TAG, ESP_LOG_DEBUG); +#endif + bool new_bus = false; + i2c_bus_t *bus = NULL; + esp_err_t ret = ESP_OK; + + if (!s_i2c_platform.buses[port_num]) { + new_bus = true; + bus = heap_caps_calloc(1, sizeof(i2c_bus_t), I2C_MEM_ALLOC_CAPS); + if (bus) { + s_i2c_platform.buses[port_num] = bus; + bus->port_num = port_num; + bus->spinlock = (portMUX_TYPE)portMUX_INITIALIZER_UNLOCKED; + bus->bus_mode = mode; + + // Enable the I2C module + periph_module_enable(i2c_periph_signal[port_num].module); + periph_module_reset(i2c_periph_signal[port_num].module); + i2c_hal_init(&bus->hal, port_num); + } + } else { + ESP_LOGE(TAG, "I2C bus id(%d) has already been acquired", port_num); + bus = s_i2c_platform.buses[port_num]; + ret = ESP_ERR_INVALID_STATE; + } + if (bus) { + s_i2c_platform.count[port_num]++; + } + + if (new_bus) { + ESP_LOGD(TAG, "new bus(%d) at %p", port_num, bus); + } + *i2c_new_bus = bus; + return ret; +} + +bool i2c_bus_occupied(i2c_port_num_t port_num) +{ + bool bus_occupied = false; + _lock_acquire(&s_i2c_platform.mutex); + if (s_i2c_platform.buses[port_num]) { + bus_occupied = true; + } + _lock_release(&s_i2c_platform.mutex); + return bus_occupied; +} + +uint8_t i2c_release_bus_handle(i2c_bus_handle_t i2c_bus) +{ + int port_num = i2c_bus->port_num; + i2c_clock_source_t clk_src = i2c_bus->clk_src; + bool do_deinitialize = false; + _lock_acquire(&s_i2c_platform.mutex); + if (s_i2c_platform.buses[port_num]) { + s_i2c_platform.count[port_num]--; + if (s_i2c_platform.count[port_num] == 0) { + do_deinitialize = true; + s_i2c_platform.buses[port_num] = NULL; + + // Disable I2C module + periph_module_disable(i2c_periph_signal[port_num].module); + free(i2c_bus); + } + } + _lock_release(&s_i2c_platform.mutex); + + switch (clk_src) { +#if SOC_I2C_SUPPORT_RTC + case I2C_CLK_SRC_RC_FAST: + periph_rtc_dig_clk8m_disable(); + break; +#endif // SOC_I2C_SUPPORT_RTC + default: + break; + } + + if (do_deinitialize) { + ESP_LOGD(TAG, "delete bus %d", port_num); + } + return s_i2c_platform.count[port_num]; +} + +esp_err_t i2c_select_periph_clock(i2c_bus_handle_t handle, i2c_clock_source_t clk_src) +{ + esp_err_t ret = ESP_OK; + ESP_RETURN_ON_FALSE(handle, ESP_ERR_INVALID_ARG, TAG, "I2C empty controller handle"); + uint32_t periph_src_clk_hz = 0; + bool clock_selection_conflict = 0; + + portENTER_CRITICAL(&handle->spinlock); + if (handle->clk_src == 0) { + handle->clk_src = clk_src; + } else { + clock_selection_conflict = (handle->clk_src != clk_src); + } + portEXIT_CRITICAL(&handle->spinlock); + ESP_RETURN_ON_FALSE(!clock_selection_conflict, ESP_ERR_INVALID_STATE, TAG, + "group clock conflict, already is %d but attempt to %d", handle->clk_src, clk_src); + + // TODO: [clk_tree] to use a generic clock enable/disable or acquire/release function for all clock source +#if SOC_I2C_SUPPORT_RTC + if (clk_src == I2C_CLK_SRC_RC_FAST) { + // RC_FAST clock is not enabled automatically on start up, we enable it here manually. + // Note there's a ref count in the enable/disable function, we must call them in pair in the driver. + periph_rtc_dig_clk8m_enable(); + } +#endif // SOC_I2C_SUPPORT_RTC + + ESP_RETURN_ON_ERROR(esp_clk_tree_src_get_freq_hz(clk_src, ESP_CLK_TREE_SRC_FREQ_PRECISION_APPROX, &periph_src_clk_hz), TAG, "i2c get clock frequency error"); + + handle->clk_src_freq_hz = periph_src_clk_hz; + +#if CONFIG_PM_ENABLE + bool need_pm_lock = true; + // to make the I2C work reliable, the source clock must stay alive and unchanged + // driver will create different pm lock for that purpose, according to different clock source + esp_pm_lock_type_t pm_lock_type = ESP_PM_NO_LIGHT_SLEEP; + +#if SOC_I2C_SUPPORT_RTC + if (clk_src == I2C_CLK_SRC_RC_FAST) { + // I2C use fifo, which connected to APB, so we cannot use I2C either when in light sleep. + need_pm_lock = ESP_PM_NO_LIGHT_SLEEP; + } +#endif // SOC_I2C_SUPPORT_RTC + +#if SOC_I2C_SUPPORT_APB + if (clk_src == I2C_CLK_SRC_APB) { + // APB clock frequency can be changed during DFS + pm_lock_type = ESP_PM_APB_FREQ_MAX; + } +#endif // SOC_I2C_SUPPORT_APB + + if (need_pm_lock) { + sprintf(handle->pm_lock_name, "I2C_%d", handle->port_num); // e.g. PORT_0 + ret = esp_pm_lock_create(pm_lock_type, 0, handle->pm_lock_name, &handle->pm_lock); + ESP_RETURN_ON_ERROR(ret, TAG, "create pm lock failed"); + } +#endif // CONFIG_PM_ENABLE + + + ESP_LOGD(TAG, "bus clock source frequency: %"PRIu32"hz", periph_src_clk_hz); + return ret; +} + +esp_err_t i2c_common_set_pins(i2c_bus_handle_t handle) +{ + esp_err_t ret = ESP_OK; + ESP_RETURN_ON_FALSE(handle, ESP_ERR_INVALID_ARG, TAG, "I2C empty controller handle"); + int port_id = handle->port_num; + + // SDA pin configurations + gpio_config_t sda_conf = { + .intr_type = GPIO_INTR_DISABLE, + .mode = GPIO_MODE_INPUT_OUTPUT_OD, + .pull_down_en = false, + .pull_up_en = handle->pull_up_enable ? GPIO_PULLUP_ENABLE : GPIO_PULLUP_DISABLE, + .pin_bit_mask = 1ULL << handle->sda_num, + }; +#if CONFIG_IDF_TARGET_ESP32 + // on esp32, must enable internal pull-up + sda_conf.pull_up_en = GPIO_PULLUP_ENABLE; +#endif + ESP_RETURN_ON_ERROR(gpio_config(&sda_conf), TAG, "config GPIO failed"); + ESP_RETURN_ON_ERROR(gpio_set_level(handle->sda_num, 1), TAG, "i2c sda pin set level failed"); + gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[handle->sda_num], PIN_FUNC_GPIO); + esp_rom_gpio_connect_out_signal(handle->sda_num, i2c_periph_signal[port_id].sda_out_sig, 0, 0); + esp_rom_gpio_connect_in_signal(handle->sda_num, i2c_periph_signal[port_id].sda_in_sig, 0); + + + // SCL pin configurations + gpio_config_t scl_conf = { + .intr_type = GPIO_INTR_DISABLE, + .mode = GPIO_MODE_INPUT_OUTPUT_OD, + .pull_down_en = false, + .pull_up_en = handle->pull_up_enable ? GPIO_PULLUP_ENABLE : GPIO_PULLUP_DISABLE, + .pin_bit_mask = 1ULL << handle->scl_num, + }; +#if CONFIG_IDF_TARGET_ESP32 + // on esp32, must enable internal pull-up + scl_conf.pull_up_en = GPIO_PULLUP_ENABLE; +#endif + ESP_RETURN_ON_ERROR(gpio_config(&scl_conf), TAG, "config GPIO failed"); + ESP_RETURN_ON_ERROR(gpio_set_level(handle->scl_num, 1), TAG, "i2c scl pin set level failed"); + gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[handle->scl_num], PIN_FUNC_GPIO); + esp_rom_gpio_connect_out_signal(handle->scl_num, i2c_periph_signal[port_id].scl_out_sig, 0, 0); + esp_rom_gpio_connect_in_signal(handle->scl_num, i2c_periph_signal[port_id].scl_in_sig, 0); + return ret; +} diff --git a/components/driver/i2c/i2c_master.c b/components/driver/i2c/i2c_master.c new file mode 100644 index 0000000000..3f2e4aced2 --- /dev/null +++ b/components/driver/i2c/i2c_master.c @@ -0,0 +1,1102 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include "sdkconfig.h" +#include "esp_types.h" +#include "esp_attr.h" +#include "esp_check.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_intr_alloc.h" +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" +#include "soc/i2c_periph.h" +#include "esp_private/periph_ctrl.h" +#include "esp_private/esp_clk.h" +#include "esp_rom_gpio.h" +#include "driver/i2c_master.h" +#include "i2c_private.h" +#include "driver/gpio.h" +#include "clk_ctrl_os.h" +#include "hal/i2c_types.h" +#include "hal/i2c_hal.h" +#include "hal/gpio_hal.h" +#include "esp_memory_utils.h" +#include "freertos/idf_additions.h" + +static const char *TAG = "i2c.master"; +static _lock_t s_i2c_bus_acquire; + +#define DIM(array) (sizeof(array)/sizeof(*array)) +#define I2C_ADDRESS_TRANS_WRITE(device_address) (((device_address) << 1) | 0) +#define I2C_ADDRESS_TRANS_READ(device_address) (((device_address) << 1) | 1) + +static esp_err_t s_i2c_master_clear_bus(i2c_bus_handle_t handle) +{ +#if !SOC_I2C_SUPPORT_HW_FSM_RST + const int scl_half_period = 5; // use standard 100kHz data rate + int i = 0; + gpio_set_direction(handle->scl_num, GPIO_MODE_OUTPUT_OD); + gpio_set_direction(handle->sda_num, GPIO_MODE_INPUT_OUTPUT_OD); + // If a SLAVE device was in a read operation when the bus was interrupted, the SLAVE device is controlling SDA. + // The only bit during the 9 clock cycles of a READ byte the MASTER(ESP32) is guaranteed control over is during the ACK bit + // period. If the slave is sending a stream of ZERO bytes, it will only release SDA during the ACK bit period. + // So, this reset code needs to synchronous the bit stream with, Either, the ACK bit, Or a 1 bit to correctly generate + // a STOP condition. + gpio_set_level(handle->scl_num, 0); + gpio_set_level(handle->sda_num, 1); + esp_rom_delay_us(scl_half_period); + while (!gpio_get_level(handle->sda_num) && (i++ < 9)) { + gpio_set_level(handle->scl_num, 1); + esp_rom_delay_us(scl_half_period); + gpio_set_level(handle->scl_num, 0); + esp_rom_delay_us(scl_half_period); + } + gpio_set_level(handle->sda_num, 0); // setup for STOP + gpio_set_level(handle->scl_num, 1); + esp_rom_delay_us(scl_half_period); + gpio_set_level(handle->sda_num, 1); // STOP, SDA low -> high while SCL is HIGH + i2c_common_set_pins(handle); +#else + i2c_hal_context_t *hal = &handle->hal; + i2c_ll_master_clr_bus(hal->dev, I2C_LL_RESET_SLV_SCL_PULSE_NUM_DEFAULT); +#endif + return ESP_OK; +} + +/** + * @brief Reset I2C hardware fsm. + * 1. Store filter and timing stuff(they are I2C hardware configure stuff) + * so, the configuration is same after reset. + * + * @param[in] i2c_master I2C master handle + */ +static esp_err_t s_i2c_hw_fsm_reset(i2c_master_bus_handle_t i2c_master) +{ + i2c_hal_context_t *hal = &i2c_master->base->hal; + i2c_hal_timing_config_t timing_config; + uint8_t filter_cfg; + + i2c_hal_get_timing_config(hal, &timing_config); + i2c_ll_master_get_filter(hal->dev, &filter_cfg); + + //to reset the I2C hw module, we need re-enable the hw + s_i2c_master_clear_bus(i2c_master->base); + periph_module_disable(i2c_periph_signal[i2c_master->base->port_num].module); + periph_module_enable(i2c_periph_signal[i2c_master->base->port_num].module); + + i2c_hal_master_init(hal); + i2c_ll_disable_intr_mask(hal->dev, I2C_LL_INTR_MASK); + i2c_ll_clear_intr_mask(hal->dev, I2C_LL_INTR_MASK); + i2c_hal_set_timing_config(hal, &timing_config); + i2c_ll_master_set_filter(hal->dev, filter_cfg); + return ESP_OK; +} + +//////////////////////////////////////I2C operation functions//////////////////////////////////////////// +/** + * @brief This function is used to send I2C write command, which is divided in two parts. + * -1. If write buffer is smaller than hardware fifo, it can be sent out in one single time, + * so the hardware command(step) is simply like start(1)->write(2)->end(3) + * -2. If write buffer is larger than hardware fifo, it cannot be sent out in one time, so it needs + * to be separated in to different transactions by interrupt. In this time, the hardware command(step) + * simply looks like start(1)->write_part(2)--interrupt--...--write(1)->end(2). + * + * @param[in] i2c_master I2C master handle + * @param[in] i2c_operation Pointer to I2C trans operation structure. + * @param[in] fifo_fill will be populated with the number of bytes written to the harware FIFO by this function + * @param[in] address_fill I2C device address with read or write information. + */ +static bool s_i2c_write_command(i2c_master_bus_handle_t i2c_master, i2c_operation_t *i2c_operation, uint8_t *fifo_fill, uint8_t *address_fill, BaseType_t *do_yield) +{ + i2c_master->async_break = false; + const size_t remaining_bytes = i2c_operation->total_bytes - i2c_operation->bytes_used; + const i2c_ll_hw_cmd_t hw_end_cmd = { + .op_code = I2C_LL_CMD_END + }; + uint8_t *write_pr = NULL; + i2c_hal_context_t *hal = &i2c_master->base->hal; + i2c_bus_handle_t handle = i2c_master->base; + i2c_ll_hw_cmd_t hw_cmd = i2c_operation->hw_cmd; + uint8_t data_fill = 0; + + // data_fill refers to the length to fill the data + data_fill = MIN(remaining_bytes, SOC_I2C_FIFO_LEN - *address_fill); + write_pr = i2c_operation->data + i2c_operation->bytes_used; + i2c_operation->bytes_used += data_fill; + hw_cmd.byte_num = data_fill + *address_fill; + *address_fill = 0; + atomic_store(&i2c_master->status, I2C_STATUS_WRITE); + portENTER_CRITICAL_SAFE(&handle->spinlock); + i2c_ll_write_txfifo(hal->dev, write_pr, data_fill); + i2c_ll_master_write_cmd_reg(hal->dev, hw_cmd, i2c_master->cmd_idx); + portEXIT_CRITICAL_SAFE(&handle->spinlock); + i2c_master->w_r_size = data_fill; +#if SOC_I2C_STOP_INDEPENDENT + i2c_ll_master_write_cmd_reg(hal->dev, hw_end_cmd, i2c_master->cmd_idx + 1); + i2c_master->cmd_idx = 0; + if (i2c_master->i2c_trans.ops[i2c_master->trans_idx].total_bytes == i2c_master->i2c_trans.ops[i2c_master->trans_idx].bytes_used) { + i2c_master->i2c_trans.cmd_count--; + i2c_master->trans_idx++; + } + portENTER_CRITICAL_SAFE(&handle->spinlock); + if (i2c_master->asnyc_trans == false) { + i2c_hal_master_trans_start(hal); + } else { + i2c_master->async_break = true; + } + portEXIT_CRITICAL_SAFE(&handle->spinlock); + +#else + // If data cannot be sent in one time, send data out. Otherwise, continue configuring command. + if ((remaining_bytes - data_fill) != 0) { + portENTER_CRITICAL_SAFE(&handle->spinlock); + i2c_ll_master_write_cmd_reg(hal->dev, hw_end_cmd, i2c_master->cmd_idx + 1); + portEXIT_CRITICAL_SAFE(&handle->spinlock); + i2c_master->cmd_idx = 0; + if (i2c_master->asnyc_trans == false) { + i2c_hal_master_trans_start(hal); + } else { + i2c_master->async_break = true; + } + } else { + i2c_master->cmd_idx++; + i2c_master->trans_idx++; + i2c_master->i2c_trans.cmd_count--; + if (i2c_master->asnyc_trans == false) { + if (xPortInIsrContext()) { + xSemaphoreGiveFromISR(i2c_master->cmd_semphr, do_yield); + } else { + xSemaphoreGive(i2c_master->cmd_semphr); + } + } + } +#endif + *fifo_fill = data_fill; + + return i2c_master->async_break; +} + +/** + * @brief This function is used to send I2C read command, which is divided in three parts. + * -1. If read buffer is smaller than hardware fifo, it can be sent out in one single time, + * so the hardware command(step) is simply like start(1)->read_ack(2)->read_nack(3)->end(4) + * -2. If read buffer is larger than hardware fifo, it cannot be sent out in one time, so it needs + * to be separated in to different transactions by interrupt. In this time, the hardware command(step) + * simply looks like start(1)->read_part(2)--interrupt--...--read(1)->end(2). + * -3. If only one byte is waiting to be read. only send nack command. like start(1)->read_nack(2)->end(3) + * + * @param[in] i2c_master I2C master handle + * @param[in] i2c_operation Pointer to I2C trans operation structure. + * @param[out] fifo_fill Pointer to read buffer length + */ +static bool s_i2c_read_command(i2c_master_bus_handle_t i2c_master, i2c_operation_t *i2c_operation, uint8_t *fifo_fill, BaseType_t *do_yield) +{ + i2c_master->async_break = false; + const size_t remaining_bytes = i2c_operation->total_bytes - i2c_operation->bytes_used; + const i2c_ll_hw_cmd_t hw_end_cmd = { + .op_code = I2C_LL_CMD_END + }; + i2c_hal_context_t *hal = &i2c_master->base->hal; + i2c_bus_handle_t handle = i2c_master->base; + i2c_ll_hw_cmd_t hw_cmd = i2c_operation->hw_cmd; + + *fifo_fill = MIN(remaining_bytes, SOC_I2C_FIFO_LEN - i2c_master->read_len_static); + i2c_master->rx_cnt = *fifo_fill; + hw_cmd.byte_num = *fifo_fill; + + i2c_master->contains_read = true; +#if !SOC_I2C_STOP_INDEPENDENT + if (remaining_bytes < SOC_I2C_FIFO_LEN - 1) { + if (i2c_operation->hw_cmd.ack_val == ACK_VAL) { + if (remaining_bytes != 0) { + i2c_ll_master_write_cmd_reg(hal->dev, hw_cmd, i2c_master->cmd_idx); + i2c_master->read_len_static = i2c_master->rx_cnt; + i2c_master->cmd_idx++; + } + i2c_master->read_buf_pos = i2c_master->trans_idx; + } else { + i2c_ll_master_write_cmd_reg(hal->dev, hw_cmd, i2c_master->cmd_idx); + i2c_master->cmd_idx++; + } + i2c_master->trans_idx++; + i2c_master->i2c_trans.cmd_count--; + if (i2c_master->asnyc_trans == false) { + if (xPortInIsrContext()) { + xSemaphoreGiveFromISR(i2c_master->cmd_semphr, do_yield); + } else { + xSemaphoreGive(i2c_master->cmd_semphr); + } + } + } else { + atomic_store(&i2c_master->status, I2C_STATUS_READ); + portENTER_CRITICAL_SAFE(&handle->spinlock); + i2c_ll_master_write_cmd_reg(hal->dev, hw_cmd, i2c_master->cmd_idx); + i2c_ll_master_write_cmd_reg(hal->dev, hw_end_cmd, i2c_master->cmd_idx + 1); + if (i2c_master->asnyc_trans == false) { + i2c_hal_master_trans_start(hal); + } else { + i2c_master->async_break = true; + } + portEXIT_CRITICAL_SAFE(&handle->spinlock); + } +#else + portENTER_CRITICAL_SAFE(&handle->spinlock); + i2c_ll_master_write_cmd_reg(hal->dev, hw_cmd, i2c_master->cmd_idx); + i2c_ll_master_write_cmd_reg(hal->dev, hw_end_cmd, i2c_master->cmd_idx + 1); + portEXIT_CRITICAL_SAFE(&handle->spinlock); + i2c_master->status = I2C_STATUS_READ; + portENTER_CRITICAL_SAFE(&handle->spinlock); + if (i2c_master->asnyc_trans == false) { + i2c_hal_master_trans_start(hal); + } else { + i2c_master->async_break = true; + } + portEXIT_CRITICAL_SAFE(&handle->spinlock); +#endif + + return i2c_master->async_break; +} + +/** + * @brief This function is used to send I2C start or stop command, which is divided in two parts. + * If start command is accepted, a write address command must be followed. So prepared one address + * data here. Send with write or read command. + * + * @param[in] i2c_master I2C master handle + * @param[in] i2c_operation Pointer to I2C trans operation structure. + * @param[in] address_fill I2C device address with read or write information. + */ +static void s_i2c_start_end_command(i2c_master_bus_handle_t i2c_master, i2c_operation_t *i2c_operation, uint8_t *address_fill, BaseType_t *do_yield) +{ + i2c_hal_context_t *hal = &i2c_master->base->hal; + i2c_ll_hw_cmd_t hw_cmd = i2c_operation->hw_cmd; + +#if SOC_I2C_SUPPORT_10BIT_ADDR + uint8_t cmd_address = (i2c_master->addr_10bits_bus == I2C_ADDR_BIT_LEN_7) ? i2c_master->i2c_trans.device_address : ((i2c_master->i2c_trans.device_address >> 8) | 0x78); + uint8_t addr_byte = (i2c_master->addr_10bits_bus == I2C_ADDR_BIT_LEN_7) ? 1 : 2; +#else + uint8_t cmd_address = i2c_master->i2c_trans.device_address; + uint8_t addr_byte = 1; +#endif + uint8_t addr_write[addr_byte]; + uint8_t addr_read[addr_byte]; + + addr_write[0] = I2C_ADDRESS_TRANS_WRITE(cmd_address); + addr_read[0] = I2C_ADDRESS_TRANS_READ(cmd_address); +#if SOC_I2C_SUPPORT_10BIT_ADDR + if (i2c_master->addr_10bits_bus == I2C_ADDR_BIT_LEN_10) { + addr_write[1] = i2c_master->i2c_trans.device_address & 0xff; + addr_read[1] = i2c_master->i2c_trans.device_address & 0xff; + } +#endif + + portENTER_CRITICAL_SAFE(&i2c_master->base->spinlock); + i2c_ll_master_write_cmd_reg(hal->dev, hw_cmd, i2c_master->cmd_idx); + i2c_master->cmd_idx++; + portEXIT_CRITICAL_SAFE(&i2c_master->base->spinlock); + if (i2c_operation->hw_cmd.op_code == I2C_LL_CMD_RESTART) { + + if (i2c_master->i2c_trans.ops[i2c_master->trans_idx + 1].hw_cmd.op_code != I2C_LL_CMD_WRITE) { +#if SOC_I2C_SUPPORT_10BIT_ADDR + if (i2c_master->addr_10bits_bus == I2C_ADDR_BIT_LEN_10) { + i2c_ll_hw_cmd_t hw_write_cmd = { + .ack_en = false, + .op_code = I2C_LL_CMD_WRITE, + .byte_num = 2, + }; + portENTER_CRITICAL_SAFE(&i2c_master->base->spinlock); + i2c_ll_write_txfifo(hal->dev, addr_write, sizeof(addr_write)); + i2c_ll_master_write_cmd_reg(hal->dev, hw_write_cmd, i2c_master->cmd_idx); + i2c_master->cmd_idx++; + portEXIT_CRITICAL_SAFE(&i2c_master->base->spinlock); + const i2c_ll_hw_cmd_t hw_start_cmd = { + .op_code = I2C_LL_CMD_RESTART, + }; + portENTER_CRITICAL_SAFE(&i2c_master->base->spinlock); + i2c_ll_master_write_cmd_reg(hal->dev, hw_start_cmd, i2c_master->cmd_idx); + i2c_master->cmd_idx++; + portEXIT_CRITICAL_SAFE(&i2c_master->base->spinlock); + + } +#endif + i2c_ll_hw_cmd_t hw_write_cmd = { + .ack_en = false, + .op_code = I2C_LL_CMD_WRITE, + .byte_num = 1, + }; + portENTER_CRITICAL_SAFE(&i2c_master->base->spinlock); + i2c_ll_write_txfifo(hal->dev, addr_read, sizeof(addr_read)); + i2c_ll_master_write_cmd_reg(hal->dev, hw_write_cmd, i2c_master->cmd_idx); + i2c_master->cmd_idx++; + portEXIT_CRITICAL_SAFE(&i2c_master->base->spinlock); + } else { + /* The next transaction is a WRITE, we can merge the device address byte + * with the next write command. No need to write the `cmd_reg` as the next + * command will do it for us, we only need to add the device address byte + * in the TX FIFO. */ + portENTER_CRITICAL_SAFE(&i2c_master->base->spinlock); + i2c_ll_write_txfifo(hal->dev, addr_write, sizeof(addr_write)); + *address_fill += sizeof(addr_write); + portEXIT_CRITICAL_SAFE(&i2c_master->base->spinlock); + } + if (i2c_master->asnyc_trans == false) { + if (xPortInIsrContext()) { + xSemaphoreGiveFromISR(i2c_master->cmd_semphr, do_yield); + } else { + xSemaphoreGive(i2c_master->cmd_semphr); + } + } + } else { + atomic_store(&i2c_master->status, I2C_STATUS_STOP); + } + portENTER_CRITICAL_SAFE(&i2c_master->base->spinlock); + i2c_master->trans_idx++; + i2c_master->i2c_trans.cmd_count--; + portEXIT_CRITICAL_SAFE(&i2c_master->base->spinlock); +} + +static void s_i2c_send_commands(i2c_master_bus_handle_t i2c_master, TickType_t ticks_to_wait) +{ + i2c_hal_context_t *hal = &i2c_master->base->hal; + uint8_t fifo_fill = 0; + uint8_t address_fill = 0; + + while (i2c_master->i2c_trans.cmd_count) { + if (xSemaphoreTake(i2c_master->cmd_semphr, ticks_to_wait) != pdTRUE) { + // Software timeout, clear the command link and finish this transaction. + i2c_master->cmd_idx = 0; + i2c_master->trans_idx = 0; + return; + } + + if (i2c_master->status == I2C_STATUS_TIMEOUT) { + s_i2c_hw_fsm_reset(i2c_master); + i2c_master->cmd_idx = 0; + i2c_master->trans_idx = 0; + xSemaphoreGive(i2c_master->cmd_semphr); + return; + } + + if (i2c_master->status == I2C_STATUS_ACK_ERROR) { + const i2c_ll_hw_cmd_t hw_stop_cmd = { + .op_code = I2C_LL_CMD_STOP, + }; + i2c_ll_master_write_cmd_reg(hal->dev, hw_stop_cmd, 0); + i2c_hal_master_trans_start(hal); + return; + } + + i2c_operation_t *i2c_operation = &i2c_master->i2c_trans.ops[i2c_master->trans_idx]; + + if (i2c_operation->hw_cmd.op_code == I2C_LL_CMD_WRITE) { + s_i2c_write_command(i2c_master, i2c_operation, &fifo_fill, &address_fill, NULL); + } else if (i2c_operation->hw_cmd.op_code == I2C_LL_CMD_READ) { + s_i2c_read_command(i2c_master, i2c_operation, &fifo_fill, NULL); + } else { + s_i2c_start_end_command(i2c_master, i2c_operation, &address_fill, NULL); + } + } + i2c_hal_master_trans_start(hal); + + // For blocking implementation, we must wait `done` interrupt to update the status. + while (i2c_master->trans_done == false) {}; + atomic_store(&i2c_master->status, I2C_STATUS_DONE); + xSemaphoreGive(i2c_master->cmd_semphr); +} + +static void s_i2c_send_command_async(i2c_master_bus_handle_t i2c_master, BaseType_t *do_yield) +{ + i2c_hal_context_t *hal = &i2c_master->base->hal; + uint8_t address_fill = 0; + bool needs_start = false; + if (i2c_master->i2c_trans.cmd_count == 0) { + // No extra commands. + i2c_master->trans_finish = true; + i2c_master->in_progress = false; + if (i2c_master->queue_trans) { + i2c_master->new_queue = true; + xQueueSendFromISR(i2c_master->trans_queues[I2C_TRANS_QUEUE_COMPLETE], &i2c_master->i2c_trans, do_yield); + } + i2c_master->sent_all = true; + return; + } + while(i2c_ll_is_bus_busy(hal->dev)){} + + while (i2c_master->i2c_trans.cmd_count && !needs_start) { + i2c_master->in_progress = true; + i2c_master->sent_all = false; + i2c_operation_t *i2c_operation = &i2c_master->i2c_trans.ops[i2c_master->trans_idx]; + uint8_t fifo_fill = 0; + + if (i2c_operation->hw_cmd.op_code == I2C_LL_CMD_WRITE) { + needs_start = s_i2c_write_command(i2c_master, i2c_operation, &fifo_fill, &address_fill, do_yield); + } else if (i2c_operation->hw_cmd.op_code == I2C_LL_CMD_READ) { + needs_start = s_i2c_read_command(i2c_master, i2c_operation, &fifo_fill, do_yield); + } else { + s_i2c_start_end_command(i2c_master, i2c_operation, &address_fill, do_yield); + + } + } + i2c_hal_master_trans_start(hal); +} + +static esp_err_t s_i2c_transaction_start(i2c_master_dev_handle_t i2c_dev, int xfer_timeout_ms) +{ + i2c_master_bus_handle_t i2c_master = i2c_dev->master_bus; + i2c_hal_context_t *hal = &i2c_master->base->hal; + TickType_t ticks_to_wait = (xfer_timeout_ms == -1) ? portMAX_DELAY : pdMS_TO_TICKS(xfer_timeout_ms); + // Sometimes when the FSM get stuck, the ACK_ERR interrupt will occur endlessly until we reset the FSM and clear bus. + esp_err_t ret = ESP_OK; + if (i2c_master->status == I2C_STATUS_TIMEOUT || i2c_ll_is_bus_busy(hal->dev)) { + s_i2c_hw_fsm_reset(i2c_master); + } + + if (i2c_master->base->pm_lock) { + ESP_RETURN_ON_ERROR(esp_pm_lock_acquire(i2c_master->base->pm_lock), TAG, "acquire pm_lock failed"); + } + + portENTER_CRITICAL(&i2c_master->base->spinlock); + atomic_init(&i2c_master->trans_idx, 0); + atomic_store(&i2c_master->status, I2C_STATUS_IDLE); + i2c_master->cmd_idx = 0; + i2c_master->rx_cnt = 0; + i2c_master->read_len_static = 0; + + i2c_hal_set_bus_timing(hal, i2c_dev->scl_speed_hz, i2c_master->base->clk_src, i2c_master->base->clk_src_freq_hz); + i2c_ll_master_set_fractional_divider(hal->dev, 0, 0); + i2c_ll_update(hal->dev); + + i2c_ll_txfifo_rst(hal->dev); + i2c_ll_rxfifo_rst(hal->dev); + i2c_ll_enable_intr_mask(hal->dev, I2C_LL_MASTER_EVENT_INTR); + portEXIT_CRITICAL(&i2c_master->base->spinlock); + if (i2c_master->asnyc_trans == true) { + s_i2c_send_command_async(i2c_master, NULL); + } else { + s_i2c_send_commands(i2c_master, ticks_to_wait); + // Wait event bits + if (i2c_master->status != I2C_STATUS_DONE) { + ret = ESP_ERR_INVALID_STATE; + } + } + + if (i2c_master->base->pm_lock) { + ESP_RETURN_ON_ERROR(esp_pm_lock_release(i2c_master->base->pm_lock), TAG, "release pm_lock failed"); + } + + return ret; +} + + +///////////////////////////////I2C DRIVERS////////////////////////////////////////////////////////////// + +IRAM_ATTR static void i2c_isr_receive_handler(i2c_master_bus_t *i2c_master) +{ + i2c_hal_context_t *hal = &i2c_master->base->hal; + while(i2c_ll_is_bus_busy(hal->dev)){} + if (i2c_master->status == I2C_STATUS_READ) { + i2c_operation_t *i2c_operation = &i2c_master->i2c_trans.ops[i2c_master->trans_idx]; + portENTER_CRITICAL_ISR(&i2c_master->base->spinlock); + i2c_ll_read_rxfifo(hal->dev, i2c_operation->data + i2c_operation->bytes_used, i2c_master->rx_cnt); + /* rx_cnt bytes have just been read, increment the number of bytes used from the buffer */ + i2c_master->w_r_size = i2c_master->rx_cnt; + i2c_operation->bytes_used += i2c_master->rx_cnt; + i2c_master->cmd_idx = 0; + + /* Test if there are still some remaining bytes to send. */ + if (i2c_operation->bytes_used == i2c_operation->total_bytes) { + i2c_master->i2c_trans.cmd_count--; + i2c_master->read_buf_pos = i2c_master->trans_idx; + i2c_master->trans_idx++; + i2c_operation->bytes_used = 0; + } + portEXIT_CRITICAL_ISR(&i2c_master->base->spinlock); + } +#if !SOC_I2C_STOP_INDEPENDENT + else { + i2c_operation_t *i2c_operation = &i2c_master->i2c_trans.ops[i2c_master->read_buf_pos]; + portENTER_CRITICAL_ISR(&i2c_master->base->spinlock); + i2c_ll_read_rxfifo(hal->dev, i2c_operation->data + i2c_operation->bytes_used, i2c_master->read_len_static); + i2c_ll_read_rxfifo(hal->dev, i2c_master->i2c_trans.ops[i2c_master->read_buf_pos + 1].data, 1); + i2c_master->w_r_size = i2c_master->read_len_static + 1; + i2c_master->contains_read = false; + portEXIT_CRITICAL_ISR(&i2c_master->base->spinlock); + } +#endif +} + +static void IRAM_ATTR i2c_master_isr_handler_default(void *arg) +{ + i2c_master_bus_handle_t i2c_master = (i2c_master_bus_t*) arg; + + i2c_hal_context_t *hal = &i2c_master->base->hal; + portBASE_TYPE HPTaskAwoken = pdFALSE; + uint32_t int_mask; + BaseType_t ret = pdTRUE; + i2c_master->trans_done = false; + i2c_ll_get_intr_mask(hal->dev, &int_mask); + i2c_ll_clear_intr_mask(hal->dev, int_mask); + if (int_mask == 0) { + return; + } + + if (int_mask == I2C_LL_INTR_NACK) { + atomic_store(&i2c_master->status, I2C_STATUS_ACK_ERROR); + i2c_master->event = I2C_EVENT_NACK; + } else if (int_mask == I2C_LL_INTR_TIMEOUT || int_mask == I2C_LL_INTR_ARBITRATION) { + atomic_store(&i2c_master->status, I2C_STATUS_TIMEOUT); + } else if (int_mask == I2C_LL_INTR_MST_COMPLETE) { + i2c_master->trans_done = true; + i2c_master->event = I2C_EVENT_DONE; + } + if (i2c_master->contains_read == true) { + i2c_isr_receive_handler(i2c_master); + } + + if (i2c_master->asnyc_trans) { + i2c_master_dev_handle_t i2c_dev = NULL; + + i2c_master_device_list_t *device_item; + xSemaphoreTakeFromISR(i2c_master->bus_lock_mux, &HPTaskAwoken); + SLIST_FOREACH(device_item, &i2c_master->device_list, next) { + if (device_item->device->device_address == i2c_master->i2c_trans.device_address) { + i2c_dev = device_item->device; + break; + } + } + xSemaphoreGiveFromISR(i2c_master->bus_lock_mux, &HPTaskAwoken); + if (i2c_dev == NULL) { + return; + } + i2c_master_event_data_t evt = { + .event = i2c_master->event, + }; + s_i2c_send_command_async(i2c_master, &HPTaskAwoken); + if (i2c_master->trans_done) { + if (i2c_dev->on_trans_done) { + i2c_dev->on_trans_done(i2c_dev, &evt, i2c_dev->user_ctx); + } + if (i2c_master->new_queue && i2c_master->num_trans_inqueue > 0 && i2c_master->in_progress == false) { + i2c_transaction_t t = {}; + i2c_master->num_trans_inqueue--; + i2c_master->new_queue = false; + t.cmd_count = 0; + t.device_address = 0; + t.ops = NULL; + ret = xQueueReceiveFromISR(i2c_master->trans_queues[I2C_TRANS_QUEUE_PROGRESS], &t, &HPTaskAwoken); + if (ret == pdTRUE) { + i2c_master->queue_trans = true; + atomic_init(&i2c_master->trans_idx, 0); + atomic_store(&i2c_master->status, I2C_STATUS_IDLE); + i2c_master->cmd_idx = 0; + i2c_master->rx_cnt = 0; + i2c_master->read_len_static = 0; + i2c_ll_txfifo_rst(hal->dev); + i2c_ll_rxfifo_rst(hal->dev); + i2c_ll_enable_intr_mask(hal->dev, I2C_LL_MASTER_EVENT_INTR); + i2c_master->i2c_trans = t; + memcpy(i2c_master->i2c_ops, t.ops, t.cmd_count * sizeof(i2c_operation_t)); + + s_i2c_send_command_async(i2c_master, &HPTaskAwoken); + } + } + } + } else { + xSemaphoreGiveFromISR(i2c_master->cmd_semphr, &HPTaskAwoken); + } + + if (HPTaskAwoken == pdTRUE) { + portYIELD_FROM_ISR(); + } + +} + +static esp_err_t i2c_param_master_config(i2c_bus_handle_t handle, const i2c_master_bus_config_t *i2c_conf) +{ + i2c_hal_context_t *hal = &handle->hal; + + ESP_RETURN_ON_ERROR(i2c_common_set_pins(handle), TAG, "i2c master set pin failed"); + ESP_RETURN_ON_ERROR(i2c_select_periph_clock(handle, i2c_conf->clk_source), TAG, "i2c select clock failed"); + handle->clk_src = i2c_conf->clk_source; + portENTER_CRITICAL(&handle->spinlock); + i2c_hal_master_init(hal); + i2c_ll_update(hal->dev); + portEXIT_CRITICAL(&handle->spinlock); + return ESP_OK; +} + +static esp_err_t i2c_master_bus_destroy(i2c_master_bus_handle_t bus_handle) +{ + ESP_RETURN_ON_FALSE(bus_handle, ESP_ERR_INVALID_ARG, TAG, "no memory for i2c master bus"); + i2c_master_bus_handle_t i2c_master = bus_handle; + if(i2c_release_bus_handle(i2c_master->base) == 0) { + if (i2c_master) { + if (i2c_master->base->intr_handle) { + ESP_RETURN_ON_ERROR(esp_intr_free(i2c_master->base->intr_handle), TAG, "delete interrupt service failed"); + } + if (i2c_master->base->pm_lock) { + ESP_RETURN_ON_ERROR(esp_pm_lock_delete(i2c_master->base->pm_lock), TAG, "delete pm_lock failed"); + } + if (i2c_master->bus_lock_mux) { + vSemaphoreDeleteWithCaps(i2c_master->bus_lock_mux); + i2c_master->bus_lock_mux = NULL; + } + if (i2c_master->cmd_semphr) { + vSemaphoreDeleteWithCaps(i2c_master->cmd_semphr); + i2c_master->cmd_semphr = NULL; + } + if (i2c_master->queues_storage) { + free(i2c_master->queues_storage); + } + if (i2c_master->i2c_anyc_ops) { + for (int i = 0; i < i2c_master->index; i++) { + if (i2c_master->i2c_anyc_ops[i]) { + free(i2c_master->i2c_anyc_ops[i]); + } + } + free(i2c_master->i2c_anyc_ops); + } + if (i2c_master->anyc_write_buffer) { + for (int i = 0; i < i2c_master->index; i++) { + if (i2c_master->anyc_write_buffer[i]) { + free(i2c_master->anyc_write_buffer[i]); + } + } + free(i2c_master->anyc_write_buffer); + } + for (int i = 0; i < I2C_TRANS_QUEUE_MAX; i++) { + if (i2c_master->trans_queues[i]) { + vQueueDelete(i2c_master->trans_queues[i]); + } + } + bus_handle = NULL; + } + + free(i2c_master); + } else { + free(i2c_master); + } + + return ESP_OK; +} + +static esp_err_t s_i2c_asynchronous_transaction(i2c_master_dev_handle_t i2c_dev, i2c_operation_t *i2c_ops, size_t ops_dim, int timeout_ms) +{ + if (i2c_dev->master_bus->sent_all == true && i2c_dev->master_bus->num_trans_inqueue == 0) { + memcpy(i2c_dev->master_bus->i2c_ops, i2c_ops, sizeof(i2c_operation_t) * ops_dim); + i2c_dev->master_bus->addr_10bits_bus = i2c_dev->addr_10bits; + i2c_dev->master_bus->i2c_trans = (i2c_transaction_t) { + .device_address = i2c_dev->device_address, + .ops = i2c_dev->master_bus->i2c_ops, + .cmd_count = ops_dim, + }; + + i2c_dev->master_bus->sent_all = false; + i2c_dev->master_bus->trans_finish = false; + i2c_dev->master_bus->queue_trans = false; + ESP_RETURN_ON_ERROR(s_i2c_transaction_start(i2c_dev, timeout_ms), TAG, "I2C transaction failed"); + } else { + i2c_dev->master_bus->i2c_anyc_ops[i2c_dev->master_bus->index]=(i2c_operation_t(*))heap_caps_calloc(1, sizeof(i2c_operation_t) * 6, I2C_MEM_ALLOC_CAPS); + memcpy(i2c_dev->master_bus->i2c_anyc_ops[i2c_dev->master_bus->index], i2c_ops, sizeof(i2c_operation_t) * ops_dim); + i2c_transaction_t i2c_queue_pre; + if (i2c_dev->master_bus->num_trans_inflight < i2c_dev->master_bus->queue_size) { + ESP_RETURN_ON_FALSE(xQueueReceive(i2c_dev->master_bus->trans_queues[I2C_TRANS_QUEUE_READY], &i2c_queue_pre, portMAX_DELAY) == pdTRUE, ESP_FAIL, TAG, "no transaction in the ready queue"); + } else { + ESP_RETURN_ON_FALSE(xQueueReceive(i2c_dev->master_bus->trans_queues[I2C_TRANS_QUEUE_COMPLETE], &i2c_queue_pre, portMAX_DELAY) == pdTRUE, ESP_FAIL, TAG, "recycle transaction from done queue failed"); + i2c_dev->master_bus->num_trans_inflight--; + } + i2c_queue_pre = (i2c_transaction_t) { + .device_address = i2c_dev->device_address, + .ops = i2c_dev->master_bus->i2c_anyc_ops[i2c_dev->master_bus->index], + .cmd_count = ops_dim, + }; + i2c_dev->master_bus->index++; + if (xQueueSend(i2c_dev->master_bus->trans_queues[I2C_TRANS_QUEUE_PROGRESS], &i2c_queue_pre, portMAX_DELAY) == pdTRUE) { + i2c_dev->master_bus->num_trans_inflight++; + i2c_dev->master_bus->num_trans_inqueue++; + if (i2c_dev->master_bus->sent_all == true) { + // Oh no, you cannot get the queue from ISR, so you get queue here. + xQueueReceive(i2c_dev->master_bus->trans_queues[I2C_TRANS_QUEUE_PROGRESS], &i2c_queue_pre, portMAX_DELAY); + i2c_dev->master_bus->num_trans_inflight--; + i2c_dev->master_bus->num_trans_inqueue--; + i2c_dev->master_bus->sent_all = false; + i2c_dev->master_bus->trans_finish = false; + i2c_dev->master_bus->queue_trans = false; + ESP_RETURN_ON_ERROR(s_i2c_transaction_start(i2c_dev, timeout_ms), TAG, "I2C transaction failed"); + } + } else { + ESP_RETURN_ON_FALSE(xQueueSend(i2c_dev->master_bus->trans_queues[I2C_TRANS_QUEUE_READY], &i2c_queue_pre, 0) == pdTRUE, ESP_ERR_INVALID_STATE, TAG, "ready queue full"); + } + } + + return ESP_OK; +} + +static esp_err_t s_i2c_synchronous_transaction(i2c_master_dev_handle_t i2c_dev, i2c_operation_t *i2c_ops, size_t ops_dim, int timeout_ms) +{ + i2c_dev->master_bus->trans_done = false; + TickType_t ticks_to_wait = (timeout_ms == -1) ? portMAX_DELAY : pdMS_TO_TICKS(timeout_ms); + if (xSemaphoreTake(i2c_dev->master_bus->bus_lock_mux, ticks_to_wait) != pdTRUE) { + return ESP_ERR_TIMEOUT; + } + memcpy(i2c_dev->master_bus->i2c_ops, i2c_ops, sizeof(i2c_operation_t) * ops_dim); + i2c_dev->master_bus->addr_10bits_bus = i2c_dev->addr_10bits; + i2c_dev->master_bus->i2c_trans = (i2c_transaction_t) { + .device_address = i2c_dev->device_address, + .ops = i2c_dev->master_bus->i2c_ops, + .cmd_count = ops_dim, + }; + + i2c_dev->master_bus->sent_all = false; + i2c_dev->master_bus->trans_finish = false; + i2c_dev->master_bus->queue_trans = false; + ESP_RETURN_ON_ERROR(s_i2c_transaction_start(i2c_dev, timeout_ms), TAG, "I2C transaction failed"); + xSemaphoreGive(i2c_dev->master_bus->bus_lock_mux); + return ESP_OK; +} + +esp_err_t i2c_new_master_bus(const i2c_master_bus_config_t *bus_config, i2c_master_bus_handle_t *ret_bus_handle) +{ +#if CONFIG_I2C_ENABLE_DEBUG_LOG + esp_log_level_set(TAG, ESP_LOG_DEBUG); +#endif + esp_err_t ret = ESP_OK; + i2c_master_bus_t *i2c_master = NULL; + i2c_port_num_t i2c_port_num = bus_config->i2c_port; + ESP_RETURN_ON_FALSE(bus_config, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + ESP_RETURN_ON_FALSE((bus_config->i2c_port < SOC_I2C_NUM || bus_config->i2c_port == -1), ESP_ERR_INVALID_ARG, TAG, "invalid i2c port number"); + ESP_RETURN_ON_FALSE(GPIO_IS_VALID_GPIO(bus_config->sda_io_num) && GPIO_IS_VALID_GPIO(bus_config->scl_io_num), ESP_ERR_INVALID_ARG, TAG, "invalid SDA/SCL pin number"); + bool bus_occupied = false; + bool bus_found = false; + + i2c_master = heap_caps_calloc(1, sizeof(i2c_master_bus_t) + 20 * sizeof(i2c_transaction_t), I2C_MEM_ALLOC_CAPS); + + ESP_RETURN_ON_FALSE(i2c_master, ESP_ERR_NO_MEM, TAG, "no memory for i2c master bus"); + + _lock_acquire(&s_i2c_bus_acquire); + if (i2c_port_num == -1) { + for (int i = 0; i < SOC_I2C_NUM; i++) { + bus_occupied = i2c_bus_occupied(i); + if (bus_occupied == false) { + ret = i2c_acquire_bus_handle(i, &i2c_master->base, I2C_BUS_MODE_MASTER); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "acquire bus failed"); + _lock_release(&s_i2c_bus_acquire); + goto err; + } + bus_found = true; + i2c_port_num = i; + break; + } + } + ESP_GOTO_ON_FALSE((bus_found == true), ESP_ERR_NOT_FOUND, err, TAG, "acquire bus failed, no free bus"); + } else { + ret = i2c_acquire_bus_handle(i2c_port_num, &i2c_master->base, I2C_BUS_MODE_MASTER); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "acquire bus failed"); + _lock_release(&s_i2c_bus_acquire); + goto err; + } + } + _lock_release(&s_i2c_bus_acquire); + + i2c_hal_context_t *hal = &i2c_master->base->hal; + i2c_master->base->scl_num = bus_config->scl_io_num; + i2c_master->base->sda_num = bus_config->sda_io_num; + i2c_master->base->pull_up_enable = bus_config->flags.enable_internal_pullup; + ESP_GOTO_ON_ERROR(i2c_param_master_config(i2c_master->base, bus_config), err, TAG, "i2c configure parameter failed"); + + i2c_master->bus_lock_mux = xSemaphoreCreateBinaryWithCaps(I2C_MEM_ALLOC_CAPS); + ESP_GOTO_ON_FALSE(i2c_master->bus_lock_mux, ESP_ERR_NO_MEM, err, TAG, "No memory for binary semaphore"); + xSemaphoreGive(i2c_master->bus_lock_mux); + + i2c_master->cmd_semphr = xSemaphoreCreateBinaryWithCaps(I2C_MEM_ALLOC_CAPS); + ESP_GOTO_ON_FALSE(i2c_master->cmd_semphr, ESP_ERR_NO_MEM, err, TAG, "no memory for i2c semaphore struct"); + + portENTER_CRITICAL(&i2c_master->base->spinlock); + i2c_ll_clear_intr_mask(hal->dev, I2C_LL_MASTER_EVENT_INTR); + portEXIT_CRITICAL(&i2c_master->base->spinlock); + + if (bus_config->intr_priority) { + ESP_RETURN_ON_FALSE(1 << (bus_config->intr_priority) & I2C_ALLOW_INTR_PRIORITY_MASK, ESP_ERR_INVALID_ARG, TAG, "invalid interrupt priority:%d", bus_config->intr_priority); + } + + xSemaphoreTake(i2c_master->bus_lock_mux, portMAX_DELAY); + SLIST_INIT(&i2c_master->device_list); + xSemaphoreGive(i2c_master->bus_lock_mux); + + // Initialize the queue + if (bus_config->trans_queue_depth) { + i2c_master->asnyc_trans = true; + i2c_master->sent_all = true; + i2c_master->trans_finish = true; + i2c_master->new_queue = true; + i2c_master->queue_size = bus_config->trans_queue_depth; + i2c_master->queues_storage = (uint8_t*)heap_caps_calloc(bus_config->trans_queue_depth * I2C_TRANS_QUEUE_MAX, sizeof(i2c_transaction_t), I2C_MEM_ALLOC_CAPS); + ESP_RETURN_ON_FALSE(i2c_master->queues_storage, ESP_ERR_NO_MEM, TAG, "no mem for queue storage"); + i2c_transaction_t **pp_trans_desc = (i2c_transaction_t **)i2c_master->queues_storage; + for (int i = 0; i < I2C_TRANS_QUEUE_MAX; i++) { + i2c_master->trans_queues[i] = xQueueCreate(bus_config->trans_queue_depth, sizeof(i2c_transaction_t)); + + pp_trans_desc += bus_config->trans_queue_depth; + // sanity check + assert(i2c_master->trans_queues[i]); + } + i2c_transaction_t trans_pre = {}; + for (int i = 0; i < bus_config->trans_queue_depth ; i++) { + trans_pre = i2c_master->i2c_trans_pool[i]; + ESP_RETURN_ON_FALSE(xQueueSend(i2c_master->trans_queues[I2C_TRANS_QUEUE_READY], &trans_pre, 0) == pdTRUE, + ESP_ERR_INVALID_STATE, TAG, "ready queue full"); + } + + i2c_master->i2c_anyc_ops = (i2c_operation_t(**))heap_caps_calloc(bus_config->trans_queue_depth, sizeof(i2c_operation_t*), I2C_MEM_ALLOC_CAPS); + i2c_master->anyc_write_buffer = (uint8_t**)heap_caps_calloc(bus_config->trans_queue_depth, sizeof(uint8_t*), I2C_MEM_ALLOC_CAPS); + } + int isr_flags = I2C_INTR_ALLOC_FLAG; + if (bus_config->intr_priority) { + isr_flags |= 1 << (bus_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_MASTER_EVENT_INTR, i2c_master_isr_handler_default, i2c_master, &i2c_master->base->intr_handle); + ESP_GOTO_ON_ERROR(ret, err, TAG, "install i2c master interrupt failed"); + atomic_init(&i2c_master->status, I2C_STATUS_IDLE); + + i2c_ll_enable_intr_mask(hal->dev, I2C_LL_MASTER_EVENT_INTR); + i2c_ll_master_set_filter(hal->dev, bus_config->glitch_ignore_cnt); + + xSemaphoreGive(i2c_master->cmd_semphr); + + *ret_bus_handle = i2c_master; + return ESP_OK; + +err: + if (i2c_master) { + i2c_master_bus_destroy(i2c_master); + } + return ret; +} + +esp_err_t i2c_master_bus_add_device(i2c_master_bus_handle_t bus_handle, const i2c_device_config_t *dev_config, i2c_master_dev_handle_t *ret_handle) +{ + esp_err_t ret = ESP_OK; + ESP_RETURN_ON_FALSE((bus_handle != NULL), ESP_ERR_INVALID_ARG, TAG, "this bus is not initialized, please call `i2c_new_master_bus`"); + if(bus_handle->base->bus_mode != I2C_BUS_MODE_MASTER) { + ESP_LOGE(TAG, "This is not master bus!"); + return ESP_ERR_INVALID_ARG; + } + i2c_master_bus_t *i2c_master = bus_handle; + i2c_master_dev_t *i2c_dev = heap_caps_calloc(1, sizeof(i2c_master_dev_t), I2C_MEM_ALLOC_CAPS); + ESP_GOTO_ON_FALSE(i2c_dev, ESP_ERR_NO_MEM, err, TAG, "no memory for i2c master device"); + i2c_dev->device_address = dev_config->device_address; + i2c_dev->scl_speed_hz = dev_config->scl_speed_hz; + i2c_dev->addr_10bits = dev_config->dev_addr_length; + i2c_dev->master_bus = i2c_master; + + i2c_master_device_list_t *device_item = (i2c_master_device_list_t *)calloc(1, sizeof(i2c_master_device_list_t)); + ESP_RETURN_ON_FALSE((device_item != NULL), ESP_ERR_NO_MEM, TAG, "no memory for i2c device item`"); + device_item->device = i2c_dev; + xSemaphoreTake(bus_handle->bus_lock_mux, portMAX_DELAY); + SLIST_INSERT_HEAD(&bus_handle->device_list, device_item, next); + xSemaphoreGive(bus_handle->bus_lock_mux); + + *ret_handle = i2c_dev; + return ret; +err: + if (i2c_dev) { + i2c_master_bus_rm_device(i2c_dev); + } + return ret; +} + +esp_err_t i2c_master_bus_rm_device(i2c_master_dev_handle_t handle) +{ + ESP_RETURN_ON_FALSE((handle != NULL), ESP_ERR_INVALID_ARG, TAG, "this device is not initialized"); + ESP_RETURN_ON_FALSE((((int)atomic_load(&handle->master_bus->status) > (int)I2C_STATUS_START)), ESP_ERR_INVALID_STATE, TAG, "Wrong I2C status, cannot delete device"); + + i2c_master_bus_handle_t i2c_master = handle->master_bus; + i2c_master_device_list_t *device_item; + xSemaphoreTake(handle->master_bus->bus_lock_mux, portMAX_DELAY); + SLIST_FOREACH(device_item, &i2c_master->device_list, next) { + if (handle == device_item->device) { + SLIST_REMOVE(&i2c_master->device_list, device_item, i2c_master_device_list, next); + free(device_item); + break; + } + } + xSemaphoreGive(handle->master_bus->bus_lock_mux); + + if (handle) { + free(handle); + handle = NULL; + } + return ESP_OK; +} + +esp_err_t i2c_del_master_bus(i2c_master_bus_handle_t bus_handle) +{ + ESP_LOGD(TAG, "del i2c bus(%d)", bus_handle->base->port_num); + ESP_RETURN_ON_ERROR(i2c_master_bus_destroy(bus_handle), TAG, "destroy i2c bus failed"); + return ESP_OK; +} + +esp_err_t i2c_master_bus_reset(i2c_master_bus_handle_t handle) +{ + ESP_RETURN_ON_FALSE((handle != NULL), ESP_ERR_INVALID_ARG, TAG, "This bus is not initialized"); + // Reset I2C master bus + ESP_RETURN_ON_ERROR(s_i2c_hw_fsm_reset(handle), TAG, "I2C master bus reset failed"); + // Reset I2C status state + atomic_store(&handle->status, I2C_STATUS_IDLE); + return ESP_OK; +} + + +esp_err_t i2c_master_transmit(i2c_master_dev_handle_t i2c_dev, const uint8_t *write_buffer, size_t write_size, int xfer_timeout_ms) +{ + ESP_RETURN_ON_FALSE(i2c_dev != NULL, ESP_ERR_INVALID_ARG, TAG, "i2c handle not initialized"); + ESP_RETURN_ON_FALSE((write_buffer != NULL) && (write_size > 0), ESP_ERR_INVALID_ARG, TAG, "i2c transmit buffer or size invalid"); + + if (i2c_dev->master_bus->asnyc_trans == false) { + i2c_operation_t i2c_ops[] = { + {.hw_cmd = I2C_TRANS_START_COMMAND}, + {.hw_cmd = I2C_TRANS_WRITE_COMMAND(false), .data = (uint8_t *)write_buffer, .total_bytes = write_size}, + {.hw_cmd = I2C_TRANS_STOP_COMMAND}, + }; + ESP_RETURN_ON_ERROR(s_i2c_synchronous_transaction(i2c_dev, i2c_ops, DIM(i2c_ops), xfer_timeout_ms), TAG, "I2C transaction failed"); + } else { + i2c_dev->master_bus->anyc_write_buffer[i2c_dev->master_bus->index] = (uint8_t*)heap_caps_calloc(1, sizeof(uint8_t)*write_size, I2C_MEM_ALLOC_CAPS); + memcpy(i2c_dev->master_bus->anyc_write_buffer[i2c_dev->master_bus->index], write_buffer, write_size); + i2c_operation_t i2c_ops[] = { + {.hw_cmd = I2C_TRANS_START_COMMAND}, + {.hw_cmd = I2C_TRANS_WRITE_COMMAND(false), .data = (uint8_t *)i2c_dev->master_bus->anyc_write_buffer[i2c_dev->master_bus->index], .total_bytes = write_size}, + {.hw_cmd = I2C_TRANS_STOP_COMMAND}, + }; + ESP_RETURN_ON_ERROR(s_i2c_asynchronous_transaction(i2c_dev, i2c_ops, DIM(i2c_ops), xfer_timeout_ms), TAG, "I2C transaction failed"); + } + return ESP_OK; +} + +esp_err_t i2c_master_transmit_receive(i2c_master_dev_handle_t i2c_dev, const uint8_t *write_buffer, size_t write_size, uint8_t *read_buffer, size_t read_size, int xfer_timeout_ms) +{ + ESP_RETURN_ON_FALSE(i2c_dev != NULL, ESP_ERR_INVALID_ARG, TAG, "i2c handle not initialized"); + ESP_RETURN_ON_FALSE((write_buffer != NULL) && (write_size > 0), ESP_ERR_INVALID_ARG, TAG, "i2c transmit buffer or size invalid"); + ESP_RETURN_ON_FALSE((read_buffer != NULL) && (read_size > 0), ESP_ERR_INVALID_ARG, TAG, "i2c receive buffer or size invalid"); + + if (i2c_dev->master_bus->asnyc_trans == false) { + i2c_operation_t i2c_ops[] = { + {.hw_cmd = I2C_TRANS_START_COMMAND}, + {.hw_cmd = I2C_TRANS_WRITE_COMMAND(false), .data = (uint8_t *)write_buffer, .total_bytes = write_size}, + {.hw_cmd = I2C_TRANS_START_COMMAND}, + {.hw_cmd = I2C_TRANS_READ_COMMAND(ACK_VAL), .data = read_buffer, .total_bytes = read_size - 1}, + {.hw_cmd = I2C_TRANS_READ_COMMAND(NACK_VAL), .data = (read_buffer + read_size - 1), .total_bytes = 1}, + {.hw_cmd = I2C_TRANS_STOP_COMMAND}, + }; + ESP_RETURN_ON_ERROR(s_i2c_synchronous_transaction(i2c_dev, i2c_ops, DIM(i2c_ops), xfer_timeout_ms), TAG, "I2C transaction failed"); + } else { + i2c_dev->master_bus->anyc_write_buffer[i2c_dev->master_bus->index] = (uint8_t*)heap_caps_calloc(1, sizeof(uint8_t)*write_size, I2C_MEM_ALLOC_CAPS); + memcpy(i2c_dev->master_bus->anyc_write_buffer[i2c_dev->master_bus->index], write_buffer, write_size); + + i2c_operation_t i2c_ops[] = { + {.hw_cmd = I2C_TRANS_START_COMMAND}, + {.hw_cmd = I2C_TRANS_WRITE_COMMAND(false), .data = (uint8_t *)i2c_dev->master_bus->anyc_write_buffer[i2c_dev->master_bus->index], .total_bytes = write_size}, + {.hw_cmd = I2C_TRANS_START_COMMAND}, + {.hw_cmd = I2C_TRANS_READ_COMMAND(ACK_VAL), .data = read_buffer, .total_bytes = read_size - 1}, + {.hw_cmd = I2C_TRANS_READ_COMMAND(NACK_VAL), .data = (read_buffer + read_size - 1), .total_bytes = 1}, + {.hw_cmd = I2C_TRANS_STOP_COMMAND}, + }; + ESP_RETURN_ON_ERROR(s_i2c_asynchronous_transaction(i2c_dev, i2c_ops, DIM(i2c_ops), xfer_timeout_ms), TAG, "I2C transaction failed"); + } + return ESP_OK; +} + +esp_err_t i2c_master_receive(i2c_master_dev_handle_t i2c_dev, uint8_t *read_buffer, size_t read_size, int xfer_timeout_ms) +{ + ESP_RETURN_ON_FALSE(i2c_dev != NULL, ESP_ERR_INVALID_ARG, TAG, "i2c handle not initialized"); + ESP_RETURN_ON_FALSE((read_buffer != NULL) && (read_size > 0), ESP_ERR_INVALID_ARG, TAG, "i2c receive buffer or size invalid"); + + i2c_operation_t i2c_ops[] = { + {.hw_cmd = I2C_TRANS_START_COMMAND}, + {.hw_cmd = I2C_TRANS_READ_COMMAND(ACK_VAL), .data = read_buffer, .total_bytes = read_size - 1}, + {.hw_cmd = I2C_TRANS_READ_COMMAND(NACK_VAL), .data = (read_buffer + read_size - 1), .total_bytes = 1}, + {.hw_cmd = I2C_TRANS_STOP_COMMAND}, + }; + + if (i2c_dev->master_bus->asnyc_trans == false) { + ESP_RETURN_ON_ERROR(s_i2c_synchronous_transaction(i2c_dev, i2c_ops, DIM(i2c_ops), xfer_timeout_ms), TAG, "I2C transaction failed"); + } else { + ESP_RETURN_ON_ERROR(s_i2c_asynchronous_transaction(i2c_dev, i2c_ops, DIM(i2c_ops), xfer_timeout_ms), TAG, "I2C transaction failed"); + } + return ESP_OK; +} + +esp_err_t i2c_master_probe(i2c_master_bus_handle_t i2c_master, uint16_t address, int xfer_timeout_ms) +{ + ESP_RETURN_ON_FALSE(i2c_master != NULL, ESP_ERR_INVALID_ARG, TAG, "i2c handle not initialized"); + + i2c_operation_t i2c_ops[] = { + {.hw_cmd = I2C_TRANS_START_COMMAND}, + {.hw_cmd = I2C_TRANS_STOP_COMMAND}, + }; + + TickType_t ticks_to_wait = (xfer_timeout_ms == -1) ? portMAX_DELAY : pdMS_TO_TICKS(xfer_timeout_ms); + i2c_master->i2c_trans = (i2c_transaction_t) { + .device_address = address, + .ops = i2c_ops, + .cmd_count = DIM(i2c_ops), + }; + s_i2c_send_commands(i2c_master, ticks_to_wait); + if (i2c_master->status == I2C_STATUS_ACK_ERROR) { + return ESP_ERR_NOT_FOUND; + } + return ESP_OK; +} + +esp_err_t i2c_master_register_event_callbacks(i2c_master_dev_handle_t i2c_dev, const i2c_master_event_callbacks_t *cbs, void *user_data) +{ + ESP_RETURN_ON_FALSE(i2c_dev != NULL, ESP_ERR_INVALID_ARG, TAG, "i2c handle not initialized"); + + if (i2c_dev->master_bus->asnyc_trans == false) { + ESP_LOGE(TAG, "I2C transaction queue is not initialized, so you can't use callback here, please resister the bus again with trans_queue_depth != 0"); + return ESP_ERR_INVALID_STATE; + } + +#if CONFIG_I2C_ISR_IRAM_SAFE + if (cbs->on_trans_done) { + ESP_RETURN_ON_FALSE(esp_ptr_in_iram(cbs->on_trans_done), ESP_ERR_INVALID_ARG, TAG, "i2c trans done 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 // CONFIG_I2C_ISR_IRAM_SAFE + + i2c_dev->on_trans_done = cbs->on_trans_done; + i2c_dev->user_ctx = user_data; + return ESP_OK; +} + +esp_err_t i2c_master_wait_all_done(i2c_master_bus_handle_t i2c_master, int timeout_ms) +{ + ESP_RETURN_ON_FALSE(i2c_master, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + TickType_t wait_ticks = timeout_ms < 0 ? portMAX_DELAY : pdMS_TO_TICKS(timeout_ms); + i2c_transaction_t t; + + size_t cnt = i2c_master->num_trans_inflight; + for (size_t i = 0; i < cnt; i++) { + ESP_RETURN_ON_FALSE(xQueueReceive(i2c_master->trans_queues[I2C_TRANS_QUEUE_COMPLETE], &t, wait_ticks) == pdTRUE, + ESP_ERR_TIMEOUT, TAG, "flush timeout"); + ESP_RETURN_ON_FALSE(xQueueSend(i2c_master->trans_queues[I2C_TRANS_QUEUE_READY], &t, 0) == pdTRUE, + ESP_ERR_INVALID_STATE, TAG, "ready queue full"); + i2c_master->num_trans_inflight--; + } + return ESP_OK; + +} diff --git a/components/driver/i2c/i2c_private.h b/components/driver/i2c/i2c_private.h new file mode 100644 index 0000000000..6167cf27bf --- /dev/null +++ b/components/driver/i2c/i2c_private.h @@ -0,0 +1,209 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include +#include +#include "esp_err.h" +#include "driver/i2c_types.h" +#include "hal/i2c_hal.h" +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" +#include "freertos/task.h" +#include "freertos/ringbuf.h" +#include "esp_pm.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#if CONFIG_I2C_ISR_IRAM_SAFE +#define I2C_MEM_ALLOC_CAPS (MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT) +#else +#define I2C_MEM_ALLOC_CAPS (MALLOC_CAP_DEFAULT) +#endif + +// I2C driver object is per-mode, the interrupt source is shared between modes +#if CONFIG_I2C_ISR_IRAM_SAFE +#define I2C_INTR_ALLOC_FLAG (ESP_INTR_FLAG_SHARED | ESP_INTR_FLAG_IRAM | ESP_INTR_FLAG_LOWMED) +#else +#define I2C_INTR_ALLOC_FLAG (ESP_INTR_FLAG_SHARED | ESP_INTR_FLAG_LOWMED) +#endif + +#define I2C_ALLOW_INTR_PRIORITY_MASK ESP_INTR_FLAG_LOWMED + +#define I2C_PM_LOCK_NAME_LEN_MAX 16 +#define I2C_STATIC_OPERATION_ARRAY_MAX 6 + +#define ACK_VAL 0 +#define NACK_VAL 1 + +#define I2C_TRANS_READ_COMMAND(ack_value) {.ack_val = (ack_value), .op_code = I2C_LL_CMD_READ} +#define I2C_TRANS_WRITE_COMMAND(ack_check) {.ack_en = (ack_check), .op_code = I2C_LL_CMD_WRITE} +#define I2C_TRANS_STOP_COMMAND {.op_code = I2C_LL_CMD_STOP} +#define I2C_TRANS_START_COMMAND {.op_code = I2C_LL_CMD_RESTART} + +typedef struct i2c_bus_t i2c_bus_t; +typedef struct i2c_master_bus_t i2c_master_bus_t; +typedef struct i2c_bus_t *i2c_bus_handle_t; +typedef struct i2c_master_dev_t i2c_master_dev_t; + +typedef enum { + I2C_BUS_MODE_MASTER = 0, + I2C_BUS_MODE_SLAVE = 1, +} i2c_bus_mode_t; + +typedef enum { + I2C_SLAVE_FIFO = 0, + I2C_SLAVE_NONFIFO = 1, +} i2c_slave_fifo_mode_t; + +enum { + I2C_TRANS_QUEUE_READY, + I2C_TRANS_QUEUE_PROGRESS, + I2C_TRANS_QUEUE_COMPLETE, + I2C_TRANS_QUEUE_MAX, +}; + +typedef struct { + i2c_ll_hw_cmd_t hw_cmd; // I2C command, defined by hardware + uint8_t *data; // Pointer to data + uint16_t bytes_used; // I2C data has been used + size_t total_bytes; // Total bytes to be transferred +} i2c_operation_t; + +typedef struct { + uint32_t device_address; // Address of I2C device + i2c_operation_t *ops; // Pointer to I2C operation structure + size_t cmd_count; // Record how many I2C hardware commands in one transaction +} i2c_transaction_t; + +struct i2c_bus_t { + i2c_port_num_t port_num; // Port(Bus) ID, index from 0 + portMUX_TYPE spinlock; // To protect pre-group register level concurrency access + i2c_hal_context_t hal; // Hal layer for each port(bus) + i2c_clock_source_t clk_src; // Record the port clock source + uint32_t clk_src_freq_hz; // Record the clock source frequency + int sda_num; // SDA pin number + int scl_num; // SCL pin number + bool pull_up_enable; // Disable pull-ups + intr_handle_t intr_handle; // I2C interrupt handle + esp_pm_lock_handle_t pm_lock; // power manange lock +#if CONFIG_PM_ENABLE + char pm_lock_name[I2C_PM_LOCK_NAME_LEN_MAX]; // pm lock name +#endif + i2c_bus_mode_t bus_mode; // I2C bus mode +}; + +typedef struct i2c_master_device_list { + i2c_master_dev_t *device; + SLIST_ENTRY(i2c_master_device_list) next; +} i2c_master_device_list_t; + +struct i2c_master_bus_t { + i2c_bus_t *base; // bus base class + SemaphoreHandle_t bus_lock_mux; // semaphore to lock bus process + int cmd_idx; //record current command index, for master mode + _Atomic i2c_master_status_t status; // record current command status, for master mode + i2c_master_event_t event; // record current i2c bus event + int rx_cnt; // record current read index, for master mode + i2c_transaction_t i2c_trans; // Pointer to I2C transfer structure + i2c_operation_t i2c_ops[I2C_STATIC_OPERATION_ARRAY_MAX]; // I2C operation array + _Atomic uint16_t trans_idx; // Index of I2C transaction command. + SemaphoreHandle_t cmd_semphr; // Semaphore between task and interrupt, using for synchronizing ISR and I2C task. + uint32_t read_buf_pos; // Read buffer position + bool contains_read; // Whether command array includes read operation, true: yes, otherwise, false. + uint32_t read_len_static; // Read static buffer length + uint32_t w_r_size; // The size send/receive last time. + bool trans_over_buffer; // Data length is more than hardware fifo length, needs interrupt. + bool asnyc_trans; // asynchronous transaction, true after callback is installed. + volatile bool trans_done; // transaction command finish + SLIST_HEAD(i2c_master_device_list_head, i2c_master_device_list) device_list; // I2C device (instance) list + // asnyc trans members + bool async_break; // break transaction loop flag. + i2c_addr_bit_len_t addr_10bits_bus; // Slave address is 10 bits. + size_t queue_size; // I2C transaction queue size. + size_t num_trans_inflight; // Indicates the number of transactions that are undergoing but not recycled to ready_queue + size_t num_trans_inqueue; // Indicates the number of transaction in queue transaction. + void* queues_storage; // storage of transaction queues + bool sent_all; // true if the queue transaction is sent + bool in_progress; // true if current transaction is in progress + bool trans_finish; // true if current command has been sent out. + bool queue_trans; // true if current transaction is in queue + bool new_queue; // true if allow a new queue transaction + size_t index; // transaction index + QueueHandle_t trans_queues[I2C_TRANS_QUEUE_MAX]; // transaction queues. + StaticQueue_t trans_queue_structs[I2C_TRANS_QUEUE_MAX]; // memory to store the static structure for trans_queues + i2c_operation_t **i2c_anyc_ops; // pointer to asynchronous operation. + uint8_t **anyc_write_buffer; // pointer to asynchronous write buffer. + i2c_transaction_t i2c_trans_pool[]; // I2C transaction pool. +}; + +struct i2c_master_dev_t { + i2c_master_bus_t *master_bus; // I2C master bus base class + uint16_t device_address; // I2C device address + uint32_t scl_speed_hz; // SCL clock frequency + i2c_addr_bit_len_t addr_10bits; // Whether I2C device is a 10-bits address device. + i2c_master_callback_t on_trans_done; // I2C master transaction done callback. + void *user_ctx; // Callback user context +}; + +/** + * @brief Acquire I2C bus handle + * + * @param port_num I2C port number. + * @return + * - ESP_OK: Acquire bus handle successfully. + * - ESP_ERR_INVALID_ARG: Argument error. + * - ESP_ERR_INVALID_STATE: Acquire bus invalid state because bus has already acquired. + */ +esp_err_t i2c_acquire_bus_handle(i2c_port_num_t port_num, i2c_bus_handle_t *i2c_new_bus, i2c_bus_mode_t mode); + +/** + * @brief Release I2C bus handle + * + * @param i2c_bus I2C bus handle, returned from `i2c_acquire_bus_handle` + * @return ESP_OK: If release successfully + */ +uint8_t i2c_release_bus_handle(i2c_bus_handle_t i2c_bus); + +/** + * @brief Set clock source for I2C peripheral + * + * @param handle I2C bus handle + * @param clk_src Clock source + * @return + * - ESP_OK: Set clock source successfully + * - ESP_ERR_NOT_SUPPORTED: Set clock source failed because the clk_src is not supported + * - ESP_ERR_INVALID_STATE: Set clock source failed because the clk_src is different from other I2C controller + * - ESP_FAIL: Set clock source failed because of other error + */ +esp_err_t i2c_select_periph_clock(i2c_bus_handle_t handle, i2c_clock_source_t clk_src); + +/** + * @brief Set I2C SCL/SDA pins + * + * @param handle I2C bus handle + * @return + * - ESP_OK: I2C set SCL/SDA pins successfully. + * - ESP_ERR_INVALID_ARG: Argument error. + * - Otherwise: Set SCL/SDA IOs error. + */ +esp_err_t i2c_common_set_pins(i2c_bus_handle_t handle); + +/** + * @brief Check whether I2C bus is occupied + * + * @param port_num I2C port number. + * @return true: occupied, otherwise, false. + */ +bool i2c_bus_occupied(i2c_port_num_t port_num); + +#ifdef __cplusplus +} +#endif diff --git a/components/driver/i2c/include/driver/i2c_master.h b/components/driver/i2c/include/driver/i2c_master.h new file mode 100644 index 0000000000..d881acbd4b --- /dev/null +++ b/components/driver/i2c/include/driver/i2c_master.h @@ -0,0 +1,213 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include "esp_err.h" +#include "driver/i2c_types.h" +#include "hal/gpio_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief I2C master bus specific configurations + */ +typedef struct { + i2c_port_num_t i2c_port; /*!< I2C port number, `-1` for auto selecting */ + gpio_num_t sda_io_num; /*!< GPIO number of I2C SDA signal, pulled-up internally */ + gpio_num_t scl_io_num; /*!< GPIO number of I2C SCL signal, pulled-up internally */ + i2c_clock_source_t clk_source; /*!< Clock source of I2C master bus, channels in the same group must use the same clock source */ + uint8_t glitch_ignore_cnt; /*!< If the glitch period on the line is less than this value, it can be filtered out, typically value is 7 (unit: I2C module clock cycle)*/ + int intr_priority; /*!< I2C interrupt priority, if set to 0, driver will select the default priority (1,2,3). */ + size_t trans_queue_depth; /*!< Depth of internal transfer queue, increase this value can support more transfers pending in the background, only valid in asynchronous transaction. (Typically max_device_num * per_transaction)*/ + struct { + uint32_t enable_internal_pullup:1; /*!< Enable internal pullups */ + } flags; +} i2c_master_bus_config_t; + +/** + * @brief I2C device configuration + */ +typedef struct { + i2c_addr_bit_len_t dev_addr_length; /*!< Select the address length of the slave device. */ + uint16_t device_address; /*!< I2C device raw address. (The 7/10 bit address without read/write bit) */ + uint32_t scl_speed_hz; /*!< I2C SCL line frequency. */ +} i2c_device_config_t; + +/** + * @brief Group of I2C master callbacks, can be used to get status during transaction or doing other small things. But take care 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_master_callback_t on_trans_done; /*!< I2C master transaction finish callback */ +} i2c_master_event_callbacks_t; + +/** + * @brief Allocate an I2C master bus + * + * @param[in] bus_config I2C master bus configuration. + * @param[out] ret_bus_handle I2C bus handle + * @return + * - ESP_OK: I2C master bus initialized successfully. + * - ESP_ERR_INVALID_ARG: I2C bus initialization failed because of invalid argument. + * - ESP_ERR_NO_MEM: Create I2C bus failed because of out of memory. + * - ESP_ERR_NOT_FOUND: No more free bus. + */ +esp_err_t i2c_new_master_bus(const i2c_master_bus_config_t *bus_config, i2c_master_bus_handle_t *ret_bus_handle); + +/** + * @brief Add I2C master BUS device. + * + * @param[in] bus_handle I2C bus handle. + * @param[in] dev_config device config. + * @param[out] ret_handle device handle. + * @return + * - ESP_OK: Create I2C master device successfully. + * - ESP_ERR_INVALID_ARG: I2C bus initialization failed because of invalid argument. + * - ESP_ERR_NO_MEM: Create I2C bus failed because of out of memory. + */ +esp_err_t i2c_master_bus_add_device(i2c_master_bus_handle_t bus_handle, const i2c_device_config_t *dev_config, i2c_master_dev_handle_t *ret_handle); + +/** + * @brief Deinitialize the I2C master bus and delete the handle. + * + * @param[in] bus_handle I2C bus handle. + * @return + * - ESP_OK: Delete I2C bus success, otherwise, failed. + * - Otherwise: Some module delete failed. + */ +esp_err_t i2c_del_master_bus(i2c_master_bus_handle_t bus_handle); + +/** + * @brief I2C master bus delete device + * + * @param handle i2c device handle + * @return + * - ESP_OK: If device is successfully deleted. + */ +esp_err_t i2c_master_bus_rm_device(i2c_master_dev_handle_t handle); + +/** + * @brief Perform a write transaction on the I2C bus. + * The transaction will be undergoing until it finishes or it reaches + * the timeout provided. + * + * @note If a callback was registered with `i2c_master_register_event_callbacks`, the transaction will be asynchronous, and thus, this function will return directly, without blocking. + * You will get finish information from callback. Besides, data buffer should always be completely prepared when callback is registered, otherwise, the data will get corrupt. + * + * @param[in] i2c_dev I2C master device handle that created by `i2c_master_bus_add_device`. + * @param[in] write_buffer Data bytes to send on the I2C bus. + * @param[in] write_size Size, in bytes, of the write buffer. + * @param[in] xfer_timeout_ms Wait timeout, in ms. Note: -1 means wait forever. + * @return + * - ESP_OK: I2C master transmit success + * - ESP_ERR_INVALID_ARG: I2c master transmit parameter invalid. + * - ESP_ERR_TIMEOUT: Operation timeout(larger than xfer_timeout_ms) because the bus is busy or hardware crash. + */ +esp_err_t i2c_master_transmit(i2c_master_dev_handle_t i2c_dev, const uint8_t *write_buffer, size_t write_size, int xfer_timeout_ms); + +/** + * @brief Perform a write-read transaction on the I2C bus. + * The transaction will be undergoing until it finishes or it reaches + * the timeout provided. + * + * @note If a callback was registered with `i2c_master_register_event_callbacks`, the transaction will be asynchronous, and thus, this function will return directly, without blocking. + * You will get finish information from callback. Besides, data buffer should always be completely prepared when callback is registered, otherwise, the data will get corrupt. + * + * @param[in] i2c_dev I2C master device handle that created by `i2c_master_bus_add_device`. + * @param[in] write_buffer Data bytes to send on the I2C bus. + * @param[in] write_size Size, in bytes, of the write buffer. + * @param[out] read_buffer Data bytes received from i2c bus. + * @param[in] read_size Size, in bytes, of the read buffer. + * @param[in] xfer_timeout_ms Wait timeout, in ms. Note: -1 means wait forever. + * @return + * - ESP_OK: I2C master transmit-receive success + * - ESP_ERR_INVALID_ARG: I2c master transmit parameter invalid. + * - ESP_ERR_TIMEOUT: Operation timeout(larger than xfer_timeout_ms) because the bus is busy or hardware crash. + */ +esp_err_t i2c_master_transmit_receive(i2c_master_dev_handle_t i2c_dev, const uint8_t *write_buffer, size_t write_size, uint8_t *read_buffer, size_t read_size, int xfer_timeout_ms); + +/** + * @brief Perform a read transaction on the I2C bus. + * The transaction will be undergoing until it finishes or it reaches + * the timeout provided. + * + * @note If a callback was registered with `i2c_master_register_event_callbacks`, the transaction will be asynchronous, and thus, this function will return directly, without blocking. + * You will get finish information from callback. Besides, data buffer should always be completely prepared when callback is registered, otherwise, the data will get corrupt. + * + * @param[in] i2c_dev I2C master device handle that created by `i2c_master_bus_add_device`. + * @param[out] read_buffer Data bytes received from i2c bus. + * @param[in] read_size Size, in bytes, of the read buffer. + * @param[in] xfer_timeout_ms Wait timeout, in ms. Note: -1 means wait forever. + * @return + * - ESP_OK: I2C master receive success + * - ESP_ERR_INVALID_ARG: I2c master receive parameter invalid. + * - ESP_ERR_TIMEOUT: Operation timeout(larger than xfer_timeout_ms) because the bus is busy or hardware crash. + */ +esp_err_t i2c_master_receive(i2c_master_dev_handle_t i2c_dev, uint8_t *read_buffer, size_t read_size, int xfer_timeout_ms); + +/** + * @brief Probe I2C address, if address is correct and ACK is received, this function will return ESP_OK. + * + * @param[in] i2c_dev I2C master device handle that created by `i2c_master_bus_add_device`. + * @param[in] xfer_timeout_ms Wait timeout, in ms. Note: -1 means wait forever (Not recommended in this function). + * @return + * - ESP_OK: I2C device probe successfully + * - ESP_ERR_TIMEOUT: Operation timeout(larger than xfer_timeout_ms) because the bus is busy or hardware crash. + */ +// esp_err_t i2c_master_probe(i2c_master_dev_handle_t i2c_dev, int xfer_timeout_ms); +esp_err_t i2c_master_probe(i2c_master_bus_handle_t i2c_master, uint16_t address, int xfer_timeout_ms); + +/** + * @brief Register I2C transaction callbacks for a master device + * + * @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. + * @note If the callback is used for helping asynchronous transaction. On the same bus, only one device can be used for performing asynchronous operation. + * + * @param[in] i2c_dev I2C master device handle that created by `i2c_master_bus_add_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_master_register_event_callbacks(i2c_master_dev_handle_t i2c_dev, const i2c_master_event_callbacks_t *cbs, void *user_data); + +/** + * @brief Reset the I2C master bus. + * + * @param handle I2C bus handle. + * @return + * - ESP_OK: Reset succeed. + * - ESP_ERR_INVALID_ARG: I2C master bus handle is not initialized. + * - Otherwise: Reset failed. + */ +esp_err_t i2c_master_bus_reset(i2c_master_bus_handle_t handle); + +/** + * @brief Wait for all pending I2C transactions done + * + * @param[in] i2c_master I2C bus handle + * @param[in] timeout_ms Wait timeout, in ms. Specially, -1 means to wait forever. + * @return + * - ESP_OK: Flush transactions successfully + * - ESP_ERR_INVALID_ARG: Flush transactions failed because of invalid argument + * - ESP_ERR_TIMEOUT: Flush transactions failed because of timeout + * - ESP_FAIL: Flush transactions failed because of other error + */ +esp_err_t i2c_master_wait_all_done(i2c_master_bus_handle_t i2c_master, int timeout_ms); + +#ifdef __cplusplus +} +#endif diff --git a/components/driver/i2c/include/driver/i2c_types.h b/components/driver/i2c/include/driver/i2c_types.h new file mode 100644 index 0000000000..dd1683852b --- /dev/null +++ b/components/driver/i2c/include/driver/i2c_types.h @@ -0,0 +1,74 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include +#include "hal/i2c_types.h" +#include "soc/soc_caps.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief I2C port number. + */ +typedef int i2c_port_num_t; + +/** + * @brief Enumeration for I2C fsm status. + */ +typedef enum { + I2C_STATUS_READ, /*!< read status for current master command */ + I2C_STATUS_WRITE, /*!< write status for current master command */ + I2C_STATUS_START, /*!< Start status for current master command */ + I2C_STATUS_STOP, /*!< stop status for current master command */ + I2C_STATUS_IDLE, /*!< idle status for current master command */ + I2C_STATUS_ACK_ERROR, /*!< ack error status for current master command */ + I2C_STATUS_DONE, /*!< I2C command done */ + I2C_STATUS_TIMEOUT, /*!< I2C bus status error, and operation timeout */ +} i2c_master_status_t; + + +typedef enum { + I2C_EVENT_ALIVE, /*!< i2c bus in alive status.*/ + I2C_EVENT_DONE, /*!< i2c bus transaction done */ + I2C_EVENT_NACK, /*!< i2c bus nack */ +} i2c_master_event_t; + +/** + * @brief Type of I2C master bus handle + */ +typedef struct i2c_master_bus_t *i2c_master_bus_handle_t; + +/** + * @brief Type of I2C master bus device handle + */ +typedef struct i2c_master_dev_t *i2c_master_dev_handle_t; + +/** + * @brief Data type used in I2C event callback + */ +typedef struct { + i2c_master_event_t event; /**< The I2C hardware event that I2C callback is called. */ +} i2c_master_event_data_t; + +/** + * @brief An callback for I2C transaction. + * + * @param[in] i2c_dev Handle for I2C device. + * @param[out] evt_data I2C capture event data, fed by driver + * @param[in] arg User data, set in `i2c_master_register_event_callbacks()` + * + * @return Whether a high priority task has been waken up by this function + */ +typedef bool (*i2c_master_callback_t)(i2c_master_dev_handle_t i2c_dev, const i2c_master_event_data_t *evt_data, void *arg); + +#ifdef __cplusplus +} +#endif diff --git a/components/driver/i2c/linker.lf b/components/driver/i2c/linker.lf new file mode 100644 index 0000000000..c16bd8c206 --- /dev/null +++ b/components/driver/i2c/linker.lf @@ -0,0 +1,8 @@ +[mapping:i2c_driver] +archive: libdriver.a +entries: + if I2C_ISR_IRAM_SAFE = y: + i2c_master: s_i2c_send_command_async (noflash) + i2c_master: s_i2c_write_command (noflash) + i2c_master: s_i2c_read_command (noflash) + i2c_master: s_i2c_start_end_command (noflash) diff --git a/components/driver/test_apps/i2c/CMakeLists.txt b/components/driver/test_apps/i2c_test_apps/CMakeLists.txt similarity index 100% rename from components/driver/test_apps/i2c/CMakeLists.txt rename to components/driver/test_apps/i2c_test_apps/CMakeLists.txt diff --git a/components/driver/test_apps/i2c/README.md b/components/driver/test_apps/i2c_test_apps/README.md similarity index 100% rename from components/driver/test_apps/i2c/README.md rename to components/driver/test_apps/i2c_test_apps/README.md diff --git a/components/driver/test_apps/i2c/main/CMakeLists.txt b/components/driver/test_apps/i2c_test_apps/main/CMakeLists.txt similarity index 100% rename from components/driver/test_apps/i2c/main/CMakeLists.txt rename to components/driver/test_apps/i2c_test_apps/main/CMakeLists.txt diff --git a/components/driver/test_apps/i2c/main/test_app_main.c b/components/driver/test_apps/i2c_test_apps/main/test_app_main.c similarity index 100% rename from components/driver/test_apps/i2c/main/test_app_main.c rename to components/driver/test_apps/i2c_test_apps/main/test_app_main.c diff --git a/components/driver/test_apps/i2c_test_apps/main/test_i2c.c b/components/driver/test_apps/i2c_test_apps/main/test_i2c.c new file mode 100644 index 0000000000..282b2526b8 --- /dev/null +++ b/components/driver/test_apps/i2c_test_apps/main/test_i2c.c @@ -0,0 +1,131 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#include +#include +#include "sdkconfig.h" +#include "unity.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_err.h" +#include "soc/gpio_periph.h" +#include "soc/clk_tree_defs.h" +#include "soc/soc_caps.h" +#include "hal/gpio_hal.h" +#include "hal/uart_ll.h" +#include "esp_private/periph_ctrl.h" +#include "driver/gpio.h" +#include "driver/i2c_master.h" +#include "esp_rom_gpio.h" +#include "esp_log.h" +#include "test_utils.h" + +static const char TAG[] = "test-i2c"; + +#define TEST_I2C_SCL_PIN 2 +#define TEST_I2C_SDA_PIN 4 +#define TEST_I2C_SCL_FREQ (100 * 1000) +#define TEST_I2C_PORT 0 +#define SLAVE_ADDR 0x58 + + +TEST_CASE("I2C bus install-uninstall test", "[i2c]") +{ + i2c_master_bus_config_t i2c_mst_config_1 = { + .clk_source = I2C_CLK_SRC_DEFAULT, + .i2c_port = TEST_I2C_PORT, + .scl_io_num = TEST_I2C_SCL_PIN, + .sda_io_num = TEST_I2C_SDA_PIN, + }; + i2c_master_bus_handle_t i2c_mst_handle1; + +#if SOC_I2C_NUM > 1 + i2c_master_bus_config_t i2c_mst_config_2 = { + .clk_source = I2C_CLK_SRC_DEFAULT, + .i2c_port = 1, + .scl_io_num = TEST_I2C_SCL_PIN, + .sda_io_num = TEST_I2C_SDA_PIN, + }; + i2c_master_bus_handle_t i2c_mst_handle2; +#endif + // Install master bus 0 + ESP_LOGI(TAG, "Initialize bus0"); + TEST_ESP_OK(i2c_new_master_bus(&i2c_mst_config_1, &i2c_mst_handle1)); +#if SOC_I2C_NUM > 1 + // Install master bus 1 + ESP_LOGI(TAG, "Initialize bus1"); + TEST_ESP_OK(i2c_new_master_bus(&i2c_mst_config_2, &i2c_mst_handle2)); +#endif + // Install master bus 0 again + ESP_LOGI(TAG, "Initialize bus0 again"); + TEST_ESP_ERR(ESP_ERR_INVALID_STATE, i2c_new_master_bus(&i2c_mst_config_1, &i2c_mst_handle1)); + ESP_LOGI(TAG, "Delete bus0"); + TEST_ESP_OK(i2c_del_master_bus(i2c_mst_handle1)); +#if SOC_I2C_NUM > 1 + ESP_LOGI(TAG, "Delete bus1"); + TEST_ESP_OK(i2c_del_master_bus(i2c_mst_handle2)); +#endif +} + +TEST_CASE("I2C driver memory leaking check", "[i2c]") +{ + i2c_master_bus_config_t i2c_mst_config_1 = { + .clk_source = I2C_CLK_SRC_DEFAULT, + .i2c_port = TEST_I2C_PORT, + .scl_io_num = TEST_I2C_SCL_PIN, + .sda_io_num = TEST_I2C_SDA_PIN, + }; + i2c_master_bus_handle_t bus_handle; + + int size = esp_get_free_heap_size(); + for (uint32_t i = 0; i <= 5; i++) { + TEST_ESP_OK(i2c_new_master_bus(&i2c_mst_config_1, &bus_handle)); + vTaskDelay(10 / portTICK_PERIOD_MS); + TEST_ESP_OK(i2c_del_master_bus(bus_handle)); + } + + TEST_ASSERT_INT_WITHIN(300, size, esp_get_free_heap_size()); +} + +TEST_CASE("I2C device add & remove check", "[i2c]") +{ + i2c_master_bus_config_t i2c_mst_config_1 = { + .clk_source = I2C_CLK_SRC_DEFAULT, + .i2c_port = TEST_I2C_PORT, + .scl_io_num = TEST_I2C_SCL_PIN, + .sda_io_num = TEST_I2C_SDA_PIN, + }; + i2c_master_bus_handle_t bus_handle; + + TEST_ESP_OK(i2c_new_master_bus(&i2c_mst_config_1, &bus_handle)); + + i2c_device_config_t dev_cfg_1 = { + .scl_speed_hz = 100*1000, + .device_address = 0x10, + }; + i2c_master_dev_handle_t dev_1; + i2c_master_bus_add_device(bus_handle, &dev_cfg_1, &dev_1); + + i2c_device_config_t dev_cfg_2 = { + .scl_speed_hz = 100*1000, + .device_address = 0x20, + }; + i2c_master_dev_handle_t dev_2; + i2c_master_bus_add_device(bus_handle, &dev_cfg_2, &dev_2); + + i2c_device_config_t dev_cfg_3 = { + .scl_speed_hz = 100*1000, + .device_address = 0x30, + }; + i2c_master_dev_handle_t dev_3; + i2c_master_bus_add_device(bus_handle, &dev_cfg_3, &dev_3); + + i2c_master_bus_rm_device(dev_1); + i2c_master_bus_rm_device(dev_2); + i2c_master_bus_rm_device(dev_3); + + TEST_ESP_OK(i2c_del_master_bus(bus_handle)); +} diff --git a/components/driver/test_apps/i2c_test_apps/pytest_i2c.py b/components/driver/test_apps/i2c_test_apps/pytest_i2c.py new file mode 100644 index 0000000000..21bd8b4348 --- /dev/null +++ b/components/driver/test_apps/i2c_test_apps/pytest_i2c.py @@ -0,0 +1,19 @@ +# SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: CC0-1.0 + +import pytest +from pytest_embedded import Dut + + +@pytest.mark.supported_targets +@pytest.mark.generic +@pytest.mark.parametrize( + 'config', + [ + 'release', + 'iram_safe', + ], + indirect=True, +) +def test_i2c(dut: Dut) -> None: + dut.run_all_single_board_cases() diff --git a/components/driver/test_apps/i2c/sdkconfig.ci.defaults b/components/driver/test_apps/i2c_test_apps/sdkconfig.ci.defaults similarity index 100% rename from components/driver/test_apps/i2c/sdkconfig.ci.defaults rename to components/driver/test_apps/i2c_test_apps/sdkconfig.ci.defaults diff --git a/components/driver/test_apps/i2c_test_apps/sdkconfig.ci.iram_safe b/components/driver/test_apps/i2c_test_apps/sdkconfig.ci.iram_safe new file mode 100644 index 0000000000..3d82486863 --- /dev/null +++ b/components/driver/test_apps/i2c_test_apps/sdkconfig.ci.iram_safe @@ -0,0 +1,6 @@ +CONFIG_PM_ENABLE=y +CONFIG_COMPILER_DUMP_RTL_FILES=y +CONFIG_FREERTOS_USE_TICKLESS_IDLE=y +CONFIG_COMPILER_OPTIMIZATION_NONE=y +CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT=y +CONFIG_I2C_ISR_IRAM_SAFE=y diff --git a/components/driver/test_apps/i2c/sdkconfig.ci.release b/components/driver/test_apps/i2c_test_apps/sdkconfig.ci.release similarity index 100% rename from components/driver/test_apps/i2c/sdkconfig.ci.release rename to components/driver/test_apps/i2c_test_apps/sdkconfig.ci.release diff --git a/components/driver/test_apps/i2c/sdkconfig.defaults b/components/driver/test_apps/i2c_test_apps/sdkconfig.defaults similarity index 100% rename from components/driver/test_apps/i2c/sdkconfig.defaults rename to components/driver/test_apps/i2c_test_apps/sdkconfig.defaults diff --git a/components/driver/test_apps/legacy_i2c_driver/CMakeLists.txt b/components/driver/test_apps/legacy_i2c_driver/CMakeLists.txt new file mode 100644 index 0000000000..1864548037 --- /dev/null +++ b/components/driver/test_apps/legacy_i2c_driver/CMakeLists.txt @@ -0,0 +1,22 @@ +# This is the project CMakeLists.txt file for the test subproject +cmake_minimum_required(VERSION 3.16) + +set(EXTRA_COMPONENT_DIRS + "$ENV{IDF_PATH}/tools/unit-test-app/components" +) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(legacy_i2c_test) + +if(CONFIG_COMPILER_DUMP_RTL_FILES) + add_custom_target(check_test_app_sections ALL + COMMAND ${PYTHON} $ENV{IDF_PATH}/tools/ci/check_callgraph.py + --rtl-dirs ${CMAKE_BINARY_DIR}/esp-idf/driver/,${CMAKE_BINARY_DIR}/esp-idf/hal/ + --elf-file ${CMAKE_BINARY_DIR}/legacy_i2c_test.elf + find-refs + --from-sections=.iram0.text + --to-sections=.flash.text,.flash.rodata + --exit-code + DEPENDS ${elf} + ) +endif() diff --git a/components/driver/test_apps/legacy_i2c_driver/README.md b/components/driver/test_apps/legacy_i2c_driver/README.md new file mode 100644 index 0000000000..a8b7833fa3 --- /dev/null +++ b/components/driver/test_apps/legacy_i2c_driver/README.md @@ -0,0 +1,2 @@ +| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C6 | ESP32-H2 | ESP32-S2 | ESP32-S3 | +| ----------------- | ----- | -------- | -------- | -------- | -------- | -------- | -------- | diff --git a/components/driver/test_apps/legacy_i2c_driver/main/CMakeLists.txt b/components/driver/test_apps/legacy_i2c_driver/main/CMakeLists.txt new file mode 100644 index 0000000000..23741b95bc --- /dev/null +++ b/components/driver/test_apps/legacy_i2c_driver/main/CMakeLists.txt @@ -0,0 +1,6 @@ +set(srcs "test_app_main.c" + "test_i2c.c" + ) + +idf_component_register(SRCS ${srcs} + WHOLE_ARCHIVE) diff --git a/components/driver/test_apps/legacy_i2c_driver/main/test_app_main.c b/components/driver/test_apps/legacy_i2c_driver/main/test_app_main.c new file mode 100644 index 0000000000..9ceeb2a3eb --- /dev/null +++ b/components/driver/test_apps/legacy_i2c_driver/main/test_app_main.c @@ -0,0 +1,42 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "unity.h" +#include "unity_test_runner.h" +#include "unity_test_utils_memory.h" +#include "esp_heap_caps.h" + +// Some resources are lazy allocated in I2C driver, so we reserved this threshold when checking memory leak +// A better way to check a potential memory leak is running a same case by twice, for the second time, the memory usage delta should be zero +#define LEAKS (400) + +void setUp(void) +{ + unity_utils_record_free_mem(); +} + +void tearDown(void) +{ + unity_utils_evaluate_leaks_direct(LEAKS); +} + +void app_main(void) +{ + + // ___ ____ ____ _____ _ + // |_ _|___ \ / ___| |_ _|__ ___| |_ + // | | __) | | | |/ _ \/ __| __| + // | | / __/| |___ | | __/\__ \ |_ + // |___|_____|\____| |_|\___||___/\__| + + printf(" ___ ____ ____ _____ _ \n"); + printf("|_ _|___ \\ / ___| |_ _|__ ___| |_ \n"); + printf(" | | __) | | | |/ _ \\/ __| __|\n"); + printf(" | | / __/| |___ | | __/\\__ \\ |_ \n"); + printf("|___|_____|\\____| |_|\\___||___/\\__|\n"); + + unity_run_menu(); +} diff --git a/components/driver/test_apps/i2c/main/test_i2c.c b/components/driver/test_apps/legacy_i2c_driver/main/test_i2c.c similarity index 100% rename from components/driver/test_apps/i2c/main/test_i2c.c rename to components/driver/test_apps/legacy_i2c_driver/main/test_i2c.c diff --git a/components/driver/test_apps/i2c/pytest_i2c.py b/components/driver/test_apps/legacy_i2c_driver/pytest_i2c_legacy.py similarity index 87% rename from components/driver/test_apps/i2c/pytest_i2c.py rename to components/driver/test_apps/legacy_i2c_driver/pytest_i2c_legacy.py index 8e016ba1c3..9d2ccbb2cf 100644 --- a/components/driver/test_apps/i2c/pytest_i2c.py +++ b/components/driver/test_apps/legacy_i2c_driver/pytest_i2c_legacy.py @@ -15,7 +15,7 @@ from pytest_embedded import Dut ], indirect=True, ) -def test_i2c(dut: Dut) -> None: +def test_i2c_legacy(dut: Dut) -> None: dut.run_all_single_board_cases() @@ -33,7 +33,7 @@ def test_i2c(dut: Dut) -> None: ], indirect=True ) -def test_i2c_multi_dev(case_tester) -> None: # type: ignore +def test_i2c_multi_dev_legacy(case_tester) -> None: # type: ignore for case in case_tester.test_menu: if case.attributes.get('test_env', 'generic_multi_device') == 'generic_multi_device': case_tester.run_multi_dev_case(case=case, reset=True) diff --git a/components/driver/test_apps/legacy_i2c_driver/sdkconfig.ci.defaults b/components/driver/test_apps/legacy_i2c_driver/sdkconfig.ci.defaults new file mode 100644 index 0000000000..b308cb2ddd --- /dev/null +++ b/components/driver/test_apps/legacy_i2c_driver/sdkconfig.ci.defaults @@ -0,0 +1,2 @@ +CONFIG_FREERTOS_HZ=1000 +CONFIG_ESP_TASK_WDT=n diff --git a/components/driver/test_apps/i2c/sdkconfig.ci.iram_safe b/components/driver/test_apps/legacy_i2c_driver/sdkconfig.ci.iram_safe similarity index 100% rename from components/driver/test_apps/i2c/sdkconfig.ci.iram_safe rename to components/driver/test_apps/legacy_i2c_driver/sdkconfig.ci.iram_safe diff --git a/components/driver/test_apps/legacy_i2c_driver/sdkconfig.ci.release b/components/driver/test_apps/legacy_i2c_driver/sdkconfig.ci.release new file mode 100644 index 0000000000..91d93f163e --- /dev/null +++ b/components/driver/test_apps/legacy_i2c_driver/sdkconfig.ci.release @@ -0,0 +1,5 @@ +CONFIG_PM_ENABLE=y +CONFIG_FREERTOS_USE_TICKLESS_IDLE=y +CONFIG_COMPILER_OPTIMIZATION_SIZE=y +CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE=y +CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT=y diff --git a/components/driver/test_apps/legacy_i2c_driver/sdkconfig.defaults b/components/driver/test_apps/legacy_i2c_driver/sdkconfig.defaults new file mode 100644 index 0000000000..b308cb2ddd --- /dev/null +++ b/components/driver/test_apps/legacy_i2c_driver/sdkconfig.defaults @@ -0,0 +1,2 @@ +CONFIG_FREERTOS_HZ=1000 +CONFIG_ESP_TASK_WDT=n diff --git a/components/hal/esp32/include/hal/i2c_ll.h b/components/hal/esp32/include/hal/i2c_ll.h index f2903de44a..6fd79c4e39 100644 --- a/components/hal/esp32/include/hal/i2c_ll.h +++ b/components/hal/esp32/include/hal/i2c_ll.h @@ -25,13 +25,13 @@ extern "C" { */ typedef union { struct { - uint32_t byte_num: 8, - ack_en: 1, - ack_exp: 1, - ack_val: 1, - op_code: 3, - reserved14: 17, - done: 1; + uint32_t byte_num: 8, /*!< Specifies the length of data (in bytes) to be read or written */ + ack_en: 1, /*!< Used to enable the I2C controller during a write operation to check whether ACK */ + ack_exp: 1, /*!< Used to configure the level of the ACK bit expected by the I2C controller during a write operation */ + ack_val: 1, /*!< Used to configure the level of the ACK bit sent by the I2C controller during a read operation */ + op_code: 3, /*!< Indicates the command */ + reserved14: 17, /*!< Reserved bits */ + done: 1; /*!< Indicates that a command has been executed */ }; uint32_t val; } i2c_ll_hw_cmd_t; @@ -44,26 +44,25 @@ typedef union { #define I2C_LL_CMD_END 4 /*!scl_low = half_cycle; @@ -94,7 +93,7 @@ static inline void i2c_ll_cal_bus_clk(uint32_t source_clk, uint32_t bus_freq, i2 * * @return None */ -static inline void i2c_ll_set_bus_timing(i2c_dev_t *hw, i2c_hal_clk_config_t *bus_cfg) +static inline void i2c_ll_master_set_bus_timing(i2c_dev_t *hw, i2c_hal_clk_config_t *bus_cfg) { /* SCL period. According to the TRM, we should always subtract 1 to SCL low period */ assert(bus_cfg->scl_low > 0); @@ -130,6 +129,18 @@ static inline void i2c_ll_set_bus_timing(i2c_dev_t *hw, i2c_hal_clk_config_t *bu hw->timeout.tout = bus_cfg->tout; } +/** + * @brief Set fractional divider + * + * @param hw Beginning address of the peripheral registers + * @param div_a The denominator of the frequency divider factor of the i2c function clock + * @param div_b The numerator of the frequency divider factor of the i2c function clock. + */ +static inline void i2c_ll_master_set_fractional_divider(i2c_dev_t *hw, uint8_t div_a, uint8_t div_b) +{ + // Not supported on ESP32 +} + /** * @brief Reset I2C txFIFO * @@ -137,6 +148,7 @@ static inline void i2c_ll_set_bus_timing(i2c_dev_t *hw, i2c_hal_clk_config_t *bu * * @return None */ +__attribute__((always_inline)) static inline void i2c_ll_txfifo_rst(i2c_dev_t *hw) { hw->fifo_conf.tx_fifo_rst = 1; @@ -150,27 +162,13 @@ static inline void i2c_ll_txfifo_rst(i2c_dev_t *hw) * * @return None */ +__attribute__((always_inline)) static inline void i2c_ll_rxfifo_rst(i2c_dev_t *hw) { hw->fifo_conf.rx_fifo_rst = 1; hw->fifo_conf.rx_fifo_rst = 0; } -/** - * @brief Configure I2C SCL timing - * - * @param hw Beginning address of the peripheral registers - * @param hight_period The I2C SCL hight period (in APB cycle) - * @param low_period The I2C SCL low period (in APB cycle) - * - * @return None. - */ -static inline void i2c_ll_set_scl_timing(i2c_dev_t *hw, int hight_period, int low_period) -{ - hw->scl_low_period.period = low_period; - hw->scl_high_period.period = hight_period; -} - /** * @brief Clear I2C interrupt status * @@ -193,6 +191,7 @@ static inline void i2c_ll_clear_intr_mask(i2c_dev_t *hw, uint32_t mask) * * @return None */ +__attribute__((always_inline)) static inline void i2c_ll_enable_intr_mask(i2c_dev_t *hw, uint32_t mask) { hw->int_ena.val |= mask; @@ -233,7 +232,7 @@ static inline void i2c_ll_get_intr_mask(i2c_dev_t *hw, uint32_t *intr_status) * * @return None */ -static inline void i2c_ll_set_fifo_mode(i2c_dev_t *hw, bool fifo_mode_en) +static inline void i2c_ll_slave_set_fifo_mode(i2c_dev_t *hw, bool fifo_mode_en) { hw->fifo_conf.nonfifo_en = fifo_mode_en ? 0 : 1; } @@ -251,6 +250,19 @@ static inline void i2c_ll_set_tout(i2c_dev_t *hw, int tout) hw->timeout.tout = tout; } +/** + * @brief Configure I2C slave broadcasting mode. + * + * @param hw Beginning address of the peripheral registers + * @param broadcast_en Set true to enable broadcast, else, set it false + * + * @return None + */ +static inline void i2c_ll_slave_broadcast_enable(i2c_dev_t *hw, bool broadcast_en) +{ + // Not supported on esp32 +} + /** * @brief Configure I2C slave address * @@ -262,8 +274,14 @@ static inline void i2c_ll_set_tout(i2c_dev_t *hw, int tout) */ static inline void i2c_ll_set_slave_addr(i2c_dev_t *hw, uint16_t slave_addr, bool addr_10bit_en) { - hw->slave_addr.addr = slave_addr; hw->slave_addr.en_10bit = addr_10bit_en; + if (addr_10bit_en) { + uint8_t addr_14_7 = (slave_addr & 0xff) << 7; + uint8_t addr_6_0 = ((slave_addr & 0x300) >> 8) || 0x78; + hw->slave_addr.addr = addr_14_7 || addr_6_0; + } else { + hw->slave_addr.addr = slave_addr; + } } /** @@ -276,7 +294,7 @@ static inline void i2c_ll_set_slave_addr(i2c_dev_t *hw, uint16_t slave_addr, boo * @return None */ __attribute__((always_inline)) -static inline void i2c_ll_write_cmd_reg(i2c_dev_t *hw, i2c_ll_hw_cmd_t cmd, int cmd_idx) +static inline void i2c_ll_master_write_cmd_reg(i2c_dev_t *hw, i2c_ll_hw_cmd_t cmd, int cmd_idx) { hw->command[cmd_idx].val = cmd.val; } @@ -290,7 +308,7 @@ static inline void i2c_ll_write_cmd_reg(i2c_dev_t *hw, i2c_ll_hw_cmd_t cmd, int * * @return None */ -static inline void i2c_ll_set_start_timing(i2c_dev_t *hw, int start_setup, int start_hold) +static inline void i2c_ll_master_set_start_timing(i2c_dev_t *hw, int start_setup, int start_hold) { hw->scl_rstart_setup.time = start_setup; hw->scl_start_hold.time = start_hold; @@ -305,7 +323,7 @@ static inline void i2c_ll_set_start_timing(i2c_dev_t *hw, int start_setup, int s * * @return None */ -static inline void i2c_ll_set_stop_timing(i2c_dev_t *hw, int stop_setup, int stop_hold) +static inline void i2c_ll_master_set_stop_timing(i2c_dev_t *hw, int stop_setup, int stop_hold) { hw->scl_stop_setup.time = stop_setup; hw->scl_stop_hold.time = stop_hold; @@ -367,21 +385,6 @@ static inline void i2c_ll_set_data_mode(i2c_dev_t *hw, i2c_trans_mode_t tx_mode, hw->ctr.rx_lsb_first = rx_mode; } -/** - * @brief Get the I2C data mode - * - * @param hw Beginning address of the peripheral registers - * @param tx_mode Pointer to accept the received bytes mode - * @param rx_mode Pointer to accept the sended bytes mode - * - * @return None - */ -static inline void i2c_ll_get_data_mode(i2c_dev_t *hw, i2c_trans_mode_t *tx_mode, i2c_trans_mode_t *rx_mode) -{ - *tx_mode = hw->ctr.tx_lsb_first; - *rx_mode = hw->ctr.rx_lsb_first; -} - /** * @brief Get I2C sda timing configuration * @@ -405,6 +408,7 @@ static inline void i2c_ll_get_sda_timing(i2c_dev_t *hw, int *sda_sample, int *sd * * @return True if I2C state machine is busy, else false will be returned */ +__attribute__((always_inline)) static inline bool i2c_ll_is_bus_busy(i2c_dev_t *hw) { return hw->status_reg.bus_busy; @@ -456,7 +460,7 @@ static inline void i2c_ll_get_tout(i2c_dev_t *hw, int *timeout) * @return None */ __attribute__((always_inline)) -static inline void i2c_ll_trans_start(i2c_dev_t *hw) +static inline void i2c_ll_master_trans_start(i2c_dev_t *hw) { hw->ctr.trans_start = 1; } @@ -491,21 +495,6 @@ static inline void i2c_ll_get_stop_timing(i2c_dev_t *hw, int *setup_time, int *h *hold_time = hw->scl_stop_hold.time; } -/** - * @brief Get I2C SCL timing configuration - * - * @param hw Beginning address of the peripheral registers - * @param high_period Pointer to accept the SCL high period - * @param low_period Pointer to accept the SCL low period - * - * @return None - */ -static inline void i2c_ll_get_scl_timing(i2c_dev_t *hw, int *high_period, int *low_period) -{ - *high_period = hw->scl_high_period.period; - *low_period = hw->scl_low_period.period; -} - /** * @brief Write the I2C hardware txFIFO * @@ -550,7 +539,7 @@ static inline void i2c_ll_read_rxfifo(i2c_dev_t *hw, uint8_t *ptr, uint8_t len) * * @return None */ -static inline void i2c_ll_set_filter(i2c_dev_t *hw, uint8_t filter_num) +static inline void i2c_ll_master_set_filter(i2c_dev_t *hw, uint8_t filter_num) { if(filter_num > 0) { hw->scl_filter_cfg.thres = filter_num; @@ -570,7 +559,7 @@ static inline void i2c_ll_set_filter(i2c_dev_t *hw, uint8_t filter_num) * * @return The hardware filter configuration */ -static inline void i2c_ll_get_filter(i2c_dev_t *hw, uint8_t *filter_conf) +static inline void i2c_ll_master_get_filter(i2c_dev_t *hw, uint8_t *filter_conf) { *filter_conf = hw->sda_filter_cfg.thres; } @@ -597,7 +586,7 @@ static inline void i2c_ll_master_fsm_rst(i2c_dev_t *hw) * * @return None */ -static inline void i2c_ll_master_clr_bus(i2c_dev_t *hw) +static inline void i2c_ll_master_clr_bus(i2c_dev_t *hw, uint32_t slave_pulses) { ;//ESP32 do not support } @@ -693,6 +682,28 @@ static inline volatile void *i2c_ll_get_interrupt_status_reg(i2c_dev_t *dev) return &dev->int_status; } +/** + * @brief Enable I2C slave clock stretch. + * + * @param dev Beginning address of the peripheral registers. + * @param enable true: Enable, false: Disable. + */ +static inline void i2c_ll_slave_enable_scl_stretch(i2c_dev_t *dev, bool enable) +{ + // Not supported on esp32 +} + +/** + * @brief Clear I2C clock stretch status + * + * @param dev Beginning address of the peripheral registers + */ +__attribute__((always_inline)) +static inline void i2c_ll_slave_clear_stretch(i2c_dev_t *dev) +{ + // Not supported on esp32 +} + //////////////////////////////////////////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)////////////////////////////// @@ -922,6 +933,50 @@ static inline uint32_t i2c_ll_get_hw_version(i2c_dev_t *hw) return hw->date; } +/** + * @brief Configure I2C SCL timing + * + * @param hw Beginning address of the peripheral registers + * @param hight_period The I2C SCL hight period (in APB cycle) + * @param low_period The I2C SCL low period (in APB cycle) + * + * @return None. + */ +static inline void i2c_ll_set_scl_timing(i2c_dev_t *hw, int hight_period, int low_period) +{ + hw->scl_low_period.period = low_period; + hw->scl_high_period.period = hight_period; +} + +/** + * @brief Get the I2C data mode + * + * @param hw Beginning address of the peripheral registers + * @param tx_mode Pointer to accept the received bytes mode + * @param rx_mode Pointer to accept the sended bytes mode + * + * @return None + */ +static inline void i2c_ll_get_data_mode(i2c_dev_t *hw, i2c_trans_mode_t *tx_mode, i2c_trans_mode_t *rx_mode) +{ + *tx_mode = hw->ctr.tx_lsb_first; + *rx_mode = hw->ctr.rx_lsb_first; +} + +/** + * @brief Get I2C SCL timing configuration + * + * @param hw Beginning address of the peripheral registers + * @param high_period Pointer to accept the SCL high period + * @param low_period Pointer to accept the SCL low period + * + * @return None + */ +static inline void i2c_ll_get_scl_timing(i2c_dev_t *hw, int *high_period, int *low_period) +{ + *high_period = hw->scl_high_period.period; + *low_period = hw->scl_low_period.period; +} #ifdef __cplusplus } diff --git a/components/hal/esp32c2/include/hal/i2c_ll.h b/components/hal/esp32c2/include/hal/i2c_ll.h index fcb37da30e..31351209d2 100644 --- a/components/hal/esp32c2/include/hal/i2c_ll.h +++ b/components/hal/esp32c2/include/hal/i2c_ll.h @@ -28,13 +28,13 @@ extern "C" { */ typedef union { struct { - uint32_t byte_num: 8, - ack_en: 1, - ack_exp: 1, - ack_val: 1, - op_code: 3, - reserved14: 17, - done: 1; + uint32_t byte_num: 8, /*!< Specifies the length of data (in bytes) to be read or written */ + ack_en: 1, /*!< Used to enable the I2C controller during a write operation to check whether ACK */ + ack_exp: 1, /*!< Used to configure the level of the ACK bit expected by the I2C controller during a write operation */ + ack_val: 1, /*!< Used to configure the level of the ACK bit sent by the I2C controller during a read operation */ + op_code: 3, /*!< Indicates the command */ + reserved14: 17, /*!< Reserved bits */ + done: 1; /*!< Indicates that a command has been executed */ }; uint32_t val; } i2c_ll_hw_cmd_t; @@ -47,25 +47,24 @@ typedef union { #define I2C_LL_CMD_END 4 /*!clk_conf, sclk_div_num, bus_cfg->clkm_div - 1); /* According to the Technical Reference Manual, the following timings must be subtracted by 1. @@ -150,6 +149,20 @@ static inline void i2c_ll_set_bus_timing(i2c_dev_t *hw, i2c_hal_clk_config_t *bu hw->to.time_out_en = 1; } +/** + * @brief Set fractional divider + * + * @param hw Beginning address of the peripheral registers + * @param div_a The denominator of the frequency divider factor of the i2c function clock + * @param div_b The numerator of the frequency divider factor of the i2c function clock. + */ +static inline void i2c_ll_master_set_fractional_divider(i2c_dev_t *hw, uint8_t div_a, uint8_t div_b) +{ + /* Set div_a and div_b to 0, as it's not necessary to use them */ + HAL_FORCE_MODIFY_U32_REG_FIELD(hw->clk_conf, sclk_div_a, div_a); + HAL_FORCE_MODIFY_U32_REG_FIELD(hw->clk_conf, sclk_div_b, div_b); +} + /** * @brief Reset I2C txFIFO * @@ -157,6 +170,7 @@ static inline void i2c_ll_set_bus_timing(i2c_dev_t *hw, i2c_hal_clk_config_t *bu * * @return None */ +__attribute__((always_inline)) static inline void i2c_ll_txfifo_rst(i2c_dev_t *hw) { hw->fifo_conf.tx_fifo_rst = 1; @@ -170,28 +184,13 @@ static inline void i2c_ll_txfifo_rst(i2c_dev_t *hw) * * @return None */ +__attribute__((always_inline)) static inline void i2c_ll_rxfifo_rst(i2c_dev_t *hw) { hw->fifo_conf.rx_fifo_rst = 1; hw->fifo_conf.rx_fifo_rst = 0; } -/** - * @brief Configure I2C SCL timing - * - * @param hw Beginning address of the peripheral registers - * @param hight_period The I2C SCL hight period (in core clock cycle, hight_period > 2) - * @param low_period The I2C SCL low period (in core clock cycle, low_period > 1) - * - * @return None. - */ -static inline void i2c_ll_set_scl_timing(i2c_dev_t *hw, int hight_period, int low_period) -{ - hw->scl_low_period.scl_low_period = low_period - 1; - hw->scl_high_period.scl_high_period = hight_period - 10; - hw->scl_high_period.scl_wait_high_period = hight_period - hw->scl_high_period.scl_high_period; -} - /** * @brief Clear I2C interrupt status * @@ -214,6 +213,7 @@ static inline void i2c_ll_clear_intr_mask(i2c_dev_t *hw, uint32_t mask) * * @return None */ +__attribute__((always_inline)) static inline void i2c_ll_enable_intr_mask(i2c_dev_t *hw, uint32_t mask) { hw->int_ena.val |= mask; @@ -254,7 +254,7 @@ static inline void i2c_ll_get_intr_mask(i2c_dev_t *hw, uint32_t *intr_status) * * @return None */ -static inline void i2c_ll_set_fifo_mode(i2c_dev_t *hw, bool fifo_mode_en) +static inline void i2c_ll_slave_set_fifo_mode(i2c_dev_t *hw, bool fifo_mode_en) { hw->fifo_conf.nonfifo_en = fifo_mode_en ? 0 : 1; } @@ -282,7 +282,7 @@ static inline void i2c_ll_set_tout(i2c_dev_t *hw, int tout) * @return None */ __attribute__((always_inline)) -static inline void i2c_ll_write_cmd_reg(i2c_dev_t *hw, i2c_ll_hw_cmd_t cmd, int cmd_idx) +static inline void i2c_ll_master_write_cmd_reg(i2c_dev_t *hw, i2c_ll_hw_cmd_t cmd, int cmd_idx) { hw->command[cmd_idx].val = cmd.val; } @@ -296,7 +296,7 @@ static inline void i2c_ll_write_cmd_reg(i2c_dev_t *hw, i2c_ll_hw_cmd_t cmd, int * * @return None */ -static inline void i2c_ll_set_start_timing(i2c_dev_t *hw, int start_setup, int start_hold) +static inline void i2c_ll_master_set_start_timing(i2c_dev_t *hw, int start_setup, int start_hold) { hw->scl_rstart_setup.scl_rstart_setup_time = start_setup; hw->scl_start_hold.scl_start_hold_time = start_hold - 1; @@ -311,7 +311,7 @@ static inline void i2c_ll_set_start_timing(i2c_dev_t *hw, int start_setup, int s * * @return None */ -static inline void i2c_ll_set_stop_timing(i2c_dev_t *hw, int stop_setup, int stop_hold) +static inline void i2c_ll_master_set_stop_timing(i2c_dev_t *hw, int stop_setup, int stop_hold) { hw->scl_stop_setup.scl_stop_setup_time = stop_setup; hw->scl_stop_hold.scl_stop_hold_time = stop_hold; @@ -373,21 +373,6 @@ static inline void i2c_ll_set_data_mode(i2c_dev_t *hw, i2c_trans_mode_t tx_mode, hw->ctr.rx_lsb_first = rx_mode; } -/** - * @brief Get the I2C data mode - * - * @param hw Beginning address of the peripheral registers - * @param tx_mode Pointer to accept the received bytes mode - * @param rx_mode Pointer to accept the sended bytes mode - * - * @return None - */ -static inline void i2c_ll_get_data_mode(i2c_dev_t *hw, i2c_trans_mode_t *tx_mode, i2c_trans_mode_t *rx_mode) -{ - *tx_mode = hw->ctr.tx_lsb_first; - *rx_mode = hw->ctr.rx_lsb_first; -} - /** * @brief Get I2C sda timing configuration * @@ -422,6 +407,7 @@ static inline uint32_t i2c_ll_get_hw_version(i2c_dev_t *hw) * * @return True if I2C state machine is busy, else false will be returned */ +__attribute__((always_inline)) static inline bool i2c_ll_is_bus_busy(i2c_dev_t *hw) { return hw->sr.bus_busy; @@ -485,7 +471,7 @@ static inline void i2c_ll_get_tout(i2c_dev_t *hw, int *timeout) * @return None */ __attribute__((always_inline)) -static inline void i2c_ll_trans_start(i2c_dev_t *hw) +static inline void i2c_ll_master_trans_start(i2c_dev_t *hw) { hw->ctr.trans_start = 1; } @@ -520,21 +506,6 @@ static inline void i2c_ll_get_stop_timing(i2c_dev_t *hw, int *setup_time, int *h *hold_time = hw->scl_stop_hold.scl_stop_hold_time; } -/** - * @brief Get I2C SCL timing configuration - * - * @param hw Beginning address of the peripheral registers - * @param high_period Pointer to accept the SCL high period - * @param low_period Pointer to accept the SCL low period - * - * @return None - */ -static inline void i2c_ll_get_scl_timing(i2c_dev_t *hw, int *high_period, int *low_period) -{ - *high_period = hw->scl_high_period.scl_high_period + hw->scl_high_period.scl_wait_high_period; - *low_period = hw->scl_low_period.scl_low_period + 1; -} - /** * @brief Write the I2C hardware txFIFO * @@ -578,7 +549,7 @@ static inline void i2c_ll_read_rxfifo(i2c_dev_t *hw, uint8_t *ptr, uint8_t len) * * @return None */ -static inline void i2c_ll_set_filter(i2c_dev_t *hw, uint8_t filter_num) +static inline void i2c_ll_master_set_filter(i2c_dev_t *hw, uint8_t filter_num) { if (filter_num > 0) { hw->filter_cfg.scl_filter_thres = filter_num; @@ -598,7 +569,7 @@ static inline void i2c_ll_set_filter(i2c_dev_t *hw, uint8_t filter_num) * * @return The hardware filter configuration */ -static inline void i2c_ll_get_filter(i2c_dev_t *hw, uint8_t *filter_conf) +static inline void i2c_ll_master_get_filter(i2c_dev_t *hw, uint8_t *filter_conf) { *filter_conf = hw->filter_cfg.scl_filter_thres; } @@ -623,12 +594,13 @@ static inline void i2c_ll_master_fsm_rst(i2c_dev_t *hw) * Note: The master cannot detect if deadlock happens, but when the scl_st_to interrupt is generated, a deadlock may occur. * * @param hw Beginning address of the peripheral registers + * @param slave_pulses When I2C master is IDLE, the number of pulses will be sent out. * * @return None */ -static inline void i2c_ll_master_clr_bus(i2c_dev_t *hw) +static inline void i2c_ll_master_clr_bus(i2c_dev_t *hw, uint32_t slave_pulses) { - hw->scl_sp_conf.scl_rst_slv_num = 9; + hw->scl_sp_conf.scl_rst_slv_num = slave_pulses; hw->scl_sp_conf.scl_rst_slv_en = 1; hw->ctr.conf_upgate = 1; // hardward will clear scl_rst_slv_en after sending SCL pulses, @@ -829,6 +801,52 @@ static inline void i2c_ll_master_disable_rx_it(i2c_dev_t *hw) hw->int_ena.val &= (~I2C_LL_MASTER_RX_INT); } +/** + * @brief Configure I2C SCL timing + * + * @param hw Beginning address of the peripheral registers + * @param hight_period The I2C SCL hight period (in core clock cycle, hight_period > 2) + * @param low_period The I2C SCL low period (in core clock cycle, low_period > 1) + * + * @return None. + */ +static inline void i2c_ll_set_scl_timing(i2c_dev_t *hw, int hight_period, int low_period) +{ + hw->scl_low_period.scl_low_period = low_period - 1; + hw->scl_high_period.scl_high_period = hight_period - 10; + hw->scl_high_period.scl_wait_high_period = hight_period - hw->scl_high_period.scl_high_period; +} + +/** + * @brief Get the I2C data mode + * + * @param hw Beginning address of the peripheral registers + * @param tx_mode Pointer to accept the received bytes mode + * @param rx_mode Pointer to accept the sended bytes mode + * + * @return None + */ +static inline void i2c_ll_get_data_mode(i2c_dev_t *hw, i2c_trans_mode_t *tx_mode, i2c_trans_mode_t *rx_mode) +{ + *tx_mode = hw->ctr.tx_lsb_first; + *rx_mode = hw->ctr.rx_lsb_first; +} + +/** + * @brief Get I2C SCL timing configuration + * + * @param hw Beginning address of the peripheral registers + * @param high_period Pointer to accept the SCL high period + * @param low_period Pointer to accept the SCL low period + * + * @return None + */ +static inline void i2c_ll_get_scl_timing(i2c_dev_t *hw, int *high_period, int *low_period) +{ + *high_period = hw->scl_high_period.scl_high_period + hw->scl_high_period.scl_wait_high_period; + *low_period = hw->scl_low_period.scl_low_period + 1; +} + #ifdef __cplusplus } #endif diff --git a/components/hal/esp32c3/include/hal/i2c_ll.h b/components/hal/esp32c3/include/hal/i2c_ll.h index 2e44b92433..b89676af69 100644 --- a/components/hal/esp32c3/include/hal/i2c_ll.h +++ b/components/hal/esp32c3/include/hal/i2c_ll.h @@ -29,13 +29,13 @@ extern "C" { */ typedef union { struct { - uint32_t byte_num: 8, - ack_en: 1, - ack_exp: 1, - ack_val: 1, - op_code: 3, - reserved14: 17, - done: 1; + uint32_t byte_num: 8, /*!< Specifies the length of data (in bytes) to be read or written */ + ack_en: 1, /*!< Used to enable the I2C controller during a write operation to check whether ACK */ + ack_exp: 1, /*!< Used to configure the level of the ACK bit expected by the I2C controller during a write operation */ + ack_val: 1, /*!< Used to configure the level of the ACK bit sent by the I2C controller during a read operation */ + op_code: 3, /*!< Indicates the command */ + reserved14: 17, /*!< Reserved bits */ + done: 1; /*!< Indicates that a command has been executed */ }; uint32_t val; } i2c_ll_hw_cmd_t; @@ -48,25 +48,32 @@ typedef union { #define I2C_LL_CMD_END 4 /*!clk_conf, sclk_div_num, bus_cfg->clkm_div - 1); /* According to the Technical Reference Manual, the following timings must be subtracted by 1. @@ -151,6 +158,20 @@ static inline void i2c_ll_set_bus_timing(i2c_dev_t *hw, i2c_hal_clk_config_t *bu hw->timeout.time_out_en = 1; } +/** + * @brief Set fractional divider + * + * @param hw Beginning address of the peripheral registers + * @param div_a The denominator of the frequency divider factor of the i2c function clock + * @param div_b The numerator of the frequency divider factor of the i2c function clock. + */ +static inline void i2c_ll_master_set_fractional_divider(i2c_dev_t *hw, uint8_t div_a, uint8_t div_b) +{ + /* Set div_a and div_b to 0, as it's not necessary to use them */ + HAL_FORCE_MODIFY_U32_REG_FIELD(hw->clk_conf, sclk_div_a, div_a); + HAL_FORCE_MODIFY_U32_REG_FIELD(hw->clk_conf, sclk_div_b, div_b); +} + /** * @brief Reset I2C txFIFO * @@ -158,6 +179,7 @@ static inline void i2c_ll_set_bus_timing(i2c_dev_t *hw, i2c_hal_clk_config_t *bu * * @return None */ +__attribute__((always_inline)) static inline void i2c_ll_txfifo_rst(i2c_dev_t *hw) { hw->fifo_conf.tx_fifo_rst = 1; @@ -171,28 +193,13 @@ static inline void i2c_ll_txfifo_rst(i2c_dev_t *hw) * * @return None */ +__attribute__((always_inline)) static inline void i2c_ll_rxfifo_rst(i2c_dev_t *hw) { hw->fifo_conf.rx_fifo_rst = 1; hw->fifo_conf.rx_fifo_rst = 0; } -/** - * @brief Configure I2C SCL timing - * - * @param hw Beginning address of the peripheral registers - * @param hight_period The I2C SCL hight period (in core clock cycle, hight_period > 2) - * @param low_period The I2C SCL low period (in core clock cycle, low_period > 1) - * - * @return None. - */ -static inline void i2c_ll_set_scl_timing(i2c_dev_t *hw, int hight_period, int low_period) -{ - hw->scl_low_period.period = low_period - 1; - hw->scl_high_period.period = hight_period - 10; - hw->scl_high_period.scl_wait_high_period = hight_period - hw->scl_high_period.period; -} - /** * @brief Clear I2C interrupt status * @@ -215,6 +222,7 @@ static inline void i2c_ll_clear_intr_mask(i2c_dev_t *hw, uint32_t mask) * * @return None */ +__attribute__((always_inline)) static inline void i2c_ll_enable_intr_mask(i2c_dev_t *hw, uint32_t mask) { hw->int_ena.val |= mask; @@ -255,7 +263,7 @@ static inline void i2c_ll_get_intr_mask(i2c_dev_t *hw, uint32_t *intr_status) * * @return None */ -static inline void i2c_ll_set_fifo_mode(i2c_dev_t *hw, bool fifo_mode_en) +static inline void i2c_ll_slave_set_fifo_mode(i2c_dev_t *hw, bool fifo_mode_en) { hw->fifo_conf.nonfifo_en = fifo_mode_en ? 0 : 1; } @@ -273,6 +281,19 @@ static inline void i2c_ll_set_tout(i2c_dev_t *hw, int tout) hw->timeout.time_out_value = tout; } +/** + * @brief Configure I2C slave broadcasting mode. + * + * @param hw Beginning address of the peripheral registers + * @param broadcast_en Set true to enable broadcast, else, set it false + * + * @return None + */ +static inline void i2c_ll_slave_broadcast_enable(i2c_dev_t *hw, bool broadcast_en) +{ + hw->ctr.addr_broadcasting_en = broadcast_en; +} + /** * @brief Configure I2C slave address * @@ -284,8 +305,15 @@ static inline void i2c_ll_set_tout(i2c_dev_t *hw, int tout) */ static inline void i2c_ll_set_slave_addr(i2c_dev_t *hw, uint16_t slave_addr, bool addr_10bit_en) { - hw->slave_addr.addr = slave_addr; hw->slave_addr.en_10bit = addr_10bit_en; + if (addr_10bit_en) { + uint16_t addr_14_7 = (slave_addr & 0xff) << 7; + uint8_t addr_6_0 = ((slave_addr & 0x300) >> 8) | 0x78; + hw->slave_addr.addr = addr_14_7 | addr_6_0; + hw->ctr.addr_10bit_rw_check_en = addr_10bit_en; + } else { + hw->slave_addr.addr = slave_addr; + } } /** @@ -298,7 +326,7 @@ static inline void i2c_ll_set_slave_addr(i2c_dev_t *hw, uint16_t slave_addr, boo * @return None */ __attribute__((always_inline)) -static inline void i2c_ll_write_cmd_reg(i2c_dev_t *hw, i2c_ll_hw_cmd_t cmd, int cmd_idx) +static inline void i2c_ll_master_write_cmd_reg(i2c_dev_t *hw, i2c_ll_hw_cmd_t cmd, int cmd_idx) { hw->command[cmd_idx].val = cmd.val; } @@ -312,7 +340,7 @@ static inline void i2c_ll_write_cmd_reg(i2c_dev_t *hw, i2c_ll_hw_cmd_t cmd, int * * @return None */ -static inline void i2c_ll_set_start_timing(i2c_dev_t *hw, int start_setup, int start_hold) +static inline void i2c_ll_master_set_start_timing(i2c_dev_t *hw, int start_setup, int start_hold) { hw->scl_rstart_setup.time = start_setup; hw->scl_start_hold.time = start_hold - 1; @@ -327,7 +355,7 @@ static inline void i2c_ll_set_start_timing(i2c_dev_t *hw, int start_setup, int s * * @return None */ -static inline void i2c_ll_set_stop_timing(i2c_dev_t *hw, int stop_setup, int stop_hold) +static inline void i2c_ll_master_set_stop_timing(i2c_dev_t *hw, int stop_setup, int stop_hold) { hw->scl_stop_setup.time = stop_setup; hw->scl_stop_hold.time = stop_hold; @@ -389,21 +417,6 @@ static inline void i2c_ll_set_data_mode(i2c_dev_t *hw, i2c_trans_mode_t tx_mode, hw->ctr.rx_lsb_first = rx_mode; } -/** - * @brief Get the I2C data mode - * - * @param hw Beginning address of the peripheral registers - * @param tx_mode Pointer to accept the received bytes mode - * @param rx_mode Pointer to accept the sended bytes mode - * - * @return None - */ -static inline void i2c_ll_get_data_mode(i2c_dev_t *hw, i2c_trans_mode_t *tx_mode, i2c_trans_mode_t *rx_mode) -{ - *tx_mode = hw->ctr.tx_lsb_first; - *rx_mode = hw->ctr.rx_lsb_first; -} - /** * @brief Get I2C sda timing configuration * @@ -438,6 +451,7 @@ static inline uint32_t i2c_ll_get_hw_version(i2c_dev_t *hw) * * @return True if I2C state machine is busy, else false will be returned */ +__attribute__((always_inline)) static inline bool i2c_ll_is_bus_busy(i2c_dev_t *hw) { return hw->sr.bus_busy; @@ -501,7 +515,7 @@ static inline void i2c_ll_get_tout(i2c_dev_t *hw, int *timeout) * @return None */ __attribute__((always_inline)) -static inline void i2c_ll_trans_start(i2c_dev_t *hw) +static inline void i2c_ll_master_trans_start(i2c_dev_t *hw) { hw->ctr.trans_start = 1; } @@ -536,21 +550,6 @@ static inline void i2c_ll_get_stop_timing(i2c_dev_t *hw, int *setup_time, int *h *hold_time = hw->scl_stop_hold.time; } -/** - * @brief Get I2C SCL timing configuration - * - * @param hw Beginning address of the peripheral registers - * @param high_period Pointer to accept the SCL high period - * @param low_period Pointer to accept the SCL low period - * - * @return None - */ -static inline void i2c_ll_get_scl_timing(i2c_dev_t *hw, int *high_period, int *low_period) -{ - *high_period = hw->scl_high_period.period + hw->scl_high_period.scl_wait_high_period; - *low_period = hw->scl_low_period.period + 1; -} - /** * @brief Write the I2C hardware txFIFO * @@ -585,6 +584,56 @@ static inline void i2c_ll_read_rxfifo(i2c_dev_t *hw, uint8_t *ptr, uint8_t len) } } +/** + * @brief Write the I2C hardware txFIFO + * + * @param hw Beginning address of the peripheral registers + * @param ram_offset Offset value of I2C RAM. + * @param ptr Pointer to data buffer + * @param len Amount of data needs to be writen + * + * @return None. + */ +static inline void i2c_ll_write_by_nonfifo(i2c_dev_t *hw, uint8_t ram_offset, const uint8_t *ptr, uint8_t len) +{ + uint32_t *fifo_addr = (uint32_t *)&hw->txfifo_start_addr; + for (int i = 0; i < len; i++) { + fifo_addr[i + ram_offset] = ptr[i]; + } +} + +/** + * @brief Read the I2C hardware ram + * + * @param hw Beginning address of the peripheral registers + * @param ram_offset Offset value of I2C RAM. + * @param ptr Pointer to data buffer + * @param len Amount of data needs read + * + * @return None + */ +static inline void i2c_ll_read_by_nonfifo(i2c_dev_t *hw, uint8_t ram_offset, uint8_t *ptr, uint8_t len) +{ + uint32_t *fifo_addr = (uint32_t *)&hw->rxfifo_start_addr; + + for (int i = 0; i < len; i++) { + ptr[i] = fifo_addr[i + ram_offset]; + } +} + +/** + * @brief Get access to I2C RAM address directly + * + * @param hw Beginning address of the peripheral registers + * @param addr_wr_en Enable I2C ram address read and write + * + * @return None +*/ +static inline void i2c_ll_enable_mem_access_nonfifo(i2c_dev_t *hw, bool addr_wr_en) +{ + hw->fifo_conf.fifo_addr_cfg_en = addr_wr_en; +} + /** * @brief Configure I2C hardware filter * @@ -594,7 +643,7 @@ static inline void i2c_ll_read_rxfifo(i2c_dev_t *hw, uint8_t *ptr, uint8_t len) * * @return None */ -static inline void i2c_ll_set_filter(i2c_dev_t *hw, uint8_t filter_num) +static inline void i2c_ll_master_set_filter(i2c_dev_t *hw, uint8_t filter_num) { if (filter_num > 0) { hw->filter_cfg.scl_thres = filter_num; @@ -614,7 +663,7 @@ static inline void i2c_ll_set_filter(i2c_dev_t *hw, uint8_t filter_num) * * @return The hardware filter configuration */ -static inline void i2c_ll_get_filter(i2c_dev_t *hw, uint8_t *filter_conf) +static inline void i2c_ll_master_get_filter(i2c_dev_t *hw, uint8_t *filter_conf) { *filter_conf = hw->filter_cfg.scl_thres; } @@ -641,12 +690,13 @@ static inline void i2c_ll_master_fsm_rst(i2c_dev_t *hw) * Note: The master cannot detect if deadlock happens, but when the scl_st_to interrupt is generated, a deadlock may occur. * * @param hw Beginning address of the peripheral registers + * @param slave_pulses When I2C master is IDLE, the number of pulses will be sent out. * * @return None */ -static inline void i2c_ll_master_clr_bus(i2c_dev_t *hw) +static inline void i2c_ll_master_clr_bus(i2c_dev_t *hw, uint32_t slave_pulses) { - hw->scl_sp_conf.scl_rst_slv_num = 9; + hw->scl_sp_conf.scl_rst_slv_num = slave_pulses; hw->scl_sp_conf.scl_rst_slv_en = 1; hw->ctr.conf_upgate = 1; // hardward will clear scl_rst_slv_en after sending SCL pulses, @@ -734,6 +784,28 @@ static inline volatile void *i2c_ll_get_interrupt_status_reg(i2c_dev_t *dev) return &dev->int_status; } +/** + * @brief Enable I2C slave clock stretch. + * + * @param dev Beginning address of the peripheral registers. + * @param enable true: Enable, false: Disable. + */ +static inline void i2c_ll_slave_enable_scl_stretch(i2c_dev_t *dev, bool enable) +{ + dev->scl_stretch_conf.slave_scl_stretch_en = enable; +} + +/** + * @brief Clear I2C clock stretch status + * + * @param dev Beginning address of the peripheral registers + */ +__attribute__((always_inline)) +static inline void i2c_ll_slave_clear_stretch(i2c_dev_t *dev) +{ + dev->scl_stretch_conf.slave_scl_stretch_clr = 1; +} + //////////////////////////////////////////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)////////////////////////////// @@ -952,6 +1024,52 @@ static inline void i2c_ll_slave_disable_rx_it(i2c_dev_t *hw) } +/** + * @brief Configure I2C SCL timing + * + * @param hw Beginning address of the peripheral registers + * @param hight_period The I2C SCL hight period (in core clock cycle, hight_period > 2) + * @param low_period The I2C SCL low period (in core clock cycle, low_period > 1) + * + * @return None. + */ +static inline void i2c_ll_set_scl_timing(i2c_dev_t *hw, int hight_period, int low_period) +{ + hw->scl_low_period.period = low_period - 1; + hw->scl_high_period.period = hight_period - 10; + hw->scl_high_period.scl_wait_high_period = hight_period - hw->scl_high_period.period; +} + +/** + * @brief Get the I2C data mode + * + * @param hw Beginning address of the peripheral registers + * @param tx_mode Pointer to accept the received bytes mode + * @param rx_mode Pointer to accept the sended bytes mode + * + * @return None + */ +static inline void i2c_ll_get_data_mode(i2c_dev_t *hw, i2c_trans_mode_t *tx_mode, i2c_trans_mode_t *rx_mode) +{ + *tx_mode = hw->ctr.tx_lsb_first; + *rx_mode = hw->ctr.rx_lsb_first; +} + +/** + * @brief Get I2C SCL timing configuration + * + * @param hw Beginning address of the peripheral registers + * @param high_period Pointer to accept the SCL high period + * @param low_period Pointer to accept the SCL low period + * + * @return None + */ +static inline void i2c_ll_get_scl_timing(i2c_dev_t *hw, int *high_period, int *low_period) +{ + *high_period = hw->scl_high_period.period + hw->scl_high_period.scl_wait_high_period; + *low_period = hw->scl_low_period.period + 1; +} + #ifdef __cplusplus } #endif diff --git a/components/hal/esp32c6/include/hal/i2c_ll.h b/components/hal/esp32c6/include/hal/i2c_ll.h index 797c197359..bc69e3f9cc 100644 --- a/components/hal/esp32c6/include/hal/i2c_ll.h +++ b/components/hal/esp32c6/include/hal/i2c_ll.h @@ -17,6 +17,7 @@ #include "soc/pcr_struct.h" #include "hal/i2c_types.h" #include "soc/clk_tree_defs.h" +#include "soc/lp_clkrst_struct.h" #ifdef __cplusplus extern "C" { @@ -27,13 +28,13 @@ extern "C" { */ typedef union { struct { - uint32_t byte_num: 8, - ack_en: 1, - ack_exp: 1, - ack_val: 1, - op_code: 3, - reserved14: 17, - done: 1; + uint32_t byte_num: 8, /*!< Specifies the length of data (in bytes) to be read or written */ + ack_en: 1, /*!< Used to enable the I2C controller during a write operation to check whether ACK */ + ack_exp: 1, /*!< Used to configure the level of the ACK bit expected by the I2C controller during a write operation */ + ack_val: 1, /*!< Used to configure the level of the ACK bit sent by the I2C controller during a read operation */ + op_code: 3, /*!< Indicates the command */ + reserved14: 17, /*!< Reserved bits */ + done: 1; /*!< Indicates that a command has been executed */ }; uint32_t val; } i2c_ll_hw_cmd_t; @@ -46,25 +47,32 @@ typedef union { #define I2C_LL_CMD_END 4 /*!clkm_div - 1); @@ -153,6 +161,20 @@ static inline void i2c_ll_set_bus_timing(i2c_dev_t *hw, i2c_hal_clk_config_t *bu hw->to.time_out_en = 1; } +/** + * @brief Set fractional divider + * + * @param hw Beginning address of the peripheral registers + * @param div_a The denominator of the frequency divider factor of the i2c function clock + * @param div_b The numerator of the frequency divider factor of the i2c function clock. + */ +static inline void i2c_ll_master_set_fractional_divider(i2c_dev_t *hw, uint8_t div_a, uint8_t div_b) +{ + /* Set div_a and div_b to 0, as it's not necessary to use them */ + HAL_FORCE_MODIFY_U32_REG_FIELD(PCR.i2c_sclk_conf, i2c_sclk_div_a, div_a); + HAL_FORCE_MODIFY_U32_REG_FIELD(PCR.i2c_sclk_conf, i2c_sclk_div_b, div_b); +} + /** * @brief Reset I2C txFIFO * @@ -160,6 +182,7 @@ static inline void i2c_ll_set_bus_timing(i2c_dev_t *hw, i2c_hal_clk_config_t *bu * * @return None */ +__attribute__((always_inline)) static inline void i2c_ll_txfifo_rst(i2c_dev_t *hw) { hw->fifo_conf.tx_fifo_rst = 1; @@ -173,28 +196,13 @@ static inline void i2c_ll_txfifo_rst(i2c_dev_t *hw) * * @return None */ +__attribute__((always_inline)) static inline void i2c_ll_rxfifo_rst(i2c_dev_t *hw) { hw->fifo_conf.rx_fifo_rst = 1; hw->fifo_conf.rx_fifo_rst = 0; } -/** - * @brief Configure I2C SCL timing - * - * @param hw Beginning address of the peripheral registers - * @param hight_period The I2C SCL hight period (in core clock cycle, hight_period > 2) - * @param low_period The I2C SCL low period (in core clock cycle, low_period > 1) - * - * @return None. - */ -static inline void i2c_ll_set_scl_timing(i2c_dev_t *hw, int hight_period, int low_period) -{ - hw->scl_low_period.scl_low_period = low_period - 1; - hw->scl_high_period.scl_high_period = hight_period - 10; - hw->scl_high_period.scl_wait_high_period = hight_period - hw->scl_high_period.scl_high_period; -} - /** * @brief Clear I2C interrupt status * @@ -217,6 +225,7 @@ static inline void i2c_ll_clear_intr_mask(i2c_dev_t *hw, uint32_t mask) * * @return None */ +__attribute__((always_inline)) static inline void i2c_ll_enable_intr_mask(i2c_dev_t *hw, uint32_t mask) { hw->int_ena.val |= mask; @@ -257,7 +266,7 @@ static inline void i2c_ll_get_intr_mask(i2c_dev_t *hw, uint32_t *intr_status) * * @return None */ -static inline void i2c_ll_set_fifo_mode(i2c_dev_t *hw, bool fifo_mode_en) +static inline void i2c_ll_slave_set_fifo_mode(i2c_dev_t *hw, bool fifo_mode_en) { hw->fifo_conf.nonfifo_en = fifo_mode_en ? 0 : 1; } @@ -275,6 +284,19 @@ static inline void i2c_ll_set_tout(i2c_dev_t *hw, int tout) hw->to.time_out_value = tout; } +/** + * @brief Configure I2C slave broadcasting mode. + * + * @param hw Beginning address of the peripheral registers + * @param broadcast_en Set true to enable broadcast, else, set it false + * + * @return None + */ +static inline void i2c_ll_slave_broadcast_enable(i2c_dev_t *hw, bool broadcast_en) +{ + hw->ctr.addr_broadcasting_en = broadcast_en; +} + /** * @brief Configure I2C slave address * @@ -286,8 +308,15 @@ static inline void i2c_ll_set_tout(i2c_dev_t *hw, int tout) */ static inline void i2c_ll_set_slave_addr(i2c_dev_t *hw, uint16_t slave_addr, bool addr_10bit_en) { - hw->slave_addr.slave_addr = slave_addr; hw->slave_addr.addr_10bit_en = addr_10bit_en; + if (addr_10bit_en) { + uint8_t addr_14_7 = (slave_addr & 0xff) << 7; + uint8_t addr_6_0 = ((slave_addr & 0x300) >> 8) | 0x78; + hw->slave_addr.slave_addr = addr_14_7 | addr_6_0; + hw->ctr.addr_10bit_rw_check_en = addr_10bit_en; + } else { + hw->slave_addr.slave_addr = slave_addr; + } } /** @@ -300,7 +329,7 @@ static inline void i2c_ll_set_slave_addr(i2c_dev_t *hw, uint16_t slave_addr, boo * @return None */ __attribute__((always_inline)) -static inline void i2c_ll_write_cmd_reg(i2c_dev_t *hw, i2c_ll_hw_cmd_t cmd, int cmd_idx) +static inline void i2c_ll_master_write_cmd_reg(i2c_dev_t *hw, i2c_ll_hw_cmd_t cmd, int cmd_idx) { hw->command[cmd_idx].val = cmd.val; } @@ -314,7 +343,7 @@ static inline void i2c_ll_write_cmd_reg(i2c_dev_t *hw, i2c_ll_hw_cmd_t cmd, int * * @return None */ -static inline void i2c_ll_set_start_timing(i2c_dev_t *hw, int start_setup, int start_hold) +static inline void i2c_ll_master_set_start_timing(i2c_dev_t *hw, int start_setup, int start_hold) { hw->scl_rstart_setup.scl_rstart_setup_time = start_setup; hw->scl_start_hold.scl_start_hold_time = start_hold - 1; @@ -329,7 +358,7 @@ static inline void i2c_ll_set_start_timing(i2c_dev_t *hw, int start_setup, int s * * @return None */ -static inline void i2c_ll_set_stop_timing(i2c_dev_t *hw, int stop_setup, int stop_hold) +static inline void i2c_ll_master_set_stop_timing(i2c_dev_t *hw, int stop_setup, int stop_hold) { hw->scl_stop_setup.scl_stop_setup_time = stop_setup; hw->scl_stop_hold.scl_stop_hold_time = stop_hold; @@ -391,21 +420,6 @@ static inline void i2c_ll_set_data_mode(i2c_dev_t *hw, i2c_trans_mode_t tx_mode, hw->ctr.rx_lsb_first = rx_mode; } -/** - * @brief Get the I2C data mode - * - * @param hw Beginning address of the peripheral registers - * @param tx_mode Pointer to accept the received bytes mode - * @param rx_mode Pointer to accept the sended bytes mode - * - * @return None - */ -static inline void i2c_ll_get_data_mode(i2c_dev_t *hw, i2c_trans_mode_t *tx_mode, i2c_trans_mode_t *rx_mode) -{ - *tx_mode = hw->ctr.tx_lsb_first; - *rx_mode = hw->ctr.rx_lsb_first; -} - /** * @brief Get I2C sda timing configuration * @@ -440,6 +454,7 @@ static inline uint32_t i2c_ll_get_hw_version(i2c_dev_t *hw) * * @return True if I2C state machine is busy, else false will be returned */ +__attribute__((always_inline)) static inline bool i2c_ll_is_bus_busy(i2c_dev_t *hw) { return hw->sr.bus_busy; @@ -503,7 +518,7 @@ static inline void i2c_ll_get_tout(i2c_dev_t *hw, int *timeout) * @return None */ __attribute__((always_inline)) -static inline void i2c_ll_trans_start(i2c_dev_t *hw) +static inline void i2c_ll_master_trans_start(i2c_dev_t *hw) { hw->ctr.trans_start = 1; } @@ -538,21 +553,6 @@ static inline void i2c_ll_get_stop_timing(i2c_dev_t *hw, int *setup_time, int *h *hold_time = hw->scl_stop_hold.scl_stop_hold_time; } -/** - * @brief Get I2C SCL timing configuration - * - * @param hw Beginning address of the peripheral registers - * @param high_period Pointer to accept the SCL high period - * @param low_period Pointer to accept the SCL low period - * - * @return None - */ -static inline void i2c_ll_get_scl_timing(i2c_dev_t *hw, int *high_period, int *low_period) -{ - *high_period = hw->scl_high_period.scl_high_period + hw->scl_high_period.scl_wait_high_period; - *low_period = hw->scl_low_period.scl_low_period + 1; -} - /** * @brief Write the I2C hardware txFIFO * @@ -587,6 +587,56 @@ static inline void i2c_ll_read_rxfifo(i2c_dev_t *hw, uint8_t *ptr, uint8_t len) } } +/** + * @brief Write the I2C hardware txFIFO + * + * @param hw Beginning address of the peripheral registers + * @param ram_offset Offset value of I2C RAM. + * @param ptr Pointer to data buffer + * @param len Amount of data needs to be writen + * + * @return None. + */ +static inline void i2c_ll_write_by_nonfifo(i2c_dev_t *hw, uint8_t ram_offset, const uint8_t *ptr, uint8_t len) +{ + uint32_t *fifo_addr = (uint32_t *)&hw->txfifo_start_addr; + for (int i = 0; i < len; i++) { + fifo_addr[i + ram_offset] = ptr[i]; + } +} + +/** + * @brief Read the I2C hardware ram + * + * @param hw Beginning address of the peripheral registers + * @param ram_offset Offset value of I2C RAM. + * @param ptr Pointer to data buffer + * @param len Amount of data needs read + * + * @return None + */ +static inline void i2c_ll_read_by_nonfifo(i2c_dev_t *hw, uint8_t ram_offset, uint8_t *ptr, uint8_t len) +{ + uint32_t *fifo_addr = (uint32_t *)&hw->rxfifo_start_addr; + + for (int i = 0; i < len; i++) { + ptr[i] = fifo_addr[i + ram_offset]; + } +} + +/** + * @brief Get access to I2C RAM address directly + * + * @param hw Beginning address of the peripheral registers + * @param addr_wr_en Enable I2C ram address read and write + * + * @return None +*/ +static inline void i2c_ll_enable_mem_access_nonfifo(i2c_dev_t *hw, bool addr_wr_en) +{ + hw->fifo_conf.fifo_addr_cfg_en = addr_wr_en; +} + /** * @brief Configure I2C hardware filter * @@ -596,7 +646,7 @@ static inline void i2c_ll_read_rxfifo(i2c_dev_t *hw, uint8_t *ptr, uint8_t len) * * @return None */ -static inline void i2c_ll_set_filter(i2c_dev_t *hw, uint8_t filter_num) +static inline void i2c_ll_master_set_filter(i2c_dev_t *hw, uint8_t filter_num) { if (filter_num > 0) { hw->filter_cfg.scl_filter_thres = filter_num; @@ -616,7 +666,7 @@ static inline void i2c_ll_set_filter(i2c_dev_t *hw, uint8_t filter_num) * * @return The hardware filter configuration */ -static inline void i2c_ll_get_filter(i2c_dev_t *hw, uint8_t *filter_conf) +static inline void i2c_ll_master_get_filter(i2c_dev_t *hw, uint8_t *filter_conf) { *filter_conf = hw->filter_cfg.scl_filter_thres; } @@ -641,12 +691,13 @@ static inline void i2c_ll_master_fsm_rst(i2c_dev_t *hw) * Note: The master cannot detect if deadlock happens, but when the scl_st_to interrupt is generated, a deadlock may occur. * * @param hw Beginning address of the peripheral registers + * @param slave_pulses When I2C master is IDLE, the number of pulses will be sent out. * * @return None */ -static inline void i2c_ll_master_clr_bus(i2c_dev_t *hw) +static inline void i2c_ll_master_clr_bus(i2c_dev_t *hw, uint32_t slave_pulses) { - hw->scl_sp_conf.scl_rst_slv_num = 9; + hw->scl_sp_conf.scl_rst_slv_num = slave_pulses; hw->scl_sp_conf.scl_rst_slv_en = 1; hw->ctr.conf_upgate = 1; // hardward will clear scl_rst_slv_en after sending SCL pulses, @@ -758,6 +809,28 @@ static inline volatile void *i2c_ll_get_interrupt_status_reg(i2c_dev_t *dev) } +/** + * @brief Enable I2C slave clock stretch. + * + * @param dev Beginning address of the peripheral registers. + * @param enable true: Enable, false: Disable. + */ +static inline void i2c_ll_slave_enable_scl_stretch(i2c_dev_t *dev, bool enable) +{ + dev->scl_stretch_conf.slave_scl_stretch_en = enable; +} + +/** + * @brief Clear I2C clock stretch status + * + * @param dev Beginning address of the peripheral registers + */ +__attribute__((always_inline)) +static inline void i2c_ll_slave_clear_stretch(i2c_dev_t *dev) +{ + dev->scl_stretch_conf.slave_scl_stretch_clr = 1; +} + //////////////////////////////////////////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)////////////////////////////// @@ -975,6 +1048,52 @@ static inline void i2c_ll_slave_disable_rx_it(i2c_dev_t *hw) hw->int_ena.val &= (~I2C_LL_SLAVE_RX_INT); } +/** + * @brief Configure I2C SCL timing + * + * @param hw Beginning address of the peripheral registers + * @param hight_period The I2C SCL hight period (in core clock cycle, hight_period > 2) + * @param low_period The I2C SCL low period (in core clock cycle, low_period > 1) + * + * @return None. + */ +static inline void i2c_ll_set_scl_timing(i2c_dev_t *hw, int hight_period, int low_period) +{ + hw->scl_low_period.scl_low_period = low_period - 1; + hw->scl_high_period.scl_high_period = hight_period - 10; + hw->scl_high_period.scl_wait_high_period = hight_period - hw->scl_high_period.scl_high_period; +} + +/** + * @brief Get the I2C data mode + * + * @param hw Beginning address of the peripheral registers + * @param tx_mode Pointer to accept the received bytes mode + * @param rx_mode Pointer to accept the sended bytes mode + * + * @return None + */ +static inline void i2c_ll_get_data_mode(i2c_dev_t *hw, i2c_trans_mode_t *tx_mode, i2c_trans_mode_t *rx_mode) +{ + *tx_mode = hw->ctr.tx_lsb_first; + *rx_mode = hw->ctr.rx_lsb_first; +} + +/** + * @brief Get I2C SCL timing configuration + * + * @param hw Beginning address of the peripheral registers + * @param high_period Pointer to accept the SCL high period + * @param low_period Pointer to accept the SCL low period + * + * @return None + */ +static inline void i2c_ll_get_scl_timing(i2c_dev_t *hw, int *high_period, int *low_period) +{ + *high_period = hw->scl_high_period.scl_high_period + hw->scl_high_period.scl_wait_high_period; + *low_period = hw->scl_low_period.scl_low_period + 1; +} + #ifdef __cplusplus } #endif diff --git a/components/hal/esp32h2/include/hal/i2c_ll.h b/components/hal/esp32h2/include/hal/i2c_ll.h index 6267b2fd93..a5790a0e0b 100644 --- a/components/hal/esp32h2/include/hal/i2c_ll.h +++ b/components/hal/esp32h2/include/hal/i2c_ll.h @@ -27,13 +27,13 @@ extern "C" { */ typedef union { struct { - uint32_t byte_num: 8, - ack_en: 1, - ack_exp: 1, - ack_val: 1, - op_code: 3, - reserved14: 17, - done: 1; + uint32_t byte_num: 8, /*!< Specifies the length of data (in bytes) to be read or written */ + ack_en: 1, /*!< Used to enable the I2C controller during a write operation to check whether ACK */ + ack_exp: 1, /*!< Used to configure the level of the ACK bit expected by the I2C controller during a write operation */ + ack_val: 1, /*!< Used to configure the level of the ACK bit sent by the I2C controller during a read operation */ + op_code: 3, /*!< Indicates the command */ + reserved14: 17, /*!< Reserved bits */ + done: 1; /*!< Indicates that a command has been executed */ }; uint32_t val; } i2c_ll_hw_cmd_t; @@ -46,26 +46,33 @@ typedef union { #define I2C_LL_CMD_END 4 /*!clkm_div - 1); - /* Set div_a and div_b to 0, as it's not necessary to use them */ - HAL_FORCE_MODIFY_U32_REG_FIELD(PCR.i2c[I2C_LL_GET_NUM(hw)].i2c_sclk_conf, i2c_sclk_div_a, 0); - HAL_FORCE_MODIFY_U32_REG_FIELD(PCR.i2c[I2C_LL_GET_NUM(hw)].i2c_sclk_conf, i2c_sclk_div_b, 0); /* According to the Technical Reference Manual, the following timings must be subtracted by 1. * However, according to the practical measurement and some hardware behaviour, if wait_high_period and scl_high minus one. * The SCL frequency would be a little higher than expected. Therefore, the solution @@ -154,6 +158,20 @@ static inline void i2c_ll_set_bus_timing(i2c_dev_t *hw, i2c_hal_clk_config_t *bu hw->to.time_out_en = 1; } +/** + * @brief Set fractional divider + * + * @param hw Beginning address of the peripheral registers + * @param div_a The denominator of the frequency divider factor of the i2c function clock + * @param div_b The numerator of the frequency divider factor of the i2c function clock. + */ +static inline void i2c_ll_master_set_fractional_divider(i2c_dev_t *hw, uint8_t div_a, uint8_t div_b) +{ + /* Set div_a and div_b to 0, as it's not necessary to use them */ + HAL_FORCE_MODIFY_U32_REG_FIELD(PCR.i2c[I2C_LL_GET_NUM(hw)].i2c_sclk_conf, i2c_sclk_div_a, div_a); + HAL_FORCE_MODIFY_U32_REG_FIELD(PCR.i2c[I2C_LL_GET_NUM(hw)].i2c_sclk_conf, i2c_sclk_div_b, div_b); +} + /** * @brief Reset I2C txFIFO * @@ -161,6 +179,7 @@ static inline void i2c_ll_set_bus_timing(i2c_dev_t *hw, i2c_hal_clk_config_t *bu * * @return None */ +__attribute__((always_inline)) static inline void i2c_ll_txfifo_rst(i2c_dev_t *hw) { hw->fifo_conf.tx_fifo_rst = 1; @@ -174,28 +193,13 @@ static inline void i2c_ll_txfifo_rst(i2c_dev_t *hw) * * @return None */ +__attribute__((always_inline)) static inline void i2c_ll_rxfifo_rst(i2c_dev_t *hw) { hw->fifo_conf.rx_fifo_rst = 1; hw->fifo_conf.rx_fifo_rst = 0; } -/** - * @brief Configure I2C SCL timing - * - * @param hw Beginning address of the peripheral registers - * @param hight_period The I2C SCL hight period (in core clock cycle, hight_period > 2) - * @param low_period The I2C SCL low period (in core clock cycle, low_period > 1) - * - * @return None. - */ -static inline void i2c_ll_set_scl_timing(i2c_dev_t *hw, int hight_period, int low_period) -{ - hw->scl_low_period.scl_low_period = low_period - 1; - hw->scl_high_period.scl_high_period = hight_period - 10; - hw->scl_high_period.scl_wait_high_period = hight_period - hw->scl_high_period.scl_high_period; -} - /** * @brief Clear I2C interrupt status * @@ -218,6 +222,7 @@ static inline void i2c_ll_clear_intr_mask(i2c_dev_t *hw, uint32_t mask) * * @return None */ +__attribute__((always_inline)) static inline void i2c_ll_enable_intr_mask(i2c_dev_t *hw, uint32_t mask) { hw->int_ena.val |= mask; @@ -258,7 +263,7 @@ static inline void i2c_ll_get_intr_mask(i2c_dev_t *hw, uint32_t *intr_status) * * @return None */ -static inline void i2c_ll_set_fifo_mode(i2c_dev_t *hw, bool fifo_mode_en) +static inline void i2c_ll_slave_set_fifo_mode(i2c_dev_t *hw, bool fifo_mode_en) { hw->fifo_conf.nonfifo_en = fifo_mode_en ? 0 : 1; } @@ -276,6 +281,19 @@ static inline void i2c_ll_set_tout(i2c_dev_t *hw, int tout) hw->to.time_out_value = tout; } +/** + * @brief Configure I2C slave broadcasting mode. + * + * @param hw Beginning address of the peripheral registers + * @param broadcast_en Set true to enable broadcast, else, set it false + * + * @return None + */ +static inline void i2c_ll_slave_broadcast_enable(i2c_dev_t *hw, bool broadcast_en) +{ + hw->ctr.addr_broadcasting_en = broadcast_en; +} + /** * @brief Configure I2C slave address * @@ -287,8 +305,15 @@ static inline void i2c_ll_set_tout(i2c_dev_t *hw, int tout) */ static inline void i2c_ll_set_slave_addr(i2c_dev_t *hw, uint16_t slave_addr, bool addr_10bit_en) { - hw->slave_addr.slave_addr = slave_addr; hw->slave_addr.addr_10bit_en = addr_10bit_en; + if (addr_10bit_en) { + uint8_t addr_14_7 = (slave_addr & 0xff) << 7; + uint8_t addr_6_0 = ((slave_addr & 0x300) >> 8) | 0x78; + hw->slave_addr.slave_addr = addr_14_7 | addr_6_0; + hw->ctr.addr_10bit_rw_check_en = addr_10bit_en; + } else { + hw->slave_addr.slave_addr = slave_addr; + } } /** @@ -301,7 +326,7 @@ static inline void i2c_ll_set_slave_addr(i2c_dev_t *hw, uint16_t slave_addr, boo * @return None */ __attribute__((always_inline)) -static inline void i2c_ll_write_cmd_reg(i2c_dev_t *hw, i2c_ll_hw_cmd_t cmd, int cmd_idx) +static inline void i2c_ll_master_write_cmd_reg(i2c_dev_t *hw, i2c_ll_hw_cmd_t cmd, int cmd_idx) { hw->command[cmd_idx].val = cmd.val; } @@ -315,7 +340,7 @@ static inline void i2c_ll_write_cmd_reg(i2c_dev_t *hw, i2c_ll_hw_cmd_t cmd, int * * @return None */ -static inline void i2c_ll_set_start_timing(i2c_dev_t *hw, int start_setup, int start_hold) +static inline void i2c_ll_master_set_start_timing(i2c_dev_t *hw, int start_setup, int start_hold) { hw->scl_rstart_setup.scl_rstart_setup_time = start_setup; hw->scl_start_hold.scl_start_hold_time = start_hold - 1; @@ -330,7 +355,7 @@ static inline void i2c_ll_set_start_timing(i2c_dev_t *hw, int start_setup, int s * * @return None */ -static inline void i2c_ll_set_stop_timing(i2c_dev_t *hw, int stop_setup, int stop_hold) +static inline void i2c_ll_master_set_stop_timing(i2c_dev_t *hw, int stop_setup, int stop_hold) { hw->scl_stop_setup.scl_stop_setup_time = stop_setup; hw->scl_stop_hold.scl_stop_hold_time = stop_hold; @@ -392,21 +417,6 @@ static inline void i2c_ll_set_data_mode(i2c_dev_t *hw, i2c_trans_mode_t tx_mode, hw->ctr.rx_lsb_first = rx_mode; } -/** - * @brief Get the I2C data mode - * - * @param hw Beginning address of the peripheral registers - * @param tx_mode Pointer to accept the received bytes mode - * @param rx_mode Pointer to accept the sended bytes mode - * - * @return None - */ -static inline void i2c_ll_get_data_mode(i2c_dev_t *hw, i2c_trans_mode_t *tx_mode, i2c_trans_mode_t *rx_mode) -{ - *tx_mode = hw->ctr.tx_lsb_first; - *rx_mode = hw->ctr.rx_lsb_first; -} - /** * @brief Get I2C sda timing configuration * @@ -441,6 +451,7 @@ static inline uint32_t i2c_ll_get_hw_version(i2c_dev_t *hw) * * @return True if I2C state machine is busy, else false will be returned */ +__attribute__((always_inline)) static inline bool i2c_ll_is_bus_busy(i2c_dev_t *hw) { return hw->sr.bus_busy; @@ -504,7 +515,7 @@ static inline void i2c_ll_get_tout(i2c_dev_t *hw, int *timeout) * @return None */ __attribute__((always_inline)) -static inline void i2c_ll_trans_start(i2c_dev_t *hw) +static inline void i2c_ll_master_trans_start(i2c_dev_t *hw) { hw->ctr.trans_start = 1; } @@ -539,21 +550,6 @@ static inline void i2c_ll_get_stop_timing(i2c_dev_t *hw, int *setup_time, int *h *hold_time = hw->scl_stop_hold.scl_stop_hold_time; } -/** - * @brief Get I2C SCL timing configuration - * - * @param hw Beginning address of the peripheral registers - * @param high_period Pointer to accept the SCL high period - * @param low_period Pointer to accept the SCL low period - * - * @return None - */ -static inline void i2c_ll_get_scl_timing(i2c_dev_t *hw, int *high_period, int *low_period) -{ - *high_period = hw->scl_high_period.scl_high_period + hw->scl_high_period.scl_wait_high_period; - *low_period = hw->scl_low_period.scl_low_period + 1; -} - /** * @brief Write the I2C hardware txFIFO * @@ -588,6 +584,56 @@ static inline void i2c_ll_read_rxfifo(i2c_dev_t *hw, uint8_t *ptr, uint8_t len) } } +/** + * @brief Write the I2C hardware txFIFO + * + * @param hw Beginning address of the peripheral registers + * @param ram_offset Offset value of I2C RAM. + * @param ptr Pointer to data buffer + * @param len Amount of data needs to be writen + * + * @return None. + */ +static inline void i2c_ll_write_by_nonfifo(i2c_dev_t *hw, uint8_t ram_offset, const uint8_t *ptr, uint8_t len) +{ + uint32_t *fifo_addr = (uint32_t *)&hw->txfifo_start_addr; + for (int i = 0; i < len; i++) { + fifo_addr[i + ram_offset] = ptr[i]; + } +} + +/** + * @brief Read the I2C hardware ram + * + * @param hw Beginning address of the peripheral registers + * @param ram_offset Offset value of I2C RAM. + * @param ptr Pointer to data buffer + * @param len Amount of data needs read + * + * @return None + */ +static inline void i2c_ll_read_by_nonfifo(i2c_dev_t *hw, uint8_t ram_offset, uint8_t *ptr, uint8_t len) +{ + uint32_t *fifo_addr = (uint32_t *)&hw->rxfifo_start_addr; + + for (int i = 0; i < len; i++) { + ptr[i] = fifo_addr[i + ram_offset]; + } +} + +/** + * @brief Get access to I2C RAM address directly + * + * @param hw Beginning address of the peripheral registers + * @param addr_wr_en Enable I2C ram address read and write + * + * @return None +*/ +static inline void i2c_ll_enable_mem_access_nonfifo(i2c_dev_t *hw, bool addr_wr_en) +{ + hw->fifo_conf.fifo_addr_cfg_en = addr_wr_en; +} + /** * @brief Configure I2C hardware filter * @@ -597,7 +643,7 @@ static inline void i2c_ll_read_rxfifo(i2c_dev_t *hw, uint8_t *ptr, uint8_t len) * * @return None */ -static inline void i2c_ll_set_filter(i2c_dev_t *hw, uint8_t filter_num) +static inline void i2c_ll_master_set_filter(i2c_dev_t *hw, uint8_t filter_num) { if (filter_num > 0) { hw->filter_cfg.scl_filter_thres = filter_num; @@ -617,7 +663,7 @@ static inline void i2c_ll_set_filter(i2c_dev_t *hw, uint8_t filter_num) * * @return The hardware filter configuration */ -static inline void i2c_ll_get_filter(i2c_dev_t *hw, uint8_t *filter_conf) +static inline void i2c_ll_master_get_filter(i2c_dev_t *hw, uint8_t *filter_conf) { *filter_conf = hw->filter_cfg.scl_filter_thres; } @@ -642,12 +688,13 @@ static inline void i2c_ll_master_fsm_rst(i2c_dev_t *hw) * Note: The master cannot detect if deadlock happens, but when the scl_st_to interrupt is generated, a deadlock may occur. * * @param hw Beginning address of the peripheral registers + * @param slave_pulses When I2C master is IDLE, the number of pulses will be sent out. * * @return None */ -static inline void i2c_ll_master_clr_bus(i2c_dev_t *hw) +static inline void i2c_ll_master_clr_bus(i2c_dev_t *hw, uint32_t slave_pulses) { - hw->scl_sp_conf.scl_rst_slv_num = 9; + hw->scl_sp_conf.scl_rst_slv_num = slave_pulses; hw->scl_sp_conf.scl_rst_slv_en = 1; hw->ctr.conf_upgate = 1; // hardward will clear scl_rst_slv_en after sending SCL pulses, @@ -735,6 +782,28 @@ static inline volatile void *i2c_ll_get_interrupt_status_reg(i2c_dev_t *dev) return &dev->int_status; } +/** + * @brief Enable I2C slave clock stretch. + * + * @param dev Beginning address of the peripheral registers. + * @param enable true: Enable, false: Disable. + */ +static inline void i2c_ll_slave_enable_scl_stretch(i2c_dev_t *dev, bool enable) +{ + dev->scl_stretch_conf.slave_scl_stretch_en = enable; +} + +/** + * @brief Clear I2C clock stretch status + * + * @param dev Beginning address of the peripheral registers + */ +__attribute__((always_inline)) +static inline void i2c_ll_slave_clear_stretch(i2c_dev_t *dev) +{ + dev->scl_stretch_conf.slave_scl_stretch_clr = 1; +} + //////////////////////////////////////////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)////////////////////////////// @@ -952,6 +1021,52 @@ static inline void i2c_ll_slave_disable_rx_it(i2c_dev_t *hw) hw->int_ena.val &= (~I2C_LL_SLAVE_RX_INT); } +/** + * @brief Configure I2C SCL timing + * + * @param hw Beginning address of the peripheral registers + * @param hight_period The I2C SCL hight period (in core clock cycle, hight_period > 2) + * @param low_period The I2C SCL low period (in core clock cycle, low_period > 1) + * + * @return None. + */ +static inline void i2c_ll_set_scl_timing(i2c_dev_t *hw, int hight_period, int low_period) +{ + hw->scl_low_period.scl_low_period = low_period - 1; + hw->scl_high_period.scl_high_period = hight_period - 10; + hw->scl_high_period.scl_wait_high_period = hight_period - hw->scl_high_period.scl_high_period; +} + +/** + * @brief Get the I2C data mode + * + * @param hw Beginning address of the peripheral registers + * @param tx_mode Pointer to accept the received bytes mode + * @param rx_mode Pointer to accept the sended bytes mode + * + * @return None + */ +static inline void i2c_ll_get_data_mode(i2c_dev_t *hw, i2c_trans_mode_t *tx_mode, i2c_trans_mode_t *rx_mode) +{ + *tx_mode = hw->ctr.tx_lsb_first; + *rx_mode = hw->ctr.rx_lsb_first; +} + +/** + * @brief Get I2C SCL timing configuration + * + * @param hw Beginning address of the peripheral registers + * @param high_period Pointer to accept the SCL high period + * @param low_period Pointer to accept the SCL low period + * + * @return None + */ +static inline void i2c_ll_get_scl_timing(i2c_dev_t *hw, int *high_period, int *low_period) +{ + *high_period = hw->scl_high_period.scl_high_period + hw->scl_high_period.scl_wait_high_period; + *low_period = hw->scl_low_period.scl_low_period + 1; +} + #ifdef __cplusplus } #endif diff --git a/components/hal/esp32s2/include/hal/i2c_ll.h b/components/hal/esp32s2/include/hal/i2c_ll.h index f9a66b61d6..db41eab87b 100644 --- a/components/hal/esp32s2/include/hal/i2c_ll.h +++ b/components/hal/esp32s2/include/hal/i2c_ll.h @@ -25,13 +25,13 @@ extern "C" { */ typedef union { struct { - uint32_t byte_num: 8, - ack_en: 1, - ack_exp: 1, - ack_val: 1, - op_code: 3, - reserved14: 17, - done: 1; + uint32_t byte_num: 8, /*!< Specifies the length of data (in bytes) to be read or written */ + ack_en: 1, /*!< Used to enable the I2C controller during a write operation to check whether ACK */ + ack_exp: 1, /*!< Used to configure the level of the ACK bit expected by the I2C controller during a write operation */ + ack_val: 1, /*!< Used to configure the level of the ACK bit sent by the I2C controller during a read operation */ + op_code: 3, /*!< Indicates the command */ + reserved14: 17, /*!< Reserved bits */ + done: 1; /*!< Indicates that a command has been executed */ }; uint32_t val; } i2c_ll_hw_cmd_t; @@ -44,26 +44,26 @@ typedef union { #define I2C_LL_CMD_END 4 /*!scl_low_period.period = bus_cfg->scl_low - 1; @@ -117,6 +117,18 @@ static inline void i2c_ll_set_bus_timing(i2c_dev_t *hw, i2c_hal_clk_config_t *bu hw->timeout.tout = bus_cfg->tout; } +/** + * @brief Set fractional divider + * + * @param hw Beginning address of the peripheral registers + * @param div_a The denominator of the frequency divider factor of the i2c function clock + * @param div_b The numerator of the frequency divider factor of the i2c function clock. + */ +static inline void i2c_ll_master_set_fractional_divider(i2c_dev_t *hw, uint8_t div_a, uint8_t div_b) +{ + // Not supported on ESP32S2 +} + /** * @brief Reset I2C txFIFO * @@ -124,6 +136,7 @@ static inline void i2c_ll_set_bus_timing(i2c_dev_t *hw, i2c_hal_clk_config_t *bu * * @return None */ +__attribute__((always_inline)) static inline void i2c_ll_txfifo_rst(i2c_dev_t *hw) { hw->fifo_conf.tx_fifo_rst = 1; @@ -137,28 +150,13 @@ static inline void i2c_ll_txfifo_rst(i2c_dev_t *hw) * * @return None */ +__attribute__((always_inline)) static inline void i2c_ll_rxfifo_rst(i2c_dev_t *hw) { hw->fifo_conf.rx_fifo_rst = 1; hw->fifo_conf.rx_fifo_rst = 0; } -/** - * @brief Configure I2C SCL timing - * - * @param hw Beginning address of the peripheral registers - * @param hight_period The I2C SCL hight period (in APB cycle, hight_period > 2) - * @param low_period The I2C SCL low period (in APB cycle, low_period > 1) - * - * @return None. - */ -static inline void i2c_ll_set_scl_timing(i2c_dev_t *hw, int hight_period, int low_period) -{ - hw->scl_low_period.period = low_period-1; - hw->scl_high_period.period = hight_period/2+2; - hw->scl_high_period.scl_wait_high_period = hight_period - hw->scl_high_period.period; -} - /** * @brief Clear I2C interrupt status * @@ -181,6 +179,7 @@ static inline void i2c_ll_clear_intr_mask(i2c_dev_t *hw, uint32_t mask) * * @return None */ +__attribute__((always_inline)) static inline void i2c_ll_enable_intr_mask(i2c_dev_t *hw, uint32_t mask) { hw->int_ena.val |= mask; @@ -221,7 +220,7 @@ static inline void i2c_ll_get_intr_mask(i2c_dev_t *hw, uint32_t *intr_status) * * @return None */ -static inline void i2c_ll_set_fifo_mode(i2c_dev_t *hw, bool fifo_mode_en) +static inline void i2c_ll_slave_set_fifo_mode(i2c_dev_t *hw, bool fifo_mode_en) { hw->fifo_conf.nonfifo_en = fifo_mode_en ? 0 : 1; } @@ -240,6 +239,19 @@ static inline void i2c_ll_set_tout(i2c_dev_t *hw, int tout) hw->timeout.time_out_en = tout > 0; } +/** + * @brief Configure I2C slave broadcasting mode. + * + * @param hw Beginning address of the peripheral registers + * @param broadcast_en Set true to enable broadcast, else, set it false + * + * @return None + */ +static inline void i2c_ll_slave_broadcast_enable(i2c_dev_t *hw, bool broadcast_en) +{ + // Not supported on esp32s2 +} + /** * @brief Configure I2C slave address * @@ -251,8 +263,14 @@ static inline void i2c_ll_set_tout(i2c_dev_t *hw, int tout) */ static inline void i2c_ll_set_slave_addr(i2c_dev_t *hw, uint16_t slave_addr, bool addr_10bit_en) { - hw->slave_addr.addr = slave_addr; hw->slave_addr.en_10bit = addr_10bit_en; + if (addr_10bit_en) { + uint8_t addr_14_7 = (slave_addr & 0xff) << 7; + uint8_t addr_6_0 = ((slave_addr & 0x300) >> 8) || 0x78; + hw->slave_addr.addr = addr_14_7 || addr_6_0; + } else { + hw->slave_addr.addr = slave_addr; + } } /** @@ -265,7 +283,7 @@ static inline void i2c_ll_set_slave_addr(i2c_dev_t *hw, uint16_t slave_addr, boo * @return None */ __attribute__((always_inline)) -static inline void i2c_ll_write_cmd_reg(i2c_dev_t *hw, i2c_ll_hw_cmd_t cmd, int cmd_idx) +static inline void i2c_ll_master_write_cmd_reg(i2c_dev_t *hw, i2c_ll_hw_cmd_t cmd, int cmd_idx) { hw->command[cmd_idx].val = cmd.val; } @@ -279,7 +297,7 @@ static inline void i2c_ll_write_cmd_reg(i2c_dev_t *hw, i2c_ll_hw_cmd_t cmd, int * * @return None */ -static inline void i2c_ll_set_start_timing(i2c_dev_t *hw, int start_setup, int start_hold) +static inline void i2c_ll_master_set_start_timing(i2c_dev_t *hw, int start_setup, int start_hold) { hw->scl_rstart_setup.time = start_setup; hw->scl_start_hold.time = start_hold-1; @@ -294,7 +312,7 @@ static inline void i2c_ll_set_start_timing(i2c_dev_t *hw, int start_setup, int s * * @return None */ -static inline void i2c_ll_set_stop_timing(i2c_dev_t *hw, int stop_setup, int stop_hold) +static inline void i2c_ll_master_set_stop_timing(i2c_dev_t *hw, int stop_setup, int stop_hold) { hw->scl_stop_setup.time = stop_setup; hw->scl_stop_hold.time = stop_hold; @@ -356,21 +374,6 @@ static inline void i2c_ll_set_data_mode(i2c_dev_t *hw, i2c_trans_mode_t tx_mode, hw->ctr.rx_lsb_first = rx_mode; } -/** - * @brief Get the I2C data mode - * - * @param hw Beginning address of the peripheral registers - * @param tx_mode Pointer to accept the received bytes mode - * @param rx_mode Pointer to accept the sended bytes mode - * - * @return None - */ -static inline void i2c_ll_get_data_mode(i2c_dev_t *hw, i2c_trans_mode_t *tx_mode, i2c_trans_mode_t *rx_mode) -{ - *tx_mode = hw->ctr.tx_lsb_first; - *rx_mode = hw->ctr.rx_lsb_first; -} - /** * @brief Get I2C sda timing configuration * @@ -405,6 +408,7 @@ static inline uint32_t i2c_ll_get_hw_version(i2c_dev_t *hw) * * @return True if I2C state machine is busy, else false will be returned */ +__attribute__((always_inline)) static inline bool i2c_ll_is_bus_busy(i2c_dev_t *hw) { return hw->status_reg.bus_busy; @@ -468,7 +472,7 @@ static inline void i2c_ll_get_tout(i2c_dev_t *hw, int *timeout) * @return None */ __attribute__((always_inline)) -static inline void i2c_ll_trans_start(i2c_dev_t *hw) +static inline void i2c_ll_master_trans_start(i2c_dev_t *hw) { hw->ctr.trans_start = 1; } @@ -503,21 +507,6 @@ static inline void i2c_ll_get_stop_timing(i2c_dev_t *hw, int *setup_time, int *h *hold_time = hw->scl_stop_hold.time; } -/** - * @brief Get I2C SCL timing configuration - * - * @param hw Beginning address of the peripheral registers - * @param high_period Pointer to accept the SCL high period - * @param low_period Pointer to accept the SCL low period - * - * @return None - */ -static inline void i2c_ll_get_scl_timing(i2c_dev_t *hw, int *high_period, int *low_period) -{ - *high_period = hw->scl_high_period.period + hw->scl_high_period.scl_wait_high_period; - *low_period = hw->scl_low_period.period + 1; -} - /** * @brief Write the I2C hardware txFIFO * @@ -563,7 +552,7 @@ static inline void i2c_ll_read_rxfifo(i2c_dev_t *hw, uint8_t *ptr, uint8_t len) * * @return None */ -static inline void i2c_ll_set_filter(i2c_dev_t *hw, uint8_t filter_num) +static inline void i2c_ll_master_set_filter(i2c_dev_t *hw, uint8_t filter_num) { if(filter_num > 0) { hw->scl_filter_cfg.thres = filter_num; @@ -583,7 +572,7 @@ static inline void i2c_ll_set_filter(i2c_dev_t *hw, uint8_t filter_num) * * @return The hardware filter configuration */ -static inline void i2c_ll_get_filter(i2c_dev_t *hw, uint8_t *filter_conf) +static inline void i2c_ll_master_get_filter(i2c_dev_t *hw, uint8_t *filter_conf) { *filter_conf = hw->sda_filter_cfg.thres; } @@ -607,12 +596,13 @@ static inline void i2c_ll_master_fsm_rst(i2c_dev_t *hw) * Note: The master cannot detect if deadlock happens, but when the scl_st_to interrupt is generated, a deadlock may occur. * * @param hw Beginning address of the peripheral registers + * @param slave_pulses When I2C master is IDLE, the number of pulses will be sent out. * * @return None */ -static inline void i2c_ll_master_clr_bus(i2c_dev_t *hw) +static inline void i2c_ll_master_clr_bus(i2c_dev_t *hw, uint32_t slave_pulses) { - hw->scl_sp_conf.scl_rst_slv_num = 9; + hw->scl_sp_conf.scl_rst_slv_num = slave_pulses; hw->scl_sp_conf.scl_rst_slv_en = 0; hw->scl_sp_conf.scl_rst_slv_en = 1; } @@ -727,6 +717,28 @@ static inline volatile void *i2c_ll_get_interrupt_status_reg(i2c_dev_t *dev) return &dev->int_status; } +/** + * @brief Enable I2C slave clock stretch. + * + * @param dev Beginning address of the peripheral registers. + * @param enable true: Enable, false: Disable. + */ +static inline void i2c_ll_slave_enable_scl_stretch(i2c_dev_t *dev, bool enable) +{ + dev->scl_stretch_conf.slave_scl_stretch_en = enable; +} + +/** + * @brief Clear I2C clock stretch status + * + * @param dev Beginning address of the peripheral registers + */ +__attribute__((always_inline)) +static inline void i2c_ll_slave_clear_stretch(i2c_dev_t *dev) +{ + dev->scl_stretch_conf.slave_scl_stretch_clr = 1; +} + //////////////////////////////////////////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)////////////////////////////// @@ -945,6 +957,52 @@ static inline void i2c_ll_slave_disable_rx_it(i2c_dev_t *hw) hw->int_ena.val &= (~I2C_LL_SLAVE_RX_INT); } +/** + * @brief Configure I2C SCL timing + * + * @param hw Beginning address of the peripheral registers + * @param hight_period The I2C SCL hight period (in APB cycle, hight_period > 2) + * @param low_period The I2C SCL low period (in APB cycle, low_period > 1) + * + * @return None. + */ +static inline void i2c_ll_set_scl_timing(i2c_dev_t *hw, int hight_period, int low_period) +{ + hw->scl_low_period.period = low_period-1; + hw->scl_high_period.period = hight_period/2+2; + hw->scl_high_period.scl_wait_high_period = hight_period - hw->scl_high_period.period; +} + +/** + * @brief Get the I2C data mode + * + * @param hw Beginning address of the peripheral registers + * @param tx_mode Pointer to accept the received bytes mode + * @param rx_mode Pointer to accept the sended bytes mode + * + * @return None + */ +static inline void i2c_ll_get_data_mode(i2c_dev_t *hw, i2c_trans_mode_t *tx_mode, i2c_trans_mode_t *rx_mode) +{ + *tx_mode = hw->ctr.tx_lsb_first; + *rx_mode = hw->ctr.rx_lsb_first; +} + +/** + * @brief Get I2C SCL timing configuration + * + * @param hw Beginning address of the peripheral registers + * @param high_period Pointer to accept the SCL high period + * @param low_period Pointer to accept the SCL low period + * + * @return None + */ +static inline void i2c_ll_get_scl_timing(i2c_dev_t *hw, int *high_period, int *low_period) +{ + *high_period = hw->scl_high_period.period + hw->scl_high_period.scl_wait_high_period; + *low_period = hw->scl_low_period.period + 1; +} + #ifdef __cplusplus } #endif diff --git a/components/hal/esp32s3/include/hal/i2c_ll.h b/components/hal/esp32s3/include/hal/i2c_ll.h index 9b051356e2..d36a331353 100644 --- a/components/hal/esp32s3/include/hal/i2c_ll.h +++ b/components/hal/esp32s3/include/hal/i2c_ll.h @@ -28,13 +28,13 @@ extern "C" { */ typedef union { struct { - uint32_t byte_num: 8, - ack_en: 1, - ack_exp: 1, - ack_val: 1, - op_code: 3, - reserved14: 17, - done: 1; + uint32_t byte_num: 8, /*!< Specifies the length of data (in bytes) to be read or written */ + ack_en: 1, /*!< Used to enable the I2C controller during a write operation to check whether ACK */ + ack_exp: 1, /*!< Used to configure the level of the ACK bit expected by the I2C controller during a write operation */ + ack_val: 1, /*!< Used to configure the level of the ACK bit sent by the I2C controller during a read operation */ + op_code: 3, /*!< Indicates the command */ + reserved14: 17, /*!< Reserved bits */ + done: 1; /*!< Indicates that a command has been executed */ }; uint32_t val; } i2c_ll_hw_cmd_t; @@ -47,25 +47,26 @@ typedef union { #define I2C_LL_CMD_END 4 /*!clk_conf, sclk_div_num, bus_cfg->clkm_div - 1); /* According to the Technical Reference Manual, the following timings must be subtracted by 1. @@ -150,6 +151,20 @@ static inline void i2c_ll_set_bus_timing(i2c_dev_t *hw, i2c_hal_clk_config_t *bu hw->to.time_out_en = 1; } +/** + * @brief Set fractional divider + * + * @param hw Beginning address of the peripheral registers + * @param div_a The denominator of the frequency divider factor of the i2c function clock + * @param div_b The numerator of the frequency divider factor of the i2c function clock. + */ +static inline void i2c_ll_master_set_fractional_divider(i2c_dev_t *hw, uint8_t div_a, uint8_t div_b) +{ + /* Set div_a and div_b to 0, as it's not necessary to use them */ + HAL_FORCE_MODIFY_U32_REG_FIELD(hw->clk_conf, sclk_div_a, div_a); + HAL_FORCE_MODIFY_U32_REG_FIELD(hw->clk_conf, sclk_div_b, div_b); +} + /** * @brief Reset I2C txFIFO * @@ -157,6 +172,7 @@ static inline void i2c_ll_set_bus_timing(i2c_dev_t *hw, i2c_hal_clk_config_t *bu * * @return None */ +__attribute__((always_inline)) static inline void i2c_ll_txfifo_rst(i2c_dev_t *hw) { hw->fifo_conf.tx_fifo_rst = 1; @@ -170,29 +186,13 @@ static inline void i2c_ll_txfifo_rst(i2c_dev_t *hw) * * @return None */ +__attribute__((always_inline)) static inline void i2c_ll_rxfifo_rst(i2c_dev_t *hw) { hw->fifo_conf.rx_fifo_rst = 1; hw->fifo_conf.rx_fifo_rst = 0; } -/** - * @brief Configure I2C SCL timing - * - * @param hw Beginning address of the peripheral registers - * @param high_period The I2C SCL hight period (in core clock cycle, hight_period > 2) - * @param low_period The I2C SCL low period (in core clock cycle, low_period > 1) - * - * @return None. - */ -static inline void i2c_ll_set_scl_timing(i2c_dev_t *hw, int high_period, int low_period) -{ - int high_period_output = high_period - 10; // The rising edge by open-drain output + internal pullup (about 50K) is slow - hw->scl_low_period.scl_low_period = low_period - 1; - hw->scl_high_period.scl_high_period = high_period_output; - hw->scl_high_period.scl_wait_high_period = high_period - high_period_output; -} - /** * @brief Clear I2C interrupt status * @@ -215,6 +215,7 @@ static inline void i2c_ll_clear_intr_mask(i2c_dev_t *hw, uint32_t mask) * * @return None */ +__attribute__((always_inline)) static inline void i2c_ll_enable_intr_mask(i2c_dev_t *hw, uint32_t mask) { hw->int_ena.val |= mask; @@ -255,7 +256,7 @@ static inline void i2c_ll_get_intr_mask(i2c_dev_t *hw, uint32_t *intr_status) * * @return None */ -static inline void i2c_ll_set_fifo_mode(i2c_dev_t *hw, bool fifo_mode_en) +static inline void i2c_ll_slave_set_fifo_mode(i2c_dev_t *hw, bool fifo_mode_en) { hw->fifo_conf.nonfifo_en = fifo_mode_en ? 0 : 1; } @@ -273,6 +274,19 @@ static inline void i2c_ll_set_tout(i2c_dev_t *hw, int tout) hw->to.time_out_value = tout; } +/** + * @brief Configure I2C slave broadcasting mode. + * + * @param hw Beginning address of the peripheral registers + * @param broadcast_en Set true to enable broadcast, else, set it false + * + * @return None + */ +static inline void i2c_ll_slave_broadcast_enable(i2c_dev_t *hw, bool broadcast_en) +{ + hw->ctr.addr_broadcasting_en = broadcast_en; +} + /** * @brief Configure I2C slave address * @@ -284,8 +298,15 @@ static inline void i2c_ll_set_tout(i2c_dev_t *hw, int tout) */ static inline void i2c_ll_set_slave_addr(i2c_dev_t *hw, uint16_t slave_addr, bool addr_10bit_en) { - hw->slave_addr.slave_addr = slave_addr; hw->slave_addr.addr_10bit_en = addr_10bit_en; + if (addr_10bit_en) { + uint8_t addr_14_7 = (slave_addr & 0xff) << 7; + uint8_t addr_6_0 = ((slave_addr & 0x300) >> 8) | 0x78; + hw->slave_addr.slave_addr = addr_14_7 | addr_6_0; + hw->ctr.addr_10bit_rw_check_en = addr_10bit_en; + } else { + hw->slave_addr.slave_addr = slave_addr; + } } /** @@ -298,7 +319,7 @@ static inline void i2c_ll_set_slave_addr(i2c_dev_t *hw, uint16_t slave_addr, boo * @return None */ __attribute__((always_inline)) -static inline void i2c_ll_write_cmd_reg(i2c_dev_t *hw, i2c_ll_hw_cmd_t cmd, int cmd_idx) +static inline void i2c_ll_master_write_cmd_reg(i2c_dev_t *hw, i2c_ll_hw_cmd_t cmd, int cmd_idx) { hw->comd[cmd_idx].val = cmd.val; } @@ -312,7 +333,7 @@ static inline void i2c_ll_write_cmd_reg(i2c_dev_t *hw, i2c_ll_hw_cmd_t cmd, int * * @return None */ -static inline void i2c_ll_set_start_timing(i2c_dev_t *hw, int start_setup, int start_hold) +static inline void i2c_ll_master_set_start_timing(i2c_dev_t *hw, int start_setup, int start_hold) { hw->scl_rstart_setup.scl_rstart_setup_time = start_setup; hw->scl_start_hold.scl_start_hold_time = start_hold - 1; @@ -327,7 +348,7 @@ static inline void i2c_ll_set_start_timing(i2c_dev_t *hw, int start_setup, int s * * @return None */ -static inline void i2c_ll_set_stop_timing(i2c_dev_t *hw, int stop_setup, int stop_hold) +static inline void i2c_ll_master_set_stop_timing(i2c_dev_t *hw, int stop_setup, int stop_hold) { hw->scl_stop_setup.scl_stop_setup_time = stop_setup; hw->scl_stop_hold.scl_stop_hold_time = stop_hold; @@ -389,21 +410,6 @@ static inline void i2c_ll_set_data_mode(i2c_dev_t *hw, i2c_trans_mode_t tx_mode, hw->ctr.rx_lsb_first = rx_mode; } -/** - * @brief Get the I2C data mode - * - * @param hw Beginning address of the peripheral registers - * @param tx_mode Pointer to accept the received bytes mode - * @param rx_mode Pointer to accept the sended bytes mode - * - * @return None - */ -static inline void i2c_ll_get_data_mode(i2c_dev_t *hw, i2c_trans_mode_t *tx_mode, i2c_trans_mode_t *rx_mode) -{ - *tx_mode = hw->ctr.tx_lsb_first; - *rx_mode = hw->ctr.rx_lsb_first; -} - /** * @brief Get I2C sda timing configuration * @@ -438,6 +444,7 @@ static inline uint32_t i2c_ll_get_hw_version(i2c_dev_t *hw) * * @return True if I2C state machine is busy, else false will be returned */ +__attribute__((always_inline)) static inline bool i2c_ll_is_bus_busy(i2c_dev_t *hw) { return hw->sr.bus_busy; @@ -501,7 +508,7 @@ static inline void i2c_ll_get_tout(i2c_dev_t *hw, int *timeout) * @return None */ __attribute__((always_inline)) -static inline void i2c_ll_trans_start(i2c_dev_t *hw) +static inline void i2c_ll_master_trans_start(i2c_dev_t *hw) { hw->ctr.trans_start = 1; } @@ -536,21 +543,6 @@ static inline void i2c_ll_get_stop_timing(i2c_dev_t *hw, int *setup_time, int *h *hold_time = hw->scl_stop_hold.scl_stop_hold_time; } -/** - * @brief Get I2C SCL timing configuration - * - * @param hw Beginning address of the peripheral registers - * @param high_period Pointer to accept the SCL high period - * @param low_period Pointer to accept the SCL low period - * - * @return None - */ -static inline void i2c_ll_get_scl_timing(i2c_dev_t *hw, int *high_period, int *low_period) -{ - *high_period = hw->scl_high_period.scl_high_period + hw->scl_high_period.scl_wait_high_period; - *low_period = hw->scl_low_period.scl_low_period + 1; -} - /** * @brief Write the I2C hardware txFIFO * @@ -585,6 +577,56 @@ static inline void i2c_ll_read_rxfifo(i2c_dev_t *hw, uint8_t *ptr, uint8_t len) } } +/** + * @brief Write the I2C hardware txFIFO + * + * @param hw Beginning address of the peripheral registers + * @param ram_offset Offset value of I2C RAM. + * @param ptr Pointer to data buffer + * @param len Amount of data needs to be writen + * + * @return None. + */ +static inline void i2c_ll_write_by_nonfifo(i2c_dev_t *hw, uint8_t ram_offset, const uint8_t *ptr, uint8_t len) +{ + uint32_t *fifo_addr = (uint32_t *)&hw->txfifo_start_addr; + for (int i = 0; i < len; i++) { + fifo_addr[i + ram_offset] = ptr[i]; + } +} + +/** + * @brief Read the I2C hardware ram + * + * @param hw Beginning address of the peripheral registers + * @param ram_offset Offset value of I2C RAM. + * @param ptr Pointer to data buffer + * @param len Amount of data needs read + * + * @return None + */ +static inline void i2c_ll_read_by_nonfifo(i2c_dev_t *hw, uint8_t ram_offset, uint8_t *ptr, uint8_t len) +{ + uint32_t *fifo_addr = (uint32_t *)&hw->rxfifo_start_addr; + + for (int i = 0; i < len; i++) { + ptr[i] = fifo_addr[i + ram_offset]; + } +} + +/** + * @brief Get access to I2C RAM address directly + * + * @param hw Beginning address of the peripheral registers + * @param addr_wr_en Enable I2C ram address read and write + * + * @return None +*/ +static inline void i2c_ll_enable_mem_access_nonfifo(i2c_dev_t *hw, bool addr_wr_en) +{ + hw->fifo_conf.fifo_addr_cfg_en = addr_wr_en; +} + /** * @brief Configure I2C hardware filter * @@ -594,7 +636,7 @@ static inline void i2c_ll_read_rxfifo(i2c_dev_t *hw, uint8_t *ptr, uint8_t len) * * @return None */ -static inline void i2c_ll_set_filter(i2c_dev_t *hw, uint8_t filter_num) +static inline void i2c_ll_master_set_filter(i2c_dev_t *hw, uint8_t filter_num) { if (filter_num > 0) { hw->filter_cfg.scl_filter_thres = filter_num; @@ -614,7 +656,7 @@ static inline void i2c_ll_set_filter(i2c_dev_t *hw, uint8_t filter_num) * * @return The hardware filter configuration */ -static inline void i2c_ll_get_filter(i2c_dev_t *hw, uint8_t *filter_conf) +static inline void i2c_ll_master_get_filter(i2c_dev_t *hw, uint8_t *filter_conf) { *filter_conf = hw->filter_cfg.scl_filter_thres; } @@ -638,12 +680,13 @@ static inline void i2c_ll_master_fsm_rst(i2c_dev_t *hw) * Note: The master cannot detect if deadlock happens, but when the scl_st_to interrupt is generated, a deadlock may occur. * * @param hw Beginning address of the peripheral registers + * @param slave_pulses When I2C master is IDLE, the number of pulses will be sent out. * * @return None */ -static inline void i2c_ll_master_clr_bus(i2c_dev_t *hw) +static inline void i2c_ll_master_clr_bus(i2c_dev_t *hw, uint32_t slave_pulses) { - hw->scl_sp_conf.scl_rst_slv_num = 9; + hw->scl_sp_conf.scl_rst_slv_num = slave_pulses; hw->scl_sp_conf.scl_rst_slv_en = 1; hw->ctr.conf_upgate = 1; } @@ -726,6 +769,28 @@ static inline volatile void *i2c_ll_get_interrupt_status_reg(i2c_dev_t *dev) return &dev->int_status; } +/** + * @brief Enable I2C slave clock stretch. + * + * @param dev Beginning address of the peripheral registers. + * @param enable true: Enable, false: Disable. + */ +static inline void i2c_ll_slave_enable_scl_stretch(i2c_dev_t *dev, bool enable) +{ + dev->scl_stretch_conf.slave_scl_stretch_en = enable; +} + +/** + * @brief Clear I2C clock stretch status + * + * @param dev Beginning address of the peripheral registers + */ +__attribute__((always_inline)) +static inline void i2c_ll_slave_clear_stretch(i2c_dev_t *dev) +{ + dev->scl_stretch_conf.slave_scl_stretch_clr = 1; +} + //////////////////////////////////////////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)////////////////////////////// @@ -943,6 +1008,53 @@ static inline void i2c_ll_slave_disable_rx_it(i2c_dev_t *hw) hw->int_ena.val &= (~I2C_LL_SLAVE_RX_INT); } +/** + * @brief Configure I2C SCL timing + * + * @param hw Beginning address of the peripheral registers + * @param high_period The I2C SCL hight period (in core clock cycle, hight_period > 2) + * @param low_period The I2C SCL low period (in core clock cycle, low_period > 1) + * + * @return None. + */ +static inline void i2c_ll_set_scl_timing(i2c_dev_t *hw, int high_period, int low_period) +{ + int high_period_output = high_period - 10; // The rising edge by open-drain output + internal pullup (about 50K) is slow + hw->scl_low_period.scl_low_period = low_period - 1; + hw->scl_high_period.scl_high_period = high_period_output; + hw->scl_high_period.scl_wait_high_period = high_period - high_period_output; +} + +/** + * @brief Get the I2C data mode + * + * @param hw Beginning address of the peripheral registers + * @param tx_mode Pointer to accept the received bytes mode + * @param rx_mode Pointer to accept the sended bytes mode + * + * @return None + */ +static inline void i2c_ll_get_data_mode(i2c_dev_t *hw, i2c_trans_mode_t *tx_mode, i2c_trans_mode_t *rx_mode) +{ + *tx_mode = hw->ctr.tx_lsb_first; + *rx_mode = hw->ctr.rx_lsb_first; +} + +/** + * @brief Get I2C SCL timing configuration + * + * @param hw Beginning address of the peripheral registers + * @param high_period Pointer to accept the SCL high period + * @param low_period Pointer to accept the SCL low period + * + * @return None + */ +static inline void i2c_ll_get_scl_timing(i2c_dev_t *hw, int *high_period, int *low_period) +{ + *high_period = hw->scl_high_period.scl_high_period + hw->scl_high_period.scl_wait_high_period; + *low_period = hw->scl_low_period.scl_low_period + 1; +} + #ifdef __cplusplus } #endif diff --git a/components/hal/i2c_hal.c b/components/hal/i2c_hal.c index efb55e94c2..80a3d3e4ca 100644 --- a/components/hal/i2c_hal.c +++ b/components/hal/i2c_hal.c @@ -14,8 +14,6 @@ void i2c_hal_slave_init(i2c_hal_context_t *hal) { i2c_ll_slave_init(hal->dev); - //Use fifo mode - i2c_ll_set_fifo_mode(hal->dev, true); //MSB i2c_ll_set_data_mode(hal->dev, I2C_DATA_MODE_MSB_FIRST, I2C_DATA_MODE_MSB_FIRST); //Reset fifo @@ -28,8 +26,8 @@ void i2c_hal_set_bus_timing(i2c_hal_context_t *hal, int scl_freq, i2c_clock_sour { i2c_ll_set_source_clk(hal->dev, src_clk); i2c_hal_clk_config_t clk_cal = {0}; - i2c_ll_cal_bus_clk(source_freq, scl_freq, &clk_cal); - i2c_ll_set_bus_timing(hal->dev, &clk_cal); + i2c_ll_master_cal_bus_clk(source_freq, scl_freq, &clk_cal); + i2c_ll_master_set_bus_timing(hal->dev, &clk_cal); } void i2c_hal_master_fsm_rst(i2c_hal_context_t *hal) @@ -40,8 +38,6 @@ void i2c_hal_master_fsm_rst(i2c_hal_context_t *hal) void i2c_hal_master_init(i2c_hal_context_t *hal) { i2c_ll_master_init(hal->dev); - //Use fifo mode - i2c_ll_set_fifo_mode(hal->dev, true); //MSB i2c_ll_set_data_mode(hal->dev, I2C_DATA_MODE_MSB_FIRST, I2C_DATA_MODE_MSB_FIRST); //Reset fifo @@ -63,12 +59,6 @@ void i2c_hal_deinit(i2c_hal_context_t *hal) hal->dev = NULL; } -void i2c_hal_master_trans_start(i2c_hal_context_t *hal) -{ - i2c_ll_update(hal->dev); - i2c_ll_trans_start(hal->dev); -} - void i2c_hal_get_timing_config(i2c_hal_context_t *hal, i2c_hal_timing_config_t *timing_config) { i2c_ll_get_scl_clk_timing(hal->dev, &timing_config->high_period, &timing_config->low_period, &timing_config->wait_high_period); @@ -81,8 +71,8 @@ void i2c_hal_get_timing_config(i2c_hal_context_t *hal, i2c_hal_timing_config_t * void i2c_hal_set_timing_config(i2c_hal_context_t *hal, i2c_hal_timing_config_t *timing_config) { i2c_ll_set_scl_clk_timing(hal->dev, timing_config->high_period, timing_config->low_period, timing_config->wait_high_period); - i2c_ll_set_start_timing(hal->dev, timing_config->rstart_setup, timing_config->start_hold); - i2c_ll_set_stop_timing(hal->dev, timing_config->stop_setup, timing_config->stop_hold); + i2c_ll_master_set_start_timing(hal->dev, timing_config->rstart_setup, timing_config->start_hold); + i2c_ll_master_set_stop_timing(hal->dev, timing_config->stop_setup, timing_config->stop_hold); i2c_ll_set_sda_timing(hal->dev, timing_config->sda_sample, timing_config->sda_hold); i2c_ll_set_tout(hal->dev, timing_config->timeout); } diff --git a/components/hal/i2c_hal_iram.c b/components/hal/i2c_hal_iram.c index d2d7e97e02..f495de5595 100644 --- a/components/hal/i2c_hal_iram.c +++ b/components/hal/i2c_hal_iram.c @@ -6,6 +6,11 @@ #include "hal/i2c_hal.h" +void i2c_hal_master_trans_start(i2c_hal_context_t *hal) +{ + i2c_ll_update(hal->dev); + i2c_ll_master_trans_start(hal->dev); +} //////////////////////////////////////////Deprecated Functions////////////////////////////////////////////////////////// /////////////////////////////The following functions are only used by the legacy driver///////////////////////////////// diff --git a/components/hal/include/hal/i2c_hal.h b/components/hal/include/hal/i2c_hal.h index 24b72675d5..797bf6e8ab 100644 --- a/components/hal/include/hal/i2c_hal.h +++ b/components/hal/include/hal/i2c_hal.h @@ -33,6 +33,22 @@ typedef struct { i2c_dev_t *dev; } i2c_hal_context_t; +/** + * @brief Timing configuration structure. Used for I2C reset internally. + */ +typedef struct { + int high_period; /*!< high_period time */ + int low_period; /*!< low_period time */ + int wait_high_period; /*!< wait_high_period time */ + int rstart_setup; /*!< restart setup */ + int start_hold; /*!< start hold time */ + int stop_setup; /*!< stop setup */ + int stop_hold; /*!< stop hold time */ + int sda_sample; /*!< high_period time */ + int sda_hold; /*!< sda hold time */ + int timeout; /*!< timeout value */ +} i2c_hal_timing_config_t; + #if SOC_I2C_SUPPORT_SLAVE /** diff --git a/components/hal/include/hal/i2c_types.h b/components/hal/include/hal/i2c_types.h index dc8398edc5..ef66199ce3 100644 --- a/components/hal/include/hal/i2c_types.h +++ b/components/hal/include/hal/i2c_types.h @@ -29,6 +29,16 @@ typedef enum { I2C_NUM_MAX, /*!< I2C port max */ } i2c_port_t; +/** + * @brief Enumeration for I2C device address bit length + */ +typedef enum { + I2C_ADDR_BIT_LEN_7 = 0, /*!< i2c address bit length 7 */ +#if SOC_I2C_SUPPORT_10BIT_ADDR + I2C_ADDR_BIT_LEN_10 = 1, /*!< i2c address bit length 10 */ +#endif +} i2c_addr_bit_len_t; + /** * @brief Data structure for calculating I2C bus timing. */ @@ -63,6 +73,7 @@ typedef enum { I2C_DATA_MODE_MAX } i2c_trans_mode_t; +__attribute__((deprecated("please use 'i2c_addr_bit_len_t' instead"))) typedef enum { I2C_ADDR_BIT_7 = 0, /*!< I2C 7bit address for slave mode */ I2C_ADDR_BIT_10, /*!< I2C 10bit address for slave mode */ @@ -76,22 +87,6 @@ typedef enum { I2C_MASTER_ACK_MAX, } i2c_ack_type_t; -/** - * @brief Timing configuration structure. Used for I2C reset internally. - */ -typedef struct { - int high_period; /*!< high_period time */ - int low_period; /*!< low_period time */ - int wait_high_period; /*!< wait_high_period time */ - int rstart_setup; /*!< restart setup */ - int start_hold; /*!< start hold time */ - int stop_setup; /*!< stop setup */ - int stop_hold; /*!< stop hold time */ - int sda_sample; /*!< high_period time */ - int sda_hold; /*!< sda hold time */ - int timeout; /*!< timeout value */ -} i2c_hal_timing_config_t; - #if SOC_I2C_SUPPORTED /** * @brief I2C group clock source diff --git a/components/soc/esp32/include/soc/Kconfig.soc_caps.in b/components/soc/esp32/include/soc/Kconfig.soc_caps.in index 5e473ef4ce..82dd1c56e1 100644 --- a/components/soc/esp32/include/soc/Kconfig.soc_caps.in +++ b/components/soc/esp32/include/soc/Kconfig.soc_caps.in @@ -315,6 +315,10 @@ config SOC_I2C_SUPPORT_APB bool default y +config SOC_I2C_STOP_INDEPENDENT + bool + default y + config SOC_I2S_NUM int default 2 diff --git a/components/soc/esp32/include/soc/soc_caps.h b/components/soc/esp32/include/soc/soc_caps.h index d6da938b42..a57ad97682 100644 --- a/components/soc/esp32/include/soc/soc_caps.h +++ b/components/soc/esp32/include/soc/soc_caps.h @@ -184,6 +184,9 @@ #define SOC_I2C_SUPPORT_APB (1) +// On ESP32, the stop bit should be independent, we can't put trans data and stop command together +#define SOC_I2C_STOP_INDEPENDENT (1) + /*-------------------------- I2S CAPS ----------------------------------------*/ // ESP32 has 2 I2S #define SOC_I2S_NUM (2U) diff --git a/components/soc/esp32c2/include/soc/Kconfig.soc_caps.in b/components/soc/esp32c2/include/soc/Kconfig.soc_caps.in index 974c1d890a..49103c8b6a 100644 --- a/components/soc/esp32c2/include/soc/Kconfig.soc_caps.in +++ b/components/soc/esp32c2/include/soc/Kconfig.soc_caps.in @@ -299,6 +299,10 @@ config SOC_I2C_SUPPORT_RTC bool default y +config SOC_I2C_SUPPORT_10BIT_ADDR + bool + default y + config SOC_LEDC_SUPPORT_PLL_DIV_CLOCK bool default y diff --git a/components/soc/esp32c2/include/soc/soc_caps.h b/components/soc/esp32c2/include/soc/soc_caps.h index 406525a187..40ad604623 100644 --- a/components/soc/esp32c2/include/soc/soc_caps.h +++ b/components/soc/esp32c2/include/soc/soc_caps.h @@ -147,6 +147,7 @@ #define SOC_I2C_SUPPORT_XTAL (1) #define SOC_I2C_SUPPORT_RTC (1) +#define SOC_I2C_SUPPORT_10BIT_ADDR (1) /*-------------------------- LEDC CAPS ---------------------------------------*/ #define SOC_LEDC_SUPPORT_PLL_DIV_CLOCK (1) diff --git a/components/soc/esp32c3/include/soc/Kconfig.soc_caps.in b/components/soc/esp32c3/include/soc/Kconfig.soc_caps.in index a6fb106362..c47fe037d9 100644 --- a/components/soc/esp32c3/include/soc/Kconfig.soc_caps.in +++ b/components/soc/esp32c3/include/soc/Kconfig.soc_caps.in @@ -395,6 +395,10 @@ config SOC_I2C_SUPPORT_RTC bool default y +config SOC_I2C_SUPPORT_10BIT_ADDR + bool + default y + config SOC_I2S_NUM int default 1 diff --git a/components/soc/esp32c3/include/soc/soc_caps.h b/components/soc/esp32c3/include/soc/soc_caps.h index ce4f2dfcbd..15ac56e552 100644 --- a/components/soc/esp32c3/include/soc/soc_caps.h +++ b/components/soc/esp32c3/include/soc/soc_caps.h @@ -186,6 +186,7 @@ #define SOC_I2C_SUPPORT_XTAL (1) #define SOC_I2C_SUPPORT_RTC (1) +#define SOC_I2C_SUPPORT_10BIT_ADDR (1) /*-------------------------- I2S CAPS ----------------------------------------*/ #define SOC_I2S_NUM (1U) diff --git a/components/soc/esp32c6/include/soc/Kconfig.soc_caps.in b/components/soc/esp32c6/include/soc/Kconfig.soc_caps.in index c3d572d073..6d08938d24 100644 --- a/components/soc/esp32c6/include/soc/Kconfig.soc_caps.in +++ b/components/soc/esp32c6/include/soc/Kconfig.soc_caps.in @@ -511,6 +511,10 @@ config SOC_I2C_SUPPORT_RTC bool default y +config SOC_I2C_SUPPORT_10BIT_ADDR + bool + default y + config SOC_LP_I2C_NUM int default 1 diff --git a/components/soc/esp32c6/include/soc/soc_caps.h b/components/soc/esp32c6/include/soc/soc_caps.h index 52d9b8f7da..5b76cd2d0f 100644 --- a/components/soc/esp32c6/include/soc/soc_caps.h +++ b/components/soc/esp32c6/include/soc/soc_caps.h @@ -228,6 +228,7 @@ #define SOC_I2C_SUPPORT_XTAL (1) #define SOC_I2C_SUPPORT_RTC (1) +#define SOC_I2C_SUPPORT_10BIT_ADDR (1) /*-------------------------- LP_I2C CAPS -------------------------------------*/ // ESP32-C6 has 1 LP_I2C diff --git a/components/soc/esp32h2/include/soc/Kconfig.soc_caps.in b/components/soc/esp32h2/include/soc/Kconfig.soc_caps.in index e019e99a72..dc6fe95702 100644 --- a/components/soc/esp32h2/include/soc/Kconfig.soc_caps.in +++ b/components/soc/esp32h2/include/soc/Kconfig.soc_caps.in @@ -499,6 +499,10 @@ config SOC_I2C_SUPPORT_RTC bool default y +config SOC_I2C_SUPPORT_10BIT_ADDR + bool + default y + config SOC_I2S_NUM int default 1 diff --git a/components/soc/esp32h2/include/soc/soc_caps.h b/components/soc/esp32h2/include/soc/soc_caps.h index 845dffb21d..2dff98240c 100644 --- a/components/soc/esp32h2/include/soc/soc_caps.h +++ b/components/soc/esp32h2/include/soc/soc_caps.h @@ -230,6 +230,7 @@ #define SOC_I2C_SUPPORT_XTAL (1) #define SOC_I2C_SUPPORT_RTC (1) +#define SOC_I2C_SUPPORT_10BIT_ADDR (1) /*-------------------------- I2S CAPS ----------------------------------------*/ #define SOC_I2S_NUM (1U) diff --git a/components/soc/esp32s3/include/soc/Kconfig.soc_caps.in b/components/soc/esp32s3/include/soc/Kconfig.soc_caps.in index 5045212762..0189fb6a20 100644 --- a/components/soc/esp32s3/include/soc/Kconfig.soc_caps.in +++ b/components/soc/esp32s3/include/soc/Kconfig.soc_caps.in @@ -455,6 +455,10 @@ config SOC_I2C_SUPPORT_RTC bool default y +config SOC_I2C_SUPPORT_10BIT_ADDR + 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 6cd12bd56e..6a3e2ffa45 100644 --- a/components/soc/esp32s3/include/soc/soc_caps.h +++ b/components/soc/esp32s3/include/soc/soc_caps.h @@ -190,6 +190,8 @@ #define SOC_I2C_SUPPORT_XTAL (1) #define SOC_I2C_SUPPORT_RTC (1) +#define SOC_I2C_SUPPORT_10BIT_ADDR (1) + /*-------------------------- I2S CAPS ----------------------------------------*/ #define SOC_I2S_NUM (2U) #define SOC_I2S_HW_VERSION_2 (1)