lp-i2c: Added support for LP I2C peripheral to LP core

This commit adds support for the LP I2C peripheral driver to be used by
the LP core. An example is also added to demonstrate the usage of the LP
I2C peripheral from the LP core.
This commit is contained in:
Sudeep Mohanty
2023-03-20 16:08:38 +01:00
parent 224430f6b4
commit ec742abb25
27 changed files with 1246 additions and 198 deletions

View File

@@ -0,0 +1,77 @@
/*
* SPDX-FileCopyrightText: 2023 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/i2c_types.h"
#include "hal/gpio_types.h"
#include "esp_err.h"
typedef struct {
gpio_num_t sda_io_num; // GPIO pin for SDA signal. Only GPIO#6 can be used as the SDA pin.
gpio_num_t scl_io_num; // GPIO pin for SCL signal. Only GPIO#7 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.
} lp_core_i2c_pin_cfg_t;
typedef struct {
uint32_t clk_speed_hz; // LP I2C clock speed for master mode
} lp_core_i2c_timing_cfg_t;
typedef struct {
lp_core_i2c_pin_cfg_t i2c_pin_cfg; // LP I2C pin configuration
lp_core_i2c_timing_cfg_t i2c_timing_cfg; // LP I2C timing configuration
soc_periph_lp_i2c_clk_src_t i2c_src_clk; // LP I2C source clock type
} lp_core_i2c_cfg_t;
/* Default LP I2C GPIO settings */
#define LP_I2C_DEFAULT_GPIO_CONFIG() \
.i2c_pin_cfg.sda_io_num = GPIO_NUM_6, \
.i2c_pin_cfg.scl_io_num = GPIO_NUM_7, \
.i2c_pin_cfg.sda_pullup_en = true, \
.i2c_pin_cfg.scl_pullup_en = true, \
/* LP I2C fast mode config. Max SCL freq of 400 KHz. */
#define LP_I2C_FAST_MODE_CONFIG() \
.i2c_timing_cfg.clk_speed_hz = 400000, \
/* LP I2C standard mode config. Max SCL freq of 100 KHz. */
#define LP_I2C_STANDARD_MODE_CONFIG() \
.i2c_timing_cfg.clk_speed_hz = 100000, \
#define LP_I2C_DEFAULT_SRC_CLK() \
.i2c_src_clk = LP_I2C_SCLK_LP_FAST, \
/* Default LP I2C GPIO settings and timing parametes */
#define LP_CORE_I2C_DEFAULT_CONFIG() \
{ \
LP_I2C_DEFAULT_GPIO_CONFIG() \
LP_I2C_FAST_MODE_CONFIG() \
LP_I2C_DEFAULT_SRC_CLK() \
}
/**
* @brief Initialize and configure the LP I2C for use by the LP core
* Currently LP I2C can only be used in master mode
*
* @param cfg Configuration parameters
* @return esp_err_t ESP_OK when successful
*
* @note The internal pull-up resistors for SDA and SCL pins, if enabled, will
* provide a weak pull-up value of about 30-50 kOhm. Users are adviced to enable
* external pull-ups for better performance at higher SCL frequencies.
*/
esp_err_t lp_core_i2c_master_init(i2c_port_t lp_i2c_num, const lp_core_i2c_cfg_t *cfg);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,104 @@
/*
* SPDX-FileCopyrightText: 2023 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/i2c_types.h"
#include "esp_err.h"
/**
* @brief Read from I2C device
*
* @note The LP I2C must have been initialized from the HP core using the lp_core_i2c_master_init() API
* before invoking this API.
*
* @param lp_i2c_num LP I2C port number
* @param device_addr I2C device address (7-bit)
* @param data_rd Buffer to hold data to be read
* @param size Size of data to be read in bytes
* @param timeout Operation timeout in CPU cycles. Set to -1 to wait forever.
*
* @return esp_err_t ESP_OK when successful
*
* @note the LP I2C does not support 10-bit I2C device addresses.
* @note the LP I2C port number is ignored at the moment.
*/
esp_err_t lp_core_i2c_master_read_from_device(i2c_port_t lp_i2c_num, uint16_t device_addr,
uint8_t *data_rs, size_t size,
int32_t ticks_to_wait);
/**
* @brief Write to I2C device
*
* @note The LP I2C must have been initialized from the HP core using the lp_core_i2c_master_init() API
* before invoking this API.
*
* @param lp_i2c_num LP I2C port number
* @param device_addr I2C device address (7-bit)
* @param data_wr Buffer which holds the data to be written
* @param size Size of data to be written in bytes
* @param timeout Operation timeout in CPU cycles. Set to -1 to wait forever.
*
* @return esp_err_t ESP_OK when successful
*
* @note the LP I2C does not support 10-bit I2C device addresses.
* @note the LP I2C port number is ignored at the moment.
*/
esp_err_t lp_core_i2c_master_write_to_device(i2c_port_t lp_i2c_num, uint16_t device_addr,
const uint8_t *data_wr, size_t size,
int32_t ticks_to_wait);
/**
* @brief Write to and then read from an I2C device in a single transaction
*
* @note The LP I2C must have been initialized from the HP core using the lp_core_i2c_master_init() API
* before invoking this API.
*
* @param lp_i2c_num LP I2C port number
* @param device_addr I2C device address (7-bit)
* @param data_wr Buffer which holds the data to be written
* @param write_size Size of data to be written in bytes
* @param data_rd Buffer to hold data to be read
* @param read_size Size of data to be read in bytes
* @param timeout Operation timeout in CPU cycles. Set to -1 to wait forever.
*
* @return esp_err_t ESP_OK when successful
*
* @note the LP I2C does not support 10-bit I2C device addresses.
* @note the LP I2C port number is ignored at the moment.
*/
esp_err_t lp_core_i2c_master_write_read_device(i2c_port_t lp_i2c_num, uint16_t device_addr,
const uint8_t *data_wr, size_t write_size,
uint8_t *data_rd, size_t read_size,
int32_t ticks_to_wait);
/**
* @brief Enable or disable ACK checking by the LP_I2C controller during write operations
*
* When ACK checking is enabled, the hardware will check the ACK/NACK level received during write
* operations against the expected ACK/NACK level. If the received ACK/NACK level does not match the
* expected ACK/NACK level then the hardware will generate the I2C_NACK_INT and a STOP condition
* will be generated to stop the data transfer.
*
* @note ACK checking is enabled by default
*
* @param lp_i2c_num LP I2C port number
* @param ack_check_en true: enable ACK check
* false: disable ACK check
*
* @note the LP I2C port number is ignored at the moment.
*/
void lp_core_i2c_master_set_ack_check_en(i2c_port_t lp_i2c_num, bool ack_check_en);
#ifdef __cplusplus
}
#endif

