mirror of
https://github.com/espressif/esp-idf.git
synced 2024-10-05 20:47:46 -04:00
332 lines
13 KiB
C
332 lines
13 KiB
C
/*
|
|
* 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, "tx not supported");
|
|
#endif
|
|
#if !LP_I2S_LL_RX_SUPPORTED
|
|
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;
|
|
#if LP_I2S_LL_TX_SUPPORTED
|
|
chan_search_mask |= ret_tx_handle ? I2S_DIR_TX : 0;
|
|
#endif
|
|
#if LP_I2S_LL_RX_SUPPORTED
|
|
chan_search_mask |= ret_rx_handle ? I2S_DIR_RX : 0;
|
|
#endif
|
|
|
|
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();
|
|
}
|
|
}
|