Bläddra i källkod

ulp: Added support for RTC I2C driver for ULP RISC-V on esp32s2 and esp32s3

This commit adds support for using the RTC I2C peripheral on the ULP
RISC-V core for esp32s2 and esp32s3. It also adds an example to demonstrate the
usage of the RTC I2C peripheral.

This commit also modifies the rtc_i2c register structure files to enable
the use of bitfields in the ULP RISC-V RTC I2C driver.
Sudeep Mohanty 3 år sedan
förälder
incheckning
4fde033a5f

+ 5 - 1
components/soc/esp32c3/include/soc/rtc_i2c_struct.h

@@ -165,7 +165,11 @@ typedef volatile struct rtc_i2c_dev_s {
     } fifo_data;
     union {
         struct {
-            uint32_t command0:     14;              /*command0*/
+            uint32_t byte_num:      8;
+            uint32_t ack_en:        1;
+            uint32_t ack_exp:       1;
+            uint32_t ack_val:       1;
+            uint32_t op_code:       3;
             uint32_t reserved14:   17;
             uint32_t done:          1;              /*command0_done*/
         };

+ 6 - 2
components/soc/esp32s2/include/soc/rtc_i2c_struct.h

@@ -166,9 +166,13 @@ typedef volatile struct rtc_i2c_dev_s {
     } fifo_data;
     union {
         struct {
-            uint32_t command0:     14;              /*command0*/
+            uint32_t byte_num:      8;
+            uint32_t ack_en:        1;
+            uint32_t ack_exp:       1;
+            uint32_t ack_val:       1;
+            uint32_t op_code:       3;
             uint32_t reserved14:   17;
-            uint32_t done:          1;              /*command0_done*/
+            uint32_t done:  1;
         };
         uint32_t val;
     } command[16];

+ 1 - 0
components/soc/esp32s2/ld/esp32s2.peripherals.ld

@@ -11,6 +11,7 @@ PROVIDE ( SDM = 0x3f404f00 );
 PROVIDE ( RTCCNTL = 0x3f408000 );
 PROVIDE ( RTCIO = 0x3f408400 );
 PROVIDE ( SENS = 0x3f408800 );
+PROVIDE ( RTC_I2C = 0x3f408C00 );
 PROVIDE ( HINF = 0x3f40B000 );
 PROVIDE ( I2S0 = 0x3f40F000 );
 PROVIDE ( UART1 = 0x3f410000 );

+ 18 - 299
components/soc/esp32s3/include/soc/rtc_i2c_struct.h

@@ -190,294 +190,27 @@ typedef union {
     uint32_t val;
 } rtc_i2c_data_reg_t;
 
-/** Type of i2c_cmd0 register
- *  i2c commond0 register
+/** Type of i2c_cmd register
+ *  i2c command register
  */
 typedef union {
     struct {
-        /** i2c_command0 : R/W; bitpos: [13:0]; default: 2307;
-         *  command0
-         */
-        uint32_t i2c_command0:14;
-        uint32_t reserved_14:17;
-        /** i2c_command0_done : RO; bitpos: [31]; default: 0;
+        /** i2c_command : R/W; bitpos: [13:0]; default: 2307;
+         *  command
+         */
+        uint32_t i2c_byte_num:8;
+        uint32_t i2c_ack_en:1;
+        uint32_t i2c_ack_exp:1;
+        uint32_t i2c_ack_val:1;
+        uint32_t i2c_op_code:3;
+        uint32_t reserved14:17;
+        /** i2c_command_done : RO; bitpos: [31]; default: 0;
          *  command0_done
          */
-        uint32_t i2c_command0_done:1;
-    };
-    uint32_t val;
-} rtc_i2c_cmd0_reg_t;
-
-/** Type of i2c_cmd1 register
- *  i2c commond1 register
- */
-typedef union {
-    struct {
-        /** i2c_command1 : R/W; bitpos: [13:0]; default: 6401;
-         *  command1
-         */
-        uint32_t i2c_command1:14;
-        uint32_t reserved_14:17;
-        /** i2c_command1_done : RO; bitpos: [31]; default: 0;
-         *  command1_done
-         */
-        uint32_t i2c_command1_done:1;
-    };
-    uint32_t val;
-} rtc_i2c_cmd1_reg_t;
-
-/** Type of i2c_cmd2 register
- *  i2c commond2 register
- */
-typedef union {
-    struct {
-        /** i2c_command2 : R/W; bitpos: [13:0]; default: 2306;
-         *  command2
-         */
-        uint32_t i2c_command2:14;
-        uint32_t reserved_14:17;
-        /** i2c_command2_done : RO; bitpos: [31]; default: 0;
-         *  command2_done
-         */
-        uint32_t i2c_command2_done:1;
-    };
-    uint32_t val;
-} rtc_i2c_cmd2_reg_t;
-
-/** Type of i2c_cmd3 register
- *  i2c commond3 register
- */
-typedef union {
-    struct {
-        /** i2c_command3 : R/W; bitpos: [13:0]; default: 257;
-         *  command3
-         */
-        uint32_t i2c_command3:14;
-        uint32_t reserved_14:17;
-        /** i2c_command3_done : RO; bitpos: [31]; default: 0;
-         *  command3_done
-         */
-        uint32_t i2c_command3_done:1;
-    };
-    uint32_t val;
-} rtc_i2c_cmd3_reg_t;
-
-/** Type of i2c_cmd4 register
- *  i2c commond4 register
- */
-typedef union {
-    struct {
-        /** i2c_command4 : R/W; bitpos: [13:0]; default: 2305;
-         *  command4
-         */
-        uint32_t i2c_command4:14;
-        uint32_t reserved_14:17;
-        /** i2c_command4_done : RO; bitpos: [31]; default: 0;
-         *  command4_done
-         */
-        uint32_t i2c_command4_done:1;
-    };
-    uint32_t val;
-} rtc_i2c_cmd4_reg_t;
-
-/** Type of i2c_cmd5 register
- *  i2c commond5_register
- */
-typedef union {
-    struct {
-        /** i2c_command5 : R/W; bitpos: [13:0]; default: 5889;
-         *  command5
-         */
-        uint32_t i2c_command5:14;
-        uint32_t reserved_14:17;
-        /** i2c_command5_done : RO; bitpos: [31]; default: 0;
-         *  command5_done
-         */
-        uint32_t i2c_command5_done:1;
-    };
-    uint32_t val;
-} rtc_i2c_cmd5_reg_t;
-
-/** Type of i2c_cmd6 register
- *  i2c commond6 register
- */
-typedef union {
-    struct {
-        /** i2c_command6 : R/W; bitpos: [13:0]; default: 6401;
-         *  command6
-         */
-        uint32_t i2c_command6:14;
-        uint32_t reserved_14:17;
-        /** i2c_command6_done : RO; bitpos: [31]; default: 0;
-         *  command6_done
-         */
-        uint32_t i2c_command6_done:1;
-    };
-    uint32_t val;
-} rtc_i2c_cmd6_reg_t;
-
-/** Type of i2c_cmd7 register
- *  i2c commond7 register
- */
-typedef union {
-    struct {
-        /** i2c_command7 : R/W; bitpos: [13:0]; default: 2308;
-         *  command7
-         */
-        uint32_t i2c_command7:14;
-        uint32_t reserved_14:17;
-        /** i2c_command7_done : RO; bitpos: [31]; default: 0;
-         *  command7_done
-         */
-        uint32_t i2c_command7_done:1;
-    };
-    uint32_t val;
-} rtc_i2c_cmd7_reg_t;
-
-/** Type of i2c_cmd8 register
- *  i2c commond8 register
- */
-typedef union {
-    struct {
-        /** i2c_command8 : R/W; bitpos: [13:0]; default: 6401;
-         *  command8
-         */
-        uint32_t i2c_command8:14;
-        uint32_t reserved_14:17;
-        /** i2c_command8_done : RO; bitpos: [31]; default: 0;
-         *  command8_done
-         */
-        uint32_t i2c_command8_done:1;
-    };
-    uint32_t val;
-} rtc_i2c_cmd8_reg_t;
-
-/** Type of i2c_cmd9 register
- *  i2c commond9 register
- */
-typedef union {
-    struct {
-        /** i2c_command9 : R/W; bitpos: [13:0]; default: 2307;
-         *  command9
-         */
-        uint32_t i2c_command9:14;
-        uint32_t reserved_14:17;
-        /** i2c_command9_done : RO; bitpos: [31]; default: 0;
-         *  command9_done
-         */
-        uint32_t i2c_command9_done:1;
+        uint32_t i2c_command_done:1;
     };
     uint32_t val;
-} rtc_i2c_cmd9_reg_t;
-
-/** Type of i2c_cmd10 register
- *  i2c commond10 register
- */
-typedef union {
-    struct {
-        /** i2c_command10 : R/W; bitpos: [13:0]; default: 257;
-         *  command10
-         */
-        uint32_t i2c_command10:14;
-        uint32_t reserved_14:17;
-        /** i2c_command10_done : RO; bitpos: [31]; default: 0;
-         *  command10_done
-         */
-        uint32_t i2c_command10_done:1;
-    };
-    uint32_t val;
-} rtc_i2c_cmd10_reg_t;
-
-/** Type of i2c_cmd11 register
- *  i2c commond11 register
- */
-typedef union {
-    struct {
-        /** i2c_command11 : R/W; bitpos: [13:0]; default: 2305;
-         *  command11
-         */
-        uint32_t i2c_command11:14;
-        uint32_t reserved_14:17;
-        /** i2c_command11_done : RO; bitpos: [31]; default: 0;
-         *  command11_done
-         */
-        uint32_t i2c_command11_done:1;
-    };
-    uint32_t val;
-} rtc_i2c_cmd11_reg_t;
-
-/** Type of i2c_cmd12 register
- *  i2c commond12 register
- */
-typedef union {
-    struct {
-        /** i2c_command12 : R/W; bitpos: [13:0]; default: 5889;
-         *  command12
-         */
-        uint32_t i2c_command12:14;
-        uint32_t reserved_14:17;
-        /** i2c_command12_done : RO; bitpos: [31]; default: 0;
-         *  command12_done
-         */
-        uint32_t i2c_command12_done:1;
-    };
-    uint32_t val;
-} rtc_i2c_cmd12_reg_t;
-
-/** Type of i2c_cmd13 register
- *  i2c commond13 register
- */
-typedef union {
-    struct {
-        /** i2c_command13 : R/W; bitpos: [13:0]; default: 6401;
-         *  command13
-         */
-        uint32_t i2c_command13:14;
-        uint32_t reserved_14:17;
-        /** i2c_command13_done : RO; bitpos: [31]; default: 0;
-         *  command13_done
-         */
-        uint32_t i2c_command13_done:1;
-    };
-    uint32_t val;
-} rtc_i2c_cmd13_reg_t;
-
-/** Type of i2c_cmd14 register
- *  i2c commond14 register
- */
-typedef union {
-    struct {
-        /** i2c_command14 : R/W; bitpos: [13:0]; default: 0;
-         *  command14
-         */
-        uint32_t i2c_command14:14;
-        uint32_t reserved_14:17;
-        /** i2c_command14_done : RO; bitpos: [31]; default: 0;
-         *  command14_done
-         */
-        uint32_t i2c_command14_done:1;
-    };
-    uint32_t val;
-} rtc_i2c_cmd14_reg_t;
-
-/** Type of i2c_cmd15 register
- *  i2c commond15 register
- */
-typedef union {
-    struct {
-        /** i2c_command15 : R/W; bitpos: [13:0]; default: 0;
-         *  command15
-         */
-        uint32_t i2c_command15:14;
-        uint32_t reserved_14:17;
-        /** i2c_command15_done : RO; bitpos: [31]; default: 0;
-         *  command15_done
-         */
-        uint32_t i2c_command15_done:1;
-    };
-    uint32_t val;
-} rtc_i2c_cmd15_reg_t;
-
+} rtc_i2c_cmd_reg_t;
 
 /** Group: status register */
 /** Type of i2c_status register
@@ -750,29 +483,15 @@ typedef struct {
     volatile rtc_i2c_int_st_reg_t i2c_int_st;
     volatile rtc_i2c_int_ena_reg_t i2c_int_ena;
     volatile rtc_i2c_data_reg_t i2c_data;
-    volatile rtc_i2c_cmd0_reg_t i2c_cmd0;
-    volatile rtc_i2c_cmd1_reg_t i2c_cmd1;
-    volatile rtc_i2c_cmd2_reg_t i2c_cmd2;
-    volatile rtc_i2c_cmd3_reg_t i2c_cmd3;
-    volatile rtc_i2c_cmd4_reg_t i2c_cmd4;
-    volatile rtc_i2c_cmd5_reg_t i2c_cmd5;
-    volatile rtc_i2c_cmd6_reg_t i2c_cmd6;
-    volatile rtc_i2c_cmd7_reg_t i2c_cmd7;
-    volatile rtc_i2c_cmd8_reg_t i2c_cmd8;
-    volatile rtc_i2c_cmd9_reg_t i2c_cmd9;
-    volatile rtc_i2c_cmd10_reg_t i2c_cmd10;
-    volatile rtc_i2c_cmd11_reg_t i2c_cmd11;
-    volatile rtc_i2c_cmd12_reg_t i2c_cmd12;
-    volatile rtc_i2c_cmd13_reg_t i2c_cmd13;
-    volatile rtc_i2c_cmd14_reg_t i2c_cmd14;
-    volatile rtc_i2c_cmd15_reg_t i2c_cmd15;
+    volatile rtc_i2c_cmd_reg_t i2c_cmd[16];
     uint32_t reserved_078[33];
     volatile rtc_i2c_date_reg_t i2c_date;
-} rtc_dev_t;
+} rtc_i2c_dev_t;
+extern rtc_i2c_dev_t RTC_I2C;
 
 
 #ifndef __cplusplus
-_Static_assert(sizeof(rtc_dev_t) == 0x100, "Invalid size of rtc_dev_t structure");
+_Static_assert(sizeof(rtc_i2c_dev_t) == 0x100, "Invalid size of rtc_i2c_dev_t structure");
 #endif
 
 #ifdef __cplusplus

+ 1 - 0
components/soc/esp32s3/ld/esp32s3.peripherals.ld

@@ -12,6 +12,7 @@ PROVIDE ( EFUSE = 0x60007000 );
 PROVIDE ( RTCCNTL = 0x60008000 );
 PROVIDE ( RTCIO = 0x60008400 );
 PROVIDE ( SENS = 0x60008800 );
+PROVIDE ( RTC_I2C = 0x60008C00 );
 PROVIDE ( HINF = 0x6000B000 );
 PROVIDE ( I2S0 = 0x6000F000 );
 PROVIDE ( I2S1 = 0x6002D000 );

+ 2 - 1
components/ulp/CMakeLists.txt

@@ -25,7 +25,8 @@ if(CONFIG_SOC_ULP_SUPPORTED OR CONFIG_SOC_RISCV_COPROC_SUPPORTED)
         list(APPEND srcs
             "ulp_riscv/ulp_riscv.c"
             "ulp_riscv/ulp_riscv_lock.c"
-            "ulp_riscv/ulp_riscv_adc.c")
+            "ulp_riscv/ulp_riscv_adc.c"
+            "ulp_riscv/ulp_riscv_i2c.c")
 
         list(APPEND includes
             ulp_riscv/include

+ 1 - 0
components/ulp/cmake/CMakeLists.txt

@@ -76,6 +76,7 @@ if(ULP_COCPU_IS_RISCV)
         "${IDF_PATH}/components/ulp/ulp_riscv/ulp_core/ulp_riscv_lock.c"
         "${IDF_PATH}/components/ulp/ulp_riscv/ulp_core/ulp_riscv_uart.c"
         "${IDF_PATH}/components/ulp/ulp_riscv/ulp_core/ulp_riscv_print.c"
+        "${IDF_PATH}/components/ulp/ulp_riscv/ulp_core/ulp_riscv_i2c.c"
         "${IDF_PATH}/components/ulp/ulp_riscv/ulp_core/ulp_riscv_utils.c")
 
     target_link_options(${ULP_APP_NAME} PRIVATE "-nostartfiles")

+ 1 - 0
components/ulp/ld/esp32s2.peripherals.ld

@@ -7,3 +7,4 @@
 PROVIDE ( RTCCNTL = 0x8000 );
 PROVIDE ( RTCIO = 0xA400 );
 PROVIDE ( SENS = 0xC800 );
+PROVIDE ( RTC_I2C = 0x8C00 );

+ 1 - 0
components/ulp/ld/esp32s3.peripherals.ld

@@ -7,3 +7,4 @@
 PROVIDE ( RTCCNTL = 0x8000 );
 PROVIDE ( RTCIO = 0xA400 );
 PROVIDE ( SENS = 0xC800 );
+PROVIDE ( RTC_I2C = 0xEC00 );

+ 99 - 0
components/ulp/ulp_riscv/include/ulp_riscv_i2c.h

@@ -0,0 +1,99 @@
+/*
+ * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#pragma once
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdint.h>
+#include <stdbool.h>
+#include "hal/gpio_types.h"
+#include "esp_err.h"
+
+typedef struct {
+    uint32_t sda_io_num;        // GPIO pin for SDA signal. Only GPIO#1 or GPIO#3 can be used as the SDA pin.
+    uint32_t scl_io_num;        // GPIO pin for SCL signal. Only GPIO#0 or GPIO#2 can be used as the SCL pin.
+    bool sda_pullup_en;         // SDA line enable internal pullup. Can be configured if external pullup is not used.
+    bool scl_pullup_en;         // SCL line enable internal pullup. Can be configured if external pullup is not used.
+} ulp_riscv_i2c_pin_cfg_t;
+
+typedef struct {
+    uint32_t scl_low_period;    // SCL low period
+    uint32_t scl_high_period;   // SCL high period
+    uint32_t sda_duty_period;   // Period between the SDA switch and the falling edge of SCL
+    uint32_t scl_start_period;  // Waiting time after the START condition
+    uint32_t scl_stop_period;   // Waiting time before the END condition
+    uint32_t i2c_trans_timeout; // I2C transaction timeout
+} ulp_riscv_i2c_timing_cfg_t;
+
+typedef struct {
+    ulp_riscv_i2c_pin_cfg_t i2c_pin_cfg;        // RTC I2C pin configuration
+    ulp_riscv_i2c_timing_cfg_t i2c_timing_cfg;  // RTC I2C timing configuration
+} ulp_riscv_i2c_cfg_t;
+
+/* Nominal default GPIO settings and timing parametes */
+#define ULP_RISCV_I2C_DEFAULT_CONFIG()          \
+    {                                           \
+        .i2c_pin_cfg.sda_io_num = GPIO_NUM_3,   \
+        .i2c_pin_cfg.scl_io_num = GPIO_NUM_2,   \
+        .i2c_pin_cfg.sda_pullup_en = true,      \
+        .i2c_pin_cfg.scl_pullup_en = true,      \
+        .i2c_timing_cfg.scl_low_period = 5,     \
+        .i2c_timing_cfg.scl_high_period = 5,    \
+        .i2c_timing_cfg.sda_duty_period = 2,    \
+        .i2c_timing_cfg.scl_start_period = 3,   \
+        .i2c_timing_cfg.scl_stop_period = 6,    \
+        .i2c_timing_cfg.i2c_trans_timeout = 20, \
+    }
+
+/**
+ * @brief Set the I2C slave device address
+ *
+ * @param slave_addr    I2C slave address (7 bit)
+ */
+void ulp_riscv_i2c_master_set_slave_addr(uint8_t slave_addr);
+
+/**
+ * @brief Set the I2C slave device sub register address
+ *
+ * @param slave_reg_addr    I2C slave sub register address
+ */
+void ulp_riscv_i2c_master_set_slave_reg_addr(uint8_t slave_reg_addr);
+
+/**
+ * @brief Read from I2C slave device
+ *
+ * @note The I2C slave device address must be configured at least once before invoking this API.
+ *
+ * @param data_rd       Buffer to hold data to be read
+ * @param size          Size of data to be read in bytes
+ */
+void ulp_riscv_i2c_master_read_from_device(uint8_t *data_rd, size_t size);
+
+/**
+ * @brief Write to I2C slave device
+ *
+ * @note The I2C slave device address must be configured at least once before invoking this API.
+ *
+ * @param data_wr       Buffer which holds the data to be written
+ * @param size          Size of data to be written in bytes
+ */
+void ulp_riscv_i2c_master_write_to_device(uint8_t *data_wr, size_t size);
+
+/**
+ * @brief Initialize and configure the RTC I2C for use by ULP RISC-V
+ * Currently RTC I2C can only be used in master mode
+ *
+ * @param cfg           Configuration parameters
+ * @return esp_err_t    ESP_OK when successful
+ */
+esp_err_t ulp_riscv_i2c_master_init(const ulp_riscv_i2c_cfg_t *cfg);
+
+#ifdef __cplusplus
+}
+#endif