View File

@@ -23,7 +23,6 @@ extern "C" {
*/
void ulp_lp_core_wakeup_main_processor(void);
/**
* @brief Makes the co-processor busy wait for a certain number of microseconds
*
@@ -31,6 +30,12 @@ void ulp_lp_core_wakeup_main_processor(void);
*/
void ulp_lp_core_delay_us(uint32_t us);
/**
* @brief Makes the co-processor busy wait for a certain number of cycles
*
* @param cycles Number of cycles to busy-wait for
*/
void ulp_lp_core_delay_cycles(uint32_t cycles);
/**
* @brief Finishes the ULP program and powers down the ULP

View File

@@ -0,0 +1,479 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "ulp_lp_core_i2c.h"
#include "ulp_lp_core_utils.h"
#include "soc/lp_i2c_reg.h"
#include "soc/i2c_struct.h"
#include "hal/i2c_ll.h"
#define LP_I2C_FIFO_LEN SOC_LP_I2C_FIFO_LEN
#define LP_I2C_READ_MODE I2C_MASTER_READ
#define LP_I2C_WRITE_MODE I2C_MASTER_WRITE
#define LP_I2C_ACK I2C_MASTER_ACK
#define LP_I2C_NACK I2C_MASTER_NACK
#define MIN(x, y) (((x) < (y)) ? (x) : (y))
/* I2C LL context */
i2c_dev_t *dev = I2C_LL_GET_HW(LP_I2C_NUM_0);
/* ACK check enable control variable. Enabled by default */
static bool s_ack_check_en = true;
/*
* The LP I2C controller uses the LP I2C HW 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 lp_core_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 (cmd_idx >= sizeof(dev->command)) {
/* We only have limited HW command registers.
* Although unlikely, make sure that we do not write to an out of bounds index.
*/
return;
}
/* Form new command */
i2c_ll_hw_cmd_t hw_cmd = {
.done = 0, // CMD Done
.op_code = op_code, // Opcode
.ack_val = ack_val, // ACK bit sent by I2C controller during READ.
// Ignored during RSTART, STOP, END and WRITE cmds.
.ack_exp = ack_expected, // ACK bit expected by I2C controller during WRITE.
// Ignored during RSTART, STOP, END and READ cmds.
.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.
.byte_num = byte_num, // Byte Num
};
/* Write new command to cmd register */
i2c_ll_write_cmd_reg(dev, hw_cmd, cmd_idx);
}
static inline esp_err_t lp_core_i2c_wait_for_interrupt(uint32_t intr_mask, int32_t ticks_to_wait)
{
uint32_t intr_status = 0;
uint32_t to = 0;
while (1) {
i2c_ll_get_intr_mask(dev, &intr_status);
if (intr_status & intr_mask) {
if (intr_status & LP_I2C_NACK_INT_ST) {
/* The ACK/NACK received during a WRITE operation does not match the expected ACK/NACK level
* Abort and return an error.
*/
i2c_ll_clear_intr_mask(dev, intr_mask);
return ESP_ERR_INVALID_RESPONSE;
} else if (intr_status & LP_I2C_TRANS_COMPLETE_INT_ST_M) {
/* Transaction complete.
* Disable and clear interrupt bits and break
*/
i2c_ll_disable_intr_mask(dev, intr_mask);
i2c_ll_clear_intr_mask(dev, intr_mask);
break;
} else {
/* We received an I2C_END_DETECT_INT.
* This means we are not yet done with the transaction.
* Simply clear the interrupt bit and break.
*/
i2c_ll_clear_intr_mask(dev, intr_mask);
break;
}
break;
}
if (ticks_to_wait > -1) {
/* If the ticks_to_wait value is not -1, keep track of ticks and
* break from the loop once the timeout is reached.
*/
ulp_lp_core_delay_cycles(1);
to++;
if (to >= ticks_to_wait) {
/* Disable and clear interrupt bits */
i2c_ll_disable_intr_mask(dev, intr_mask);
i2c_ll_clear_intr_mask(dev, intr_mask);
return ESP_ERR_TIMEOUT;
}
}
}
/* We reach here only if we are in a good state */
return ESP_OK;
}
static inline void lp_core_i2c_config_device_addr(uint32_t cmd_idx, uint16_t device_addr, uint32_t rw_mode, uint8_t *addr_len)
{
uint8_t data_byte = 0;
uint8_t data_len = 0;
/* 7-bit addressing mode. We do not support 10-bit addressing mode yet (IDF-7364) */
// Write the device address + R/W mode in the first Tx FIFO slot
data_byte = (uint8_t)(((device_addr & 0xFF) << 1) | (rw_mode << 0));
i2c_ll_write_txfifo(dev, &data_byte, 1);
data_len++;
/* Update the HW command register. Expect an ACK from the device */
lp_core_i2c_format_cmd(cmd_idx, I2C_LL_CMD_WRITE, 0, LP_I2C_ACK, s_ack_check_en, data_len);
/* Return the address length in bytes */
*addr_len = data_len;
}
void lp_core_i2c_master_set_ack_check_en(i2c_port_t lp_i2c_num, bool ack_check_en)
{
(void)lp_i2c_num;
s_ack_check_en = ack_check_en;
}
esp_err_t lp_core_i2c_master_read_from_device(i2c_port_t lp_i2c_num, uint16_t device_addr,
uint8_t *data_rd, size_t size,
int32_t ticks_to_wait)
{
(void)lp_i2c_num;
esp_err_t ret = ESP_OK;
uint32_t cmd_idx = 0;
if (size == 0) {
// Quietly return
return ESP_OK;
} else if (size > UINT8_MAX) {
// HW register only has an 8-bit byte-num field
return ESP_ERR_INVALID_SIZE;
}
/* Execute RSTART command to send the START bit */
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_RESTART, 0, 0, 0, 0);
/* Write device addr and update the HW command register */
uint8_t addr_len = 0;
lp_core_i2c_config_device_addr(cmd_idx++, device_addr, LP_I2C_READ_MODE, &addr_len);
/* Enable trans complete interrupt and end detect interrupt for read/write operation */
uint32_t intr_mask = (1 << LP_I2C_TRANS_COMPLETE_INT_ST_S) | (1 << LP_I2C_END_DETECT_INT_ST_S);
i2c_ll_enable_intr_mask(dev, intr_mask);
/* Read data */
uint32_t fifo_size = 0;
uint32_t data_idx = 0;
int32_t remaining_bytes = size;
/* The data is received in sequential slots of the Rx FIFO.
* We must account for FIFO wraparound in case the length of data being received is greater than LP_I2C_FIFO_LEN.
*/
while (remaining_bytes > 0) {
/* Select the amount of data that fits in the Rx FIFO */
fifo_size = MIN(remaining_bytes, LP_I2C_FIFO_LEN);
/* Update the number of bytes remaining to be read */
remaining_bytes -= fifo_size;
/* Update HW command register to read bytes */
if (fifo_size == 1) {
/* Read 1 byte and send NACK */
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_READ, LP_I2C_NACK, 0, 0, 1);
/* STOP */
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_STOP, 0, 0, 0, 0);
} else if ((fifo_size > 1) && (remaining_bytes == 0)) {
/* This means it is the last transaction.
* Read fifo_size - 1 bytes and send ACKs
*/
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_READ, LP_I2C_ACK, 0, 0, fifo_size - 1);
/* Read last byte and send NACK */
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_READ, LP_I2C_NACK, 0, 0, 1);
/* STOP */
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_STOP, 0, 0, 0, 0);
} else {
/* This means we have to read data more than what can fit in the Rx FIFO.
* Read fifo_size bytes and send ACKs
*/
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_READ, LP_I2C_ACK, 0, 0, fifo_size);
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_END, 0, 0, 0, 0);
cmd_idx = 0;
}
/* Initiate I2C transfer */
i2c_ll_update(dev);
i2c_ll_trans_start(dev);
/* Wait for the transfer to complete */
ret = lp_core_i2c_wait_for_interrupt(intr_mask, ticks_to_wait);
if (ret != ESP_OK) {
/* Transaction error. Abort. */
return ret;
}
/* Read Rx FIFO */
i2c_ll_read_rxfifo(dev, &data_rd[data_idx], fifo_size);
/* Update data_idx */
data_idx += fifo_size;
}
return ret;
}
esp_err_t lp_core_i2c_master_write_to_device(i2c_port_t lp_i2c_num, uint16_t device_addr,
const uint8_t *data_wr, size_t size,
int32_t ticks_to_wait)
{
(void)lp_i2c_num;
esp_err_t ret = ESP_OK;
uint32_t cmd_idx = 0;
if (size == 0) {
// Quietly return
return ESP_OK;
} else if (size > UINT8_MAX) {
// HW register only has an 8-bit byte-num field
return ESP_ERR_INVALID_SIZE;
}
/* If SCL is busy, reset the Master FSM */
if (i2c_ll_is_bus_busy(dev)) {
i2c_ll_master_fsm_rst(dev);
}
/* Reset the Tx and Rx FIFOs */
i2c_ll_txfifo_rst(dev);
i2c_ll_rxfifo_rst(dev);
/* Execute RSTART command to send the START bit */
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_RESTART, 0, 0, 0, 0);
/* Write device addr and update the HW command register */
uint8_t addr_len = 0;
lp_core_i2c_config_device_addr(cmd_idx++, device_addr, LP_I2C_WRITE_MODE, &addr_len);
/* Enable trans complete interrupt and end detect interrupt for read/write operation */
uint32_t intr_mask = (1 << LP_I2C_TRANS_COMPLETE_INT_ST_S) | (1 << LP_I2C_END_DETECT_INT_ST_S);
if (s_ack_check_en) {
/* Enable LP_I2C_NACK_INT to check for ACK errors */
intr_mask |= (1 << LP_I2C_NACK_INT_ST_S);
}
i2c_ll_enable_intr_mask(dev, intr_mask);
/* Write data */
uint32_t fifo_available = LP_I2C_FIFO_LEN - addr_len; // Initially, 1 or 2 fifo slots are taken by the device address
uint32_t fifo_size = 0;
uint32_t data_idx = 0;
int32_t remaining_bytes = size;
/* The data to be sent must occupy sequential slots of the Tx FIFO.
* We must account for FIFO wraparound in case the length of data being sent is greater than LP_I2C_FIFO_LEN.
*/
while (remaining_bytes > 0) {
/* Select the amount of data that fits in the Tx FIFO */
fifo_size = MIN(remaining_bytes, fifo_available);
/* Update the number of bytes remaining to be sent */
remaining_bytes -= fifo_size;
/* Write data to the Tx FIFO and update the HW command register. Expect ACKs from the device */
i2c_ll_write_txfifo(dev, &data_wr[data_idx], fifo_size);
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_WRITE, 0, LP_I2C_ACK, s_ack_check_en, fifo_size);
if (remaining_bytes == 0) {
/* This means it is the last transaction. Insert a Stop command. */
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_STOP, 0, 0, 0, 0);
} else {
/* This means we have to send more than what can fit in the Tx FIFO. Insert an End command. */
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_END, 0, 0, 0, 0);
cmd_idx = 0;
}
/* Initiate I2C transfer */
i2c_ll_update(dev);
i2c_ll_trans_start(dev);
/* Wait for the transfer to complete */
ret = lp_core_i2c_wait_for_interrupt(intr_mask, ticks_to_wait);
if (ret != ESP_OK) {
/* Transaction error. Abort. */
return ret;
}
/* Update data_idx */
data_idx += fifo_size;
/* We now have the full fifo available for writing */
fifo_available = LP_I2C_FIFO_LEN;
}
return ret;
}
esp_err_t lp_core_i2c_master_write_read_device(i2c_port_t lp_i2c_num, uint16_t device_addr,
const uint8_t *data_wr, size_t write_size,
uint8_t *data_rd, size_t read_size,
int32_t ticks_to_wait)
{
(void)lp_i2c_num;
esp_err_t ret = ESP_OK;
uint32_t cmd_idx = 0;
if ((write_size == 0) || (read_size == 0)) {
// Quietly return
return ESP_OK;
} else if ((write_size > UINT8_MAX) || (read_size > UINT8_MAX)) {
// HW register only has an 8-bit byte-num field
return ESP_ERR_INVALID_SIZE;
}
/* If SCL is busy, reset the Master FSM */
if (i2c_ll_is_bus_busy(dev)) {
i2c_ll_master_fsm_rst(dev);
}
/* Reset the Tx and Rx FIFOs */
i2c_ll_txfifo_rst(dev);
i2c_ll_rxfifo_rst(dev);
/* Enable trans complete interrupt and end detect interrupt for read/write operation */
uint32_t intr_mask = (1 << LP_I2C_TRANS_COMPLETE_INT_ST_S) | (1 << LP_I2C_END_DETECT_INT_ST_S);
if (s_ack_check_en) {
/* Enable LP_I2C_NACK_INT to check for ACK errors */
intr_mask |= (1 << LP_I2C_NACK_INT_ST_S);
}
i2c_ll_enable_intr_mask(dev, intr_mask);
/* Execute RSTART command to send the START bit */
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_RESTART, 0, 0, 0, 0);
/* Write device addr and update the HW command register */
uint8_t addr_len = 0;
lp_core_i2c_config_device_addr(cmd_idx++, device_addr, LP_I2C_WRITE_MODE, &addr_len);
/* Write data */
uint32_t fifo_available = LP_I2C_FIFO_LEN - addr_len; // Initially, 1 or 2 fifo slots are taken by the device address
uint32_t fifo_size = 0;
uint32_t data_idx = 0;
int32_t remaining_bytes = write_size;
/* The data to be sent must occupy sequential slots of the Tx FIFO.
* We must account for FIFO wraparound in case the length of data being sent is greater than LP_I2C_FIFO_LEN.
*/
while (remaining_bytes > 0) {
/* Select the amount of data that fits in the Tx FIFO */
fifo_size = MIN(remaining_bytes, fifo_available);
/* Update the number of bytes remaining to be sent */
remaining_bytes -= fifo_size;
/* Write data to the Tx FIFO and update the HW command register. Expect ACKs from the device */
i2c_ll_write_txfifo(dev, &data_wr[data_idx], fifo_size);
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_WRITE, 0, LP_I2C_ACK, s_ack_check_en, fifo_size);
if (remaining_bytes) {
/* This means we have to send more than what can fit in the Tx FIFO. Insert an End command. */
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_END, 0, 0, 0, 0);
cmd_idx = 0;
}
/* Initiate I2C transfer */
i2c_ll_update(dev);
i2c_ll_trans_start(dev);
/* Wait for the transfer to complete */
ret = lp_core_i2c_wait_for_interrupt(intr_mask, ticks_to_wait);
if (ret != ESP_OK) {
/* Transaction error. Abort. */
return ret;
}
/* Update data_idx */
data_idx += fifo_size;
/* We now have the full fifo available for writing */
fifo_available = LP_I2C_FIFO_LEN;
}
/* Reset command index */
cmd_idx = 0;
/* Execute RSTART command again to send a START condition for the read operation */
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_RESTART, 0, 0, 0, 0);
/* Write device addr again in read mode */
lp_core_i2c_config_device_addr(cmd_idx++, device_addr, LP_I2C_READ_MODE, &addr_len);
/* Read data */
fifo_size = 0;
data_idx = 0;
remaining_bytes = read_size;
/* The data is received in sequential slots of the Rx FIFO.
* We must account for FIFO wraparound in case the length of data being received is greater than LP_I2C_FIFO_LEN.
*/
while (remaining_bytes > 0) {
/* Select the amount of data that fits in the Rx FIFO */
fifo_size = MIN(remaining_bytes, LP_I2C_FIFO_LEN);
/* Update the number of bytes remaining to be read */
remaining_bytes -= fifo_size;
/* Update HW command register to read bytes */
if (fifo_size == 1) {
/* Read 1 byte and send NACK */
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_READ, LP_I2C_NACK, 0, 0, 1);
/* STOP */
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_STOP, 0, 0, 0, 0);
} else if ((fifo_size > 1) && (remaining_bytes == 0)) {
/* This means it is the last transaction.
* Read fifo_size - 1 bytes and send ACKs
*/
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_READ, LP_I2C_ACK, 0, 0, fifo_size - 1);
/* Read last byte and send NACK */
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_READ, LP_I2C_NACK, 0, 0, 1);
/* STOP */
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_STOP, 0, 0, 0, 0);
} else {
/* This means we have to read data more than what can fit in the Rx FIFO.
* Read fifo_size bytes and send ACKs
*/
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_READ, LP_I2C_ACK, 0, 0, fifo_size);
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_END, 0, 0, 0, 0);
cmd_idx = 0;
}
/* Initiate I2C transfer */
i2c_ll_update(dev);
i2c_ll_trans_start(dev);
/* Wait for the transfer to complete */
ret = lp_core_i2c_wait_for_interrupt(intr_mask, ticks_to_wait);
if (ret != ESP_OK) {
/* Transaction error. Abort. */
return ret;
}
/* Read Rx FIFO */
i2c_ll_read_rxfifo(dev, &data_rd[data_idx], fifo_size);
/* Update data_idx */
data_idx += fifo_size;
}
return ret;
}

