mirror of
				https://github.com/espressif/esp-idf.git
				synced 2025-11-04 00:51:42 +01:00 
			
		
		
		
	
		
			
	
	
		
			236 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
		
		
			
		
	
	
			236 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| 
								 | 
							
								// Copyright 2020 Espressif Systems (Shanghai) PTE LTD
							 | 
						||
| 
								 | 
							
								//
							 | 
						||
| 
								 | 
							
								// Licensed under the Apache License, Version 2.0 (the "License");
							 | 
						||
| 
								 | 
							
								// you may not use this file except in compliance with the License.
							 | 
						||
| 
								 | 
							
								// You may obtain a copy of the License at
							 | 
						||
| 
								 | 
							
								//
							 | 
						||
| 
								 | 
							
								//     http://www.apache.org/licenses/LICENSE-2.0
							 | 
						||
| 
								 | 
							
								//
							 | 
						||
| 
								 | 
							
								// Unless required by applicable law or agreed to in writing, software
							 | 
						||
| 
								 | 
							
								// distributed under the License is distributed on an "AS IS" BASIS,
							 | 
						||
| 
								 | 
							
								// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
							 | 
						||
| 
								 | 
							
								// See the License for the specific language governing permissions and
							 | 
						||
| 
								 | 
							
								// limitations under the License.
							 | 
						||
| 
								 | 
							
								#include <sys/cdefs.h>
							 | 
						||
| 
								 | 
							
								#include <stdatomic.h>
							 | 
						||
| 
								 | 
							
								#include "freertos/FreeRTOS.h"
							 | 
						||
| 
								 | 
							
								#include "freertos/semphr.h"
							 | 
						||
| 
								 | 
							
								#include "esp_compiler.h"
							 | 
						||
| 
								 | 
							
								#include "esp_intr_alloc.h"
							 | 
						||
| 
								 | 
							
								#include "esp_heap_caps.h"
							 | 
						||
| 
								 | 
							
								#include "esp_log.h"
							 | 
						||
| 
								 | 
							
								#include "soc/soc_caps.h"
							 | 
						||
| 
								 | 
							
								#include "soc/cp_dma_caps.h"
							 | 
						||
| 
								 | 
							
								#include "hal/cp_dma_hal.h"
							 | 
						||
| 
								 | 
							
								#include "hal/cp_dma_ll.h"
							 | 
						||
| 
								 | 
							
								#include "cp_dma.h"
							 | 
						||
| 
								 | 
							
								#include "soc/periph_defs.h"
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								static const char *TAG = "cp_dma";
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#define CP_DMA_CHECK(a, msg, tag, ret, ...)                                       \
							 | 
						||
