kopia lustrzana https://github.com/espressif/esp-idf
154 wiersze
6.0 KiB
C
154 wiersze
6.0 KiB
C
/*
|
|
* 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/lp_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_periph_set_clk_src(LP_PERIPH_I2C0_MODULE, (soc_module_clk_t)source_clk);
|
|
|
|
/* Configure LP I2C timing paramters. source_clk is ignored for LP_I2C in this call */
|
|
i2c_hal_set_bus_timing(&i2c_hal, cfg->i2c_timing_cfg.clk_speed_hz, (i2c_clock_source_t)source_clk, 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 */
|
|
lp_periph_module_enable(LP_PERIPH_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_master_set_filter(i2c_hal.dev, LP_I2C_FILTER_CYC_NUM_DEF);
|
|
|
|
/* Configure the I2C master to send a NACK when the Rx FIFO count is full */
|
|
i2c_ll_master_rx_full_ack_level(i2c_hal.dev, 1);
|
|
|
|
/* Synchronize the config register values to the LP I2C peripheral clock */
|
|
i2c_ll_update(i2c_hal.dev);
|
|
|
|
return ESP_OK;
|
|
}
|