View File

@@ -42,6 +42,21 @@ void ulp_lp_core_delay_us(uint32_t us)
}
}
/**
* @brief Makes the co-processor busy wait for a certain number of cycles
*
* @param cycles Number of cycles to busy-wait for
*/
void ulp_lp_core_delay_cycles(uint32_t cycles)
{
uint32_t start = RV_READ_CSR(mcycle);
uint32_t end = cycles;
while ((RV_READ_CSR(mcycle) - start) < end) {
/* nothing to do */
}
}
void ulp_lp_core_halt(void)
{
REG_SET_FIELD(PMU_LP_CPU_PWR1_REG, PMU_LP_CPU_SLEEP_REQ, 1);

View File

@@ -0,0 +1,150 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "lp_core_i2c.h"
#include "esp_check.h"
#include "hal/i2c_hal.h"
#include "soc/lp_io_struct.h"
#include "driver/rtc_io.h"
#include "soc/rtc_io_channel.h"
#include "esp_private/esp_clk_tree_common.h"
#include "esp_private/periph_ctrl.h"
static const char *LPI2C_TAG = "lp_core_i2c";
#define LP_I2C_FILTER_CYC_NUM_DEF (7)
/* I2C LL context */
i2c_hal_context_t i2c_hal;
/* Use the register structure to access LP_IO module registers */
lp_io_dev_t *lp_io_dev = &LP_IO;
static esp_err_t lp_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 LP IO (RTCIO) pins */
ESP_RETURN_ON_ERROR(!rtc_gpio_is_valid_gpio(sda_io_num), LPI2C_TAG, "LP I2C SDA GPIO invalid");
ESP_RETURN_ON_ERROR(!rtc_gpio_is_valid_gpio(scl_io_num), LPI2C_TAG, "LP I2C SCL GPIO invalid");
/* Verify that the SDA and SCL line belong to the LP IO Mux I2C function group */
if (sda_io_num != RTCIO_GPIO6_CHANNEL) {
ESP_LOGE(LPI2C_TAG, "SDA pin can only be configured as GPIO#6");
return ESP_ERR_INVALID_ARG;
}
if (scl_io_num != RTCIO_GPIO7_CHANNEL) {
ESP_LOGE(LPI2C_TAG, "SCL pin can only be configured as GPIO#7");
return ESP_ERR_INVALID_ARG;
}
return ESP_OK;
}
static esp_err_t lp_i2c_configure_io(gpio_num_t io_num, bool pullup_en)
{
/* Initialize IO Pin */
ESP_RETURN_ON_ERROR(rtc_gpio_init(io_num), LPI2C_TAG, "LP 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), LPI2C_TAG, "LP GPIO Set direction failed for %d", io_num);
/* Disable pulldown on the io pin */
ESP_RETURN_ON_ERROR(rtc_gpio_pulldown_dis(io_num), LPI2C_TAG, "LP 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), LPI2C_TAG, "LP GPIO pullup enable failed for %d", io_num);
} else {
ESP_RETURN_ON_ERROR(rtc_gpio_pullup_dis(io_num), LPI2C_TAG, "LP GPIO pullup disable failed for %d", io_num);
}
return ESP_OK;
}
static esp_err_t lp_i2c_set_pin(const lp_core_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 LP I2C GPIOs are valid */
ESP_RETURN_ON_ERROR(lp_i2c_gpio_is_cfg_valid(sda_io_num, scl_io_num), LPI2C_TAG, "LP I2C GPIO config invalid");
/* Initialize SDA Pin */
ESP_RETURN_ON_ERROR(lp_i2c_configure_io(sda_io_num, sda_pullup_en), LPI2C_TAG, "LP I2C SDA pin config failed");
/* Initialize SCL Pin */
ESP_RETURN_ON_ERROR(lp_i2c_configure_io(scl_io_num, scl_pullup_en), LPI2C_TAG, "LP I2C SCL pin config failed");
/* Select LP I2C function for the SDA Pin */
lp_io_dev->gpio[sda_io_num].mcu_sel = 1;
/* Select LP I2C function for the SCL Pin */
lp_io_dev->gpio[scl_io_num].mcu_sel = 1;
return ESP_OK;
}
static esp_err_t lp_i2c_config_clk(const lp_core_i2c_cfg_t *cfg)
{
esp_err_t ret = ESP_OK;
uint32_t source_freq = 0;
soc_periph_lp_i2c_clk_src_t source_clk = LP_I2C_SCLK_DEFAULT;
/* Check if we have a valid user configured source clock */
soc_periph_lp_i2c_clk_src_t clk_srcs[] = SOC_LP_I2C_CLKS;
for (int i = 0; i < sizeof(clk_srcs)/sizeof(clk_srcs[0]); i++) {
if (clk_srcs[i] == cfg->i2c_src_clk) {
/* Clock source matches. Override the source clock type with the user configured value */
source_clk = cfg->i2c_src_clk;
break;
}
}
/* Fetch the clock source fequency */
ESP_RETURN_ON_ERROR(esp_clk_tree_src_get_freq_hz(source_clk, ESP_CLK_TREE_SRC_FREQ_PRECISION_APPROX, &source_freq), LPI2C_TAG, "Invalid LP I2C source clock selected");
/* Verify that the I2C_SCLK operates at a frequency 20 times larger than the requested SCL bus frequency */
ESP_RETURN_ON_FALSE(cfg->i2c_timing_cfg.clk_speed_hz * 20 <= source_freq, ESP_ERR_INVALID_ARG, LPI2C_TAG, "I2C_SCLK frequency (%"PRId32") should operate at a frequency at least 20 times larger than the I2C SCL bus frequency (%"PRId32")", source_freq, cfg->i2c_timing_cfg.clk_speed_hz);
/* Set LP I2C source clock */
lp_i2c_ll_set_source_clk(i2c_hal.dev, source_clk);
/* Configure LP I2C timing paramters. source_clk is ignored for LP_I2C in this call */
i2c_hal_set_bus_timing(&i2c_hal, (i2c_clock_source_t)source_clk, cfg->i2c_timing_cfg.clk_speed_hz, source_freq);
return ret;
}
esp_err_t lp_core_i2c_master_init(i2c_port_t lp_i2c_num, const lp_core_i2c_cfg_t *cfg)
{
/* Verify LP_I2C port number */
ESP_RETURN_ON_FALSE((lp_i2c_num == LP_I2C_NUM_0), ESP_ERR_INVALID_ARG, LPI2C_TAG, "LP I2C port number incorrect");
/* Verify that the input cfg param is valid */
ESP_RETURN_ON_FALSE(cfg, ESP_ERR_INVALID_ARG, LPI2C_TAG, "LP I2C configuration is NULL");
/* Configure LP I2C GPIOs */
ESP_RETURN_ON_ERROR(lp_i2c_set_pin(cfg), LPI2C_TAG, "Failed to configure LP I2C GPIOs");
/* Initialize LP I2C HAL */
i2c_hal_init(&i2c_hal, lp_i2c_num);
/* Enable LP I2C controller clock */
periph_module_enable(PERIPH_LP_I2C0_MODULE);
/* Initialize LP I2C Master mode */
i2c_hal_master_init(&i2c_hal);
/* Configure LP I2C clock and timing paramters */
ESP_RETURN_ON_ERROR(lp_i2c_config_clk(cfg), LPI2C_TAG, "Failed to configure LP I2C source clock");
/* Enable SDA and SCL filtering. This configuration matches the HP I2C filter config */
i2c_ll_set_filter(i2c_hal.dev, LP_I2C_FILTER_CYC_NUM_DEF);
/* Synchronize the config register values to the LP I2C peripheral clock */
i2c_ll_update(i2c_hal.dev);
return ESP_OK;
}