+ 52 - 0
components/ulp/ulp_riscv/ulp_core/include/ulp_riscv_i2c_ulp_core.h

@@ -0,0 +1,52 @@
+/*
+ * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#pragma once
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stddef.h>
+#include <stdint.h>
+
+/**
+ * @brief Set the I2C slave device address
+ *
+ * @param slave_addr    I2C slave address (7 bit)
+ */
+void ulp_riscv_i2c_master_set_slave_addr(uint8_t slave_addr);
+
+/**
+ * @brief Set the I2C slave device sub register address
+ *
+ * @param slave_reg_addr    I2C slave register address
+ */
+void ulp_riscv_i2c_master_set_slave_reg_addr(uint8_t slave_reg_addr);
+
+/**
+ * @brief Read from I2C slave device
+ *
+ * @note The I2C slave device address must be configured at least once before invoking this API.
+ *
+ * @param data_rd       Buffer to hold data to be read
+ * @param size          Size of data to be read in bytes
+ */
+void ulp_riscv_i2c_master_read_from_device(uint8_t *data_rd, size_t size);
+
+/**
+ * @brief Write to I2C slave device
+ *
+ * @note The I2C slave device address must be configured at least once before invoking this API.
+ *
+ * @param data_wr       Buffer which holds the data to be written
+ * @param size          Size of data to be written in bytes
+ */
+void ulp_riscv_i2c_master_write_to_device(uint8_t *data_wr, size_t size);
+
+#ifdef __cplusplus
+}
+#endif

+ 225 - 0
components/ulp/ulp_riscv/ulp_core/ulp_riscv_i2c.c