| 
								 | 
							
								    do {                                                                          \
							 | 
						||
| 
								 | 
							
								        if (unlikely(!(a))) {                                                     \
							 | 
						||
| 
								 | 
							
								            ESP_LOGE(TAG, "%s(%d): " msg, __FUNCTION__, __LINE__, ##__VA_ARGS__); \
							 | 
						||
| 
								 | 
							
								            ret_code = ret;                                                       \
							 | 
						||
| 
								 | 
							
								            goto tag;                                                             \
							 | 
						||
| 
								 | 
							
								        }                                                                         \
							 | 
						||
| 
								 | 
							
								    } while (0)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * @brief Stream is high level abstraction over descriptor.
							 | 
						||
| 
								 | 
							
								 *        It combines the descriptor used by DMA and the callback function registered by user.
							 | 
						||
| 
								 | 
							
								 *        The benifit is, we can converter the descriptor address into stream handle.
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								typedef struct {
							 | 
						||
| 
								 | 
							
								    cp_dma_descriptor_t tx_desc;
							 | 
						||
| 
								 | 
							
								    cp_dma_isr_cb_t cb;
							 | 
						||
| 
								 | 
							
								    void *cb_args;
							 | 
						||
| 
								 | 
							
								} cp_dma_out_stream_t;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								typedef struct {
							 | 
						||
| 
								 | 
							
								    cp_dma_descriptor_t rx_desc;
							 | 
						||
| 
								 | 
							
								    cp_dma_isr_cb_t cb;
							 | 
						||
| 
								 | 
							
								    void *cb_args;
							 | 
						||
| 
								 | 
							
								} cp_dma_in_stream_t;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								typedef struct cp_dma_driver_context_s {
							 | 
						||
| 
								 | 
							
								    uint32_t max_out_stream;
							 | 
						||
| 
								 | 
							
								    uint32_t max_in_stream;
							 | 
						||
| 
								 | 
							
								    uint32_t flags;
							 | 
						||
| 
								 | 
							
								    cp_dma_hal_context_t hal; // HAL context
							 | 
						||
| 
								 | 
							
								    intr_handle_t intr_hdl; // interrupt handle
							 | 
						||
| 
								 | 
							
								    portMUX_TYPE spin_lock;
							 | 
						||
| 
								 | 
							
								    cp_dma_out_stream_t *out_streams; // pointer to the first out stream
							 | 
						||
| 
								 | 
							
								    cp_dma_in_stream_t *in_streams; // pointer to the first in stream
							 | 
						||
| 
								 | 
							
								    uint8_t streams[0]; // stream buffer (out streams + in streams), the size if configured by user
							 | 
						||
| 
								 | 
							
								} cp_dma_driver_context_t;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								static void cp_dma_isr_default_handler(void *arg) IRAM_ATTR;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								esp_err_t cp_dma_driver_install(const cp_dma_config_t *config, cp_dma_driver_t *drv_hdl)
							 | 
						||
| 
								 | 
							
								{
							 | 
						||
| 
								 | 
							
								    esp_err_t ret_code = ESP_OK;
							 | 
						||
| 
								 | 
							
								    cp_dma_driver_context_t *cp_dma_driver = NULL;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    CP_DMA_CHECK(config, "configuration can't be null", err, ESP_ERR_INVALID_ARG);
							 | 
						||
| 
								 | 
							
								    CP_DMA_CHECK(drv_hdl, "driver handle can't be null", err, ESP_ERR_INVALID_ARG);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    size_t total_malloc_size = sizeof(cp_dma_driver_context_t) + sizeof(cp_dma_out_stream_t) * config->max_out_stream + sizeof(cp_dma_in_stream_t) * config->max_in_stream;
							 | 
						||
| 
								 | 
							
								    if (config->flags & CP_DMA_FLAGS_WORK_WITH_CACHE_DISABLED) {
							 | 
						||
| 
								 | 
							
								        // to work when cache is disabled, make sure to put driver handle in DRAM
							 | 
						||
| 
								 | 
							
								        cp_dma_driver = heap_caps_calloc(1, total_malloc_size, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
							 | 
						||
| 
								 | 
							
								    } else {
							 | 
						||
| 
								 | 
							
								        cp_dma_driver = calloc(1, total_malloc_size);
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    CP_DMA_CHECK(cp_dma_driver, "allocate driver memory failed", err, ESP_ERR_NO_MEM);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    int int_flags = 0;
							 | 
						||
| 
								 | 
							
								    if (config->flags & CP_DMA_FLAGS_WORK_WITH_CACHE_DISABLED) {
							 | 
						||
| 
								 | 
							
								        int_flags |= ESP_INTR_FLAG_IRAM; // make sure interrupt can still work when cache is disabled
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    ret_code = esp_intr_alloc(ETS_DMA_COPY_INTR_SOURCE, int_flags, cp_dma_isr_default_handler, cp_dma_driver, &cp_dma_driver->intr_hdl);
							 | 
						||
| 
								 | 
							
								    CP_DMA_CHECK(ret_code == ESP_OK, "allocate intr failed", err, ret_code);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    cp_dma_driver->out_streams = (cp_dma_out_stream_t *)cp_dma_driver->streams;
							 | 
						||
| 
								 | 
							
								    cp_dma_driver->in_streams = (cp_dma_in_stream_t *)(cp_dma_driver->streams + config->max_out_stream * sizeof(cp_dma_out_stream_t));
							 | 
						||
| 
								 | 
							
								    // HAL layer has no idea about "data stream" but TX/RX descriptors
							 | 
						||
| 
								 | 
							
								    // We put all descritprs' addresses into an array, HAL driver will make it a loop during initialization
							 | 
						||
| 
								 | 
							
								    {
							 | 
						||
| 
								 | 
							
								        cp_dma_descriptor_t *tx_descriptors[config->max_out_stream];
							 | 
						||
| 
								 | 
							
								        cp_dma_descriptor_t *rx_descriptors[config->max_in_stream];
							 | 
						||
| 
								 | 
							
								        for (int i = 0; i < config->max_out_stream; i++) {
							 | 
						||
| 
								 | 
							
								            tx_descriptors[i] = &cp_dma_driver->out_streams[i].tx_desc;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        for (int i = 0; i < config->max_in_stream; i++) {
							 | 
						||
| 
								 | 
							
								            rx_descriptors[i] = &cp_dma_driver->in_streams[i].rx_desc;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        cp_dma_hal_init(&cp_dma_driver->hal, tx_descriptors, config->max_out_stream, rx_descriptors, config->max_in_stream);
							 | 
						||
| 
								 | 
							
								    } // limit the scope of tx_descriptors and rx_descriptors so that goto can jump after this code block
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    cp_dma_driver->spin_lock = (portMUX_TYPE)portMUX_INITIALIZER_UNLOCKED;
							 | 
						||
| 
								 | 
							
								    cp_dma_driver->max_in_stream = config->max_in_stream;
							 | 
						||
| 
								 | 
							
								    cp_dma_driver->max_out_stream = config->max_out_stream;
							 | 
						||
| 
								 | 
							
								    *drv_hdl = cp_dma_driver;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    cp_dma_hal_start(&cp_dma_driver->hal); // enable DMA and interrupt
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    return ESP_OK;
							 | 
						||
| 
								 | 
							
								err:
							 | 
						||
| 
								 | 
							
								    if (cp_dma_driver) {
							 | 
						||
| 
								 | 
							
								        if (cp_dma_driver->intr_hdl) {
							 | 
						||
| 
								 | 
							
								            esp_intr_free(cp_dma_driver->intr_hdl);
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        free(cp_dma_driver);
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    if (drv_hdl) {
							 | 
						||
| 
								 | 
							
								        *drv_hdl = NULL;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    return ret_code;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								esp_err_t cp_dma_driver_uninstall(cp_dma_driver_t drv_hdl)
							 | 
						||
| 
								 | 
							
								{
							 | 
						||
| 
								 | 
							
								    esp_err_t ret_code = ESP_OK;
							 | 
						||
| 
								 | 
							
								    CP_DMA_CHECK(drv_hdl, "driver handle can't be null", err, ESP_ERR_INVALID_ARG);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    esp_intr_free(drv_hdl->intr_hdl);
							 | 
						||
| 
								 | 
							
								    cp_dma_hal_stop(&drv_hdl->hal);
							 | 
						||
| 
								 | 
							
								    cp_dma_hal_deinit(&drv_hdl->hal);
							 | 
						||
| 
								 | 
							
								    free(drv_hdl);
							 | 
						||
| 
								 | 
							
								    return ESP_OK;
							 | 
						||
| 
								 | 
							
								err:
							 | 
						||
| 
								 | 
							
								    return ret_code;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								esp_err_t cp_dma_memcpy(cp_dma_driver_t drv_hdl, void *dst, void *src, size_t n, cp_dma_isr_cb_t cb_isr, void *cb_args)
							 | 
						||
| 
								 | 
							
								{
							 | 
						||
| 
								 | 
							
								    esp_err_t ret_code = ESP_OK;
							 | 
						||
| 
								 | 
							
								    cp_dma_descriptor_t *rx_start_desc = NULL;
							 | 
						||
| 
								 | 
							
								    cp_dma_descriptor_t *rx_end_desc = NULL;
							 | 
						||
| 
								 | 
							
								    cp_dma_descriptor_t *tx_start_desc = NULL;
							 | 
						||
| 
								 | 
							
								    cp_dma_descriptor_t *tx_end_desc = NULL;
							 | 
						||
| 
								 | 
							
								    int rx_prepared_size = 0;
							 | 
						||
| 
								 | 
							
								    int tx_prepared_size = 0;
							 | 
						||
| 
								 | 
							
								    CP_DMA_CHECK(drv_hdl, "driver handle can't be null", err, ESP_ERR_INVALID_ARG);
							 | 
						||
| 
								 | 
							
								    // CP_DMA can only access SRAM
							 | 
						||
| 
								 | 
							
								    CP_DMA_CHECK(esp_ptr_internal(src) && esp_ptr_internal(dst), "address not in SRAM", err, ESP_ERR_INVALID_ARG);
							 | 
						||
| 
								 | 
							
								    CP_DMA_CHECK(n <= SOC_CP_DMA_MAX_BUFFER_SIZE * drv_hdl->max_out_stream, "exceed max num of tx stream", err, ESP_ERR_INVALID_ARG);
							 | 
						||
| 
								 | 
							
								    CP_DMA_CHECK(n <= SOC_CP_DMA_MAX_BUFFER_SIZE * drv_hdl->max_in_stream, "exceed max num of rx stream", err, ESP_ERR_INVALID_ARG);
							 | 
						||
| 
								 | 
							
								    if (cb_isr && (drv_hdl->flags & CP_DMA_FLAGS_WORK_WITH_CACHE_DISABLED)) {
							 | 
						||
| 
								 | 
							
								        CP_DMA_CHECK(esp_ptr_in_iram(cb_isr), "callback(%p) not in IRAM", err, ESP_ERR_INVALID_ARG, cb_isr);
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // Prepare TX and RX descriptor
							 | 
						||
| 
								 | 
							
								    portENTER_CRITICAL_SAFE(&drv_hdl->spin_lock);
							 | 
						||
| 
								 | 
							
								    // prepare functions will not change internal status of HAL until cp_dma_hal_restart_* are called
							 | 
						||
| 
								 | 
							
								    rx_prepared_size = cp_dma_hal_prepare_receive(&drv_hdl->hal, dst, n, &rx_start_desc, &rx_end_desc);
							 | 
						||
| 
								 | 
							
								    tx_prepared_size = cp_dma_hal_prepare_transmit(&drv_hdl->hal, src, n, &tx_start_desc, &tx_end_desc);
							 | 
						||
| 
								 | 
							
								    if ((rx_prepared_size == n) && (tx_prepared_size == n)) {
							 | 
						||
| 
								 | 
							
								        // register user callback to the end-of-frame descriptor (must before we restart RX)
							 | 
						||
| 
								 | 
							
								        cp_dma_in_stream_t *data_stream_rx = __containerof(rx_end_desc, cp_dma_in_stream_t, rx_desc);
							 | 
						||
| 
								 | 
							
								        data_stream_rx->cb = cb_isr;
							 | 
						||
| 
								 | 
							
								        data_stream_rx->cb_args = cb_args;
							 | 
						||
| 
								 | 
							
								        // The restart should be called with the exact returned start and end desc from previous successful prepare calls
							 | 
						||
| 
								 | 
							
								        cp_dma_hal_restart_rx(&drv_hdl->hal, rx_start_desc, rx_end_desc);
							 | 
						||
| 
								 | 
							
								        cp_dma_hal_restart_tx(&drv_hdl->hal, tx_start_desc, tx_end_desc);
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    portEXIT_CRITICAL_SAFE(&drv_hdl->spin_lock);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    CP_DMA_CHECK(rx_prepared_size == n, "out of rx descriptor", err, ESP_FAIL);
							 | 
						||
| 
								 | 
							
								    // It's unlikely that we have space for rx descriptor but no space for tx descriptor
							 | 
						||
| 
								 | 
							
								    // Because in CP_DMA, both tx and rx descriptor should move in the same pace
							 | 
						||
| 
								 | 
							
								    CP_DMA_CHECK(tx_prepared_size == n, "out of tx descriptor", err, ESP_FAIL);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    return ESP_OK;
							 | 
						||
| 
								 | 
							
								err:
							 | 
						||
| 
								 | 
							
								    return ret_code;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * @brief Default ISR handler provided by ESP-IDF
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								static void cp_dma_isr_default_handler(void *args)
							 | 
						||
| 
								 | 
							
								{
							 | 
						||
| 
								 | 
							
								    cp_dma_driver_context_t *cp_dma_driver = (cp_dma_driver_context_t *)args;
							 | 
						||
| 
								 | 
							
								    cp_dma_in_stream_t *in_stream = NULL;
							 | 
						||
| 
								 | 
							
								    cp_dma_descriptor_t *next_desc = NULL;
							 | 
						||
| 
								 | 
							
								    bool need_yield = false;
							 | 
						||
| 
								 | 
							
								    bool to_continue = false;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    portENTER_CRITICAL_ISR(&cp_dma_driver->spin_lock);
							 | 
						||
| 
								 | 
							
								    uint32_t status = cp_dma_hal_get_intr_status(&cp_dma_driver->hal);
							 | 
						||
| 
								 | 
							
								    cp_dma_hal_clear_intr_status(&cp_dma_driver->hal, status);
							 | 
						||
| 
								 | 
							
								    portEXIT_CRITICAL_ISR(&cp_dma_driver->spin_lock);
							 | 
						||
| 
								 | 
							
								    ESP_EARLY_LOGD(TAG, "intr status=0x%x", status);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // End-Of-Frame on RX side
							 | 
						||
| 
								 | 
							
								    if (status & CP_DMA_LL_EVENT_RX_EOF) {
							 | 
						||
| 
								 | 
							
								        cp_dma_descriptor_t *eof = (cp_dma_descriptor_t *)cp_dma_ll_get_rx_eof_descriptor_address(cp_dma_driver->hal.dev);
							 | 
						||
| 
								 | 
							
								        // traversal all unchecked descriptors
							 | 
						||
| 
								 | 
							
								        do {
							 | 
						||
| 
								 | 
							
								            portENTER_CRITICAL_ISR(&cp_dma_driver->spin_lock);
							 | 
						||
| 
								 | 
							
								            // There is an assumption that the usage of rx descriptors are in the same pace as tx descriptors (this is determined by CP DMA working mechanism)
							 | 
						||
| 
								 | 
							
								            // And once the rx descriptor is recycled, the corresponding tx desc is guaranteed to be returned by DMA
							 | 
						||
| 
								 | 
							
								            to_continue = cp_dma_hal_get_next_rx_descriptor(&cp_dma_driver->hal, eof, &next_desc);
							 | 
						||
| 
								 | 
							
								            portEXIT_CRITICAL_ISR(&cp_dma_driver->spin_lock);
							 | 
						||
| 
								 | 
							
								            if (next_desc) {
							 | 
						||
| 
								 | 
							
								                in_stream = __containerof(next_desc, cp_dma_in_stream_t, rx_desc);
							 | 
						||
| 
								 | 
							
								                // invoke user registered callback if available
							 | 
						||
| 
								 | 
							
								                if (in_stream->cb) {
							 | 
						||
| 
								 | 
							
								                    cp_dma_event_t e = {.id = CP_DMA_EVENT_M2M_DONE};
							 | 
						||
| 
								 | 
							
								                    if (in_stream->cb(cp_dma_driver, &e, in_stream->cb_args)) {
							 | 
						||
| 
								 | 
							
								                        need_yield = true;
							 | 
						||
| 
								 | 
							
								                    }
							 | 
						||
| 
								 | 
							
								                    in_stream->cb = NULL;
							 | 
						||
| 
								 | 
							
								                    in_stream->cb_args = NULL;
							 | 
						||
| 
								 | 
							
								                }
							 | 
						||
| 
								 | 
							
								            }
							 | 
						||
| 
								 | 
							
								        } while (to_continue);
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (need_yield) {
							 | 
						||
| 
								 | 
							
								        portYIELD_FROM_ISR();
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								}
							 |