feat(lp_i2s): lp_i2s driver

This commit is contained in:
Armando 2024-07-03 15:39:23 +08:00
parent b5ab82ce3c
commit ee8933f651
29 changed files with 1494 additions and 59 deletions

View File

@ -101,7 +101,7 @@ extern portMUX_TYPE rtc_spinlock; //TODO: Will be placed in the appropriate posi
#define adc_dma_disable_intr(adc_dma) i2s_ll_enable_intr(s_adc_digi_ctx->adc_i2s_dev, ADC_DMA_INTR_MASK, false);
#define adc_dma_deinit(adc_dma) do { \
esp_intr_free(s_adc_digi_ctx->intr_hdl); \
i2s_platform_release_occupation(ADC_DMA_I2S_HOST); \
i2s_platform_release_occupation(I2S_CTLR_HP, ADC_DMA_I2S_HOST); \
} while (0)
#endif
@ -328,7 +328,7 @@ esp_err_t adc_digi_initialize(const adc_digi_init_config_t *init_config)
s_adc_digi_ctx->adc_spi_dev = SPI_LL_GET_HW(ADC_DMA_SPI_HOST);
#elif CONFIG_IDF_TARGET_ESP32
//ADC utilises I2S0 DMA on ESP32
ret = i2s_platform_acquire_occupation(ADC_DMA_I2S_HOST, "adc");
ret = i2s_platform_acquire_occupation(I2S_CTLR_HP, ADC_DMA_I2S_HOST, "adc");
if (ret != ESP_OK) {
ret = ESP_ERR_NOT_FOUND;
goto cleanup;

View File

@ -1637,7 +1637,7 @@ esp_err_t i2s_driver_uninstall(i2s_port_t i2s_num)
}
#endif
/* Disable module clock */
i2s_platform_release_occupation(i2s_num);
i2s_platform_release_occupation(I2S_CTLR_HP, i2s_num);
free(obj);
p_i2s[i2s_num] = NULL;
return ESP_OK;
@ -1656,7 +1656,7 @@ esp_err_t i2s_driver_install(i2s_port_t i2s_num, const i2s_config_t *i2s_config,
/* Step 2: Allocate driver object and register to platform */
i2s_obj_t *i2s_obj = calloc(1, sizeof(i2s_obj_t));
ESP_RETURN_ON_FALSE(i2s_obj, ESP_ERR_NO_MEM, TAG, "no mem for I2S driver");
if (i2s_platform_acquire_occupation(i2s_num, "i2s_legacy") != ESP_OK) {
if (i2s_platform_acquire_occupation(I2S_CTLR_HP, i2s_num, "i2s_legacy") != ESP_OK) {
free(i2s_obj);
ESP_LOGE(TAG, "register I2S object to platform failed");
return ESP_ERR_INVALID_STATE;

View File

@ -51,7 +51,7 @@ esp_err_t adc_dma_init(adc_dma_t *adc_dma)
{
esp_err_t ret = ESP_OK;
//ADC utilises I2S0 DMA on ESP32
ret = i2s_platform_acquire_occupation(ADC_DMA_I2S_HOST, "adc");
ret = i2s_platform_acquire_occupation(I2S_CTLR_HP, ADC_DMA_I2S_HOST, "adc");
if (ret != ESP_OK) {
return ESP_ERR_NOT_FOUND;
}
@ -62,7 +62,7 @@ esp_err_t adc_dma_init(adc_dma_t *adc_dma)
esp_err_t adc_dma_deinit(adc_dma_t adc_dma)
{
esp_intr_free(adc_dma.dma_intr_hdl);
i2s_platform_release_occupation(ADC_DMA_I2S_HOST);
i2s_platform_release_occupation(I2S_CTLR_HP, ADC_DMA_I2S_HOST);
return ESP_OK;
}

View File

@ -113,7 +113,7 @@ esp_err_t dac_dma_periph_init(uint32_t freq_hz, bool is_alternate, bool is_apll)
#endif
esp_err_t ret = ESP_OK;
/* Acquire DMA peripheral */
ESP_RETURN_ON_ERROR(i2s_platform_acquire_occupation(DAC_DMA_PERIPH_I2S_NUM, "dac_dma"), TAG, "Failed to acquire DAC DMA peripheral");
ESP_RETURN_ON_ERROR(i2s_platform_acquire_occupation(I2S_CTLR_HP, DAC_DMA_PERIPH_I2S_NUM, "dac_dma"), TAG, "Failed to acquire DAC DMA peripheral");
/* Allocate DAC DMA peripheral object */
s_ddp = (dac_dma_periph_i2s_t *)heap_caps_calloc(1, sizeof(dac_dma_periph_i2s_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
ESP_GOTO_ON_FALSE(s_ddp, ESP_ERR_NO_MEM, err, TAG, "No memory for DAC DMA object");
@ -150,7 +150,7 @@ err:
esp_err_t dac_dma_periph_deinit(void)
{
ESP_RETURN_ON_FALSE(s_ddp->intr_handle == NULL, ESP_ERR_INVALID_STATE, TAG, "The interrupt is not deregistered yet");
ESP_RETURN_ON_ERROR(i2s_platform_release_occupation(DAC_DMA_PERIPH_I2S_NUM), TAG, "Failed to release DAC DMA peripheral");
ESP_RETURN_ON_ERROR(i2s_platform_release_occupation(I2S_CTLR_HP, DAC_DMA_PERIPH_I2S_NUM), TAG, "Failed to release DAC DMA peripheral");
i2s_ll_enable_intr(s_ddp->periph_dev, I2S_LL_EVENT_TX_EOF | I2S_LL_EVENT_TX_TEOF, false);
if (s_ddp) {
if (s_ddp->use_apll) {

View File

@ -100,13 +100,13 @@ TEST_CASE("DAC_API_basic_logic_test", "[dac]")
};
/* DMA peripheral availability test */
#if CONFIG_IDF_TARGET_ESP32
TEST_ESP_OK(i2s_platform_acquire_occupation(0, "dac_test"));
TEST_ESP_OK(i2s_platform_acquire_occupation(I2S_CTLR_HP, 0, "dac_test"));
#elif CONFIG_IDF_TARGET_ESP32S2
TEST_ASSERT(spicommon_periph_claim(SPI3_HOST, "dac_test"));
#endif
TEST_ASSERT(dac_continuous_new_channels(&cont_cfg, &cont_handle) == ESP_ERR_NOT_FOUND);
#if CONFIG_IDF_TARGET_ESP32
TEST_ESP_OK(i2s_platform_release_occupation(0));
TEST_ESP_OK(i2s_platform_release_occupation(I2S_CTLR_HP, 0));
#elif CONFIG_IDF_TARGET_ESP32S2
TEST_ASSERT(spicommon_periph_free(SPI3_HOST));
#endif

View File

@ -10,7 +10,6 @@ set(include "include")
# I2S related source files
if(CONFIG_SOC_I2S_SUPPORTED)
list(APPEND srcs "i2s_common.c"
"i2s_platform.c"
"i2s_std.c")
if(CONFIG_SOC_I2S_SUPPORTS_PDM)
list(APPEND srcs "i2s_pdm.c")
@ -23,6 +22,14 @@ if(CONFIG_SOC_I2S_SUPPORTED)
endif()
endif()
if(CONFIG_SOC_I2S_SUPPORTED OR CONFIG_SOC_LP_I2S_SUPPORTED)
list(APPEND srcs "i2s_platform.c")
endif()
if(CONFIG_SOC_LP_I2S_SUPPORTED)
list(APPEND srcs "lp_i2s.c" "lp_i2s_std.c" "lp_i2s_pdm.c")
endif()
idf_component_register(SRCS ${srcs}
INCLUDE_DIRS ${include}
PRIV_REQUIRES esp_driver_gpio esp_pm esp_mm

View File

@ -164,7 +164,7 @@ static esp_err_t i2s_destroy_controller_obj(i2s_controller_t **i2s_obj)
#endif
free(*i2s_obj);
*i2s_obj = NULL;
return i2s_platform_release_occupation(id);
return i2s_platform_release_occupation(I2S_CTLR_HP, id);
}
/**
@ -196,7 +196,7 @@ static i2s_controller_t *i2s_acquire_controller_obj(int id)
i2s_controller_t *i2s_obj = NULL;
/* Try to occupy this i2s controller */
if (i2s_platform_acquire_occupation(id, "i2s_driver") == ESP_OK) {
if (i2s_platform_acquire_occupation(I2S_CTLR_HP, id, "i2s_driver") == ESP_OK) {
portENTER_CRITICAL(&g_i2s.spinlock);
i2s_obj = pre_alloc;
g_i2s.controller[id] = i2s_obj;

View File

@ -1,11 +1,12 @@
/*
* SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "esp_check.h"
#include "i2s_private.h"
#include "esp_private/i2s_platform.h"
static const char *TAG = "i2s_platform";
@ -17,6 +18,10 @@ i2s_platform_t g_i2s = {
.spinlock = (portMUX_TYPE)portMUX_INITIALIZER_UNLOCKED,
.controller[0 ...(SOC_I2S_NUM - 1)] = NULL, // groups will be lazy installed
.comp_name[0 ...(SOC_I2S_NUM - 1)] = NULL,
#if SOC_LP_I2S_SUPPORTED
.lp_controller[0 ...(SOC_LP_I2S_NUM - 1)] = NULL,
.lp_comp_name[0 ...(SOC_LP_I2S_NUM - 1)] = NULL,
#endif
};
/*---------------------------------------------------------------------------
@ -25,47 +30,87 @@ i2s_platform_t g_i2s = {
Scope: This file and ADC/DAC/LCD driver
----------------------------------------------------------------------------*/
esp_err_t i2s_platform_acquire_occupation(int id, const char *comp_name)
esp_err_t i2s_platform_acquire_occupation(i2s_ctlr_t type, int id, const char *comp_name)
{
esp_err_t ret = ESP_OK;
const char *occupied_comp = NULL;
ESP_RETURN_ON_FALSE(id < SOC_I2S_NUM, ESP_ERR_INVALID_ARG, TAG, "invalid i2s port id");
portENTER_CRITICAL(&g_i2s.spinlock);
if ((!g_i2s.controller[id]) && (g_i2s.comp_name[id] == NULL)) {
g_i2s.comp_name[id] = comp_name;
/* Enable module clock */
I2S_RCC_ATOMIC() {
i2s_ll_enable_bus_clock(id, true);
i2s_ll_reset_register(id);
i2s_ll_enable_core_clock(I2S_LL_GET_HW(id), true);
if (type == I2S_CTLR_HP) {
portENTER_CRITICAL(&g_i2s.spinlock);
if ((!g_i2s.controller[id]) && (g_i2s.comp_name[id] == NULL)) {
g_i2s.comp_name[id] = comp_name;
/* Enable module clock */
I2S_RCC_ATOMIC() {
i2s_ll_enable_bus_clock(id, true);
i2s_ll_reset_register(id);
i2s_ll_enable_core_clock(I2S_LL_GET_HW(id), true);
}
} else {
occupied_comp = g_i2s.comp_name[id];
ret = ESP_ERR_NOT_FOUND;
}
} else {
occupied_comp = g_i2s.comp_name[id];
ret = ESP_ERR_NOT_FOUND;
portEXIT_CRITICAL(&g_i2s.spinlock);
}
portEXIT_CRITICAL(&g_i2s.spinlock);
#if SOC_LP_I2S_SUPPORTED
else {
portENTER_CRITICAL(&g_i2s.spinlock);
if ((!g_i2s.lp_controller[id]) && (g_i2s.lp_comp_name[id] == NULL)) {
g_i2s.lp_comp_name[id] = comp_name;
/* Enable module clock */
I2S_RCC_ATOMIC() {
lp_i2s_ll_enable_module_clock(id, true);
lp_i2s_ll_reset_module_clock(id);
lp_i2s_ll_enable_rx_module_clock(id, true);
}
} else {
occupied_comp = g_i2s.lp_comp_name[id];
ret = ESP_ERR_NOT_FOUND;
}
portEXIT_CRITICAL(&g_i2s.spinlock);
}
#endif
if (occupied_comp != NULL) {
ESP_LOGW(TAG, "i2s controller %d has been occupied by %s", id, occupied_comp);
}
return ret;
}
esp_err_t i2s_platform_release_occupation(int id)
esp_err_t i2s_platform_release_occupation(i2s_ctlr_t type, int id)
{
esp_err_t ret = ESP_OK;
ESP_RETURN_ON_FALSE(id < SOC_I2S_NUM, ESP_ERR_INVALID_ARG, TAG, "invalid i2s port id");
portENTER_CRITICAL(&g_i2s.spinlock);
if (!g_i2s.controller[id]) {
g_i2s.comp_name[id] = NULL;
/* Disable module clock */
I2S_RCC_ATOMIC() {
i2s_ll_enable_bus_clock(id, false);
i2s_ll_enable_core_clock(I2S_LL_GET_HW(id), false);
if (type == I2S_CTLR_HP) {
portENTER_CRITICAL(&g_i2s.spinlock);
if (!g_i2s.controller[id]) {
g_i2s.comp_name[id] = NULL;
/* Disable module clock */
I2S_RCC_ATOMIC() {
i2s_ll_enable_bus_clock(id, false);
i2s_ll_enable_core_clock(I2S_LL_GET_HW(id), false);
}
} else {
ret = ESP_ERR_INVALID_STATE;
}
} else {
ret = ESP_ERR_INVALID_STATE;
portEXIT_CRITICAL(&g_i2s.spinlock);
}
portEXIT_CRITICAL(&g_i2s.spinlock);
#if SOC_LP_I2S_SUPPORTED
else {
portENTER_CRITICAL(&g_i2s.spinlock);
if (!g_i2s.lp_controller[id]) {
g_i2s.lp_comp_name[id] = NULL;
/* Disable module clock */
I2S_RCC_ATOMIC() {
lp_i2s_ll_enable_module_clock(id, false);
lp_i2s_ll_enable_rx_module_clock(id, false);
}
} else {
ret = ESP_ERR_INVALID_STATE;
}
portEXIT_CRITICAL(&g_i2s.spinlock);
}
#endif
return ret;
}

View File

@ -12,6 +12,10 @@
#include "soc/lldesc.h"
#include "soc/soc_caps.h"
#include "hal/i2s_hal.h"
#include "hal/lp_i2s_hal.h"
#if SOC_LP_I2S_SUPPORTED
#include "hal/lp_i2s_ll.h"
#endif
#include "driver/i2s_types.h"
#if CONFIG_IDF_TARGET_ESP32
#include "esp_clock_output.h"
@ -85,6 +89,14 @@ typedef struct {
*/
} i2s_event_callbacks_internal_t;
/**
* @brief LP I2S callbacks
*/
typedef struct {
lp_i2s_callback_t on_thresh_met; ///< Triggered when the received bytes are bigger than `lp_i2s_chan_config_t:threshold`
lp_i2s_callback_t on_request_new_trans; ///< Triggered when a new transaction buffer is needed, when this callback is registered, you don't need to use `lp_i2s_channel_read` to get data, you can get data via this callback asynchronously
} lp_i2s_evt_cbs_internal_t;
/**
* @brief i2s channel level configurations
* @note It performs as channel handle
@ -154,14 +166,46 @@ struct i2s_channel_obj_t {
void (*stop)(i2s_chan_handle_t); /*!< stop tx/rx channel */
};
/**
* @brief lp i2s controller type
*/
typedef struct {
int id; /*!< lp i2s port id */
lp_i2s_hal_context_t hal; /*!< hal context */
uint32_t chan_occupancy; /*!< channel occupancy (rx/tx) */
lp_i2s_chan_handle_t rx_chan; /*!< rx channel handle */
intr_handle_t intr; /*!< interrupt handle */
} lp_i2s_controller_t;
/**
* @brief lp i2s channel object type
*/
struct lp_i2s_channel_obj_t {
/* Channel basic information */
lp_i2s_controller_t *ctlr; /*!< Parent pointer to controller object */
i2s_comm_mode_t mode; /*!< lp i2s channel communication mode */
i2s_role_t role; /*!< lp i2s role */
i2s_dir_t dir; /*!< lp i2s channel direction */
i2s_state_t state; /*!< lp i2s driver state. Ensuring the driver working in a correct sequence */
SemaphoreHandle_t semphr; /*!< lp i2s event semphr*/
lp_i2s_trans_t trans; /*!< transaction */
size_t threshold; /*!< lp i2s threshold*/
lp_i2s_evt_cbs_internal_t cbs; /*!< callbacks */
void *user_data; /*!< user data */
};
/**
* @brief i2s platform level configurations
* @note All i2s controllers' resources are involved
*/
typedef struct {
portMUX_TYPE spinlock; /*!< Platform level lock */
i2s_controller_t *controller[SOC_I2S_NUM]; /*!< Controller object */
const char *comp_name[SOC_I2S_NUM]; /*!< The component name that occupied i2s controller */
portMUX_TYPE spinlock; /*!< Platform level lock */
i2s_controller_t *controller[SOC_I2S_NUM]; /*!< Controller object */
const char *comp_name[SOC_I2S_NUM]; /*!< The component name that occupied i2s controller */
#if SOC_LP_I2S_SUPPORTED
lp_i2s_controller_t *lp_controller[SOC_LP_I2S_NUM]; /*!< LP controller object*/
const char *lp_comp_name[SOC_I2S_NUM]; /*!< The component name that occupied lp i2s controller */
#endif
} i2s_platform_t;
extern i2s_platform_t g_i2s; /*!< Global i2s instance for driver internal use */

View File

@ -62,6 +62,15 @@ typedef enum {
I2S_MCLK_MULTIPLE_1152 = 1152, /*!< MCLK = sample_rate * 1152 (24-bit compatible) */
} i2s_mclk_multiple_t;
/**
* @brief LP I2S transaction type
*/
typedef struct {
void *buffer; ///< Pointer to buffer
size_t buflen; ///< Buffer len, this should be in the multiple of 4
size_t received_size; ///< Received size
} lp_i2s_trans_t;
/**
* @brief Event structure used in I2S event queue
*/
@ -78,7 +87,15 @@ typedef struct {
*/
} i2s_event_data_t;
typedef struct i2s_channel_obj_t *i2s_chan_handle_t; /*!< I2S channel object handle, the control unit of the I2S driver*/
/**
* @brief Event data structure for LP I2S
*/
typedef struct {
lp_i2s_trans_t trans; ///< LP I2S transaction
} lp_i2s_evt_data_t;
typedef struct i2s_channel_obj_t *i2s_chan_handle_t; /*!< I2S channel object handle, the control unit of the I2S driver*/
typedef struct lp_i2s_channel_obj_t *lp_i2s_chan_handle_t; /*!< I2S channel object handle, the control unit of the I2S driver*/
/**
* @brief I2S event callback
@ -90,6 +107,17 @@ typedef struct i2s_channel_obj_t *i2s_chan_handle_t; /*!< I2S channel object
*/
typedef bool (*i2s_isr_callback_t)(i2s_chan_handle_t handle, i2s_event_data_t *event, void *user_ctx);
/**
* @brief LP I2S event callback type
*
* @param[in] handle LP I2S channel handle
* @param[in] event Event data
* @param[in] user_ctx User data
*
* @return Whether a high priority task has been waken up by this callback function
*/
typedef bool (*lp_i2s_callback_t)(lp_i2s_chan_handle_t handle, lp_i2s_evt_data_t *event, void *user_ctx);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,133 @@
/*
* SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "driver/i2s_types.h"
#include "hal/i2s_types.h"
#include "esp_types.h"
#include "esp_err.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief LP_I2S max timeout value
*/
#define LP_I2S_MAX_DELAY UINT32_MAX
/**
* @brief LP I2S controller channel configuration
*/
typedef struct {
int id; /*!< LP I2S port id */
i2s_role_t role; /*!< LP I2S role, only I2S_ROLE_SLAVE supported */
size_t threshold; /*!< When LP I2S received bytes are bigger than this value, the `on_thresh_met` callback will be triggered. Must be in multiple of 4 */
} lp_i2s_chan_config_t;
/**
* @brief LP I2S event callbacks
*/
typedef struct {
lp_i2s_callback_t on_thresh_met; ///< Triggered when the received bytes are bigger than `lp_i2s_chan_config_t:threshold`
lp_i2s_callback_t on_request_new_trans; ///< Triggered when a new transaction buffer is needed, when this callback is registered, you don't need to use `lp_i2s_channel_read` to get data, you can get data via this callback asynchronously
} lp_i2s_evt_cbs_t;
/**
* @brief Allocate new LP I2S channel(s)
*
* @param[in] chan_cfg LP I2S controller channel configurations
* @param[out] ret_tx_handle LP I2S channel handler used for managing the sending channel(optional), this one is not supported and is kept here for future-proof.
* @param[out] ret_rx_handle LP I2S channel handler used for managing the receiving channel(optional)
* @return
* - ESP_OK Allocate new channel(s) success
* - ESP_ERR_NOT_SUPPORTED The communication mode is not supported on the current chip
* - ESP_ERR_INVALID_ARG NULL pointer or illegal parameter in lp_i2s_chan_config_t
* - ESP_ERR_NOT_FOUND No available LP I2S channel found
*/
esp_err_t lp_i2s_new_channel(const lp_i2s_chan_config_t *chan_cfg, lp_i2s_chan_handle_t *ret_tx_handle, lp_i2s_chan_handle_t *ret_rx_handle);
/**
* @brief Register LP I2S event callbacks
*
* @param[in] handle LP I2S channel handle
* @param[in] cbs Callbacks
* @param[in] user_data User data
*
* @return
* - ESP_OK: On success
* - ESP_ERR_INVALID_ARG: Invalid argument
* - ESP_ERR_INVALID_STATE: Invalid state
*/
esp_err_t lp_i2s_register_event_callbacks(lp_i2s_chan_handle_t handle, const lp_i2s_evt_cbs_t *cbs, void *user_data);
/**
* @brief Enable LP I2S driver
*
* @param[in] handle LP I2S channel handle
*
* @return
* - ESP_OK: On success
* - ESP_ERR_INVALID_ARG: Invalid argument
* - ESP_ERR_INVALID_STATE: Invalid state
*/
esp_err_t lp_i2s_channel_enable(lp_i2s_chan_handle_t chan);
/**
* @brief Read LP I2S received data
*
* @param[in] handle LP I2S channel handle
* @param[in] trans LP I2S transaction
* @param[in] timeout_ms Timeout in ms, set to `LP_I2S_MAX_DELAY` to wait until read is done
*
* @return
* - ESP_OK: On success
* - ESP_ERR_INVALID_ARG: Invalid argument
* - ESP_ERR_INVALID_STATE: Invalid state, e.g. `on_request_new_trans` callback is registered
*/
esp_err_t lp_i2s_channel_read(lp_i2s_chan_handle_t chan, lp_i2s_trans_t *trans, uint32_t timeout_ms);
/**
* @brief Read LP I2S received data until certain bytes
*
* @param[in] handle LP I2S channel handle
* @param[in] trans LP I2S transaction
*
* @return
* - ESP_OK: On success
* - ESP_ERR_INVALID_ARG: Invalid argument
* - ESP_ERR_INVALID_STATE: Invalid state, e.g. `on_request_new_trans` callback is registered
*/
esp_err_t lp_i2s_channel_read_until_bytes(lp_i2s_chan_handle_t chan, lp_i2s_trans_t *trans);
/**
* @brief Disable LP I2S driver
*
* @param[in] handle LP I2S channel handle
*
* @return
* - ESP_OK: On success
* - ESP_ERR_INVALID_ARG: Invalid argument
* - ESP_ERR_INVALID_STATE: Invalid state
*/
esp_err_t lp_i2s_channel_disable(lp_i2s_chan_handle_t chan);
/**
* @brief Delete the LP I2S channel
*
* @param[in] handle LP I2S channel handler
*
* @return
* - ESP_OK Delete successfully
* - ESP_ERR_INVALID_ARG NULL pointer
*/
esp_err_t lp_i2s_del_channel(lp_i2s_chan_handle_t handle);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,100 @@
/*
* SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "esp_types.h"
#include "esp_err.h"
#include "driver/i2s_types.h"
#include "hal/i2s_types.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief PDM format in 2 slots(RX)
* @param bits_per_sample I2S data bit width, only support 16 bits for PDM mode
* @param mono_or_stereo I2S_SLOT_MODE_MONO or I2S_SLOT_MODE_STEREO
*/
#define LP_I2S_PDM_RX_SLOT_DEFAULT_CONFIG(bits_per_sample, mono_or_stereo) { \
.data_bit_width = bits_per_sample, \
.slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO, \
.slot_mode = mono_or_stereo, \
.slot_mask = (mono_or_stereo == I2S_SLOT_MODE_MONO) ? \
I2S_PDM_SLOT_LEFT : I2S_PDM_SLOT_BOTH, \
.hp_en = true, \
.hp_cut_off_freq_hz = 35.5, \
.amplify_num = 1, \
}
/**
* @brief LP I2S pin configurations
*/
typedef struct {
int clk; /*!< clk pin number */
int din; /*!< din pin number */
} lp_i2s_pdm_rx_gpio_config_t;
/*
High Pass Filter Cut-off Frequency Sheet
+----------------+------------------+----------------+------------------+----------------+------------------+
| param0, param5 | cut-off freq(Hz) | param0, param5 | cut-off freq(Hz) | param0, param5 | cut-off freq(Hz) |
+----------------+------------------+----------------+------------------+----------------+------------------+
| (0, 0) | 185 | (3, 3) | 115 | (5, 5) | 69 |
| (0, 1) | 172 | (1, 7) | 106 | (4, 7) | 63 |
| (1, 1) | 160 | (2, 4) | 104 | (5, 6) | 58 |
| (1, 2) | 150 | (4, 4) | 92 | (5, 7) | 49 |
| (2, 2) | 137 | (2, 7) | 91.5 | (6, 6) | 46 |
| (2, 3) | 126 | (4, 5) | 81 | (6, 7) | 35.5 |
| (0, 3) | 120 | (3, 7) | 77.2 | (7, 7) | 23.3 |
+----------------+------------------+----------------+------------------+----------------+------------------+
*/
/**
* @brief I2S slot configuration for PDM RX mode
*/
typedef struct {
/* General fields */
i2s_data_bit_width_t data_bit_width; /*!< I2S sample data bit width (valid data bits per sample), only support 16 bits for PDM mode */
i2s_slot_bit_width_t slot_bit_width; /*!< I2S slot bit width (total bits per slot) , only support 16 bits for PDM mode */
i2s_slot_mode_t slot_mode; /*!< Set mono or stereo mode with I2S_SLOT_MODE_MONO or I2S_SLOT_MODE_STEREO */
/* Particular fields */
i2s_pdm_slot_mask_t slot_mask; /*!< Choose the slots to activate */
bool ws_pol; /*!< WS signal polarity, set true to enable high lever first */
bool hp_en; /*!< High pass filter enable */
float hp_cut_off_freq_hz; /*!< High pass filter cut-off frequency, range 23.3Hz ~ 185Hz, see cut-off frequency sheet above */
uint32_t amplify_num; /*!< The amplification number of the final conversion result.
* The data that have converted from PDM to PCM module, will time `amplify_num` additionally to amplify the final result.
* Note that it's only a multiplier of the digital PCM data, not the gain of the analog signal
* range 1~15, default 1 */
} lp_i2s_pdm_rx_slot_config_t;
/**
* @brief LP I2S PDM configuration
*/
typedef struct {
lp_i2s_pdm_rx_gpio_config_t pin_cfg; /*!< Pin configuration */
lp_i2s_pdm_rx_slot_config_t slot_cfg; /*!< PDM mode slot configuration */
/* LP I2S only support slave mode, not support to configure the clock */
} lp_i2s_pdm_rx_config_t;
/**
* @brief Init LP I2S to PDM mode
*
* @param[in] handle LP I2S channel handle
* @param[in] pdm_cfg PDM configuration
*
* @return
* - ESP_OK: On success
* - ESP_ERR_INVALID_ARG: Invalid argument
* - ESP_ERR_INVALID_STATE: Invalid state
*/
esp_err_t lp_i2s_channel_init_pdm_rx_mode(lp_i2s_chan_handle_t handle, const lp_i2s_pdm_rx_config_t *pdm_cfg);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,124 @@
/*
* SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "esp_types.h"
#include "esp_err.h"
#include "driver/i2s_types.h"
#include "hal/i2s_types.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Philips format in active slot that enabled by mask
* @param bits_per_sample I2S data bit width
* @param mono_or_stereo I2S_SLOT_MODE_MONO or I2S_SLOT_MODE_STEREO
*/
#define LP_I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(bits_per_sample, mono_or_stereo) { \
.data_bit_width = (bits_per_sample), \
.slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO, \
.slot_mode = mono_or_stereo, \
.slot_mask = I2S_STD_SLOT_BOTH, \
.ws_width = bits_per_sample, \
.ws_pol = false, \
.bit_shift = true, \
.left_align = true, \
.big_endian = false, \
.bit_order_lsb = false, \
}
/**
* @brief MSB format in active slot enabled that by mask
* @param bits_per_sample I2S data bit width
* @param mono_or_stereo I2S_SLOT_MODE_MONO or I2S_SLOT_MODE_STEREO
*/
#define LP_I2S_STD_MSB_SLOT_DEFAULT_CONFIG(bits_per_sample, mono_or_stereo) { \
.data_bit_width = (bits_per_sample), \
.slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO, \
.slot_mode = mono_or_stereo, \
.slot_mask = I2S_STD_SLOT_BOTH, \
.ws_width = bits_per_sample, \
.ws_pol = false, \
.bit_shift = false, \
.left_align = true, \
.big_endian = false, \
.bit_order_lsb = false, \
}
/**
* @brief PCM(short) format in active slot that enabled by mask
* @param bits_per_sample I2S data bit width
* @param mono_or_stereo I2S_SLOT_MODE_MONO or I2S_SLOT_MODE_STEREO
*/
#define LP_I2S_STD_PCM_SHORT_SLOT_DEFAULT_CONFIG(bits_per_sample, mono_or_stereo) { \
.data_bit_width = (bits_per_sample), \
.slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO, \
.slot_mode = mono_or_stereo, \
.slot_mask = I2S_STD_SLOT_BOTH, \
.ws_width = 1, \
.ws_pol = true, \
.bit_shift = true, \
.left_align = true, \
.big_endian = false, \
.bit_order_lsb = false, \
}
/**
* @brief LP I2S pin configurations
*/
typedef struct {
int bck; /*!< bck pin number */
int ws; /*!< ws pin number */
int din; /*!< din pin number */
} lp_i2s_std_gpio_config_t;
/**
* @brief LP I2S slot configuration for standard mode
*/
typedef struct {
/* General fields */
i2s_data_bit_width_t data_bit_width; /*!< I2S sample data bit width (valid data bits per sample) */
i2s_slot_bit_width_t slot_bit_width; /*!< I2S slot bit width (total bits per slot) */
i2s_slot_mode_t slot_mode; /*!< Set mono or stereo mode with I2S_SLOT_MODE_MONO or I2S_SLOT_MODE_STEREO */
/* Particular fields */
i2s_std_slot_mask_t slot_mask; /*!< Select the left, right or both slot */
uint32_t ws_width; /*!< WS signal width (i.e. the number of BCLK ticks that WS signal is high) */
bool ws_pol; /*!< WS signal polarity, set true to enable high lever first */
bool bit_shift; /*!< Set true to enable bit shift in Philips mode */
bool left_align; /*!< Set true to enable left alignment */
bool big_endian; /*!< Set true to enable big endian */
bool bit_order_lsb; /*!< Set true to enable lsb first */
} lp_i2s_std_slot_config_t;
/**
* @brief LP I2S STD configuration
*/
typedef struct {
lp_i2s_std_gpio_config_t pin_cfg; /*!< Pin configuration */
lp_i2s_std_slot_config_t slot_cfg; /*!< STD mode slot configuration, can be generated by macros I2S_STD_[mode]_SLOT_DEFAULT_CONFIG, [mode] can be replaced with PHILIPS/MSB/PCM_SHORT/PCM_LONG */
/* LP I2S only support slave mode, not support to configure the clock */
} lp_i2s_std_config_t;
/**
* @brief Init LP I2S to STD mode
*
* @param[in] handle LP I2S channel handle
* @param[in] std_cfg STD configuration
*
* @return
* - ESP_OK: On success
* - ESP_ERR_INVALID_ARG: Invalid argument
* - ESP_ERR_INVALID_STATE: Invalid state
*/
esp_err_t lp_i2s_channel_init_std_mode(lp_i2s_chan_handle_t handle, const lp_i2s_std_config_t *std_cfg);
#ifdef __cplusplus
}
#endif

View File

@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@ -15,12 +15,21 @@
extern "C" {
#endif
/**
* @brief I2S controller type
*/
typedef enum {
I2S_CTLR_HP, ///< HP I2S
I2S_CTLR_LP, ///< LP I2S
} i2s_ctlr_t;
/**
* @brief Hold the I2S port occupation
*
* @note This private API is used to avoid applications from using the same I2S instance for different purpose.
* @note This function will help enable the peripheral APB clock as well.
*
* @param[in] type I2S controller type
* @param id I2S port number
* @param comp_name The name of compnant that occupied this i2s controller
* @return
@ -28,20 +37,21 @@ extern "C" {
* - ESP_ERR_INVALID_ARG: Invalid argument, e.g. wrong port_id
* - ESP_ERR_NOT_FOUND Specific I2S port is not available
*/
esp_err_t i2s_platform_acquire_occupation(int id, const char *comp_name);
esp_err_t i2s_platform_acquire_occupation(i2s_ctlr_t type, int id, const char *comp_name);
/**
* @brief Release the I2S port occupation
*
* @note This function will help disable the peripheral APB clock as well.
*
* @param[in] type I2S controller type
* @param id I2S port number
* @return
* - ESP_OK: Deregister I2S port successfully (i.e. that I2S port can used used by other users after this function returns)
* - ESP_ERR_INVALID_ARG: Invalid argument, e.g. wrong port_id
* - ESP_ERR_INVALID_STATE: Specific I2S port is free already
*/
esp_err_t i2s_platform_release_occupation(int id);
esp_err_t i2s_platform_release_occupation(i2s_ctlr_t type, int id);
/**
* @brief This function is only used for getting DMA buffer offset in `test_i2s_iram.c`

View File

@ -0,0 +1,331 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <esp_types.h>
#include <sys/lock.h>
#include "sdkconfig.h"
#if CONFIG_I2S_ENABLE_DEBUG_LOG
// The local log level must be defined before including esp_log.h
// Set the maximum log level for this source file
#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
#endif
#include "esp_log.h"
#include "esp_check.h"
#include "esp_heap_caps.h"
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "esp_clk_tree.h"
#include "hal/hal_utils.h"
#include "hal/lp_i2s_hal.h"
#include "hal/lp_i2s_ll.h"
#include "driver/i2s_types.h"
#include "driver/lp_i2s.h"
#include "esp_private/periph_ctrl.h"
#include "esp_private/i2s_platform.h"
#include "i2s_private.h"
#include "soc/i2s_periph.h"
#define LP_I2S_MEM_ALLOC_CAPS (MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT)
static const char *TAG = "LP_I2S";
static esp_err_t s_i2s_register_channel(lp_i2s_controller_t *ctlr, i2s_dir_t dir);
static inline bool s_lp_i2s_take_available_channel(lp_i2s_controller_t *ctlr, uint8_t chan_search_mask);
static void s_i2s_default_isr(void *arg);
esp_err_t lp_i2s_new_channel(const lp_i2s_chan_config_t *chan_cfg, lp_i2s_chan_handle_t *ret_tx_handle, lp_i2s_chan_handle_t *ret_rx_handle)
{
#if CONFIG_I2S_ENABLE_DEBUG_LOG
esp_log_level_set(TAG, ESP_LOG_DEBUG);
#endif
ESP_RETURN_ON_FALSE(chan_cfg, ESP_ERR_INVALID_ARG, TAG, "invalid argument: null pointer");
ESP_RETURN_ON_FALSE(chan_cfg->id < SOC_LP_I2S_NUM, ESP_ERR_INVALID_ARG, TAG, "invalid LP I2S port id");
ESP_RETURN_ON_FALSE(chan_cfg->role == I2S_ROLE_SLAVE, ESP_ERR_INVALID_ARG, TAG, "invalid argument: LP I2S not support master");
#if LP_I2S_LL_TX_SUPPORTED
ESP_RETURN_ON_FALSE(ret_tx_handle, ESP_ERR_INVALID_ARG, TAG, "invalid argument: null pointer");
#else
ESP_RETURN_ON_FALSE(!ret_tx_handle, ESP_ERR_INVALID_ARG, TAG, "tx not supported");
#endif
#if LP_I2S_LL_RX_SUPPORTED
ESP_RETURN_ON_FALSE(ret_rx_handle, ESP_ERR_INVALID_ARG, TAG, "invalid argument: null pointer");
#else
ESP_RETURN_ON_FALSE(!ret_rx_handle, ESP_ERR_INVALID_ARG, TAG, "rx not supported");
#endif
ESP_RETURN_ON_FALSE(chan_cfg->threshold % 4 == 0, ESP_ERR_INVALID_ARG, TAG, "threshold must be in multiple of 4");
ESP_RETURN_ON_FALSE(chan_cfg->threshold <= LP_I2S_LL_RX_MEM_THRESH_BYTES_MAX, ESP_ERR_INVALID_ARG, TAG, "invalid threshold");
esp_err_t ret = ESP_OK;
ESP_RETURN_ON_ERROR(i2s_platform_acquire_occupation(I2S_CTLR_LP, chan_cfg->id, "lp_i2s_driver"), TAG, "failed to acquire lp i2s controller");
lp_i2s_controller_t *ctlr = heap_caps_calloc(1, sizeof(lp_i2s_controller_t), LP_I2S_MEM_ALLOC_CAPS);
ESP_RETURN_ON_FALSE(ctlr, ESP_ERR_NO_MEM, TAG, "no mem");
ctlr->id = chan_cfg->id;
ESP_GOTO_ON_ERROR(esp_intr_alloc(lp_i2s_periph_signal[ctlr->id].irq, ESP_INTR_FLAG_IRAM, s_i2s_default_isr, ctlr, &ctlr->intr), err1, TAG, "allocate interrupt failed");
uint8_t chan_search_mask = 0;
chan_search_mask |= ret_tx_handle ? I2S_DIR_TX : 0;
chan_search_mask |= ret_rx_handle ? I2S_DIR_RX : 0;
portENTER_CRITICAL(&g_i2s.spinlock);
g_i2s.lp_controller[chan_cfg->id] = ctlr;
portEXIT_CRITICAL(&g_i2s.spinlock);
bool channel_found = s_lp_i2s_take_available_channel(ctlr, chan_search_mask);
ESP_GOTO_ON_FALSE(channel_found, ESP_ERR_NOT_FOUND, err1, TAG, "no available channel found");
if (ret_rx_handle) {
ESP_GOTO_ON_ERROR(s_i2s_register_channel(ctlr, I2S_DIR_RX), err1, TAG, "register lp i2s rx channel failed");
ctlr->rx_chan->role = chan_cfg->role;
ctlr->rx_chan->threshold = chan_cfg->threshold;
*ret_rx_handle = ctlr->rx_chan;
ESP_LOGD(TAG, "rx channel is registered on LP_I2S%d successfully", ctlr->id);
ctlr->rx_chan->semphr = xSemaphoreCreateBinaryWithCaps(LP_I2S_MEM_ALLOC_CAPS);
ESP_GOTO_ON_FALSE(ctlr->rx_chan->semphr, ESP_ERR_NO_MEM, err0, TAG, "No memory for binary semaphore");
}
PERIPH_RCC_ATOMIC() {
lp_i2s_ll_enable_module_clock(chan_cfg->id, true);
lp_i2s_ll_reset_module_clock(chan_cfg->id);
if (chan_search_mask & I2S_DIR_RX) {
lp_i2s_ll_enable_rx_module_clock(chan_cfg->id, true);
}
}
lp_i2s_hal_init(&ctlr->hal, ctlr->id);
lp_i2s_ll_enable_mem(chan_cfg->id, true);
lp_i2s_ll_clk_gate_en(ctlr->hal.dev, true);
lp_i2s_ll_rx_mem_clk_gate_en(ctlr->hal.dev, true);
lp_i2s_ll_rx_reg_clk_gate_en(ctlr->hal.dev, true);
lp_i2s_ll_rx_reset(ctlr->hal.dev);
lp_i2s_ll_rx_reset_fifo(ctlr->hal.dev);
lp_i2s_ll_rx_reset_fifo_mem(ctlr->hal.dev);
if (ctlr->rx_chan->role == I2S_ROLE_SLAVE) {
lp_i2s_ll_set_rx_master_slave_mode(ctlr->hal.dev, true);
}
lp_i2s_ll_set_rx_stop_mode(ctlr->hal.dev, LP_I2S_LL_RX_STOP_MODE_START_CLEAR);
lp_i2s_ll_set_rx_mem_threshold(ctlr->hal.dev, chan_cfg->threshold / 4);
lp_i2s_ll_rx_clear_interrupt_status(ctlr->hal.dev, LP_I2S_LL_EVENT_RX_MEM_THRESHOLD_INT);
return ESP_OK;
err0:
if (ctlr->rx_chan) {
vSemaphoreDeleteWithCaps(ctlr->rx_chan->semphr);
free(ctlr->rx_chan);
ctlr->rx_chan = NULL;
}
err1:
/* if the controller object has no channel, find the corresponding global object and destroy it */
if (ctlr != NULL && ctlr->rx_chan == NULL) {
esp_intr_free(ctlr->intr);
i2s_platform_release_occupation(I2S_CTLR_LP, chan_cfg->id);
free(ctlr);
}
return ret;
}
esp_err_t lp_i2s_channel_enable(lp_i2s_chan_handle_t chan)
{
ESP_RETURN_ON_FALSE(chan, ESP_ERR_INVALID_ARG, TAG, "invalid argument: null pointer");
ESP_RETURN_ON_FALSE(chan->state < I2S_CHAN_STATE_RUNNING, ESP_ERR_INVALID_STATE, TAG, "the channel is in enabled state already");
lp_i2s_evt_data_t evt_data = {};
if (chan->cbs.on_request_new_trans) {
chan->cbs.on_request_new_trans(chan, &evt_data, chan->user_data);
ESP_RETURN_ON_FALSE(evt_data.trans.buffer, ESP_ERR_INVALID_STATE, TAG, "no transaction buffer");
ESP_RETURN_ON_FALSE(((evt_data.trans.buflen % LP_I2S_LL_RX_MEM_POP_BYTES) == 0), ESP_ERR_INVALID_STATE, TAG, "invalid transaction buflen, not aligned to %d", LP_I2S_LL_RX_MEM_POP_BYTES);
ESP_LOGD(TAG, "evt_data.trans.buffer: %p, evt_data.trans.buflen: %d", evt_data.trans.buffer, evt_data.trans.buflen);
chan->trans = evt_data.trans;
}
chan->state = I2S_CHAN_STATE_RUNNING;
portENTER_CRITICAL(&g_i2s.spinlock);
lp_i2s_ll_rx_enable_interrupt(chan->ctlr->hal.dev, LP_I2S_LL_EVENT_RX_MEM_THRESHOLD_INT, true);
lp_i2s_ll_rx_start(chan->ctlr->hal.dev);
portEXIT_CRITICAL(&g_i2s.spinlock);
return ESP_OK;
}
esp_err_t lp_i2s_channel_read(lp_i2s_chan_handle_t chan, lp_i2s_trans_t *trans, uint32_t timeout_ms)
{
ESP_RETURN_ON_FALSE(chan, ESP_ERR_INVALID_ARG, TAG, "invalid argument: null pointer");
ESP_RETURN_ON_FALSE(chan->state == I2S_CHAN_STATE_RUNNING, ESP_ERR_INVALID_STATE, TAG, "the channel isn't enabled");
ESP_RETURN_ON_FALSE(!chan->cbs.on_request_new_trans, ESP_ERR_INVALID_STATE, TAG, "on_request_new_trans registered, no use of this read API");
TickType_t ticks_to_wait = timeout_ms / portTICK_PERIOD_MS;
if (timeout_ms == LP_I2S_MAX_DELAY) {
ticks_to_wait = portMAX_DELAY;
}
BaseType_t r = xSemaphoreTake(chan->semphr, ticks_to_wait);
if (r != pdTRUE) {
ESP_LOGW(TAG, "lp_i2s read API, new data receiving timeout");
return ESP_ERR_TIMEOUT;
}
size_t len = MIN(lp_i2s_ll_get_rx_mem_fifo_cnt(chan->ctlr->hal.dev), trans->buflen);
lp_i2s_ll_read_buffer(chan->ctlr->hal.dev, trans->buffer, len);
trans->received_size = len;
portENTER_CRITICAL(&g_i2s.spinlock);
lp_i2s_ll_rx_clear_interrupt_status(chan->ctlr->hal.dev, LP_I2S_LL_EVENT_RX_MEM_THRESHOLD_INT);
lp_i2s_ll_rx_enable_interrupt(chan->ctlr->hal.dev, LP_I2S_LL_EVENT_RX_MEM_THRESHOLD_INT, true);
portEXIT_CRITICAL(&g_i2s.spinlock);
return ESP_OK;
}
esp_err_t lp_i2s_channel_read_until_bytes(lp_i2s_chan_handle_t chan, lp_i2s_trans_t *trans)
{
size_t received_size = 0;
size_t recv_len = trans->buflen;
lp_i2s_trans_t t = {};
while (1) {
t.buffer = trans->buffer + received_size;
t.buflen = MIN(chan->threshold, recv_len);
ESP_RETURN_ON_ERROR(lp_i2s_channel_read(chan, &t, LP_I2S_MAX_DELAY), TAG, "failed to do lp i2s read");
received_size += t.received_size;
recv_len -= t.received_size;
ESP_LOGD(TAG, "received_size: %d, recv_len: %d, t.received_size: %d", received_size, recv_len, t.received_size);
if (recv_len == 0) {
break;
}
}
trans->received_size = received_size;
return ESP_OK;
}
esp_err_t lp_i2s_channel_disable(lp_i2s_chan_handle_t chan)
{
ESP_RETURN_ON_FALSE(chan, ESP_ERR_INVALID_ARG, TAG, "invalid argument: null pointer");
ESP_RETURN_ON_FALSE(chan->state > I2S_CHAN_STATE_READY, ESP_ERR_INVALID_STATE, TAG, "the channel is disabled already");
chan->state = I2S_CHAN_STATE_READY;
portENTER_CRITICAL(&g_i2s.spinlock);
lp_i2s_ll_rx_enable_interrupt(chan->ctlr->hal.dev, LP_I2S_LL_EVENT_RX_MEM_THRESHOLD_INT, false);
lp_i2s_ll_rx_stop(chan->ctlr->hal.dev);
portEXIT_CRITICAL(&g_i2s.spinlock);
return ESP_OK;
}
esp_err_t lp_i2s_del_channel(lp_i2s_chan_handle_t chan)
{
ESP_RETURN_ON_FALSE(chan, ESP_ERR_INVALID_ARG, TAG, "invalid argument: null pointer");
ESP_RETURN_ON_FALSE(chan->state < I2S_CHAN_STATE_RUNNING, ESP_ERR_INVALID_STATE, TAG, "the channel can't be deleted unless it is disabled");
int id = chan->ctlr->id;
portENTER_CRITICAL(&g_i2s.spinlock);
if (chan->dir == I2S_DIR_RX) {
g_i2s.lp_controller[chan->ctlr->id]->rx_chan = NULL;
}
g_i2s.lp_controller[id] = NULL;
portEXIT_CRITICAL(&g_i2s.spinlock);
ESP_RETURN_ON_ERROR(esp_intr_free(chan->ctlr->intr), TAG, "failed to free intr");
vSemaphoreDeleteWithCaps(chan->semphr);
ESP_RETURN_ON_ERROR(i2s_platform_release_occupation(I2S_CTLR_LP, id), TAG, "failed to release lp i2s controller");
free(chan->ctlr);
free(chan);
return ESP_OK;
}
#ifndef __cplusplus
/* To make sure the i2s_event_callbacks_t is same size as i2s_event_callbacks_internal_t */
_Static_assert(sizeof(lp_i2s_evt_cbs_t) == sizeof(lp_i2s_evt_cbs_internal_t), "Invalid size of lp_i2s_evt_cbs_t structure");
#endif
esp_err_t lp_i2s_register_event_callbacks(lp_i2s_chan_handle_t handle, const lp_i2s_evt_cbs_t *cbs, void *user_data)
{
ESP_RETURN_ON_FALSE(handle && cbs, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
ESP_RETURN_ON_FALSE(handle->state < I2S_CHAN_STATE_RUNNING, ESP_ERR_INVALID_STATE, TAG, "the channel is in enabled state already");
handle->cbs.on_thresh_met = cbs->on_thresh_met;
handle->cbs.on_request_new_trans = cbs->on_request_new_trans;
handle->user_data = user_data;
return ESP_OK;
}
static inline bool s_lp_i2s_take_available_channel(lp_i2s_controller_t *ctlr, uint8_t chan_search_mask)
{
bool is_available = false;
portENTER_CRITICAL(&g_i2s.spinlock);
if (!(chan_search_mask & ctlr->chan_occupancy)) {
ctlr->chan_occupancy |= chan_search_mask;
is_available = true;
}
portEXIT_CRITICAL(&g_i2s.spinlock);
return is_available;
}
static esp_err_t s_i2s_register_channel(lp_i2s_controller_t *ctlr, i2s_dir_t dir)
{
esp_err_t ret = ESP_OK;
lp_i2s_chan_handle_t new_chan = (lp_i2s_chan_handle_t)heap_caps_calloc(1, sizeof(struct lp_i2s_channel_obj_t), LP_I2S_MEM_ALLOC_CAPS);
ESP_RETURN_ON_FALSE(new_chan, ESP_ERR_NO_MEM, TAG, "No memory for new channel");
new_chan->mode = I2S_COMM_MODE_NONE;
new_chan->role = I2S_ROLE_MASTER;
new_chan->dir = dir;
new_chan->state = I2S_CHAN_STATE_REGISTER;
new_chan->ctlr = ctlr;
if (dir == I2S_DIR_RX) {
ctlr->rx_chan = new_chan;
}
return ret;
}
/*---------------------------------------------------------------
INTR
---------------------------------------------------------------*/
static void IRAM_ATTR s_i2s_default_isr(void *arg)
{
lp_i2s_controller_t *ctlr = (lp_i2s_controller_t *)arg;
bool need_yield = false;
BaseType_t high_task_woken = pdFALSE;
ESP_DRAM_LOGD(TAG, "in isr, rx_mem_fifo_cnt: %d bytes", lp_i2s_ll_get_rx_mem_fifo_cnt(ctlr->hal.dev));
if (ctlr->rx_chan->cbs.on_request_new_trans) {
size_t len = MIN(lp_i2s_ll_get_rx_mem_fifo_cnt(ctlr->hal.dev), ctlr->rx_chan->trans.buflen);
ESP_DRAM_LOGD(TAG, "len: %d", len);
lp_i2s_ll_read_buffer(ctlr->hal.dev, ctlr->rx_chan->trans.buffer, len);
lp_i2s_ll_rx_clear_interrupt_status(ctlr->hal.dev, LP_I2S_LL_EVENT_RX_MEM_THRESHOLD_INT);
ctlr->rx_chan->trans.received_size = len;
lp_i2s_evt_data_t edata = {
.trans = ctlr->rx_chan->trans,
};
if (ctlr->rx_chan->cbs.on_thresh_met) {
need_yield |= ctlr->rx_chan->cbs.on_thresh_met(ctlr->rx_chan, &edata, ctlr->rx_chan->user_data);
}
assert(ctlr->rx_chan->cbs.on_request_new_trans);
lp_i2s_evt_data_t new_edata = {};
ctlr->rx_chan->cbs.on_request_new_trans(ctlr->rx_chan, &new_edata, ctlr->rx_chan->user_data);
memcpy(&ctlr->rx_chan->trans, &new_edata.trans, sizeof(lp_i2s_trans_t));
} else {
portENTER_CRITICAL_ISR(&g_i2s.spinlock);
lp_i2s_ll_rx_enable_interrupt(ctlr->hal.dev, LP_I2S_LL_EVENT_RX_MEM_THRESHOLD_INT, false);
portEXIT_CRITICAL_ISR(&g_i2s.spinlock);
xSemaphoreGiveFromISR(ctlr->rx_chan->semphr, &high_task_woken);
}
need_yield |= high_task_woken == pdTRUE;
if (need_yield) {
portYIELD_FROM_ISR();
}
}

View File

@ -0,0 +1,114 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <esp_types.h>
#include <sys/lock.h>
#include "sdkconfig.h"
#include "esp_log.h"
#include "esp_check.h"
#include "hal/lp_i2s_hal.h"
#include "hal/lp_i2s_ll.h"
#include "driver/i2s_types.h"
#include "driver/lp_i2s.h"
#include "driver/lp_i2s_pdm.h"
#include "driver/lp_io.h"
#include "driver/rtc_io.h"
#include "esp_private/periph_ctrl.h"
#include "i2s_private.h"
static const char *TAG = "LP_I2S_PDM";
static esp_err_t s_io_init(lp_i2s_chan_handle_t chan, const lp_i2s_pdm_rx_gpio_config_t *config);
static void s_get_cut_off_coef(uint32_t freq, uint32_t *param0, uint32_t *param5)
{
uint32_t cut_off_coef[LP_I2S_LL_CUT_OFF_COEF_X_NUM][LP_I2S_LL_CUT_OFF_COEF_Y_NUM] = LP_I2S_LL_CUT_OFF_COEF;
uint8_t cnt = 0;
uint32_t min = 10000;
/* Find the closest cut-off frequency and its coefficients */
for (int i = 0; i < LP_I2S_LL_CUT_OFF_COEF_X_NUM; i++) {
uint32_t tmp = cut_off_coef[i][0] < freq ? freq - cut_off_coef[i][0] : cut_off_coef[i][0] - freq;
if (tmp < min) {
min = tmp;
cnt = i;
}
}
*param0 = (uint32_t)cut_off_coef[cnt][1];
*param5 = (uint32_t)cut_off_coef[cnt][2];
}
esp_err_t lp_i2s_channel_init_pdm_rx_mode(lp_i2s_chan_handle_t handle, const lp_i2s_pdm_rx_config_t *pdm_cfg)
{
ESP_RETURN_ON_FALSE(handle && pdm_cfg, ESP_ERR_INVALID_ARG, TAG, "invalid argument: null pointer");
ESP_RETURN_ON_FALSE(handle->state < I2S_CHAN_STATE_RUNNING, ESP_ERR_INVALID_STATE, TAG, "the channel is in enabled state already");
//Pin config
ESP_RETURN_ON_ERROR(s_io_init(handle, &pdm_cfg->pin_cfg), TAG, "failed to init IOs");
//Clock config
if (handle->role == I2S_ROLE_SLAVE) {
PERIPH_RCC_ATOMIC() {
lp_i2s_ll_select_rx_clk_source(handle->ctlr->id, LP_I2S_CLK_SRC_XTAL_D2);
}
lp_i2s_ll_clk_source_div_num(handle->ctlr->id, 2);
lp_i2s_ll_rx_set_raw_clk_div(handle->ctlr->id, 0, 0);
lp_i2s_ll_rx_set_bck_div_num(handle->ctlr->hal.dev, 1);
}
// TODO: make this divisions configurable when support master mode.
//Slot config
uint32_t slot_bit_width = (int)pdm_cfg->slot_cfg.slot_bit_width < (int)pdm_cfg->slot_cfg.data_bit_width ?
pdm_cfg->slot_cfg.data_bit_width : pdm_cfg->slot_cfg.slot_bit_width;
lp_i2s_ll_rx_set_sample_bit(handle->ctlr->hal.dev, slot_bit_width, pdm_cfg->slot_cfg.data_bit_width);
lp_i2s_ll_rx_enable_mono_mode(handle->ctlr->hal.dev, false);
uint32_t slot_mask = pdm_cfg->slot_cfg.slot_mode == I2S_SLOT_MODE_STEREO ? I2S_PDM_SLOT_BOTH : pdm_cfg->slot_cfg.slot_mask;
lp_i2s_ll_rx_set_active_chan(handle->ctlr->hal.dev, slot_mask);
lp_i2s_ll_rx_set_half_sample_bits(handle->ctlr->hal.dev, 16);
lp_i2s_ll_rx_set_ws_pol(handle->ctlr->hal.dev, pdm_cfg->slot_cfg.ws_pol);
uint32_t param0 = 0;
uint32_t param5 = 0;
s_get_cut_off_coef(pdm_cfg->slot_cfg.hp_cut_off_freq_hz, &param0, &param5);
lp_i2s_ll_rx_enable_pdm_hp_filter(handle->ctlr->hal.dev, pdm_cfg->slot_cfg.hp_en);
lp_i2s_ll_rx_set_pdm_hp_filter_param0(handle->ctlr->hal.dev, param0);
lp_i2s_ll_rx_set_pdm_hp_filter_param5(handle->ctlr->hal.dev, param5);
/* Set the amplification number, the default and the minimum value is 1. 0 will mute the channel */
lp_i2s_ll_rx_set_pdm_amplify_num(handle->ctlr->hal.dev, pdm_cfg->slot_cfg.amplify_num ? pdm_cfg->slot_cfg.amplify_num : 1);
lp_i2s_ll_rx_enable_pdm(handle->ctlr->hal.dev);
lp_i2s_ll_rx_set_pdm2pcm_en(handle->ctlr->hal.dev, true);
return ESP_OK;
}
/*---------------------------------------------------------------
IO
---------------------------------------------------------------*/
static esp_err_t s_io_init(lp_i2s_chan_handle_t chan, const lp_i2s_pdm_rx_gpio_config_t *config)
{
if (config->clk >= 0) {
rtc_gpio_set_direction(config->clk, RTC_GPIO_MODE_INPUT_ONLY);
rtc_gpio_init(config->clk);
rtc_gpio_iomux_func_sel(config->clk, 0);
if (chan->role == I2S_ROLE_MASTER) {
lp_gpio_connect_in_signal(config->clk, lp_i2s_periph_signal[chan->ctlr->id].m_rx_ws_sig, 0);
} else {
lp_gpio_connect_in_signal(config->clk, lp_i2s_periph_signal[chan->ctlr->id].s_rx_ws_sig, 0);
}
ESP_LOGD(TAG, "clk io: %d, role: %d, signal: %"PRId8, config->clk, chan->role, chan->role ? lp_i2s_periph_signal[chan->ctlr->id].s_rx_ws_sig : lp_i2s_periph_signal[chan->ctlr->id].m_rx_ws_sig);
}
if (config->din >= 0) {
rtc_gpio_set_direction(config->din, RTC_GPIO_MODE_INPUT_ONLY);
rtc_gpio_init(config->din);
rtc_gpio_iomux_func_sel(config->din, 0);
lp_gpio_connect_in_signal(config->din, lp_i2s_periph_signal[chan->ctlr->id].data_in_sigs[0], 0);
ESP_LOGD(TAG, "din io: %d, signal: %"PRId8, config->din, lp_i2s_periph_signal[chan->ctlr->id].data_in_sigs[0]);
}
return ESP_OK;
}

View File

@ -0,0 +1,102 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <esp_types.h>
#include <sys/lock.h>
#include "sdkconfig.h"
#include "esp_log.h"
#include "esp_check.h"
#include "hal/lp_i2s_hal.h"
#include "hal/lp_i2s_ll.h"
#include "driver/i2s_types.h"
#include "driver/lp_i2s.h"
#include "driver/lp_i2s_std.h"
#include "driver/lp_io.h"
#include "driver/rtc_io.h"
#include "esp_private/periph_ctrl.h"
#include "i2s_private.h"
static const char *TAG = "LP_I2S_STD";
static esp_err_t s_io_init(lp_i2s_chan_handle_t chan, const lp_i2s_std_gpio_config_t *config);
esp_err_t lp_i2s_channel_init_std_mode(lp_i2s_chan_handle_t handle, const lp_i2s_std_config_t *std_cfg)
{
ESP_RETURN_ON_FALSE(handle && std_cfg, ESP_ERR_INVALID_ARG, TAG, "invalid argument: null pointer");
ESP_RETURN_ON_FALSE(std_cfg->slot_cfg.data_bit_width <= LP_I2S_LL_TDM_MAX_DATA_BIT_WIDTH && std_cfg->slot_cfg.slot_bit_width <= LP_I2S_LL_TDM_MAX_CHAN_BIT_WIDTH, ESP_ERR_INVALID_ARG, TAG, "invalid argument: wrong data bit width or slot bit width");
ESP_RETURN_ON_FALSE(handle->state < I2S_CHAN_STATE_RUNNING, ESP_ERR_INVALID_STATE, TAG, "the channel is in enabled state already");
//Pin config
ESP_RETURN_ON_ERROR(s_io_init(handle, &std_cfg->pin_cfg), TAG, "failed to init IOs");
//Clock config
if (handle->role == I2S_ROLE_SLAVE) {
PERIPH_RCC_ATOMIC() {
lp_i2s_ll_select_rx_clk_source(handle->ctlr->id, LP_I2S_CLK_SRC_XTAL_D2);
}
lp_i2s_ll_clk_source_div_num(handle->ctlr->id, 2);
lp_i2s_ll_rx_set_raw_clk_div(handle->ctlr->id, 0, 0);
lp_i2s_ll_rx_set_bck_div_num(handle->ctlr->hal.dev, 1);
}
// TODO: make this divisions configurable when support master mode.
//Slot config
uint32_t slot_bit_width = (int)std_cfg->slot_cfg.slot_bit_width < (int)std_cfg->slot_cfg.data_bit_width ?
std_cfg->slot_cfg.data_bit_width : std_cfg->slot_cfg.slot_bit_width;
lp_i2s_ll_rx_set_sample_bit(handle->ctlr->hal.dev, slot_bit_width, std_cfg->slot_cfg.data_bit_width);
lp_i2s_ll_rx_enable_mono_mode(handle->ctlr->hal.dev, std_cfg->slot_cfg.slot_mode == I2S_SLOT_MODE_MONO);
lp_i2s_ll_rx_enable_msb_shift(handle->ctlr->hal.dev, std_cfg->slot_cfg.bit_shift);
lp_i2s_ll_rx_set_ws_width(handle->ctlr->hal.dev, std_cfg->slot_cfg.ws_width);
lp_i2s_ll_rx_select_std_chan(handle->ctlr->hal.dev, (uint32_t)std_cfg->slot_cfg.slot_mask);
lp_i2s_ll_rx_set_half_sample_bits(handle->ctlr->hal.dev, slot_bit_width);
lp_i2s_ll_rx_set_ws_pol(handle->ctlr->hal.dev, std_cfg->slot_cfg.ws_pol);
lp_i2s_ll_rx_set_bit_order(handle->ctlr->hal.dev, std_cfg->slot_cfg.bit_order_lsb);
lp_i2s_ll_rx_set_alignment_mode(handle->ctlr->hal.dev, std_cfg->slot_cfg.left_align);
lp_i2s_ll_rx_set_endian(handle->ctlr->hal.dev, std_cfg->slot_cfg.big_endian);
lp_i2s_ll_rx_enable_tdm(handle->ctlr->hal.dev);
return ESP_OK;
}
/*---------------------------------------------------------------
IO
---------------------------------------------------------------*/
static esp_err_t s_io_init(lp_i2s_chan_handle_t chan, const lp_i2s_std_gpio_config_t *config)
{
if (config->bck >= 0) {
rtc_gpio_set_direction(config->bck, RTC_GPIO_MODE_INPUT_ONLY);
rtc_gpio_init(config->bck);
rtc_gpio_iomux_func_sel(config->bck, 0);
if (chan->role == I2S_ROLE_MASTER) {
lp_gpio_connect_in_signal(config->bck, lp_i2s_periph_signal[chan->ctlr->id].m_rx_bck_sig, 0);
} else {
lp_gpio_connect_in_signal(config->bck, lp_i2s_periph_signal[chan->ctlr->id].s_rx_bck_sig, 0);
}
ESP_LOGD(TAG, "bck io: %d, role: %d, signal: %"PRId8, config->bck, chan->role, chan->role ? lp_i2s_periph_signal[chan->ctlr->id].s_rx_bck_sig : lp_i2s_periph_signal[chan->ctlr->id].m_rx_bck_sig);
}
if (config->ws >= 0) {
rtc_gpio_set_direction(config->ws, RTC_GPIO_MODE_INPUT_ONLY);
rtc_gpio_init(config->ws);
rtc_gpio_iomux_func_sel(config->ws, 0);
if (chan->role == I2S_ROLE_MASTER) {
lp_gpio_connect_in_signal(config->ws, lp_i2s_periph_signal[chan->ctlr->id].m_rx_ws_sig, 0);
} else {
lp_gpio_connect_in_signal(config->ws, lp_i2s_periph_signal[chan->ctlr->id].s_rx_ws_sig, 0);
}
ESP_LOGD(TAG, "ws io: %d, role: %d, signal: %"PRId8, config->ws, chan->role, chan->role ? lp_i2s_periph_signal[chan->ctlr->id].s_rx_ws_sig : lp_i2s_periph_signal[chan->ctlr->id].m_rx_ws_sig);
}
if (config->din >= 0) {
rtc_gpio_set_direction(config->din, RTC_GPIO_MODE_INPUT_ONLY);
rtc_gpio_init(config->din);
rtc_gpio_iomux_func_sel(config->din, 0);
lp_gpio_connect_in_signal(config->din, lp_i2s_periph_signal[chan->ctlr->id].data_in_sigs[0], 0);
ESP_LOGD(TAG, "din io: %d, signal: %"PRId8, config->din, lp_i2s_periph_signal[chan->ctlr->id].data_in_sigs[0]);
}
return ESP_OK;
}

View File

@ -21,3 +21,9 @@ components/esp_driver_i2s/test_apps/i2s_multi_dev:
reason: lack of runners
depends_components:
- esp_driver_i2s
components/esp_driver_i2s/test_apps/lp_i2s:
disable:
- if: SOC_LP_I2S_SUPPORTED != 1
depends_components:
- esp_driver_i2s

View File

@ -167,7 +167,7 @@ esp_err_t esp_lcd_new_i80_bus(const esp_lcd_i80_bus_config_t *bus_config, esp_lc
// LCD mode can't work with other modes at the same time, we need to register the driver object to the I2S platform
int bus_id = -1;
for (int i = 0; i < SOC_LCD_I80_BUSES; i++) {
if (i2s_platform_acquire_occupation(i, "esp_lcd_panel_io_i2s") == ESP_OK) {
if (i2s_platform_acquire_occupation(I2S_CTLR_HP, i, "esp_lcd_panel_io_i2s") == ESP_OK) {
bus_id = i;
break;
}
@ -231,7 +231,7 @@ err:
esp_intr_free(bus->intr);
}
if (bus->bus_id >= 0) {
i2s_platform_release_occupation(bus->bus_id);
i2s_platform_release_occupation(I2S_CTLR_HP, bus->bus_id);
}
if (bus->format_buffer) {
free(bus->format_buffer);
@ -250,7 +250,7 @@ esp_err_t esp_lcd_del_i80_bus(esp_lcd_i80_bus_handle_t bus)
ESP_GOTO_ON_FALSE(bus, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
ESP_GOTO_ON_FALSE(LIST_EMPTY(&bus->device_list), ESP_ERR_INVALID_STATE, err, TAG, "device list not empty");
int bus_id = bus->bus_id;
i2s_platform_release_occupation(bus_id);
i2s_platform_release_occupation(I2S_CTLR_HP, bus_id);
esp_intr_free(bus->intr);
if (bus->pm_lock) {
esp_pm_lock_delete(bus->pm_lock);

View File

@ -92,6 +92,10 @@ if(NOT BOOTLOADER_BUILD)
list(APPEND srcs "isp_hal.c")
endif()
if(CONFIG_SOC_LP_I2S_SUPPORTED)
list(APPEND srcs "lp_i2s_hal.c")
endif()
if(CONFIG_SOC_RMT_SUPPORTED)
list(APPEND srcs "rmt_hal.c")
endif()

View File

@ -16,8 +16,12 @@
#include <math.h>
#include "hal/misc.h"
#include "hal/assert.h"
#include "hal/i2s_types.h"
#include "soc/lp_i2s_struct.h"
#include "soc/lpperi_struct.h"
#include "soc/lp_system_struct.h"
#include "soc/clk_tree_defs.h"
#include "soc/reg_base.h"
#ifdef __cplusplus
extern "C" {
@ -29,9 +33,28 @@ extern "C" {
#define LP_I2S_LL_EVENT_RX_HUNG_INT_INT (1<<1)
#define LP_I2S_LL_EVENT_RX_FIFOMEM_UDF_INT (1<<2)
#define LP_I2S_LL_EVENT_RX_MEM_THRESHOLD_INT (1<<5)
#define LP_I2S_LL_TDM_CH_MASK (0x03UL)
#define LP_I2S_LL_RX_SUPPORTED 1
#define LP_I2S_LL_TDM_MAX_DATA_BIT_WIDTH 16
#define LP_I2S_LL_TDM_MAX_CHAN_BIT_WIDTH 16
#define LP_I2S_LL_RX_MEM_THRESH_BYTES_MAX 1020
#define LP_I2S_LL_RX_MEM_POP_BYTES 4
#define LP_I2S_LL_CUT_OFF_COEF_X_NUM 21
#define LP_I2S_LL_CUT_OFF_COEF_Y_NUM 3
/* PDM high pass filter cut-off frequency and coefficients list
* [0]: cut-off frequency * 10; [1]: param0; [2]: param5
* NOTE: the cut-off frequency was timed 10 to use integer type */
#define LP_I2S_LL_CUT_OFF_COEF {{1850, 0, 0}, {1720, 0, 1}, {1600, 1, 1}, \
{1500, 1, 2}, {1370, 2, 2}, {1260, 2, 3}, \
{1200, 0, 3}, {1150, 3, 3}, {1060, 1, 7}, \
{1040, 2, 4}, {920, 4, 4}, {915, 2, 7}, \
{810, 4, 5}, {772, 3, 7}, {690, 5, 5}, \
{630, 4, 7}, {580, 5, 6}, {490, 5, 7}, \
{460, 6, 6}, {355, 6, 7}, {233, 7, 7}}
/**
* @brief LP I2S rx stop mode enum type
*/
@ -72,6 +95,26 @@ typedef enum {
LP_I2S_LL_RX_BIG_ENDIAN,
} lp_i2s_ll_rx_endian_t;
/*---------------------------------------------------------------
Mem
---------------------------------------------------------------*/
/**
* @brief Enable the internal memory for LP I2S module
*
* @param id Instance id
* @param en enable / disable
*/
static inline void lp_i2s_ll_enable_mem(int id, bool en)
{
LP_SYS.lp_mem_aux_ctrl.lp_mem_aux_ctrl = !en;
}
static inline void lp_i2s_ll_set_rx_mem_threshold(lp_i2s_dev_t *hw, uint32_t words)
{
LP_I2S.rx_mem_conf.rx_mem_threshold = words;
}
/*---------------------------------------------------------------
Clock
---------------------------------------------------------------*/
@ -120,6 +163,33 @@ static inline void lp_i2s_ll_enable_rx_module_clock(int id, bool en)
/// the critical section needs to declare the __DECLARE_RCC_ATOMIC_ENV variable in advance
#define lp_i2s_ll_enable_rx_module_clock(...) (void)__DECLARE_RCC_ATOMIC_ENV; lp_i2s_ll_enable_rx_module_clock(__VA_ARGS__)
/**
* @brief Select ISP clock source
*
* @param id Instance id
* @param clk_src clock source, see valid sources in type `soc_periph_lp_i2s_clk_src_t`
*/
static inline void lp_i2s_ll_select_rx_clk_source(int id, soc_periph_lp_i2s_clk_src_t clk_src)
{
uint8_t clk_val = 0;
switch (clk_src) {
case LP_I2S_CLK_SRC_LP_PERI:
clk_val = 0;
break;
case LP_I2S_CLK_SRC_XTAL_D2:
clk_val = 1;
break;
default:
HAL_ASSERT(false);
break;
}
LPPERI.core_clk_sel.lp_i2s_rx_clk_sel = clk_val;
}
/// use a macro to wrap the function, force the caller to use it in a critical section
/// the critical section needs to declare the __DECLARE_RCC_ATOMIC_ENV variable in advance
#define lp_i2s_ll_select_rx_clk_source(...) (void)__DECLARE_RCC_ATOMIC_ENV; lp_i2s_ll_select_rx_clk_source(__VA_ARGS__)
/**
* @brief Set LP I2S clock source div num
*
@ -146,7 +216,7 @@ static inline void lp_i2s_ll_clk_source_div_num(int id, uint32_t val)
* @param a div a
* @param b div b
*/
static inline void lp_i2s_ll_tx_set_raw_clk_div(int id, uint32_t a, uint32_t b)
static inline void lp_i2s_ll_rx_set_raw_clk_div(int id, uint32_t a, uint32_t b)
{
if (b <= a / 2) {
LPPERI.lp_i2s_rxclk_div_xyz.lp_i2s_rx_clkm_div_yn1 = 0;
@ -170,7 +240,7 @@ static inline void lp_i2s_ll_tx_set_raw_clk_div(int id, uint32_t a, uint32_t b)
/**
* @brief Set LP I2S rx bck div num
*
* @param id Instance id
* @param hw LP I2S hardware instance
* @param val value to set rx bck div num
*/
static inline void lp_i2s_ll_rx_set_bck_div_num(lp_i2s_dev_t *hw, uint32_t val)
@ -178,13 +248,46 @@ static inline void lp_i2s_ll_rx_set_bck_div_num(lp_i2s_dev_t *hw, uint32_t val)
hw->rx_conf1.rx_bck_div_num = val - 1;
}
/**
* @brief Enable/Disable clock gate
*
* @param hw LP I2S hardware instance
* @param enable enable or disable
*/
static inline void lp_i2s_ll_clk_gate_en(lp_i2s_dev_t *hw, bool enable)
{
hw->clk_gate.clk_en = enable;
}
/**
* @brief Enable/Disable rx mem clock gate
*
* @param hw LP I2S hardware instance
* @param enable enable or disable
*/
static inline void lp_i2s_ll_rx_mem_clk_gate_en(lp_i2s_dev_t *hw, bool enable)
{
hw->clk_gate.rx_mem_cg_force_on = enable;
}
/**
* @brief Enable/Disable rx reg clock gate
*
* @param hw LP I2S hardware instance
* @param enable enable or disable
*/
static inline void lp_i2s_ll_rx_reg_clk_gate_en(lp_i2s_dev_t *hw, bool enable)
{
hw->clk_gate.rx_reg_cg_force_on = enable;
}
/*---------------------------------------------------------------
Reset
---------------------------------------------------------------*/
/**
* @brief Reset LP I2S RX module
*
* @param id Instance id
* @param hw LP I2S hardware instance
*/
static inline void lp_i2s_ll_rx_reset(lp_i2s_dev_t *hw)
{
@ -195,7 +298,7 @@ static inline void lp_i2s_ll_rx_reset(lp_i2s_dev_t *hw)
/**
* @brief Reset LP I2S RX FIFO
*
* @param id Instance id
* @param hw LP I2S hardware instance
*/
static inline void lp_i2s_ll_rx_reset_fifo(lp_i2s_dev_t *hw)
{
@ -203,13 +306,24 @@ static inline void lp_i2s_ll_rx_reset_fifo(lp_i2s_dev_t *hw)
hw->rx_conf.rx_fifo_reset = 0;
}
/**
* @brief Reset LP I2S RX FIFO mem
*
* @param hw LP I2S hardware instance
*/
static inline void lp_i2s_ll_rx_reset_fifo_mem(lp_i2s_dev_t *hw)
{
hw->rx_conf.rx_fifomem_reset = 1;
hw->rx_conf.rx_fifomem_reset = 0;
}
/*---------------------------------------------------------------
Master/Slave
---------------------------------------------------------------*/
/**
* @brief Start LP I2S RX
*
* @param id Instance id
* @param hw LP I2S hardware instance
*/
static inline void lp_i2s_ll_rx_start(lp_i2s_dev_t *hw)
{
@ -221,7 +335,7 @@ static inline void lp_i2s_ll_rx_start(lp_i2s_dev_t *hw)
/**
* @brief Set LP I2S RX stop mode
*
* @param id Instance id
* @param hw LP I2S hardware instance
*/
static inline void lp_i2s_ll_set_rx_stop_mode(lp_i2s_dev_t *hw, lp_i2s_ll_rx_stop_mode_t rx_stop_mode)
{
@ -231,7 +345,7 @@ static inline void lp_i2s_ll_set_rx_stop_mode(lp_i2s_dev_t *hw, lp_i2s_ll_rx_sto
/**
* @brief Configure the received length to trigger in_suc_eof interrupt
*
* @param id Instance id
* @param hw LP I2S hardware instance
* @param eof_num the byte length to trigger in_suc_eof interrupt
*/
static inline void lp_i2s_ll_rx_set_eof_num(lp_i2s_dev_t *hw, int eof_num)
@ -242,13 +356,24 @@ static inline void lp_i2s_ll_rx_set_eof_num(lp_i2s_dev_t *hw, int eof_num)
/**
* @brief Stop LP I2S RX
*
* @param id Instance id
* @param hw LP I2S hardware instance
*/
static inline void lp_i2s_ll_rx_stop(lp_i2s_dev_t *hw)
{
hw->rx_conf.rx_start = 0;
}
/**
* @brief Stop LP I2S RX
*
* @param hw LP I2S hardware instance
* @param is_slave Is slave or master
*/
static inline void lp_i2s_ll_set_rx_master_slave_mode(lp_i2s_dev_t *hw, bool is_slave)
{
hw->rx_conf.rx_slave_mod = is_slave;
}
/*---------------------------------------------------------------
Receive
---------------------------------------------------------------*/
@ -291,11 +416,64 @@ static inline void lp_i2s_ll_rx_enable_pdm(lp_i2s_dev_t *hw)
hw->rx_conf.rx_tdm_en = 0;
}
/**
* @brief Enable LP I2S RX PDM high pass filter
*
* @param hw LP I2S hardware instance
* @param enable Set true to enable I2S RX PDM high pass filter, set false to bypass it
*/
static inline void lp_i2s_ll_rx_enable_pdm_hp_filter(lp_i2s_dev_t *hw, bool enable)
{
hw->rx_pdm_conf.rx_pdm_hp_bypass = !enable;
}
/**
* @brief Set LP_I2S RX PDM high pass filter param0
*
* @param hw Peripheral LP_I2S hardware instance address.
* @param param The fourth parameter of PDM RX IIR_HP filter stage 1 is (504 + LP_I2S_RX_IIR_HP_MULT12_0[2:0])
*/
static inline void lp_i2s_ll_rx_set_pdm_hp_filter_param0(lp_i2s_dev_t *hw, uint32_t param)
{
hw->rx_pdm_conf.rx_iir_hp_mult12_0 = param;
}
/**
* @brief Set LP I2S RX PDM high pass filter param5
*
* @param hw Peripheral LP_I2S hardware instance address.
* @param param The fourth parameter of PDM RX IIR_HP filter stage 2 is (504 + LP_I2S_RX_IIR_HP_MULT12_5[2:0])
*/
static inline void lp_i2s_ll_rx_set_pdm_hp_filter_param5(lp_i2s_dev_t *hw, uint32_t param)
{
hw->rx_pdm_conf.rx_iir_hp_mult12_5 = param;
}
/**
* @brief Configure RX PDM amplify number
* @note This is the amplification number of the digital amplifier,
* which is added after the PDM to PCM conversion result and mainly used for
* amplify the small PDM signal under the VAD scenario
* pcm_result = pdm_input * amplify_num
* pcm_result = 0 if amplify_num = 0
*
* @param hw Peripheral LP_I2S hardware instance address.
* @param amp_num PDM RX amplify number
*/
static inline void lp_i2s_ll_rx_set_pdm_amplify_num(lp_i2s_dev_t *hw, uint32_t amp_num)
{
hw->rx_pdm_conf.rx_pdm2pcm_amplify_num = amp_num;
}
/*---------------------------------------------------------------
General
---------------------------------------------------------------*/
/**
* @brief Configure LP I2S rx channel bits and bits mode
*
* @param hw LP I2S hardware instance
* @param chan_bits Chan bits
* @param bits_mode Bits mode
*/
static inline void lp_i2s_ll_rx_set_sample_bit(lp_i2s_dev_t *hw, int chan_bits, int bits_mode)
{
@ -351,6 +529,34 @@ static inline void lp_i2s_ll_rx_set_active_chan(lp_i2s_dev_t *hw, uint32_t chan_
hw->rx_tdm_ctrl.val = tdm_ctrl;
}
/**
* @brief Set LP I2S rx std channels
*
* @param hw LP I2S hardware instance
* @param chan_mask select chan to receive data
*/
static inline void lp_i2s_ll_rx_select_std_chan(lp_i2s_dev_t *hw, i2s_std_slot_mask_t chan_mask)
{
hw->rx_tdm_ctrl.rx_tdm_tot_chan_num = 1;
uint32_t mask = 0;
switch (chan_mask)
{
case I2S_STD_SLOT_LEFT:
mask = 0x01;
break;
case I2S_STD_SLOT_RIGHT:
mask = 0x02;
break;
case I2S_STD_SLOT_BOTH:
mask = 0x03;
break;
default:
HAL_ASSERT(false);
break;
}
lp_i2s_ll_rx_set_active_chan(hw, mask);
}
/**
* @brief Set LP I2S rx pdm2pcm enable
*
@ -406,6 +612,68 @@ static inline void lp_i2s_ll_rx_set_endian(lp_i2s_dev_t *hw, lp_i2s_ll_rx_endian
hw->rx_conf.rx_big_endian = endian;
}
/**
* @brief Enable RX mono mode
*
* @param hw LP I2S hardware instance
* @param mono_ena Set true to enable mono mde.
*/
static inline void lp_i2s_ll_rx_enable_mono_mode(lp_i2s_dev_t *hw, bool mono_ena)
{
hw->rx_conf.rx_mono = mono_ena;
hw->rx_conf.rx_mono_fst_vld = mono_ena;
}
/**
* @brief Enable RX MSB shift, the data will be launch at the first BCK clock
*
* @param hw LP I2S hardware instance
* @param msb_shift_enable Set true to enable MSB shift
*/
static inline void lp_i2s_ll_rx_enable_msb_shift(lp_i2s_dev_t *hw, bool msb_shift_enable)
{
hw->rx_conf1.rx_msb_shift = msb_shift_enable;
}
/**
* Read from LP I2S hardware data buffer.
*
* @param hw Beginning address of the peripheral registers.
* @param buffer_to_rcv Address of a buffer to read data from hardware data buffer
* @param byte_len Length to read, in bytes.
*/
static inline void lp_i2s_ll_read_buffer(lp_i2s_dev_t *hw, void *buffer_to_rcv, size_t byte_len)
{
for (int i = 0; i < byte_len; i += 4) {
*(uint32_t *)(buffer_to_rcv + i) = *((uint32_t *)LP_I2S_RAM_BASE);
}
}
/**
* Write from LP I2S hardware data buffer.
*
* @param hw Beginning address of the peripheral registers.
* @param buffer_to_rcv Address of a buffer to write data from hardware data buffer
* @param bitlbyte_lenen Length to write, in bytes.
*/
static inline void lp_i2s_ll_write_buffer(lp_i2s_dev_t *hw, const void *buffer_to_send, size_t byte_len)
{
for (int i = 0; i < byte_len; i += 4) {
//Use memcpy to get around alignment issues for txdata
*((uint32_t *)LP_I2S_RAM_BASE) = *(uint32_t *)(buffer_to_send + i);
}
}
/**
* Get RX mem fifo cnt, in bytes
*
* @param hw Beginning address of the peripheral registers.
*/
static inline uint32_t lp_i2s_ll_get_rx_mem_fifo_cnt(lp_i2s_dev_t *hw)
{
return hw->rx_mem_conf.rx_mem_fifo_cnt * 4;
}
/*---------------------------------------------------------------
Interrupt
---------------------------------------------------------------*/
@ -425,6 +693,18 @@ static inline uint32_t lp_i2s_ll_rx_get_interrupt_status(lp_i2s_dev_t *hw, bool
}
}
/**
* @brief Get interrupt status reg address
*
* @param[in] hw LP I2S hardware instance
*
* @return Interrupt status reg address
*/
static inline uint32_t lp_i2s_ll_get_intr_status_reg_addr(lp_i2s_dev_t *hw)
{
return (uint32_t)&(hw->int_st);
}
/**
* @brief Enable LP I2S RX channel interrupt
*

View File

@ -0,0 +1,46 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
/*******************************************************************************
* NOTICE
* The hal is not public api, don't use in application code.
* See readme.md in hal/include/hal/readme.md
******************************************************************************/
#pragma once
#include <stdint.h>
#include "soc/soc_caps.h"
#ifdef __cplusplus
extern "C" {
#endif
#if SOC_LP_I2S_SUPPORTED
typedef struct lp_i2s_dev_t *lp_i2s_soc_handle_t; // LP I2S SOC layer handle
#else
typedef uint32_t *lp_i2s_soc_handle_t; // LP I2S SOC layer handle
#endif
/**
* Context that should be maintained by both the driver and the HAL
*/
typedef struct {
lp_i2s_soc_handle_t dev; // LP I2S SOC layer handle
} lp_i2s_hal_context_t;
/**
* @brief Init the LP I2S hal and set the LP I2S to the default configuration.
* @note This function should be called first before other hal layer function is called.
*
* @param hal Context of the HAL layer
* @param group_id LP I2S group ID
*/
void lp_i2s_hal_init(lp_i2s_hal_context_t *hal, int group_id);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,16 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
// The HAL layer for LP I2S
#include "soc/soc.h"
#include "hal/lp_i2s_hal.h"
#include "hal/lp_i2s_ll.h"
void lp_i2s_hal_init(lp_i2s_hal_context_t *hal, int group_id)
{
hal->dev = LP_I2S_LL_GET_HW(group_id);
}

View File

@ -1,11 +1,12 @@
/*
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "soc/i2s_periph.h"
#include "soc/gpio_sig_map.h"
#include "soc/lp_gpio_sig_map.h"
/*
Bunch of constants for every I2S peripheral: GPIO signals, irqs, hw addr of registers etc
@ -84,3 +85,29 @@ const i2s_signal_conn_t i2s_periph_signal[SOC_I2S_NUM] = {
.module = PERIPH_I2S2_MODULE,
},
};
const i2s_signal_conn_t lp_i2s_periph_signal[SOC_LP_I2S_NUM] = {
[0] = {
.mck_out_sig = -1,
.mck_in_sig = -1,
.m_tx_bck_sig = LP_I2S_O_BCK_PAD_OUT_IDX,
.m_rx_bck_sig = LP_I2S_I_BCK_PAD_OUT_IDX,
.m_tx_ws_sig = LP_I2S_O_WS_PAD_OUT_IDX,
.m_rx_ws_sig = LP_I2S_I_WS_PAD_OUT_IDX,
.s_tx_bck_sig = LP_I2S_O_BCK_PAD_IN_IDX,
.s_rx_bck_sig = LP_I2S_I_BCK_PAD_IN_IDX,
.s_tx_ws_sig = LP_I2S_O_WS_PAD_IN_IDX,
.s_rx_ws_sig = LP_I2S_I_WS_PAD_IN_IDX,
.data_out_sigs[0] = LP_I2S_O_SD_PAD_OUT_IDX,
.data_out_sigs[1] = -1,
.data_in_sigs[0] = LP_I2S_I_SD_PAD_IN_IDX,
.data_in_sigs[1] = -1,
.data_in_sigs[2] = -1,
.data_in_sigs[3] = -1,
.irq = ETS_LP_I2S_INTR_SOURCE,
},
};

View File

@ -243,6 +243,10 @@ config SOC_LP_I2C_SUPPORTED
bool
default y
config SOC_LP_I2S_SUPPORTED
bool
default y
config SOC_LP_SPI_SUPPORTED
bool
default y
@ -815,6 +819,10 @@ config SOC_I2S_TDM_FULL_DATA_WIDTH
bool
default y
config SOC_LP_I2S_NUM
int
default 1
config SOC_ISP_BF_SUPPORTED
bool
default y

View File

@ -889,7 +889,7 @@ typedef union {
} lp_i2s_date_reg_t;
typedef struct {
typedef struct lp_i2s_dev_t {
volatile lp_i2s_vad_conf_reg_t vad_conf;
volatile lp_i2s_vad_result_reg_t vad_result;
volatile lp_i2s_rx_mem_conf_reg_t rx_mem_conf;

View File

@ -163,9 +163,11 @@
/**
* @brief: Special memory address
*/
#define LP_I2S_RAM_BASE 0x50125c00
#define MIPI_CSI_BRG_MEM_BASE 0x50104000
#define MIPI_DSI_BRG_MEM_BASE 0x50105000
/**
* This are module helper MACROs for quick module reference
* including some module(renamed) address

View File

@ -78,6 +78,7 @@
#define SOC_LP_GPIO_MATRIX_SUPPORTED 1
#define SOC_LP_PERIPHERALS_SUPPORTED 1
#define SOC_LP_I2C_SUPPORTED 1
#define SOC_LP_I2S_SUPPORTED 1
#define SOC_LP_SPI_SUPPORTED 1
#define SOC_SPIRAM_SUPPORTED 1
#define SOC_PSRAM_DMA_CAPABLE 1
@ -325,6 +326,9 @@
#define SOC_I2S_PDM_MAX_RX_LINES (4) // On I2S0
#define SOC_I2S_TDM_FULL_DATA_WIDTH (1) /*!< No limitation to data bit width when using multiple slots */
/*-------------------------- LP_I2S CAPS -------------------------------------*/
#define SOC_LP_I2S_NUM (1U)
/*-------------------------- ISP CAPS ----------------------------------------*/
#define SOC_ISP_BF_SUPPORTED 1
#define SOC_ISP_CCM_SUPPORTED 1

View File

@ -56,6 +56,10 @@ typedef struct {
extern const i2s_signal_conn_t i2s_periph_signal[SOC_I2S_NUM];
#if SOC_LP_I2S_SUPPORTED
extern const i2s_signal_conn_t lp_i2s_periph_signal[SOC_LP_I2S_NUM];
#endif
#endif // SOC_I2S_SUPPORTED
#ifdef __cplusplus