@@ -0,0 +1,225 @@
+/*
+ * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#include "ulp_riscv_i2c_ulp_core.h"
+#include "ulp_riscv_utils.h"
+#include "soc/rtc_i2c_reg.h"
+#include "soc/rtc_i2c_struct.h"
+#include "soc/rtc_io_reg.h"
+#include "soc/sens_reg.h"
+#include "hal/i2c_ll.h"
+
+#define I2C_CTRL_SLAVE_ADDR_MASK        (0xFF << 0)
+#define I2C_CTRL_SLAVE_REG_ADDR_MASK    (0xFF << 11)
+#define I2C_CTRL_MASTER_TX_DATA_MASK    (0xFF << 19)
+
+#if CONFIG_IDF_TARGET_ESP32S3
+#define ULP_I2C_CMD_RESTART 0                   /*!<I2C restart command */
+#define ULP_I2C_CMD_WRITE   1                   /*!<I2C write command */
+#define ULP_I2C_CMD_READ    2                   /*!<I2C read command */
+#define ULP_I2C_CMD_STOP    3                   /*!<I2C stop command */
+#define ULP_I2C_CMD_END     4                   /*!<I2C end command */
+#else
+#define ULP_I2C_CMD_RESTART I2C_LL_CMD_RESTART  /*!<I2C restart command */
+#define ULP_I2C_CMD_WRITE   I2C_LL_CMD_WRITE    /*!<I2C write command */
+#define ULP_I2C_CMD_READ    I2C_LL_CMD_READ     /*!<I2C read command */
+#define ULP_I2C_CMD_STOP    I2C_LL_CMD_STOP     /*!<I2C stop command */
+#define ULP_I2C_CMD_END     I2C_LL_CMD_END      /*!<I2C end command */
+#endif // CONFIG_IDF_TARGET_ESP32S3
+
+/*
+ * The RTC I2C controller follows the I2C command registers to perform read/write operations.
+ * The cmd registers have the following format:
+ *
+ *      31        30:14     13:11      10         9           8         7:0
+ * |----------|----------|---------|---------|----------|------------|---------|
+ * | CMD_DONE | Reserved |  OPCODE |ACK Value|ACK Expect|ACK Check En|Byte Num |
+ * |----------|----------|---------|---------|----------|------------|---------|
+ */
+static void ulp_riscv_i2c_format_cmd(uint32_t cmd_idx, uint8_t op_code, uint8_t ack_val,
+        uint8_t ack_expected, uint8_t ack_check_en, uint8_t byte_num)
+{
+    uint32_t reg_addr = RTC_I2C_CMD0_REG + 4*cmd_idx;
+
+    CLEAR_PERI_REG_MASK(reg_addr, 0xFFFFFFFF);
+
+    WRITE_PERI_REG(reg_addr,
+            (0 << 31) |                     // CMD Done
+
+            ((op_code & 0x3) << 11) |       // Opcode
+
+            ((ack_val & 0x1) << 10) |       // ACK bit sent by I2C controller during READ.
+                                            // Ignored during RSTART, STOP, END and WRITE cmds.
+
+            ((ack_expected & 0x1) << 9) |   // ACK bit expected by I2C controller during WRITE.
+                                            // Ignored during RSTART, STOP, END and READ cmds.
+
+            ((ack_check_en & 0x1) << 8) |   // I2C controller verifies that the ACK bit sent by the slave device matches
+                                            // the ACK expected bit during WRITE.
+                                            // Ignored during RSTART, STOP, END and READ cmds.
+            ((byte_num & 0xFF) << 0));      // Byte Num
+}
+
+void ulp_riscv_i2c_master_set_slave_addr(uint8_t slave_addr)
+{
+    CLEAR_PERI_REG_MASK(SENS_SAR_I2C_CTRL_REG, I2C_CTRL_SLAVE_ADDR_MASK);
+    SET_PERI_REG_BITS(SENS_SAR_I2C_CTRL_REG, 0xFF, slave_addr, 0);
+}
+
+void ulp_riscv_i2c_master_set_slave_reg_addr(uint8_t slave_reg_addr)
+{
+    CLEAR_PERI_REG_MASK(SENS_SAR_I2C_CTRL_REG, I2C_CTRL_SLAVE_REG_ADDR_MASK);
+    SET_PERI_REG_BITS(SENS_SAR_I2C_CTRL_REG, 0xFF, slave_reg_addr, 11);
+}
+
+/*
+ * I2C transactions when master reads one byte of data from the slave device:
+ *
+ * |--------|--------|---------|--------|--------|--------|--------|---------|--------|--------|--------|--------|
+ * | Master | START  | SAD + W |        |  SUB   |        |   SR   | SAD + R |        |        |  NACK  |  STOP  |
+ * |--------|--------|---------|--------|--------|--------|--------|---------|--------|--------|--------|--------|
+ * | Slave  |        |         |  ACK   |        |   ACK  |        |         |   ACK  |  DATA  |        |        |
+ * |--------|--------|---------|--------|--------|--------|--------|---------|--------|--------|--------|--------|
+ *
+ * I2C transactions when master reads multiple bytes of data from the slave device:
+ *
+ * |--------|--------|---------|--------|--------|--------|--------|---------|--------|--------|--------|--------|--------|--------|
+ * | Master | START  | SAD + W |        |  SUB   |        |   SR   | SAD + R |        |        |   ACK  |        |   NACK |  STOP  |
+ * |--------|--------|---------|--------|--------|--------|--------|---------|--------|--------|--------|--------|--------|--------|
+ * | Slave  |        |         |  ACK   |        |   ACK  |        |         |   ACK  |  DATA  |        |  DATA  |        |        |
+ * |--------|--------|---------|--------|--------|--------|--------|---------|--------|--------|--------|--------|--------|--------|
+ */
+void ulp_riscv_i2c_master_read_from_device(uint8_t *data_rd, size_t size)
+{
+    uint32_t i = 0;
+    uint32_t cmd_idx = 0;
+
+    if (size == 0) {
+        // Quietly return
+        return;
+    }
+
+    /* By default, RTC I2C controller is hard wired to use CMD2 register onwards for read operations */
+    cmd_idx = 2;
+
+    /* Write slave addr */
+    ulp_riscv_i2c_format_cmd(cmd_idx++, ULP_I2C_CMD_WRITE, 0, 0, 1, 2);
+
+    /* Repeated START */
+    ulp_riscv_i2c_format_cmd(cmd_idx++, ULP_I2C_CMD_RESTART, 0, 0, 0, 0);
+
+    /* Write slave register addr */
+    ulp_riscv_i2c_format_cmd(cmd_idx++, ULP_I2C_CMD_WRITE, 0, 0, 1, 1);
+
+    if (size > 1) {
+        /* Read n - 1 bytes */
+        ulp_riscv_i2c_format_cmd(cmd_idx++, ULP_I2C_CMD_READ, 0, 0, 1, size - 1);
+    }
+
+    /* Read last byte + NACK */
+    ulp_riscv_i2c_format_cmd(cmd_idx++, ULP_I2C_CMD_READ, 1, 1, 1, 1);
+
+    /* STOP */
+    ulp_riscv_i2c_format_cmd(cmd_idx++, ULP_I2C_CMD_STOP, 0, 0, 0, 0);
+
+    /* Configure the RTC I2C controller in read mode */
+    SET_PERI_REG_BITS(SENS_SAR_I2C_CTRL_REG, 0x1, 0, 27);
+
+    /* Enable Rx data interrupt */
+    SET_PERI_REG_MASK(RTC_I2C_INT_ENA_REG, RTC_I2C_RX_DATA_INT_ENA);
+
+    /* Start RTC I2C transmission */
+    SET_PERI_REG_MASK(SENS_SAR_I2C_CTRL_REG, SENS_SAR_I2C_START_FORCE);
+    SET_PERI_REG_MASK(SENS_SAR_I2C_CTRL_REG, SENS_SAR_I2C_START);
+
+    for (i = 0; i < size; i++) {
+        /* Poll for RTC I2C Rx Data interrupt bit to be set */
+        while (!REG_GET_FIELD(RTC_I2C_INT_ST_REG, RTC_I2C_RX_DATA_INT_ST)) {  }
+
+        /* Read the data
+         *
+         * Unfortunately, the RTC I2C has no fifo buffer to help us with reading and storing
+         * multiple bytes of data. Therefore, we need to read one byte at a time and clear the
+         * Rx interrupt to get ready for the next byte.
+         */
+#if CONFIG_IDF_TARGET_ESP32S2
+        data_rd[i] = REG_GET_FIELD(RTC_I2C_DATA_REG, RTC_I2C_RDATA);
+#elif CONFIG_IDF_TARGET_ESP32S3
+        data_rd[i] = REG_GET_FIELD(RTC_I2C_DATA_REG, RTC_I2C_I2C_RDATA);
+#endif // CONFIG_IDF_TARGET_ESP32S2
+
+        /* Clear the Rx data interrupt bit */
+        SET_PERI_REG_MASK(RTC_I2C_INT_CLR_REG, RTC_I2C_RX_DATA_INT_CLR);
+    }
+
+    /* Clear the RTC I2C transmission bits */
+    CLEAR_PERI_REG_MASK(SENS_SAR_I2C_CTRL_REG, SENS_SAR_I2C_START_FORCE);
+    CLEAR_PERI_REG_MASK(SENS_SAR_I2C_CTRL_REG, SENS_SAR_I2C_START);
+}
+
+/*
+ * I2C transactions when master writes one byte of data to the slave device:
+ *
+ * |--------|--------|---------|--------|--------|--------|--------|--------|--------|
+ * | Master | START  | SAD + W |        |  SUB   |        |  DATA  |        |  STOP  |
+ * |--------|--------|---------|--------|--------|--------|--------|--------|--------|
+ * | Slave  |        |         |  ACK   |        |   ACK  |        |   ACK  |        |
+ * |--------|--------|---------|--------|--------|--------|--------|--------|--------|
+ *
+ * I2C transactions when master writes multiple bytes of data to the slave device:
+ *
+ * |--------|--------|---------|--------|--------|--------|--------|--------|--------|--------|--------|
+ * | Master | START  | SAD + W |        |  SUB   |        |  DATA  |        |  DATA  |        |  STOP  |
+ * |--------|--------|---------|--------|--------|--------|--------|--------|--------|--------|--------|
+ * | Slave  |        |         |  ACK   |        |   ACK  |        |   ACK  |        |   ACK  |        |
+ * |--------|--------|---------|--------|--------|--------|--------|--------|--------|--------|--------|
+ */
+void ulp_riscv_i2c_master_write_to_device(uint8_t *data_wr, size_t size)
+{
+    uint32_t i = 0;
+    uint32_t cmd_idx = 0;
+
+    if (size == 0) {
+        // Quietly return
+        return;
+    }
+
+    /* By default, RTC I2C controller is hard wired to use CMD0 and CMD1 registers for write operations */
+    cmd_idx = 0;
+
+    /* Write slave addr + reg addr + data */
+    ulp_riscv_i2c_format_cmd(cmd_idx++, ULP_I2C_CMD_WRITE, 0, 0, 1, 2 + size);
+
+    /* Stop */
+    ulp_riscv_i2c_format_cmd(cmd_idx++, ULP_I2C_CMD_STOP, 0, 0, 0, 0);
+
+    /* Configure the RTC I2C controller in write mode */
+    SET_PERI_REG_BITS(SENS_SAR_I2C_CTRL_REG, 0x1, 1, 27);
+
+    /* Enable Tx data interrupt */
+    SET_PERI_REG_MASK(RTC_I2C_INT_ENA_REG, RTC_I2C_TX_DATA_INT_ENA);
+
+    for (i = 0; i < size; i++) {
+        /* Write the data to be transmitted */
+        CLEAR_PERI_REG_MASK(SENS_SAR_I2C_CTRL_REG, I2C_CTRL_MASTER_TX_DATA_MASK);
+        SET_PERI_REG_BITS(SENS_SAR_I2C_CTRL_REG, 0xFF, data_wr[i], 19);
+
+        if (i == 0) {
+            /* Start RTC I2C transmission. (Needn't do it for every byte) */
+            SET_PERI_REG_MASK(SENS_SAR_I2C_CTRL_REG, SENS_SAR_I2C_START_FORCE);
+            SET_PERI_REG_MASK(SENS_SAR_I2C_CTRL_REG, SENS_SAR_I2C_START);
+        }
+
+        /* Poll for RTC I2C Tx Data interrupt bit to be set */
+        while (!REG_GET_FIELD(RTC_I2C_INT_ST_REG, RTC_I2C_TX_DATA_INT_ST)) {  }
+
+        /* Clear the Tx data interrupt bit */
+        SET_PERI_REG_MASK(RTC_I2C_INT_CLR_REG, RTC_I2C_TX_DATA_INT_CLR);
+    }
+
+    /* Clear the RTC I2C transmission bits */
+    CLEAR_PERI_REG_MASK(SENS_SAR_I2C_CTRL_REG, SENS_SAR_I2C_START_FORCE);
+    CLEAR_PERI_REG_MASK(SENS_SAR_I2C_CTRL_REG, SENS_SAR_I2C_START);
+}

+ 441 - 0
components/ulp/ulp_riscv/ulp_riscv_i2c.c

