Skip to content

Commit b30bd7a

Browse files
committed
esp32s2: add CP_DMA driver
1 parent 6434c1e commit b30bd7a

File tree

19 files changed

+2511
-746
lines changed

19 files changed

+2511
-746
lines changed

components/esp32s2/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ else()
1414
set(srcs "cache_err_int.c"
1515
"memprot.c"
1616
"clk.c"
17+
"cp_dma.c"
1718
"crosscore_int.c"
1819
"dport_access.c"
1920
"hw_random.c"

components/esp32s2/cp_dma.c

Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
// Copyright 2020 Espressif Systems (Shanghai) PTE LTD
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
#include <sys/cdefs.h>
15+
#include <stdatomic.h>
16+
#include "freertos/FreeRTOS.h"
17+
#include "freertos/semphr.h"
18+
#include "esp_compiler.h"
19+
#include "esp_intr_alloc.h"
20+
#include "esp_heap_caps.h"
21+
#include "esp_log.h"
22+
#include "soc/soc_caps.h"
23+
#include "soc/cp_dma_caps.h"
24+
#include "hal/cp_dma_hal.h"
25+
#include "hal/cp_dma_ll.h"
26+
#include "cp_dma.h"
27+
#include "soc/periph_defs.h"
28+
29+
static const char *TAG = "cp_dma";
30+
31+
#define CP_DMA_CHECK(a, msg, tag, ret, ...) \
32+
do { \
33+
if (unlikely(!(a))) { \
34+
ESP_LOGE(TAG, "%s(%d): " msg, __FUNCTION__, __LINE__, ##__VA_ARGS__); \
35+
ret_code = ret; \
36+
goto tag; \
37+
} \
38+
} while (0)
39+
40+
/**
41+
* @brief Stream is high level abstraction over descriptor.
42+
* It combines the descriptor used by DMA and the callback function registered by user.
43+
* The benifit is, we can converter the descriptor address into stream handle.
44+
*/
45+
typedef struct {
46+
cp_dma_descriptor_t tx_desc;
47+
cp_dma_isr_cb_t cb;
48+
void *cb_args;
49+
} cp_dma_out_stream_t;
50+
51+
typedef struct {
52+
cp_dma_descriptor_t rx_desc;
53+
cp_dma_isr_cb_t cb;
54+
void *cb_args;
55+
} cp_dma_in_stream_t;
56+
57+
typedef struct cp_dma_driver_context_s {
58+
uint32_t max_out_stream;
59+
uint32_t max_in_stream;
60+
uint32_t flags;
61+
cp_dma_hal_context_t hal; // HAL context
62+
intr_handle_t intr_hdl; // interrupt handle
63+
portMUX_TYPE spin_lock;
64+
cp_dma_out_stream_t *out_streams; // pointer to the first out stream
65+
cp_dma_in_stream_t *in_streams; // pointer to the first in stream
66+
uint8_t streams[0]; // stream buffer (out streams + in streams), the size if configured by user
67+
} cp_dma_driver_context_t;
68+
69+
static void cp_dma_isr_default_handler(void *arg) IRAM_ATTR;
70+
71+
esp_err_t cp_dma_driver_install(const cp_dma_config_t *config, cp_dma_driver_t *drv_hdl)
72+
{
73+
esp_err_t ret_code = ESP_OK;
74+
cp_dma_driver_context_t *cp_dma_driver = NULL;
75+
76+
CP_DMA_CHECK(config, "configuration can't be null", err, ESP_ERR_INVALID_ARG);
77+
CP_DMA_CHECK(drv_hdl, "driver handle can't be null", err, ESP_ERR_INVALID_ARG);
78+
79+
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;
80+
if (config->flags & CP_DMA_FLAGS_WORK_WITH_CACHE_DISABLED) {
81+
// to work when cache is disabled, make sure to put driver handle in DRAM
82+
cp_dma_driver = heap_caps_calloc(1, total_malloc_size, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
83+
} else {
84+
cp_dma_driver = calloc(1, total_malloc_size);
85+
}
86+
CP_DMA_CHECK(cp_dma_driver, "allocate driver memory failed", err, ESP_ERR_NO_MEM);
87+
88+
int int_flags = 0;
89+
if (config->flags & CP_DMA_FLAGS_WORK_WITH_CACHE_DISABLED) {
90+
int_flags |= ESP_INTR_FLAG_IRAM; // make sure interrupt can still work when cache is disabled
91+
}
92+
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);
93+
CP_DMA_CHECK(ret_code == ESP_OK, "allocate intr failed", err, ret_code);
94+
95+
cp_dma_driver->out_streams = (cp_dma_out_stream_t *)cp_dma_driver->streams;
96+
cp_dma_driver->in_streams = (cp_dma_in_stream_t *)(cp_dma_driver->streams + config->max_out_stream * sizeof(cp_dma_out_stream_t));
97+
// HAL layer has no idea about "data stream" but TX/RX descriptors
98+
// We put all descritprs' addresses into an array, HAL driver will make it a loop during initialization
99+
{
100+
cp_dma_descriptor_t *tx_descriptors[config->max_out_stream];
101+
cp_dma_descriptor_t *rx_descriptors[config->max_in_stream];
102+
for (int i = 0; i < config->max_out_stream; i++) {
103+
tx_descriptors[i] = &cp_dma_driver->out_streams[i].tx_desc;
104+
}
105+
for (int i = 0; i < config->max_in_stream; i++) {
106+
rx_descriptors[i] = &cp_dma_driver->in_streams[i].rx_desc;
107+
}
108+
cp_dma_hal_init(&cp_dma_driver->hal, tx_descriptors, config->max_out_stream, rx_descriptors, config->max_in_stream);
109+
} // limit the scope of tx_descriptors and rx_descriptors so that goto can jump after this code block
110+
111+
cp_dma_driver->spin_lock = (portMUX_TYPE)portMUX_INITIALIZER_UNLOCKED;
112+
cp_dma_driver->max_in_stream = config->max_in_stream;
113+
cp_dma_driver->max_out_stream = config->max_out_stream;
114+
*drv_hdl = cp_dma_driver;
115+
116+
cp_dma_hal_start(&cp_dma_driver->hal); // enable DMA and interrupt
117+
118+
return ESP_OK;
119+
err:
120+
if (cp_dma_driver) {
121+
if (cp_dma_driver->intr_hdl) {
122+
esp_intr_free(cp_dma_driver->intr_hdl);
123+
}
124+
free(cp_dma_driver);
125+
}
126+
if (drv_hdl) {
127+
*drv_hdl = NULL;
128+
}
129+
return ret_code;
130+
}
131+
132+
esp_err_t cp_dma_driver_uninstall(cp_dma_driver_t drv_hdl)
133+
{
134+
esp_err_t ret_code = ESP_OK;
135+
CP_DMA_CHECK(drv_hdl, "driver handle can't be null", err, ESP_ERR_INVALID_ARG);
136+
137+
esp_intr_free(drv_hdl->intr_hdl);
138+
cp_dma_hal_stop(&drv_hdl->hal);
139+
cp_dma_hal_deinit(&drv_hdl->hal);
140+
free(drv_hdl);
141+
return ESP_OK;
142+
err:
143+
return ret_code;
144+
}
145+
146+
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)
147+
{
148+
esp_err_t ret_code = ESP_OK;
149+
cp_dma_descriptor_t *rx_start_desc = NULL;
150+
cp_dma_descriptor_t *rx_end_desc = NULL;
151+
cp_dma_descriptor_t *tx_start_desc = NULL;
152+
cp_dma_descriptor_t *tx_end_desc = NULL;
153+
int rx_prepared_size = 0;
154+
int tx_prepared_size = 0;
155+
CP_DMA_CHECK(drv_hdl, "driver handle can't be null", err, ESP_ERR_INVALID_ARG);
156+
// CP_DMA can only access SRAM
157+
CP_DMA_CHECK(esp_ptr_internal(src) && esp_ptr_internal(dst), "address not in SRAM", err, ESP_ERR_INVALID_ARG);
158+
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);
159+
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);
160+
if (cb_isr && (drv_hdl->flags & CP_DMA_FLAGS_WORK_WITH_CACHE_DISABLED)) {
161+
CP_DMA_CHECK(esp_ptr_in_iram(cb_isr), "callback(%p) not in IRAM", err, ESP_ERR_INVALID_ARG, cb_isr);
162+
}
163+
164+
// Prepare TX and RX descriptor
165+
portENTER_CRITICAL_SAFE(&drv_hdl->spin_lock);
166+
// prepare functions will not change internal status of HAL until cp_dma_hal_restart_* are called
167+
rx_prepared_size = cp_dma_hal_prepare_receive(&drv_hdl->hal, dst, n, &rx_start_desc, &rx_end_desc);
168+
tx_prepared_size = cp_dma_hal_prepare_transmit(&drv_hdl->hal, src, n, &tx_start_desc, &tx_end_desc);
169+
if ((rx_prepared_size == n) && (tx_prepared_size == n)) {
170+
// register user callback to the end-of-frame descriptor (must before we restart RX)
171+
cp_dma_in_stream_t *data_stream_rx = __containerof(rx_end_desc, cp_dma_in_stream_t, rx_desc);
172+
data_stream_rx->cb = cb_isr;
173+
data_stream_rx->cb_args = cb_args;
174+
// The restart should be called with the exact returned start and end desc from previous successful prepare calls
175+
cp_dma_hal_restart_rx(&drv_hdl->hal, rx_start_desc, rx_end_desc);
176+
cp_dma_hal_restart_tx(&drv_hdl->hal, tx_start_desc, tx_end_desc);
177+
}
178+
portEXIT_CRITICAL_SAFE(&drv_hdl->spin_lock);
179+
180+
CP_DMA_CHECK(rx_prepared_size == n, "out of rx descriptor", err, ESP_FAIL);
181+
// It's unlikely that we have space for rx descriptor but no space for tx descriptor
182+
// Because in CP_DMA, both tx and rx descriptor should move in the same pace
183+
CP_DMA_CHECK(tx_prepared_size == n, "out of tx descriptor", err, ESP_FAIL);
184+
185+
return ESP_OK;
186+
err:
187+
return ret_code;
188+
}
189+
190+
/**
191+
* @brief Default ISR handler provided by ESP-IDF
192+
*/
193+
static void cp_dma_isr_default_handler(void *args)
194+
{
195+
cp_dma_driver_context_t *cp_dma_driver = (cp_dma_driver_context_t *)args;
196+
cp_dma_in_stream_t *in_stream = NULL;
197+
cp_dma_descriptor_t *next_desc = NULL;
198+
bool need_yield = false;
199+
bool to_continue = false;
200+
201+
portENTER_CRITICAL_ISR(&cp_dma_driver->spin_lock);
202+
uint32_t status = cp_dma_hal_get_intr_status(&cp_dma_driver->hal);
203+
cp_dma_hal_clear_intr_status(&cp_dma_driver->hal, status);
204+
portEXIT_CRITICAL_ISR(&cp_dma_driver->spin_lock);
205+
ESP_EARLY_LOGD(TAG, "intr status=0x%x", status);
206+
207+
// End-Of-Frame on RX side
208+
if (status & CP_DMA_LL_EVENT_RX_EOF) {
209+
cp_dma_descriptor_t *eof = (cp_dma_descriptor_t *)cp_dma_ll_get_rx_eof_descriptor_address(cp_dma_driver->hal.dev);
210+
// traversal all unchecked descriptors
211+
do {
212+
portENTER_CRITICAL_ISR(&cp_dma_driver->spin_lock);
213+
// 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)
214+
// And once the rx descriptor is recycled, the corresponding tx desc is guaranteed to be returned by DMA
215+
to_continue = cp_dma_hal_get_next_rx_descriptor(&cp_dma_driver->hal, eof, &next_desc);
216+
portEXIT_CRITICAL_ISR(&cp_dma_driver->spin_lock);
217+
if (next_desc) {
218+
in_stream = __containerof(next_desc, cp_dma_in_stream_t, rx_desc);
219+
// invoke user registered callback if available
220+
if (in_stream->cb) {
221+
cp_dma_event_t e = {.id = CP_DMA_EVENT_M2M_DONE};
222+
if (in_stream->cb(cp_dma_driver, &e, in_stream->cb_args)) {
223+
need_yield = true;
224+
}
225+
in_stream->cb = NULL;
226+
in_stream->cb_args = NULL;
227+
}
228+
}
229+
} while (to_continue);
230+
}
231+
232+
if (need_yield) {
233+
portYIELD_FROM_ISR();
234+
}
235+
}

