mirror of
https://github.com/espressif/esp-idf.git
synced 2024-10-05 20:47:46 -04:00
feat(lp_i2s): lp_i2s driver
This commit is contained in:
parent
b5ab82ce3c
commit
ee8933f651
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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 */
|
||||
|
@ -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
|
||||
|
133
components/esp_driver_i2s/include/driver/lp_i2s.h
Normal file
133
components/esp_driver_i2s/include/driver/lp_i2s.h
Normal 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
|
100
components/esp_driver_i2s/include/driver/lp_i2s_pdm.h
Normal file
100
components/esp_driver_i2s/include/driver/lp_i2s_pdm.h
Normal 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
|
124
components/esp_driver_i2s/include/driver/lp_i2s_std.h
Normal file
124
components/esp_driver_i2s/include/driver/lp_i2s_std.h
Normal 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
|
@ -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`
|
||||
|
331
components/esp_driver_i2s/lp_i2s.c
Normal file
331
components/esp_driver_i2s/lp_i2s.c
Normal 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();
|
||||
}
|
||||
}
|
114
components/esp_driver_i2s/lp_i2s_pdm.c
Normal file
114
components/esp_driver_i2s/lp_i2s_pdm.c
Normal 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, ¶m0, ¶m5);
|
||||
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;
|
||||
}
|
102
components/esp_driver_i2s/lp_i2s_std.c
Normal file
102
components/esp_driver_i2s/lp_i2s_std.c
Normal 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;
|
||||
}
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
*
|
||||
|
46
components/hal/include/hal/lp_i2s_hal.h
Normal file
46
components/hal/include/hal/lp_i2s_hal.h
Normal 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
|
16
components/hal/lp_i2s_hal.c
Normal file
16
components/hal/lp_i2s_hal.c
Normal 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);
|
||||
}
|
@ -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,
|
||||
},
|
||||
};
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user