@@ -0,0 +1,441 @@
+/*
+ * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include "ulp_riscv_i2c.h"
+#include "esp_check.h"
+#include "soc/rtc_i2c_reg.h"
+#include "soc/rtc_i2c_struct.h"
+#include "soc/rtc_io_struct.h"
+#include "soc/sens_reg.h"
+#include "soc/clk_tree_defs.h"
+#include "hal/i2c_ll.h"
+#include "driver/rtc_io.h"
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+
+static const char *RTCI2C_TAG = "ulp_riscv_i2c";
+
+#define I2C_CTRL_SLAVE_ADDR_MASK        (0xFF << 0)
+#define I2C_CTRL_SLAVE_REG_ADDR_MASK    (0xFF << 11)
+#define I2C_CTRL_MASTER_TX_DATA_MASK    (0xFF << 19)
+
+#if CONFIG_IDF_TARGET_ESP32S3
+#define ULP_I2C_CMD_RESTART 0                   /*!<I2C restart command */
+#define ULP_I2C_CMD_WRITE   1                   /*!<I2C write command */
+#define ULP_I2C_CMD_READ    2                   /*!<I2C read command */
+#define ULP_I2C_CMD_STOP    3                   /*!<I2C stop command */
+#define ULP_I2C_CMD_END     4                   /*!<I2C end command */
+#else
+#define ULP_I2C_CMD_RESTART I2C_LL_CMD_RESTART  /*!<I2C restart command */
+#define ULP_I2C_CMD_WRITE   I2C_LL_CMD_WRITE    /*!<I2C write command */
+#define ULP_I2C_CMD_READ    I2C_LL_CMD_READ     /*!<I2C read command */
+#define ULP_I2C_CMD_STOP    I2C_LL_CMD_STOP     /*!<I2C stop command */
+#define ULP_I2C_CMD_END     I2C_LL_CMD_END      /*!<I2C end command */
+#endif // CONFIG_IDF_TARGET_ESP32S3
+
+/* Use the register structure to access RTC_I2C and RTCIO module registers */
+rtc_i2c_dev_t *i2c_dev = &RTC_I2C;
+rtc_io_dev_t *rtc_io_dev = &RTCIO;
+
+#define MICROSEC_TO_RTC_FAST_CLK(period)    (period) * ((SOC_CLK_RC_FAST_FREQ_APPROX) / (1000000))
+
+static esp_err_t i2c_gpio_is_cfg_valid(gpio_num_t sda_io_num, gpio_num_t scl_io_num)
+{
+    /* Verify that the SDA and SCL GPIOs are valid RTC I2C io pins */
+    ESP_RETURN_ON_ERROR(!rtc_gpio_is_valid_gpio(sda_io_num), RTCI2C_TAG, "RTC I2C SDA GPIO invalid");
+    ESP_RETURN_ON_ERROR(!rtc_gpio_is_valid_gpio(scl_io_num), RTCI2C_TAG, "RTC I2C SCL GPIO invalid");
+
+    /* Verify that the SDA and SCL line belong to the RTC IO I2C function group */
+    if ((sda_io_num != GPIO_NUM_1) && (sda_io_num != GPIO_NUM_3)) {
+        ESP_LOGE(RTCI2C_TAG, "SDA pin can only be configured as GPIO#1 or GPIO#3");
+        return ESP_ERR_INVALID_ARG;
+    }
+
+    if ((scl_io_num != GPIO_NUM_0) && (scl_io_num != GPIO_NUM_2)) {
+        ESP_LOGE(RTCI2C_TAG, "SCL pin can only be configured as GPIO#0 or GPIO#2");
+        return ESP_ERR_INVALID_ARG;
+    }
+
+    return ESP_OK;
+}
+
+static esp_err_t i2c_configure_io(gpio_num_t io_num, bool pullup_en)
+{
+    /* Initialize IO Pin */
+    ESP_RETURN_ON_ERROR(rtc_gpio_init(io_num), RTCI2C_TAG, "RTC GPIO Init failed for GPIO %d", io_num);
+    /* Set direction to input+output */
+    ESP_RETURN_ON_ERROR(rtc_gpio_set_direction(io_num, RTC_GPIO_MODE_INPUT_OUTPUT), RTCI2C_TAG, "RTC GPIO Set direction failed for %d", io_num);
+    /* Disable pulldown on the io pin */
+    ESP_RETURN_ON_ERROR(rtc_gpio_pulldown_dis(io_num), RTCI2C_TAG, "RTC GPIO pulldown disable failed for %d", io_num);
+    /* Enable pullup based on pullup_en flag */
+    if (pullup_en) {
+        ESP_RETURN_ON_ERROR(rtc_gpio_pullup_en(io_num), RTCI2C_TAG, "RTC GPIO pullup enable failed for %d", io_num);
+    } else {
+        ESP_RETURN_ON_ERROR(rtc_gpio_pullup_dis(io_num), RTCI2C_TAG, "RTC GPIO pullup disable failed for %d", io_num);
+    }
+
+    return ESP_OK;
+}
+
+static esp_err_t i2c_set_pin(const ulp_riscv_i2c_cfg_t *cfg)
+{
+    gpio_num_t sda_io_num = cfg->i2c_pin_cfg.sda_io_num;
+    gpio_num_t scl_io_num = cfg->i2c_pin_cfg.scl_io_num;
+    bool sda_pullup_en = cfg->i2c_pin_cfg.sda_pullup_en;
+    bool scl_pullup_en = cfg->i2c_pin_cfg.scl_pullup_en;
+
+    /* Verify that the I2C GPIOs are valid */
+    ESP_RETURN_ON_ERROR(i2c_gpio_is_cfg_valid(sda_io_num, scl_io_num), RTCI2C_TAG, "RTC I2C GPIO config invalid");
+
+    /* Initialize SDA Pin */
+    ESP_RETURN_ON_ERROR(i2c_configure_io(sda_io_num, sda_pullup_en), RTCI2C_TAG, "RTC I2C SDA pin config failed");
+
+    /* Initialize SCL Pin */
+    ESP_RETURN_ON_ERROR(i2c_configure_io(scl_io_num, scl_pullup_en), RTCI2C_TAG, "RTC I2C SCL pin config failed");
+
+    /* Route SDA IO signal to the RTC subsystem */
+    rtc_io_dev->touch_pad[sda_io_num].mux_sel = 1;
+
+    /* Route SCL IO signal to the RTC subsystem */
+    rtc_io_dev->touch_pad[scl_io_num].mux_sel = 1;
+
+    /* Select RTC I2C function for SDA pin */
+    rtc_io_dev->touch_pad[sda_io_num].fun_sel = 3;
+
+    /* Select RTC I2C function for SCL pin */
+    rtc_io_dev->touch_pad[scl_io_num].fun_sel = 3;
+
+    /* Map the SDA and SCL signals to the RTC I2C controller */
+    if (sda_io_num == GPIO_NUM_1) {
+        rtc_io_dev->sar_i2c_io.sda_sel = 0;
+    } else {
+        rtc_io_dev->sar_i2c_io.sda_sel = 1;
+    }
+
+    if (scl_io_num == GPIO_NUM_0) {
+        rtc_io_dev->sar_i2c_io.scl_sel = 0;
+    } else {
+        rtc_io_dev->sar_i2c_io.scl_sel = 1;
+    }
+
+    return ESP_OK;
+}
+
+static esp_err_t i2c_set_timing(const ulp_riscv_i2c_cfg_t *cfg)
+{
+    /* Convert all timing parameters from micro-seconds to period in RTC_FAST_CLK cycles.
+     * RTC_FAST_CLK = 8.5 MHz for esp32s2 and 17.5 MHz for esp32s3.
+     * The following calculations approximate the period for each parameter.
+     */
+    uint32_t scl_low_period = MICROSEC_TO_RTC_FAST_CLK(cfg->i2c_timing_cfg.scl_low_period);
+    uint32_t scl_high_period = MICROSEC_TO_RTC_FAST_CLK(cfg->i2c_timing_cfg.scl_high_period);
+    uint32_t sda_duty_period = MICROSEC_TO_RTC_FAST_CLK(cfg->i2c_timing_cfg.sda_duty_period);
+    uint32_t scl_start_period = MICROSEC_TO_RTC_FAST_CLK(cfg->i2c_timing_cfg.scl_start_period);
+    uint32_t scl_stop_period = MICROSEC_TO_RTC_FAST_CLK(cfg->i2c_timing_cfg.scl_stop_period);
+    uint32_t i2c_trans_timeout = MICROSEC_TO_RTC_FAST_CLK(cfg->i2c_timing_cfg.i2c_trans_timeout);
+    uint32_t setup_time_start = (cfg->i2c_timing_cfg.scl_high_period + cfg->i2c_timing_cfg.sda_duty_period);
+    uint32_t hold_time_start = (cfg->i2c_timing_cfg.scl_start_period - cfg->i2c_timing_cfg.sda_duty_period);
+    uint32_t setup_time_data = (cfg->i2c_timing_cfg.scl_low_period - cfg->i2c_timing_cfg.sda_duty_period);
+
+    /* Verify timing constraints */
+    ESP_RETURN_ON_FALSE((float)cfg->i2c_timing_cfg.scl_low_period > 1.3, ESP_ERR_INVALID_ARG, RTCI2C_TAG, "SCL low period cannot be less than 1.3 micro seconds");
+    ESP_RETURN_ON_FALSE((float)cfg->i2c_timing_cfg.scl_high_period > 0.6, ESP_ERR_INVALID_ARG, RTCI2C_TAG, "SCL high period cannot be less than 0.6 micro seconds");
+    ESP_RETURN_ON_FALSE((float)setup_time_start > 0.6, ESP_ERR_INVALID_ARG, RTCI2C_TAG, "Setup time cannot be less than 0.6 micro seconds");
+    ESP_RETURN_ON_FALSE((float)hold_time_start > 0.6, ESP_ERR_INVALID_ARG, RTCI2C_TAG, "Data hold time cannot be less than 0.6 micro seconds");
+    ESP_RETURN_ON_FALSE((float)cfg->i2c_timing_cfg.scl_stop_period > 0.6, ESP_ERR_INVALID_ARG, RTCI2C_TAG, "Setup time cannot be less than 0.6 micro seconds");
+    ESP_RETURN_ON_FALSE((float)cfg->i2c_timing_cfg.sda_duty_period < 3.45, ESP_ERR_INVALID_ARG, RTCI2C_TAG, "Data hold time cannot be greater than 3.45 micro seconds");
+    ESP_RETURN_ON_FALSE((float)(setup_time_data * 1000) > 250, ESP_ERR_INVALID_ARG, RTCI2C_TAG, "Data setup time cannot be less than 250 nano seconds");
+
+    /* Verify filtering constrains
+     *
+     * I2C may have glitches on the transition edge, so the edge will be filtered in the design,
+     * which will also affect the value of the timing parameter register.
+     * Therefore, the following filtering constraints must be followed:
+     */
+    ESP_RETURN_ON_FALSE(scl_stop_period > scl_high_period, ESP_ERR_INVALID_ARG, RTCI2C_TAG, "SCL Stop period cannot be greater than SCL high period");
+    ESP_RETURN_ON_FALSE(sda_duty_period < scl_low_period, ESP_ERR_INVALID_ARG, RTCI2C_TAG, "SDA duty period cannot be less than the SCL low period");
+    ESP_RETURN_ON_FALSE(scl_start_period > 8, ESP_ERR_INVALID_ARG, RTCI2C_TAG, "SCL start period must be greater than 8 RTC_FAST_CLK cycles");
+    ESP_RETURN_ON_FALSE((scl_low_period + scl_high_period - sda_duty_period) > 8, ESP_ERR_INVALID_ARG, RTCI2C_TAG, "SCL low + SCL high - SDA duty must be greater than 8 RTC_FAST_CLK cycles");
+
+    /* Verify SDA duty num constraints */
+    ESP_RETURN_ON_FALSE(sda_duty_period > 14, ESP_ERR_INVALID_ARG, RTCI2C_TAG, "SDA duty period must be greater than 14 RTC_FAST_CLK cycles");
+
+    /* Set the RTC I2C timing parameters */
+#if CONFIG_IDF_TARGET_ESP32S2
+    i2c_dev->scl_low.val = scl_low_period;                  // SCL low period
+    i2c_dev->scl_high.val = scl_high_period;                // SCL high period
+    i2c_dev->sda_duty.val = sda_duty_period;                // SDA duty cycle
+    i2c_dev->scl_start_period.val = scl_start_period;       // Wait time after START condition
+    i2c_dev->scl_stop_period.val = scl_stop_period;         // Wait time before END condition
+    i2c_dev->timeout.val = i2c_trans_timeout;               // I2C transaction timeout
+#elif CONFIG_IDF_TARGET_ESP32S3
+    i2c_dev->i2c_scl_low.val = scl_low_period;              // SCL low period
+    i2c_dev->i2c_scl_high.val = scl_high_period;            // SCL high period
+    i2c_dev->i2c_sda_duty.val = sda_duty_period;            // SDA duty cycle
+    i2c_dev->i2c_scl_start_period.val = scl_start_period;   // Wait time after START condition
+    i2c_dev->i2c_scl_stop_period.val = scl_stop_period;     // Wait time before END condition
+    i2c_dev->i2c_to.val = i2c_trans_timeout;                // I2C transaction timeout
+#endif // CONFIG_IDF_TARGET_ESP32S2
+
+    return ESP_OK;
+}
+
+/*
+ * The RTC I2C controller follows the I2C command registers to perform read/write operations.
+ * The cmd registers have the following format:
+ *
+ *      31        30:14     13:11      10         9           8         7:0
+ * |----------|----------|---------|---------|----------|------------|---------|
+ * | CMD_DONE | Reserved |  OPCODE |ACK Value|ACK Expect|ACK Check En|Byte Num |
+ * |----------|----------|---------|---------|----------|------------|---------|
+ */
+static void ulp_riscv_i2c_format_cmd(uint32_t cmd_idx, uint8_t op_code, uint8_t ack_val,
+        uint8_t ack_expected, uint8_t ack_check_en, uint8_t byte_num)
+{
+#if CONFIG_IDF_TARGET_ESP32S2
+    /* Reset cmd register */
+    i2c_dev->command[cmd_idx].val = 0;
+
+    /* Write new command to cmd register */
+    i2c_dev->command[cmd_idx].done = 0;                     // CMD Done
+    i2c_dev->command[cmd_idx].op_code = op_code;            // Opcode
+    i2c_dev->command[cmd_idx].ack_val = ack_val;            // ACK bit sent by I2C controller during READ.
+                                                            // Ignored during RSTART, STOP, END and WRITE cmds.
+    i2c_dev->command[cmd_idx].ack_exp = ack_expected;       // ACK bit expected by I2C controller during WRITE.
+                                                            // Ignored during RSTART, STOP, END and READ cmds.
+    i2c_dev->command[cmd_idx].ack_en = ack_check_en;        // I2C controller verifies that the ACK bit sent by the
+                                                            // slave device matches the ACK expected bit during WRITE.
+                                                            // Ignored during RSTART, STOP, END and READ cmds.
+    i2c_dev->command[cmd_idx].byte_num = byte_num;          // Byte Num
+#elif CONFIG_IDF_TARGET_ESP32S3
+    /* Reset cmd register */
+    i2c_dev->i2c_cmd[cmd_idx].val = 0;
+
+    /* Write new command to cmd register */
+    i2c_dev->i2c_cmd[cmd_idx].i2c_command_done = 0;         // CMD Done
+    i2c_dev->i2c_cmd[cmd_idx].i2c_op_code = op_code;        // Opcode
+    i2c_dev->i2c_cmd[cmd_idx].i2c_ack_val = ack_val;        // ACK bit sent by I2C controller during READ.
+                                                            // Ignored during RSTART, STOP, END and WRITE cmds.
+    i2c_dev->i2c_cmd[cmd_idx].i2c_ack_exp = ack_expected;   // ACK bit expected by I2C controller during WRITE.
+                                                            // Ignored during RSTART, STOP, END and READ cmds.
+    i2c_dev->i2c_cmd[cmd_idx].i2c_ack_en = ack_check_en;    // I2C controller verifies that the ACK bit sent by the
+                                                            // slave device matches the ACK expected bit during WRITE.
+                                                            // Ignored during RSTART, STOP, END and READ cmds.
+    i2c_dev->i2c_cmd[cmd_idx].i2c_byte_num = byte_num;      // Byte Num
+#endif // CONFIG_IDF_TARGET_ESP32S2
+}
+
+void ulp_riscv_i2c_master_set_slave_addr(uint8_t slave_addr)
+{
+    CLEAR_PERI_REG_MASK(SENS_SAR_I2C_CTRL_REG, I2C_CTRL_SLAVE_ADDR_MASK);
+    SET_PERI_REG_BITS(SENS_SAR_I2C_CTRL_REG, 0xFF, slave_addr, 0);
+}
+
+void ulp_riscv_i2c_master_set_slave_reg_addr(uint8_t slave_reg_addr)
+{
+    CLEAR_PERI_REG_MASK(SENS_SAR_I2C_CTRL_REG, I2C_CTRL_SLAVE_REG_ADDR_MASK);
+    SET_PERI_REG_BITS(SENS_SAR_I2C_CTRL_REG, 0xFF, slave_reg_addr, 11);
+}
+
+/*
+ * I2C transactions when master reads one byte of data from the slave device:
+ *
+ * |--------|--------|---------|--------|--------|--------|--------|---------|--------|--------|--------|--------|
+ * | Master | START  | SAD + W |        |  SUB   |        |   SR   | SAD + R |        |        |  NACK  |  STOP  |
+ * |--------|--------|---------|--------|--------|--------|--------|---------|--------|--------|--------|--------|
+ * | Slave  |        |         |  ACK   |        |   ACK  |        |         |   ACK  |  DATA  |        |        |
+ * |--------|--------|---------|--------|--------|--------|--------|---------|--------|--------|--------|--------|
+ *
+ * I2C transactions when master reads multiple bytes of data from the slave device:
+ *
+ * |--------|--------|---------|--------|--------|--------|--------|---------|--------|--------|--------|--------|--------|--------|
+ * | Master | START  | SAD + W |        |  SUB   |        |   SR   | SAD + R |        |        |   ACK  |        |   NACK |  STOP  |
+ * |--------|--------|---------|--------|--------|--------|--------|---------|--------|--------|--------|--------|--------|--------|
+ * | Slave  |        |         |  ACK   |        |   ACK  |        |         |   ACK  |  DATA  |        |  DATA  |        |        |
+ * |--------|--------|---------|--------|--------|--------|--------|---------|--------|--------|--------|--------|--------|--------|
+ */
+void ulp_riscv_i2c_master_read_from_device(uint8_t *data_rd, size_t size)
+{
+    uint32_t i = 0;
+    uint32_t cmd_idx = 0;
+
+    if (size == 0) {
+        // Quietly return
+        return;
+    }
+
+    /* By default, RTC I2C controller is hard wired to use CMD2 register onwards for read operations */
+    cmd_idx = 2;
+
+    /* Write slave addr */
+    ulp_riscv_i2c_format_cmd(cmd_idx++, ULP_I2C_CMD_WRITE, 0, 0, 1, 2);
+
+    /* Repeated START */
+    ulp_riscv_i2c_format_cmd(cmd_idx++, ULP_I2C_CMD_RESTART, 0, 0, 0, 0);
+
+    /* Write slave register addr */
+    ulp_riscv_i2c_format_cmd(cmd_idx++, ULP_I2C_CMD_WRITE, 0, 0, 1, 1);
+
+    if (size > 1) {
+        /* Read n - 1 bytes */
+        ulp_riscv_i2c_format_cmd(cmd_idx++, ULP_I2C_CMD_READ, 0, 0, 1, size - 1);
+    }
+
+    /* Read last byte + NACK */
+    ulp_riscv_i2c_format_cmd(cmd_idx++, ULP_I2C_CMD_READ, 1, 1, 1, 1);
+
+    /* STOP */
+    ulp_riscv_i2c_format_cmd(cmd_idx++, ULP_I2C_CMD_STOP, 0, 0, 0, 0);
+
+    /* Configure the RTC I2C controller in read mode */
+    SET_PERI_REG_BITS(SENS_SAR_I2C_CTRL_REG, 0x1, 0, 27);
+
+    /* Enable Rx data interrupt */
+    SET_PERI_REG_MASK(RTC_I2C_INT_ENA_REG, RTC_I2C_RX_DATA_INT_ENA);
+
+    /* Start RTC I2C transmission */
+    SET_PERI_REG_MASK(SENS_SAR_I2C_CTRL_REG, SENS_SAR_I2C_START_FORCE);
+    SET_PERI_REG_MASK(SENS_SAR_I2C_CTRL_REG, SENS_SAR_I2C_START);
+
+    for (i = 0; i < size; i++) {
+        /* Poll for RTC I2C Rx Data interrupt bit to be set */
+        while (!REG_GET_FIELD(RTC_I2C_INT_ST_REG, RTC_I2C_RX_DATA_INT_ST)) {
+            /* Minimal delay to avoid hogging the CPU */
+            vTaskDelay(1);
+        }
+
+        /* Read the data
+         *
+         * Unfortunately, the RTC I2C has no fifo buffer to help us with reading and storing
+         * multiple bytes of data. Therefore, we need to read one byte at a time and clear the
+         * Rx interrupt to get ready for the next byte.
+         */
+#if CONFIG_IDF_TARGET_ESP32S2
+        data_rd[i] = REG_GET_FIELD(RTC_I2C_DATA_REG, RTC_I2C_RDATA);
+#elif CONFIG_IDF_TARGET_ESP32S3
+        data_rd[i] = REG_GET_FIELD(RTC_I2C_DATA_REG, RTC_I2C_I2C_RDATA);
+#endif // CONFIG_IDF_TARGET_ESP32S2
+
+        /* Clear the Rx data interrupt bit */
+        SET_PERI_REG_MASK(RTC_I2C_INT_CLR_REG, RTC_I2C_RX_DATA_INT_CLR);
+    }
+
+    /* Clear the RTC I2C transmission bits */
+    CLEAR_PERI_REG_MASK(SENS_SAR_I2C_CTRL_REG, SENS_SAR_I2C_START_FORCE);
+    CLEAR_PERI_REG_MASK(SENS_SAR_I2C_CTRL_REG, SENS_SAR_I2C_START);
+}
+
+/*
+ * I2C transactions when master writes one byte of data to the slave device:
+ *
+ * |--------|--------|---------|--------|--------|--------|--------|--------|--------|
+ * | Master | START  | SAD + W |        |  SUB   |        |  DATA  |        |  STOP  |
+ * |--------|--------|---------|--------|--------|--------|--------|--------|--------|
+ * | Slave  |        |         |  ACK   |        |   ACK  |        |   ACK  |        |
+ * |--------|--------|---------|--------|--------|--------|--------|--------|--------|
+ *
+ * I2C transactions when master writes multiple bytes of data to the slave device:
+ *
+ * |--------|--------|---------|--------|--------|--------|--------|--------|--------|--------|--------|
+ * | Master | START  | SAD + W |        |  SUB   |        |  DATA  |        |  DATA  |        |  STOP  |
+ * |--------|--------|---------|--------|--------|--------|--------|--------|--------|--------|--------|
+ * | Slave  |        |         |  ACK   |        |   ACK  |        |   ACK  |        |   ACK  |        |
+ * |--------|--------|---------|--------|--------|--------|--------|--------|--------|--------|--------|
+ */
+void ulp_riscv_i2c_master_write_to_device(uint8_t *data_wr, size_t size)
+{
+    uint32_t i = 0;
+    uint32_t cmd_idx = 0;
+
+    if (size == 0) {
+        // Quietly return
+        return;
+    }
+
+    /* By default, RTC I2C controller is hard wired to use CMD0 and CMD1 registers for write operations */
+    cmd_idx = 0;
+
+    /* Write slave addr + reg addr + data */
+    ulp_riscv_i2c_format_cmd(cmd_idx++, ULP_I2C_CMD_WRITE, 0, 0, 1, 2 + size);
+
+    /* Stop */
+    ulp_riscv_i2c_format_cmd(cmd_idx++, ULP_I2C_CMD_STOP, 0, 0, 0, 0);
+
+    /* Configure the RTC I2C controller in write mode */
+    SET_PERI_REG_BITS(SENS_SAR_I2C_CTRL_REG, 0x1, 1, 27);
+
+    /* Enable Tx data interrupt */
+    SET_PERI_REG_MASK(RTC_I2C_INT_ENA_REG, RTC_I2C_TX_DATA_INT_ENA);
+
+    for (i = 0; i < size; i++) {
+        /* Write the data to be transmitted */
+        CLEAR_PERI_REG_MASK(SENS_SAR_I2C_CTRL_REG, I2C_CTRL_MASTER_TX_DATA_MASK);
+        SET_PERI_REG_BITS(SENS_SAR_I2C_CTRL_REG, 0xFF, data_wr[i], 19);
+
+        if (i == 0) {
+            /* Start RTC I2C transmission. (Needn't do it for every byte) */
+            SET_PERI_REG_MASK(SENS_SAR_I2C_CTRL_REG, SENS_SAR_I2C_START_FORCE);
+            SET_PERI_REG_MASK(SENS_SAR_I2C_CTRL_REG, SENS_SAR_I2C_START);
+        }
+
+        /* Poll for RTC I2C Tx Data interrupt bit to be set */
+        while (!REG_GET_FIELD(RTC_I2C_INT_ST_REG, RTC_I2C_TX_DATA_INT_ST)) {
+            /* Minimal delay to avoid hogging the CPU */
+            vTaskDelay(1);
+        }
+
+        /* Clear the Tx data interrupt bit */
+        SET_PERI_REG_MASK(RTC_I2C_INT_CLR_REG, RTC_I2C_TX_DATA_INT_CLR);
+    }
+
+    /* Clear the RTC I2C transmission bits */
+    CLEAR_PERI_REG_MASK(SENS_SAR_I2C_CTRL_REG, SENS_SAR_I2C_START_FORCE);
+    CLEAR_PERI_REG_MASK(SENS_SAR_I2C_CTRL_REG, SENS_SAR_I2C_START);
+}
+
+esp_err_t ulp_riscv_i2c_master_init(const ulp_riscv_i2c_cfg_t *cfg)
+{
+    /* Reset RTC I2C */
+#if CONFIG_IDF_TARGET_ESP32S2
+    i2c_dev->ctrl.i2c_reset = 1;
+    esp_rom_delay_us(20);
+    i2c_dev->ctrl.i2c_reset = 0;
+#elif CONFIG_IDF_TARGET_ESP32S3
+    SET_PERI_REG_MASK(SENS_SAR_PERI_RESET_CONF_REG, SENS_RTC_I2C_RESET);
+    i2c_dev->i2c_ctrl.i2c_i2c_reset = 1;
+    esp_rom_delay_us(20);
+    i2c_dev->i2c_ctrl.i2c_i2c_reset = 0;
+    CLEAR_PERI_REG_MASK(SENS_SAR_PERI_RESET_CONF_REG, SENS_RTC_I2C_RESET);
+#endif // CONFIG_IDF_TARGET_ESP32S2
+
+    /* Verify that the input cfg param is valid */
+    ESP_RETURN_ON_FALSE(cfg, ESP_ERR_INVALID_ARG, RTCI2C_TAG, "RTC I2C configuration is NULL");
+
+    /* Configure RTC I2C GPIOs */
+    ESP_RETURN_ON_ERROR(i2c_set_pin(cfg), RTCI2C_TAG, "Failed to configure RTC I2C GPIOs");
+
+#if CONFIG_IDF_TARGET_ESP32S2
+    /* Configure the RTC I2C controller in master mode */
+    i2c_dev->ctrl.ms_mode = 1;
+
+    /* Enable RTC I2C Clock gate */
+    i2c_dev->ctrl.i2c_ctrl_clk_gate_en = 1;
+#elif CONFIG_IDF_TARGET_ESP32S3
+    /* For esp32s3, we need to enable the rtc_i2c clock gate before accessing rtc i2c registers */
+    SET_PERI_REG_MASK(SENS_SAR_PERI_CLK_GATE_CONF_REG, SENS_RTC_I2C_CLK_EN);
+
+    /* Configure the RTC I2C controller in master mode */
+    i2c_dev->i2c_ctrl.i2c_ms_mode = 1;
+
+    /* Enable RTC I2C Clock gate */
+    i2c_dev->i2c_ctrl.i2c_i2c_ctrl_clk_gate_en = 1;
+#endif // CONFIG_IDF_TARGET_ESP32S2
+
+    /* Configure RTC I2C timing paramters */
+    ESP_RETURN_ON_ERROR(i2c_set_timing(cfg), RTCI2C_TAG, "Failed to configure RTC I2C timing");
+
+    return ESP_OK;
+}

