Skip to content

Commit

Permalink
Merge branch 'feature/i2c_sleep' into 'master'
Browse files Browse the repository at this point in the history
feat(i2c): Support i2c sleep retention on esp32c6/h2

Closes IDF-8458

See merge request espressif/esp-idf!28885
  • Loading branch information
mythbuster5 committed Feb 24, 2024
2 parents ff94741 + 27b2f7a commit 2302dd5
Show file tree
Hide file tree
Showing 27 changed files with 589 additions and 71 deletions.
16 changes: 15 additions & 1 deletion components/driver/i2c/i2c.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
Expand Down Expand Up @@ -28,6 +28,10 @@
#include "esp_rom_sys.h"
#include <sys/param.h>
#include "soc/clk_tree_defs.h"
#if CONFIG_PM_POWER_DOWN_PERIPHERAL_IN_LIGHT_SLEEP
#include "esp_private/sleep_retention.h"
#endif


#if SOC_I2C_SUPPORT_APB || SOC_I2C_SUPPORT_XTAL
#include "esp_private/esp_clk.h"
Expand Down Expand Up @@ -316,6 +320,7 @@ esp_err_t i2c_driver_install(i2c_port_t i2c_num, i2c_mode_t mode, size_t slv_rx_
#if CONFIG_SPIRAM_USE_MALLOC
p_i2c->intr_alloc_flags = intr_alloc_flags;
#endif

#if SOC_I2C_SUPPORT_SLAVE
if (mode == I2C_MODE_SLAVE) {

Expand Down Expand Up @@ -409,6 +414,11 @@ esp_err_t i2c_driver_install(i2c_port_t i2c_num, i2c_mode_t mode, size_t slv_rx_
i2c_ll_slave_enable_rx_it(i2c_context[i2c_num].hal.dev);
}
#endif // SOC_I2C_SUPPORT_SLAVE

#if CONFIG_PM_POWER_DOWN_PERIPHERAL_IN_LIGHT_SLEEP
ret = sleep_retention_entries_create(i2c_regs_retention[i2c_num].link_list, i2c_regs_retention[i2c_num].link_num, REGDMA_LINK_PRI_7, I2C_SLEEP_RETENTION_MODULE(i2c_num));
ESP_GOTO_ON_ERROR(ret, err, I2C_TAG, "failed to allocate mem for sleep retention");
#endif
return ESP_OK;

err:
Expand Down Expand Up @@ -461,6 +471,10 @@ esp_err_t i2c_driver_delete(i2c_port_t i2c_num)
esp_intr_free(p_i2c->intr_handle);
p_i2c->intr_handle = NULL;

#if CONFIG_PM_POWER_DOWN_PERIPHERAL_IN_LIGHT_SLEEP
sleep_retention_entries_destroy(I2C_SLEEP_RETENTION_MODULE(i2c_num));
#endif

if (p_i2c->cmd_mux) {
// Let any command in progress finish.
xSemaphoreTake(p_i2c->cmd_mux, portMAX_DELAY);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@ set(srcs "test_app_main.c"
"test_i2c.c"
)

# Only build this file with `CONFIG_PM_POWER_DOWN_CPU_IN_LIGHT_SLEEP` and `CONFIG_IEEE802154_ENABLED` enabled
# Enable `CONFIG_IEEE802154_ENABLED` is for modem domain really power down.
# This reliable can be removed if the sleep retention got finished.
if(CONFIG_PM_POWER_DOWN_CPU_IN_LIGHT_SLEEP AND CONFIG_IEEE802154_ENABLED)
list(APPEND srcs "test_legacy_i2c_sleep_retention.c")
endif()

idf_component_register(SRCS ${srcs}
PRIV_REQUIRES unity test_utils driver
PRIV_REQUIRES unity test_utils driver ieee802154
WHOLE_ARCHIVE)
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

// 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)
#define LEAKS (900)

void setUp(void)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/

#include <stdio.h>
#include <string.h>
#include "unity.h"
#include "unity_config.h"
#include "driver/i2c.h"
#include "esp_attr.h"
#include "esp_log.h"
#include "esp_system.h"
#include "hal/i2c_types.h"
#include "test_utils.h"
#include "esp_sleep.h"
#include "esp_private/sleep_cpu.h"
#include "esp_ieee802154.h"
#include "esp_pm.h"

#define DATA_LENGTH 100 /*!<Data buffer length for test buffer*/

#define I2C_SLAVE_SCL_IO 4 /*!<gpio number for i2c slave clock */
#define I2C_SLAVE_SDA_IO 5 /*!<gpio number for i2c slave data */

#define I2C_SLAVE_NUM I2C_NUM_0 /*!<I2C port number for slave dev */
#define I2C_SLAVE_TX_BUF_LEN (2*DATA_LENGTH) /*!<I2C slave tx buffer size */
#define I2C_SLAVE_RX_BUF_LEN (2*DATA_LENGTH) /*!<I2C slave rx buffer size */

#define I2C_MASTER_SCL_IO 4 /*!<gpio number for i2c master clock */
#define I2C_MASTER_SDA_IO 5 /*!<gpio number for i2c master data */

#define I2C_MASTER_NUM I2C_NUM_0 /*!< I2C port number for master dev */
#define I2C_MASTER_TX_BUF_DISABLE 0 /*!< I2C master do not need buffer */
#define I2C_MASTER_RX_BUF_DISABLE 0 /*!< I2C master do not need buffer */
#define I2C_MASTER_FREQ_HZ 100000 /*!< I2C master clock frequency */

#define ESP_SLAVE_ADDR 0x28 /*!< ESP32 slave address, you can set any 7bit value */
#define WRITE_BIT I2C_MASTER_WRITE /*!< I2C master write */
#define ACK_CHECK_EN 0x1 /*!< I2C master will check ack from slave*/


static esp_err_t i2c_master_write_slave(i2c_port_t i2c_num, uint8_t *data_wr, size_t size)
{
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
TEST_ESP_OK(i2c_master_write_byte(cmd, ( ESP_SLAVE_ADDR << 1 ) | WRITE_BIT, ACK_CHECK_EN));
TEST_ESP_OK(i2c_master_write(cmd, data_wr, size, ACK_CHECK_EN));
TEST_ESP_OK(i2c_master_stop(cmd));
esp_err_t ret = i2c_master_cmd_begin(i2c_num, cmd, 5000 / portTICK_PERIOD_MS);
i2c_cmd_link_delete(cmd);
return ret;
}

static i2c_config_t i2c_master_init(void)
{
i2c_config_t conf_master = {
.mode = I2C_MODE_MASTER,
.sda_pullup_en = GPIO_PULLUP_ENABLE,
.scl_pullup_en = GPIO_PULLUP_ENABLE,
.master.clk_speed = I2C_MASTER_FREQ_HZ,
.sda_io_num = I2C_MASTER_SDA_IO,
.scl_io_num = I2C_MASTER_SCL_IO,
.clk_flags = 0,
};
return conf_master;
}

static i2c_config_t i2c_slave_init(void)
{
i2c_config_t conf_slave = {
.mode = I2C_MODE_SLAVE,
.sda_io_num = I2C_SLAVE_SDA_IO,
.scl_io_num = I2C_SLAVE_SCL_IO,
.sda_pullup_en = GPIO_PULLUP_ENABLE,
.scl_pullup_en = GPIO_PULLUP_ENABLE,
.slave.addr_10bit_en = 0,
.slave.slave_addr = ESP_SLAVE_ADDR,
};
return conf_slave;
}

// print the reading buffer
static void disp_buf(uint8_t *buf, int len)
{
int i;
for (i = 0; i < len; i++) {
printf("%02x ", buf[i]);
if (( i + 1 ) % 16 == 0) {
printf("\n");
}
}
printf("\n");
}

static void i2c_master_write_sleep_test(void)
{
uint8_t *data_wr = (uint8_t *) malloc(DATA_LENGTH);
int i;

i2c_config_t conf_master = i2c_master_init();
TEST_ESP_OK(i2c_param_config(I2C_MASTER_NUM, &conf_master));

TEST_ESP_OK(i2c_driver_install(I2C_MASTER_NUM, I2C_MODE_MASTER,
I2C_MASTER_RX_BUF_DISABLE,
I2C_MASTER_TX_BUF_DISABLE, 0));
unity_wait_for_signal("i2c slave init finish");

unity_send_signal("master write and sleep");
for (i = 0; i < DATA_LENGTH; i++) {
data_wr[i] = i;
}
i2c_master_write_slave(I2C_MASTER_NUM, data_wr, DATA_LENGTH);
disp_buf(data_wr, i + 1);

TEST_ESP_OK(esp_ieee802154_enable());
TEST_ESP_OK(sleep_cpu_configure(true));
TEST_ESP_OK(esp_sleep_enable_timer_wakeup(3 * 1000 * 1000));
TEST_ESP_OK(esp_light_sleep_start());

printf("Waked up!!\n");
unity_wait_for_signal("i2c slave receive once");

for (i = 0; i < DATA_LENGTH; i++) {
data_wr[i] = i;
}
i2c_master_write_slave(I2C_MASTER_NUM, data_wr, DATA_LENGTH);
disp_buf(data_wr, i + 1);
unity_send_signal("master write again");

free(data_wr);
unity_wait_for_signal("ready to delete");
TEST_ESP_OK(sleep_cpu_configure(false));
TEST_ESP_OK(esp_ieee802154_disable());
TEST_ESP_OK(i2c_driver_delete(I2C_MASTER_NUM));
}

static void i2c_slave_read_sleep_test(void)
{
uint8_t *data_rd = (uint8_t *) malloc(DATA_LENGTH);
int size_rd = 0;
int len = 0;

i2c_config_t conf_slave = i2c_slave_init();
TEST_ESP_OK(i2c_param_config( I2C_SLAVE_NUM, &conf_slave));
TEST_ESP_OK(i2c_driver_install(I2C_SLAVE_NUM, I2C_MODE_SLAVE,
I2C_SLAVE_RX_BUF_LEN,
I2C_SLAVE_TX_BUF_LEN, 0));
unity_send_signal("i2c slave init finish");

unity_wait_for_signal("master write and sleep");
while (1) {
len = i2c_slave_read_buffer( I2C_SLAVE_NUM, data_rd + size_rd, DATA_LENGTH, 10000 / portTICK_PERIOD_MS);
if (len == 0) {
break;
}
size_rd += len;
}
disp_buf(data_rd, size_rd);
for (int i = 0; i < size_rd; i++) {
TEST_ASSERT(data_rd[i] == i);
}

TEST_ESP_OK(esp_ieee802154_enable());
TEST_ESP_OK(sleep_cpu_configure(true));
TEST_ESP_OK(esp_sleep_enable_timer_wakeup(1 * 1000 * 1000));
TEST_ESP_OK(esp_light_sleep_start());

unity_send_signal("i2c slave receive once");
unity_wait_for_signal("master write again");

memset(data_rd, 0, DATA_LENGTH);
size_rd = 0;
while (1) {
len = i2c_slave_read_buffer( I2C_SLAVE_NUM, data_rd + size_rd, DATA_LENGTH, 10000 / portTICK_PERIOD_MS);
if (len == 0) {
break;
}
size_rd += len;
}
disp_buf(data_rd, size_rd);
for (int i = 0; i < size_rd; i++) {
TEST_ASSERT(data_rd[i] == i);
}

free(data_rd);
unity_send_signal("ready to delete");
TEST_ESP_OK(sleep_cpu_configure(false));
TEST_ESP_OK(esp_ieee802154_disable());
TEST_ESP_OK(i2c_driver_delete(I2C_SLAVE_NUM));
}

TEST_CASE_MULTIPLE_DEVICES("I2C legacy sleep retention test", "[i2c][test_env=generic_multi_device][timeout=250]", i2c_master_write_sleep_test, i2c_slave_read_sleep_test);
19 changes: 17 additions & 2 deletions components/driver/test_apps/legacy_i2c_driver/pytest_i2c_legacy.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
# SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
# SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: CC0-1.0

import pytest
from pytest_embedded import Dut

Expand Down Expand Up @@ -38,3 +37,19 @@ 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, timeout=120)


@pytest.mark.esp32c6
@pytest.mark.esp32h2
@pytest.mark.generic_multi_device
@pytest.mark.parametrize(
'count, config',
[
(2, 'sleep_retention',),
],
indirect=True
)
def test_i2c_sleep_retention_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, timeout=250)
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
CONFIG_PM_ENABLE=y
CONFIG_PM_POWER_DOWN_CPU_IN_LIGHT_SLEEP=y
CONFIG_FREERTOS_USE_TICKLESS_IDLE=y
CONFIG_IEEE802154_ENABLED=y
CONFIG_IEEE802154_SLEEP_ENABLE=y
13 changes: 12 additions & 1 deletion components/esp_driver_i2c/i2c_common.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
Expand All @@ -26,6 +26,9 @@
#include "soc/i2c_periph.h"
#include "esp_clk_tree.h"
#include "clk_ctrl_os.h"
#if CONFIG_PM_POWER_DOWN_PERIPHERAL_IN_LIGHT_SLEEP
#include "esp_private/sleep_retention.h"
#endif

static const char *TAG = "i2c.common";

Expand Down Expand Up @@ -55,6 +58,11 @@ static esp_err_t s_i2c_bus_handle_aquire(i2c_port_num_t port_num, i2c_bus_handle
bus->spinlock = (portMUX_TYPE)portMUX_INITIALIZER_UNLOCKED;
bus->bus_mode = mode;

#if CONFIG_PM_POWER_DOWN_PERIPHERAL_IN_LIGHT_SLEEP
ret = sleep_retention_entries_create(i2c_regs_retention[port_num].link_list, i2c_regs_retention[port_num].link_num, REGDMA_LINK_PRI_7, I2C_SLEEP_RETENTION_MODULE(port_num));
ESP_RETURN_ON_ERROR(ret, TAG, "failed to allocate mem for sleep retention");
#endif

// Enable the I2C module
I2C_RCC_ATOMIC() {
i2c_ll_enable_bus_clock(bus->port_num, true);
Expand Down Expand Up @@ -128,6 +136,9 @@ esp_err_t i2c_release_bus_handle(i2c_bus_handle_t i2c_bus)
if (s_i2c_platform.count[port_num] == 0) {
do_deinitialize = true;
s_i2c_platform.buses[port_num] = NULL;
#if CONFIG_PM_POWER_DOWN_PERIPHERAL_IN_LIGHT_SLEEP
sleep_retention_entries_destroy(I2C_SLEEP_RETENTION_MODULE(port_num));
#endif
if (i2c_bus->intr_handle) {
ESP_RETURN_ON_ERROR(esp_intr_free(i2c_bus->intr_handle), TAG, "delete interrupt service failed");
}
Expand Down
2 changes: 1 addition & 1 deletion components/esp_driver_i2c/i2c_slave.c
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ esp_err_t i2c_new_slave_device(const i2c_slave_config_t *slave_config, i2c_slave
ESP_RETURN_ON_FALSE(slave_config->i2c_port < SOC_I2C_NUM || slave_config->i2c_port == -1, ESP_ERR_INVALID_ARG, TAG, "invalid i2c port number");
ESP_RETURN_ON_FALSE((slave_config->send_buf_depth > 0), ESP_ERR_INVALID_ARG, TAG, "invalid SCL speed");
#if SOC_I2C_SLAVE_SUPPORT_BROADCAST
ESP_GOTO_ON_FALSE(((slave_config->addr_bit_len != I2C_ADDR_BIT_LEN_10) || (!slave_config->flags.broadcast_en)), ESP_ERR_INVALID_STATE, err, TAG, "10bits address cannot used together with broadcast");
ESP_RETURN_ON_FALSE(((slave_config->addr_bit_len != I2C_ADDR_BIT_LEN_10) || (!slave_config->flags.broadcast_en)), ESP_ERR_INVALID_STATE, TAG, "10bits address cannot used together with broadcast");
#endif

int i2c_port_num = slave_config->i2c_port;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,13 @@ if(CONFIG_SOC_I2C_SUPPORT_10BIT_ADDR AND CONFIG_SOC_I2C_SUPPORT_SLAVE)
list(APPEND srcs "test_i2c_10bit.c")
endif()

# Only build this file with `CONFIG_PM_POWER_DOWN_CPU_IN_LIGHT_SLEEP` and `CONFIG_IEEE802154_ENABLED` enabled
# Enable `CONFIG_IEEE802154_ENABLED` is for modem domain really power down.
# This reliable can be removed if the sleep retention got finished.
if(CONFIG_PM_POWER_DOWN_CPU_IN_LIGHT_SLEEP AND CONFIG_IEEE802154_ENABLED)
list(APPEND srcs "test_i2c_sleep_retention.c")
endif()

idf_component_register(SRCS ${srcs}
PRIV_REQUIRES unity driver test_utils
PRIV_REQUIRES unity driver test_utils ieee802154
WHOLE_ARCHIVE)
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

// 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)
#define LEAKS (1000)

void setUp(void)
{
Expand Down
Loading

0 comments on commit 2302dd5

Please sign in to comment.