components/esp32s2/include/cp_dma.h

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
// Copyright 2020 Espressif Systems (Shanghai) PTE LTD
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#pragma once
16+
17+
#ifdef __cplusplus
18+
extern "C" {
19+
#endif
20+
21+
#include <stddef.h>
22+
#include "esp_err.h"
23+
24+
/**
25+
* @brief Handle of CP_DMA driver
26+
*
27+
*/
28+
typedef struct cp_dma_driver_context_s *cp_dma_driver_t;
29+
30+
/**
31+
* @brief CP_DMA event ID
32+
*
33+
*/
34+
typedef enum {
35+
CP_DMA_EVENT_M2M_DONE, /*!< One or more memory copy transactions are done */
36+
} cp_dma_event_id_t;
37+
38+
/**
39+
* @brief Type defined for CP_DMA event object (including event ID, event data)
40+
*
41+
*/
42+
typedef struct {
43+
cp_dma_event_id_t id; /*!< Event ID */
44+
void *data; /*!< Event data */
45+
} cp_dma_event_t;
46+
47+
/**
48+
* @brief Type defined for cp_dma ISR callback function
49+
*
50+
* @param drv_hdl Handle of CP_DMA driver
51+
* @param event Event object, which contains the event ID, event data, and so on
52+
* @param cb_args User defined arguments for the callback function. It's passed in cp_dma_memcpy function
53+
* @return Whether a high priority task is woken up by the callback function
54+
*
55+
*/
56+
typedef bool (*cp_dma_isr_cb_t)(cp_dma_driver_t drv_hdl, cp_dma_event_t *event, void *cb_args);
57+
58+
/**
59+
* @brief Type defined for configuration of CP_DMA driver
60+
*
61+
*/
62+
typedef struct {
63+
uint32_t max_out_stream; /*!< maximum number of out link streams that can work simultaneously */
64+
uint32_t max_in_stream; /*!< maximum number of in link streams that can work simultaneously */
65+
uint32_t flags; /*!< Extra flags to control some special behaviour of CP_DMA, OR'ed of CP_DMA_FLAGS_xxx macros */
66+
} cp_dma_config_t;
67+
68+
#define CP_DMA_FLAGS_WORK_WITH_CACHE_DISABLED (1 << 0) /*!< CP_DMA can work even when cache is diabled */
69+
70+
/**
71+
* @brief Default configuration for CP_DMA driver
72+
*
73+
*/
74+
#define CP_DMA_DEFAULT_CONFIG() \
75+
{ \
76+
.max_out_stream = 8, \
77+
.max_in_stream = 8, \
78+
.flags = 0, \
79+
}
80+
81+
/**
82+
* @brief Install CP_DMA driver
83+
*
84+
* @param[in] config Configuration of CP_DMA driver
85+
* @param[out] drv_hdl Returned handle of CP_DMA driver or NULL if driver installation failed
86+
* @return
87+
* - ESP_OK: Install CP_DMA driver successfully
88+
* - ESP_ERR_INVALID_ARG: Install CP_DMA driver failed because of some invalid argument
89+
* - ESP_ERR_NO_MEM: Install CP_DMA driver failed because there's no enough capable memory
90+
* - ESP_FAIL: Install CP_DMA driver failed because of other error
91+
*/
92+
esp_err_t cp_dma_driver_install(const cp_dma_config_t *config, cp_dma_driver_t *drv_hdl);
93+
94+
/**
95+
* @brief Uninstall CP_DMA driver
96+
*
97+
* @param[in] drv_hdl Handle of CP_DMA driver that returned from cp_dma_driver_install
98+
* @return
99+
* - ESP_OK: Uninstall CP_DMA driver successfully
100+
* - ESP_ERR_INVALID_ARG: Uninstall CP_DMA driver failed because of some invalid argument
101+
* - ESP_FAIL: Uninstall CP_DMA driver failed because of other error
102+
*/
103+
esp_err_t cp_dma_driver_uninstall(cp_dma_driver_t drv_hdl);
104+
105+
/**
106+
* @brief Send an asynchronous memory copy request
107+
*
108+
* @param[in] drv_hdl Handle of CP_DMA driver that returned from cp_dma_driver_install
109+
* @param[in] dst Destination address (copy to)
110+
* @param[in] src Source address (copy from)
111+
* @param[in] n Number of bytes to copy
112+
* @param[in] cb_isr Callback function, which got invoked in ISR context. A NULL pointer here can bypass the callback.
113+
* @param[in] cb_args User defined argument to be passed to the callback function
114+
* @return
115+
* - ESP_OK: Send memcopy request successfully
116+
* - ESP_ERR_INVALID_ARG: Send memcopy request failed because of some invalid argument
117+
* - ESP_FAIL: Send memcopy request failed because of other error
118+
*
119+
* @note The callback function is invoked in ISR context, please never handle heavy load in the callback.
120+
* The default ISR handler is placed in IRAM, please place callback function in IRAM as well by applying IRAM_ATTR to it.
121+
*/
122+
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);
123+
124+
#ifdef __cplusplus
125+
}
126+
#endif

components/esp32s2/ld/esp32s2.peripherals.ld

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ PROVIDE ( RMTMEM = 0x3f416400 );
1717
PROVIDE ( PCNT = 0x3f417000 );
1818
PROVIDE ( SLC = 0x3f418000 );
1919
PROVIDE ( LEDC = 0x3f419000 );
20-
PROVIDE ( MCP = 0x3f4c3000 );
20+
PROVIDE ( CP_DMA = 0x3f4c3000 );
2121
PROVIDE ( TIMERG0 = 0x3f41F000 );
2222
PROVIDE ( TIMERG1 = 0x3f420000 );
2323
PROVIDE ( GPSPI2 = 0x3f424000 );

0 commit comments

Comments
 (0)