+ 4 - 0
examples/system/.build-test-rules.yml

@@ -194,6 +194,10 @@ examples/system/ulp_riscv/gpio_interrupt:
       temporary: true
       reason: the other targets are not tested yet
 
+examples/system/ulp_riscv/i2c:
+  enable:
+    - if: SOC_RISCV_COPROC_SUPPORTED == 1
+
 examples/system/ulp_riscv/uart_print:
   enable:
     - if: SOC_RISCV_COPROC_SUPPORTED == 1

+ 6 - 0
examples/system/ulp_riscv/i2c/CMakeLists.txt

@@ -0,0 +1,6 @@
+# The following lines of boilerplate have to be in your project's CMakeLists
+# in this exact order for cmake to work correctly
+cmake_minimum_required(VERSION 3.16)
+
+include($ENV{IDF_PATH}/tools/cmake/project.cmake)
+project(ulp-riscv-rtc-i2c-example)

+ 76 - 0
examples/system/ulp_riscv/i2c/README.md

@@ -0,0 +1,76 @@
+| Supported Targets | ESP32-S2 | ESP32-S3 |
+| ----------------- | -------- | -------- |
+
+# ULP RISC-V I2C Example
+
+This example demonstrates how to use the RTC I2C peripheral from the ULP RISC-V coprocessor in deep sleep.
+
+The ULP program is based on the BMP180 Temperature and Pressure sensor (https://cz.mouser.com/datasheet/2/783/BST-BMP180-DS000-1509579.pdf) which has an I2C interface. The main CPU initializes the RTC I2C peripheral, the BMP180 sensor and loads the ULP program. It then goes into deep sleep.
+
+The ULP program periodically measures the temperature and pressure values from the BMP180 sensor and wakesup the main CPU when the values are above a certain thershold.
+### Hardware Required
+
+* A development board with a SOC which has a RISC-V ULP coprocessor (e.g., ESP32-S2 Saola)
+* A BMP180 sensor module
+* A USB cable for power supply and programming
+
+## Example output
+
+Below is the output from this example.
+
+```
+Not a ULP-RISC V wakeup (cause = 0)
+Initializing RTC I2C ...
+RTC_I2C_STATUS_REG = 0x00000000
+Reading calibration data from BMP180 ...
+ac1 = 7819
+ac2 = -1152
+ac3 = -14317
+ac4 = 34252
+ac5 = 25122
+ac6 = 14289
+b1 = 6515
+b2 = 44
+mb = -32768
+mc = -11786
+md = 2746
+
+Reading initial uncompensated temperature and pressure data ...
+Uncompensated Temperature = 22865
+Uncompensated Pressure = 41768
+
+Real Temperature = 24.900000 deg celcius
+Real Pressure = 990.640000 hPa
+
+Entering in deep sleep
+
+ESP-ROM:esp32s2-rc4-20191025
+Build:Oct 25 2019
+rst:0x5 (DSLEEP),boot:0x9 (SPI_FAST_FLASH_BOOT)
+SPIWP:0xee
+mode:DIO, clock div:1
+load:0x3ffe6108,len:0x1298
+load:0x4004c000,len:0x92c
+load:0x40050000,len:0x2f04
+entry 0x4004c154
+W (76) spi_flash: Detected size(4096k) larger than the size in the binary image header(2048k). Using the size in the binary image header.
+ULP RISC-V woke up the main CPU
+Uncompensated Temperature = 22865
+Uncompensated Pressure = 41765
+Reading calibration data from BMP180 ...
+ac1 = 7819
+ac2 = -1152
+ac3 = -14317
+ac4 = 34252
+ac5 = 25122
+ac6 = 14289
+b1 = 6515
+b2 = 44
+mb = -32768
+mc = -11786
+md = 2746
+
+New Real Temperature = 24.900000 deg celcius
+New Real Pressure = 990.550000 hPa
+Entering in deep sleep
+```

+ 25 - 0
examples/system/ulp_riscv/i2c/main/CMakeLists.txt

@@ -0,0 +1,25 @@
+idf_component_register(SRCS "ulp_riscv_rtc_i2c_example_main.c"
+                    INCLUDE_DIRS ""
+                    REQUIRES soc ulp)
+target_compile_options(${COMPONENT_LIB} PRIVATE "-Wno-format")
+
+#
+# ULP support additions to component CMakeLists.txt.
+#
+# 1. The ULP app name must be unique (if multiple components use ULP).
+set(ulp_app_name ulp_${COMPONENT_NAME})
+#
+# 2. Specify all C and Assembly source files.
+#    Files should be placed into a separate directory (in this case, ulp/),
+#    which should not be added to COMPONENT_SRCS.
+set(ulp_riscv_sources "ulp/main.c")
+
+#
+# 3. List all the component source files which include automatically
+#    generated ULP export file, ${ulp_app_name}.h:
+set(ulp_exp_dep_srcs "ulp_riscv_rtc_i2c_example_main.c")
+
+#
+# 4. Call function to build ULP binary and embed in project using the argument
+#    values above.
+ulp_embed_binary(${ulp_app_name} "${ulp_riscv_sources}" "${ulp_exp_dep_srcs}")

+ 110 - 0
examples/system/ulp_riscv/i2c/main/bmp180_defs.h

@@ -0,0 +1,110 @@
+/*
+ * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Unlicense OR CC0-1.0
+ */
+#pragma once
+
+/***************************************************
+ * BMP180 Register Addresses
+ ***************************************************/
+#define BMP180_SENSOR_I2C_ADDR                  0x77
+#define BMP180_SENSOR_REG_ADDR_WHO_AM_I         0xD0
+#define BMP180_SENSOR_REG_ADDR_SOFT_RESET       0xE0
+#define BMP180_SENSOR_REG_ADDR_AC1_MSB          0xAA
+#define BMP180_SENSOR_REG_ADDR_AC1_LSB          0xAB
+#define BMP180_SENSOR_REG_ADDR_AC2_MSB          0xAC
+#define BMP180_SENSOR_REG_ADDR_AC2_LSB          0xAD
+#define BMP180_SENSOR_REG_ADDR_AC3_MSB          0xAE
+#define BMP180_SENSOR_REG_ADDR_AC3_LSB          0xAF
+#define BMP180_SENSOR_REG_ADDR_AC4_MSB          0xB0
+#define BMP180_SENSOR_REG_ADDR_AC4_LSB          0xB1
+#define BMP180_SENSOR_REG_ADDR_AC5_MSB          0xB2
+#define BMP180_SENSOR_REG_ADDR_AC5_LSB          0xB3
+#define BMP180_SENSOR_REG_ADDR_AC6_MSB          0xB4
+#define BMP180_SENSOR_REG_ADDR_AC6_LSB          0xB5
+#define BMP180_SENSOR_REG_ADDR_B1_MSB           0xB6
+#define BMP180_SENSOR_REG_ADDR_B1_LSB           0xB7
+#define BMP180_SENSOR_REG_ADDR_B2_MSB           0xB8
+#define BMP180_SENSOR_REG_ADDR_B2_LSB           0xB9
+#define BMP180_SENSOR_REG_ADDR_MB_MSB           0xBA
+#define BMP180_SENSOR_REG_ADDR_MB_LSB           0xBB
+#define BMP180_SENSOR_REG_ADDR_MC_MSB           0xBC
+#define BMP180_SENSOR_REG_ADDR_MC_LSB           0xBD
+#define BMP180_SENSOR_REG_ADDR_MD_MSB           0xBE
+#define BMP180_SENSOR_REG_ADDR_MD_LSB           0xBF
+#define BMP180_SENSOR_REG_ADDR_CTRL_REG         0xF4
+#define BMP180_SENSOR_REG_ADDR_SENSOR_DATA_MSB  0xF6
+#define BMP180_SENSOR_REG_ADDR_SENSOR_DATA_LSB  0xF7
+#define BMP180_SENSOR_REG_ADDR_SENSOR_DATA_XLSB 0xF8
+
+/***************************************************
+ * BMP180 Control Commands
+ ***************************************************/
+#define BMP180_SENSOR_CMD_READ_TEMPERATURE      0x2E
+#define BMP180_SENSOR_CMD_READ_PRESSURE_OSS_0   0x34
+#define BMP180_SENSOR_CMD_READ_PRESSURE_OSS_1   0x74
+#define BMP180_SENSOR_CMD_READ_PRESSURE_OSS_2   0xB4
+#define BMP180_SENSOR_CMD_READ_PRESSURE_OSS_3   0xF4
+#define BMP180_SENSOR_CMD_SOFT_RESET            0xB6
+
+/***************************************************
+ * BMP180 Chip ID
+ ***************************************************/
+#define BMP180_SENSOR_CHIP_ID                   0x55
+
+/***************************************************
+ * BMP180 Calibration Data
+ ***************************************************/
+typedef struct {
+    int16_t  ac1;
+    int16_t  ac2;
+    int16_t  ac3;
+    uint16_t ac4;
+    uint16_t ac5;
+    uint16_t ac6;
+    int16_t  b1;
+    int16_t  b2;
+    int16_t  mb;
+    int16_t  mc;
+    int16_t  md;
+} bmp180_cal_data_t;
+
+bmp180_cal_data_t bmp180_cal_data;
+
+/***************************************************
+ * BMP180 Oversampling setting to measure pressure
+ ***************************************************/
+typedef enum {
+    OSS_0 = 0,  // Ultra low power
+    OSS_1 = 1,  // Standard
+    OSS_2 = 2,  // High resolution
+    OSS_3 = 3   // Ultra high resolution
+} oss_mode_t;
+
+/***************************************************
+ * BMP180 Interaction APIs
+ ***************************************************/
+static void bmp180_read_cal_data(void);                                 // Read cal data
+static void bmp180_read_ut_data(int16_t *ut_data);                      // Read uncompensated temperature
+static void bmp180_read_up_data(int32_t *up_data, oss_mode_t oss_mode); // Read uncompensated pressure
+
+/************************************************
+ * BMP180 Utility APIs
+ ************************************************/
+static void bmp180_read16(uint16_t *data_out, uint32_t reg_msb, uint32_t reg_lsb);
+static int32_t bmp180_calculate_real_temp(int32_t ut_data);
+static int32_t bmp180_calculate_real_pressure(int32_t up_data, int32_t ut_data, oss_mode_t oss_mode);
+
+/************************************************
+ * Pressure measurement mode
+ ************************************************/
+#define EXAMPLE_OSS_MODE        OSS_0
+
+/************************************************
+ * Temperature and Pressure thresholds (uncompensated) to wake up Main CPU
+ * The threshold values have been selected for demp purposes and may not
+ * represent real world use case.
+ ************************************************/
+#define EXAMPLE_UT_THRESHOLD    20000
+#define EXAMPLE_UP_THRESHOLD    40000

+ 126 - 0
examples/system/ulp_riscv/i2c/main/ulp/main.c

@@ -0,0 +1,126 @@
+/*
+ * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Unlicense OR CC0-1.0
+ */
+/* ULP RISC-V RTC I2C example
+
+   This example code is in the Public Domain (or CC0 licensed, at your option.)
+
+   Unless required by applicable law or agreed to in writing, this
+   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+   CONDITIONS OF ANY KIND, either express or implied.
+
+   This code runs on ULP RISC-V coprocessor
+*/
+
+#include <stdint.h>
+#include "ulp_riscv.h"
+#include "ulp_riscv_utils.h"
+#include "ulp_riscv_i2c_ulp_core.h"
+#include "../bmp180_defs.h"
+
+/************************************************
+ * Shared data between main CPU and ULP
+ ************************************************/
+int16_t ut_data = 0;
+int32_t up_data = 0;
+int32_t ut_threshold = EXAMPLE_UT_THRESHOLD;
+int32_t up_threshold = EXAMPLE_UP_THRESHOLD;
+oss_mode_t oss_mode = EXAMPLE_OSS_MODE;
+
+int main (void)
+{
+    /* Read uncompensated temperature */
+    bmp180_read_ut_data(&ut_data);
+
+    /* Read uncompensated pressure */
+    bmp180_read_up_data(&up_data, oss_mode);
+
+    /* Wakeup the main CPU if either the uncompensated temperature or uncompensated pressure values
+     * are more than their respective threshold values.
+     */
+    if ((ut_data > ut_threshold) || (up_data > up_threshold)) {
+        ulp_riscv_wakeup_main_processor();
+    }
+
+    return 0;
+}
+
+static void bmp180_read16(uint16_t *data_out, uint32_t reg_msb, uint32_t reg_lsb)
+{
+    uint8_t data_rd = 0;
+    *data_out = 0;
+
+    ulp_riscv_i2c_master_set_slave_reg_addr(reg_msb);
+    ulp_riscv_i2c_master_read_from_device(&data_rd, 1);
+    *data_out |= (uint16_t)(data_rd << 8);
+    ulp_riscv_i2c_master_set_slave_reg_addr(reg_lsb);
+    data_rd = 0;
+    ulp_riscv_i2c_master_read_from_device(&data_rd, 1);
+    *data_out |= (uint16_t)(data_rd);
+}
+
+static void bmp180_read_ut_data(int16_t *ut_data)
+{
+    /* Set slave register address to the control register */
+    ulp_riscv_i2c_master_set_slave_reg_addr(BMP180_SENSOR_REG_ADDR_CTRL_REG);
+
+    /* Setup control register to read temperature */
+    uint8_t cmd = BMP180_SENSOR_CMD_READ_TEMPERATURE;
+    ulp_riscv_i2c_master_write_to_device(&cmd, 1);
+
+    /* Wait at least 4.5 milliseconds for the sensor to complete the reading */
+    ulp_riscv_delay_cycles(5 * ULP_RISCV_CYCLES_PER_US * 1000);
+
+    /* Read uncompensated temperature data */
+    bmp180_read16((uint16_t *)ut_data, BMP180_SENSOR_REG_ADDR_SENSOR_DATA_MSB, BMP180_SENSOR_REG_ADDR_SENSOR_DATA_LSB);
+}
+
+static void bmp180_read_up_data(int32_t *up_data, oss_mode_t oss_mode)
+{
+    uint16_t press_high;
+    uint8_t press_low;
+
+    /* Set slave register address to the control register */
+    ulp_riscv_i2c_master_set_slave_reg_addr(BMP180_SENSOR_REG_ADDR_CTRL_REG);
+
+    /* Setup control register to read pressure */
+    uint8_t cmd = 0;
+    uint8_t wait = 0;
+    switch(oss_mode)
+    {
+        case OSS_0:
+            cmd = BMP180_SENSOR_CMD_READ_PRESSURE_OSS_0;
+            wait = 5; // Wait atleast 4.5 msec
+            break;
+        case OSS_1:
+            cmd = BMP180_SENSOR_CMD_READ_PRESSURE_OSS_1;
+            wait = 8; // Wait atleast 7.5 msec
+            break;
+        case OSS_2:
+            cmd = BMP180_SENSOR_CMD_READ_PRESSURE_OSS_2;
+            wait = 14; // Wait atleast 13.5 msec
+            break;
+        case OSS_3:
+            cmd = BMP180_SENSOR_CMD_READ_PRESSURE_OSS_3;
+            wait = 26; // Wait atleast 25.5 msec
+            break;
+    }
+
+    ulp_riscv_i2c_master_write_to_device(&cmd, 1);
+
+    /* Wait for the required amount of time for the sensor to complete the reading */
+    ulp_riscv_delay_cycles(wait * ULP_RISCV_CYCLES_PER_US * 1000);
+
+    /* Read uncompensated temperature data */
+
+    /* Read MSB + LSB */
+    bmp180_read16(&press_high, BMP180_SENSOR_REG_ADDR_SENSOR_DATA_MSB, BMP180_SENSOR_REG_ADDR_SENSOR_DATA_LSB);
+
+    /* Read XLSB */
+    ulp_riscv_i2c_master_set_slave_reg_addr(BMP180_SENSOR_REG_ADDR_SENSOR_DATA_XLSB);
+    ulp_riscv_i2c_master_read_from_device(&press_low, 1);
+
+    *up_data = (((uint32_t)press_high << 8) + (uint32_t)press_low) >> (8 - oss_mode);
+}

+ 351 - 0
examples/system/ulp_riscv/i2c/main/ulp_riscv_rtc_i2c_example_main.c

@@ -0,0 +1,351 @@
+/*
+ * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Unlicense OR CC0-1.0
+ */
+/* ULP RISC-V RTC I2C example
+
+   This example code is in the Public Domain (or CC0 licensed, at your option.)
+
+   Unless required by applicable law or agreed to in writing, this
+   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+   CONDITIONS OF ANY KIND, either express or implied.
+*/
+
+#include <stdio.h>
+#include <math.h>
+#include "esp_sleep.h"
+#include "ulp_riscv.h"
+#include "ulp_riscv_i2c.h"
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "ulp_main.h"
+#include "bmp180_defs.h"
+
+extern const uint8_t ulp_main_bin_start[] asm("_binary_ulp_main_bin_start");
+extern const uint8_t ulp_main_bin_end[]   asm("_binary_ulp_main_bin_end");
+
+/************************************************
+ * ULP utility APIs
+ ************************************************/
+static void init_ulp_program(void);
+
+/************************************************
+ * RTC I2C utility APIs
+ ************************************************/
+static void init_i2c(void);
+
+void app_main(void)
+{
+    uint8_t data_rd = 0;
+    int16_t ut_data = 0;
+    int32_t up_data = 0;
+    int32_t temperature = 0;
+    int32_t pressure = 0;
+    oss_mode_t oss_mode;
+
+    esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause();
+
+    /* Not a wakeup from ULP
+     * Initialize RTC I2C
+     * Setup BMP180 sensor
+     * Store current temperature and pressure values
+     * Load the ULP firmware
+     * Go to deep sleep
+     */
+    if (cause != ESP_SLEEP_WAKEUP_ULP) {
+        printf("Not a ULP-RISC V wakeup (cause = %d)\n", cause);
+
+        /* Initialize RTC I2C */
+        init_i2c();
+
+        /* Configure I2C slave address */
+        ulp_riscv_i2c_master_set_slave_addr(BMP180_SENSOR_I2C_ADDR);
+
+        /* Reset the BMP180 sensor*/
+        ulp_riscv_i2c_master_set_slave_reg_addr(BMP180_SENSOR_REG_ADDR_SOFT_RESET);
+        uint8_t data_wr = BMP180_SENSOR_CMD_SOFT_RESET;
+        ulp_riscv_i2c_master_write_to_device(&data_wr, 1);
+
+        /* Confirm that the sensor is alive
+         * The BMP180 returns the chip id 0x55 on quering reg addr 0xD0
+         */
+        ulp_riscv_i2c_master_set_slave_reg_addr(BMP180_SENSOR_REG_ADDR_WHO_AM_I);
+        ulp_riscv_i2c_master_read_from_device(&data_rd, 1);
+        if (data_rd != BMP180_SENSOR_CHIP_ID) {
+            printf("ERROR: Cannot communicate with I2C sensor\n");
+            abort();
+        }
+
+        /* Read the calibration data */
+        printf("Reading calibration data from BMP180 ...\n");
+        bmp180_read_cal_data();
+        printf("\n");
+
+        /* Read uncompensated temperature and pressure */
+        printf("Reading initial uncompensated temperature and pressure data ...\n");
+        ut_data = 0;
+        up_data = 0;
+        oss_mode = EXAMPLE_OSS_MODE;
+        bmp180_read_ut_data(&ut_data);
+        bmp180_read_up_data(&up_data, oss_mode);
+        printf("Uncompensated Temperature = %d\n", ut_data);
+        printf("Uncompensated Pressure = %d\n", up_data);
+        printf("\n");
+
+        /* Calculate real temperature value */
+        temperature = bmp180_calculate_real_temp((int32_t)ut_data);
+        printf("Real Temperature = %f deg celcius\n", (float)(temperature/10.0));
+
+        /* Calculate real pressure value */
+        pressure = bmp180_calculate_real_pressure(up_data, (int32_t)ut_data, oss_mode);
+        printf("Real Pressure = %f hPa\n", pressure / 100.0);
+        printf("\n");
+
+        /* Load ULP firmware
+         *
+         * The ULP is responsible of monitoring the temperature and pressure values
+         * periodically. It will wakeup the main CPU if the temperature and pressure
+         * values are above a certain threshold.
+         */
+        init_ulp_program();
+    }
+
+    /* ULP RISC-V read and detected a temperature or pressure above the limit */
+    if (cause == ESP_SLEEP_WAKEUP_ULP) {
+        printf("ULP RISC-V woke up the main CPU\n");
+
+        /* Pause ULP while we are using the RTC I2C from the main CPU */
+        ulp_timer_stop();
+        ulp_riscv_halt();
+
+        printf("Uncompensated Temperature = %d\n", ulp_ut_data);
+        printf("Uncompensated Pressure = %d\n", ulp_up_data);
+
+        /* Read the calibration data again */
+        printf("Reading calibration data from BMP180 ...\n");
+        bmp180_read_cal_data();
+        printf("\n");
+
+        /* Calculate real temperature and pressure again */
+        temperature = 0;
+        temperature = bmp180_calculate_real_temp((int32_t)ulp_ut_data);
+        printf("New Real Temperature = %f deg celcius\n", (float)(temperature/10.0));
+
+        /* Calculate real pressure value */
+        pressure = 0;
+        pressure = bmp180_calculate_real_pressure(ulp_up_data, (int32_t)ulp_ut_data, oss_mode);
+        printf("New Real Pressure = %f hPa\n", pressure / 100.0);
+
+        /* Resume ULP and go to deep sleep again */
+        ulp_timer_resume();
+    }
+
+
+    /* Add a delay for everything to the printed before heading in to light sleep */
+    vTaskDelay(100);
+
+    /* Go back to sleep, only the ULP RISC-V will run */
+    printf("Entering deep sleep\n\n");
+
+    /* RTC peripheral power domain needs to be kept on to keep RTC I2C related configs during sleep */
+    esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
+
+    ESP_ERROR_CHECK(esp_sleep_enable_ulp_wakeup());
+
+    esp_deep_sleep_start();
+}
+
+static void init_i2c(void)
+{
+    /* Configure RTC I2C */
+    printf("Initializing RTC I2C ...\n");
+    ulp_riscv_i2c_cfg_t i2c_cfg = ULP_RISCV_I2C_DEFAULT_CONFIG();
+    ulp_riscv_i2c_master_init(&i2c_cfg);
+}
+
+static void bmp180_read16(uint16_t *data_out, uint32_t reg_msb, uint32_t reg_lsb)
+{
+    uint8_t data_rd = 0;
+    *data_out = 0;
+
+    ulp_riscv_i2c_master_set_slave_reg_addr(reg_msb);
+    ulp_riscv_i2c_master_read_from_device(&data_rd, 1);
+    *data_out |= (uint16_t)(data_rd << 8);
+    ulp_riscv_i2c_master_set_slave_reg_addr(reg_lsb);
+    data_rd = 0;
+    ulp_riscv_i2c_master_read_from_device(&data_rd, 1);
+    *data_out |= (uint16_t)(data_rd);
+}
+
+static void bmp180_read_cal_data(void)
+{
+    /* AC1 */
+    bmp180_read16((uint16_t *)&bmp180_cal_data.ac1, BMP180_SENSOR_REG_ADDR_AC1_MSB, BMP180_SENSOR_REG_ADDR_AC1_LSB);
+    printf("ac1 = %d\n", bmp180_cal_data.ac1);
+
+    /* AC2 */
+    bmp180_read16((uint16_t *)&bmp180_cal_data.ac2, BMP180_SENSOR_REG_ADDR_AC2_MSB, BMP180_SENSOR_REG_ADDR_AC2_LSB);
+    printf("ac2 = %d\n", bmp180_cal_data.ac2);
+
+    /* AC3 */
+    bmp180_read16((uint16_t *)&bmp180_cal_data.ac3, BMP180_SENSOR_REG_ADDR_AC3_MSB, BMP180_SENSOR_REG_ADDR_AC3_LSB);
+    printf("ac3 = %d\n", bmp180_cal_data.ac3);
+
+    /* AC4 */
+    bmp180_read16(&bmp180_cal_data.ac4, BMP180_SENSOR_REG_ADDR_AC4_MSB, BMP180_SENSOR_REG_ADDR_AC4_LSB);
+    printf("ac4 = %u\n", bmp180_cal_data.ac4);
+
+    /* AC5 */
+    bmp180_read16(&bmp180_cal_data.ac5, BMP180_SENSOR_REG_ADDR_AC5_MSB, BMP180_SENSOR_REG_ADDR_AC5_LSB);
+    printf("ac5 = %u\n", bmp180_cal_data.ac5);
+
+    /* AC6 */
+    bmp180_read16(&bmp180_cal_data.ac6, BMP180_SENSOR_REG_ADDR_AC6_MSB, BMP180_SENSOR_REG_ADDR_AC6_LSB);
+    printf("ac6 = %u\n", bmp180_cal_data.ac6);
+
+    /* B1 */
+    bmp180_read16((uint16_t *)&bmp180_cal_data.b1, BMP180_SENSOR_REG_ADDR_B1_MSB, BMP180_SENSOR_REG_ADDR_B1_LSB);
+    printf("b1 = %d\n", bmp180_cal_data.b1);
+
+    /* B2 */
+    bmp180_read16((uint16_t *)&bmp180_cal_data.b2, BMP180_SENSOR_REG_ADDR_B2_MSB, BMP180_SENSOR_REG_ADDR_B2_LSB);
+    printf("b2 = %d\n", bmp180_cal_data.b2);
+
+    /* MB */
+    bmp180_read16((uint16_t *)&bmp180_cal_data.mb, BMP180_SENSOR_REG_ADDR_MB_MSB, BMP180_SENSOR_REG_ADDR_MB_LSB);
+    printf("mb = %d\n", bmp180_cal_data.mb);
+
+    /* MC */
+    bmp180_read16((uint16_t *)&bmp180_cal_data.mc, BMP180_SENSOR_REG_ADDR_MC_MSB, BMP180_SENSOR_REG_ADDR_MC_LSB);
+    printf("mc = %d\n", bmp180_cal_data.mc);
+
+    /* MD */
+    bmp180_read16((uint16_t *)&bmp180_cal_data.md, BMP180_SENSOR_REG_ADDR_MD_MSB, BMP180_SENSOR_REG_ADDR_MD_LSB);
+    printf("md = %d\n", bmp180_cal_data.md);
+}
+
+static void bmp180_read_ut_data(int16_t *ut_data)
+{
+    /* Set slave register address to the control register */
+    ulp_riscv_i2c_master_set_slave_reg_addr(BMP180_SENSOR_REG_ADDR_CTRL_REG);
+
+    /* Setup control register to read temperature */
+    uint8_t cmd = BMP180_SENSOR_CMD_READ_TEMPERATURE;
+    ulp_riscv_i2c_master_write_to_device(&cmd, 1);
+
+    /* Wait at least 4.5 milliseconds for the sensor to complete the reading */
+    vTaskDelay(pdMS_TO_TICKS(5));
+
+    /* Read uncompensated temperature data */
+    bmp180_read16((uint16_t *)ut_data, BMP180_SENSOR_REG_ADDR_SENSOR_DATA_MSB, BMP180_SENSOR_REG_ADDR_SENSOR_DATA_LSB);
+}
+
+static int32_t computeb5(int32_t ut_data)
+{
+    int32_t x1 = (ut_data - (int32_t)bmp180_cal_data.ac6) * ((int32_t)bmp180_cal_data.ac5) >> 15;
+    int32_t x2 = ((int32_t)bmp180_cal_data.mc << 11) / (x1 + (int32_t)bmp180_cal_data.md);
+    return x1 + x2;
+}
+
+static int32_t bmp180_calculate_real_temp(int32_t ut_data)
+{
+    int32_t b5 = computeb5(ut_data);
+    int32_t t = (b5 + 8) >> 4;
+
+    return t;
+}
+
+static void bmp180_read_up_data(int32_t *up_data, oss_mode_t oss_mode)
+{
+    uint16_t press_high;
+    uint8_t press_low;
+
+    /* Set slave register address to the control register */
+    ulp_riscv_i2c_master_set_slave_reg_addr(BMP180_SENSOR_REG_ADDR_CTRL_REG);
+
+    /* Setup control register to read pressure */
+    uint8_t cmd = 0;
+    uint8_t wait = 0;
+    switch(oss_mode)
+    {
+        case OSS_0:
+            cmd = BMP180_SENSOR_CMD_READ_PRESSURE_OSS_0;
+            wait = 5; // Wait atleast 4.5 msec
+            break;
+        case OSS_1:
+            cmd = BMP180_SENSOR_CMD_READ_PRESSURE_OSS_1;
+            wait = 8; // Wait atleast 7.5 msec
+            break;
+        case OSS_2:
+            cmd = BMP180_SENSOR_CMD_READ_PRESSURE_OSS_2;
+            wait = 14; // Wait atleast 13.5 msec
+            break;
+        case OSS_3:
+            cmd = BMP180_SENSOR_CMD_READ_PRESSURE_OSS_3;
+            wait = 26; // Wait atleast 25.5 msec
+            break;
+    }
+
+    ulp_riscv_i2c_master_write_to_device(&cmd, 1);
+
+    /* Wait for the required amount of time for the sensor to complete the reading */
+    vTaskDelay(pdMS_TO_TICKS(wait));
+
+    /* Read uncompensated temperature data */
+
+    /* Read MSB + LSB */
+    bmp180_read16(&press_high, BMP180_SENSOR_REG_ADDR_SENSOR_DATA_MSB, BMP180_SENSOR_REG_ADDR_SENSOR_DATA_LSB);
+
+    /* Read XLSB */
+    ulp_riscv_i2c_master_set_slave_reg_addr(BMP180_SENSOR_REG_ADDR_SENSOR_DATA_XLSB);
+    ulp_riscv_i2c_master_read_from_device(&press_low, 1);
+
+    *up_data = (((uint32_t)press_high << 8) + (uint32_t)press_low) >> (8 - oss_mode);
+}
+
+static int32_t bmp180_calculate_real_pressure(int32_t up_data, int32_t ut_data, oss_mode_t oss_mode)
+{
+    int32_t p, x1, x2, x3, b3, b5, b6;
+    uint32_t b4, b7;
+
+    b5 = computeb5(ut_data);
+    b6 = b5 - 4000;
+    x1 = (bmp180_cal_data.b2 * ((b6 * b6) >> 12)) >> 11;
+    x2 = (bmp180_cal_data.ac2 * b6) >> 11;
+    x3 = x1 + x2;
+    b3 = (((((int32_t) bmp180_cal_data.ac1) * 4 + x3) << oss_mode) + 2) >> 2;
+    x1 = (bmp180_cal_data.ac3 * b6) >> 13;
+    x2 = (bmp180_cal_data.b1 * ((b6 * b6) >> 12)) >> 16;
+    x3 = ((x1 + x2) + 2) >> 2;
+    b4 = (bmp180_cal_data.ac4 * (uint32_t) (x3 + 32768)) >> 15;
+    b7 = ((uint32_t) (up_data - b3) * (50000 >> oss_mode));
+
+    if (b7 < 0x80000000) {
+        p = (b7 << 1) / b4;
+    } else {
+        p = (b7 / b4) << 1;
+    }
+
+    x1 = (p >> 8) * (p >> 8);
+    x1 = (x1 * 3038) >> 16;
+    x2 = (-7357 * p) >> 16;
+    p = p + ((x1 + x2 + 3791) >> 4);
+
+    return p;
+}
+
+static void init_ulp_program(void)
+{
+    esp_err_t err = ulp_riscv_load_binary(ulp_main_bin_start, (ulp_main_bin_end - ulp_main_bin_start));
+    ESP_ERROR_CHECK(err);
+
+    /* The first argument is the period index, which is not used by the ULP-RISC-V timer
+     * The second argument is the period in microseconds, which gives a wakeup time period of: 20ms
+     */
+    ulp_set_wakeup_period(0, 20000);
+
+    /* Start the program */
+    err = ulp_riscv_run();
+    ESP_ERROR_CHECK(err);
+}

+ 9 - 0
examples/system/ulp_riscv/i2c/sdkconfig.defaults

@@ -0,0 +1,9 @@
+# Enable ULP
+CONFIG_ULP_COPROC_ENABLED=y
+CONFIG_ULP_COPROC_RISCV=y
+CONFIG_ULP_COPROC_RESERVE_MEM=4096
+# Set log level to Warning to produce clean output
+CONFIG_BOOTLOADER_LOG_LEVEL_WARN=y
+CONFIG_BOOTLOADER_LOG_LEVEL=2
+CONFIG_LOG_DEFAULT_LEVEL_WARN=y
+CONFIG_LOG_DEFAULT_LEVEL=2