DMA Migration: DMADRV to DMA 2.0
Overview
The legacy DMADRV driver is being deprecated in favor of a new two-component DMA 2.0 architecture:
- DMA Manager (
sl_dma_manager) — a service that owns everything global to the DMA peripheral: enabling/disabling the peripheral, channel allocation, sync-bit allocation, round-robin configuration, and IRQ dispatching. - DMA Channel Driver (
sl_dma_channel) — a per-channel driver that submits and controls data transfers. Requires a channel number previously allocated by the DMA Manager.
Deprecation Timeline
| Milestone | Release |
|---|---|
| DMA Manager + DMA Channel Driver available, DMADRV deprecated (no new features) | 2026.6.0 |
| DMADRV removed from SDK | 2027.6.0 |
All SiSDK components have migrated to DMA 2.0 starting with 2026.6.0. Keeping
dmadrvin your application increases code size because DMADRV is pulled in solely for your application, in addition to the new components already used by the rest of the SDK.
When to Use
Use this skill when:
- A source file includes
"dmadrv.h". - A
.slcpproject file listsid: dmadrvin itscomponentsection. - Source code contains calls to any
DMADRV_*API. - A user requests migration of an application to "DMA 2.0" or the "new DMA driver".
Instructions
Step 1 — Discover Affected Files
Search the application directory for all DMADRV usage before making any changes.
Find source files that include the legacy header:
grep -r "#include \"dmadrv.h\"" app/Find
.slcpproject files that declare the DMADRV component:grep -r "id: dmadrv" app/ --include="*.slcp"Collect every
DMADRV_*call site so nothing is missed:grep -rn "DMADRV_" app/ --include="*.c" --include="*.h"
Record the full list of files and call sites before proceeding.
Step 2 — Update SLCP Component Declarations
In each affected .slcp file:
Remove:
- id: dmadrv
Add:
- id: dma_manager
- id: dma_channel
If the application only needs channel allocation (e.g., it drives the channel using sl_hal_ldma or direct register access), you may omit dma_channel and add only dma_manager.
The
dma_manager_initanddma_descriptor_allocatorcomponents are automatically included as dependencies — do not add them explicitly.
LDMAXBAR clock when using dma_manager without dma_channel
The DMA Manager only enables the LDMA peripheral clock (SL_BUS_CLOCK_LDMA0). The LDMAXBAR crossbar — which routes peripheral trigger signals to DMA channels via LDMAXBAR->CH[n].REQSEL — has a separate clock (SL_BUS_CLOCK_LDMAXBAR0) that the DMA Manager does not enable on its own.
Without LDMAXBAR clocked, sl_hal_ldma_init_transfer will bus-fault on the first write to LDMAXBAR->CH[n].REQSEL, leaving all channel registers at zero and the transfer never starting.
When dma_channel is present, it handles LDMAXBAR internally. When using sl_hal_ldma_* directly (hybrid pattern), explicitly enable the LDMAXBAR0 clock in the application before calling sl_hal_ldma_init_transfer:
// DMA Manager is auto-initialized via SL Main (LDMA clock, LDMA init, NVIC setup).
// LDMAXBAR has a separate bus clock that the DMA Manager does not enable. Without it,
// sl_hal_ldma_init_transfer faults when writing to LDMAXBAR->CH[n].REQSEL. Enable it
// manually whenever sl_hal_ldma_* is used directly instead of the dma_channel driver.
sl_clock_manager_enable_bus_clock(SL_BUS_CLOCK_LDMAXBAR0);
sl_dma_manager_reserve_channel(NULL, LDMA_CHANNEL);
Example — segment_lcd_ldma/segment_lcd_app.c:
void segment_lcd_app_init(void)
{
// ... LCD and buffer init ...
// DMA Manager is auto-initialized via SL Main (LDMA clock, LDMA init, NVIC setup).
// LDMAXBAR has a separate bus clock that the DMA Manager does not enable. Without it,
// sl_hal_ldma_init_transfer faults when writing to LDMAXBAR->CH[n].REQSEL. Enable it
// manually whenever sl_hal_ldma_* is used directly instead of the dma_channel driver.
sl_clock_manager_enable_bus_clock(SL_BUS_CLOCK_LDMAXBAR0);
// Reserve the fixed channel so the DMA Manager does not allocate it to others.
sl_dma_manager_reserve_channel(NULL, LDMA_CHANNEL);
// ... descriptor setup and sl_hal_ldma_init_transfer / sl_hal_ldma_start_transfer ...
}
SL_BUS_CLOCK_LDMAXBAR0 is declared in sl_device_clock.h, which is included transitively via sl_dma_manager.h. The clock_manager component must be present in the .slcp (it already is when migrating from hal_ldma).
Step 3 — Update Headers
In every .c / .h file that includes "dmadrv.h":
Remove:
#include "dmadrv.h"
Add:
#include "sl_dma_manager.h"
#include "sl_dma_channel.h"
Step 4 — Update Local Variable Types
DMADRV uses unsigned int for channel identifiers. DMA 2.0 uses uint8_t for channel numbers and sl_dma_channel_handle_t for the per-channel driver context.
Before:
static unsigned int tx_channel, rx_channel;
After:
static uint8_t tx_channel, rx_channel;
static sl_dma_channel_handle_t tx_handle, rx_handle;
Step 5 — Update Initialization
The DMA Manager auto-initializes via SL Main (sl_platform_init() → sl_dma_manager_instances_init()). Remove all DMADRV_Init() calls — no manual init is needed. Similarly, remove DMADRV_DeInit() calls — there is no global deinit equivalent.
Before:
void app_init(void)
{
DMADRV_Init();
Ecode_t status = DMADRV_AllocateChannel(&tx_channel, NULL);
EFM_ASSERT(status == ECODE_EMDRV_DMADRV_OK);
status = DMADRV_AllocateChannel(&rx_channel, NULL);
EFM_ASSERT(status == ECODE_EMDRV_DMADRV_OK);
}
After:
void app_init(void)
{
// No init call needed — DMA Manager auto-initializes via SL Main.
sl_status_t status = sl_dma_manager_allocate_channel(NULL, &tx_channel);
EFM_ASSERT(status == SL_STATUS_OK);
status = sl_dma_manager_allocate_channel(NULL, &rx_channel);
EFM_ASSERT(status == SL_STATUS_OK);
// Signature: sl_dma_channel_init(handle*, peripheral, channel_number, callback, user_data)
// TX channel: no callback — transfer completion is polled via sl_dma_channel_get_status.
status = sl_dma_channel_init(&tx_handle, SL_PERIPHERAL_LDMA0, tx_channel, NULL, NULL);
EFM_ASSERT(status == SL_STATUS_OK);
status = sl_dma_channel_init(&rx_handle, SL_PERIPHERAL_LDMA0, rx_channel, rx_callback, NULL);
EFM_ASSERT(status == SL_STATUS_OK);
// Set peripheral signals once; not repeated per transfer.
// Verify exact signal names in sl_dma_signals.h for the target device.
sl_dma_channel_set_peripheral_signal(&tx_handle, SL_DMA_SIGNAL_EUSART0_TXFL);
sl_dma_channel_set_peripheral_signal(&rx_handle, SL_DMA_SIGNAL_EUSART0_RXFL);
}
Key differences:
DMADRV_Init()removed entirely — DMA Manager is auto-initialized by SL Main.sl_dma_channel_initsignature:(handle*, peripheral, channel_number, callback, user_data).sl_dma_channel_initreturnssl_status_t— check its return value.- The peripheral signal is set once on the handle, not passed on every transfer call.
- Returns
sl_status_tinstead ofEcode_t.
Step 6 — Update Transfer Calls
Important — Transfer size unit change: DMADRV used item count for the
lenparameter. The new DMA Channel Driver uses byte count forsize. Convert as follows:
dmadrvDataSize1(byte):size = len × 1(same value)dmadrvDataSize2(halfword):size = len × 2dmadrvDataSize4(word):size = len × 4
Memory → Peripheral
Before:
DMADRV_MemoryPeripheral(tx_channel,
dmadrvPeripheralSignal_EUSART0_TXBL,
(void *)&(EUSART0->TXDATA),
tx_buffer,
true, // srcInc
strlen(tx_buffer), // item count
dmadrvDataSize1,
NULL, // callback
NULL); // user param
After:
sl_dma_channel_submit_transfer_m2p(&tx_handle,
tx_buffer,
(void *)&(EUSART0->TXDATA),
strlen(tx_buffer), // byte count
SL_DMA_CTRL_SIZE_BYTE,
NULL); // NULL = auto-allocate descriptor
Peripheral → Memory
Before:
DMADRV_PeripheralMemory(rx_channel,
dmadrvPeripheralSignal_EUSART0_RXDATAV,
rx_buffer,
(void *)&(EUSART0->RXDATA),
true,
RX_BUFFER_SIZE,
dmadrvDataSize1,
rx_callback,
NULL);
After:
sl_dma_channel_submit_transfer_p2m(&rx_handle,
(void *)&(EUSART0->RXDATA),
rx_buffer,
RX_BUFFER_SIZE,
SL_DMA_CTRL_SIZE_BYTE,
NULL);
Memory → Memory (new in DMA 2.0)
DMADRV had no dedicated M2M API. DMA 2.0 adds one with automatic unit-size selection and large-transfer segmentation:
sl_dma_channel_submit_transfer_m2m(&handle,
src_buffer,
dst_buffer,
size, // byte count
NULL);
Ping-Pong Transfers
Dedicated sl_dma_channel_submit_ping_pong_transfer_m2p / _p2m APIs are available. The callback is still invoked on each buffer completion, but its return type is void — call sl_dma_channel_abort() to stop instead of returning false.
Before:
DMADRV_PeripheralMemoryPingPong(dma_channel,
dmadrvPeripheralSignal_EUSART0_RXDATAV,
rx_buf0,
rx_buf1,
(void *)&(EUSART0->RXDATA),
true,
BUFFER_SIZE,
dmadrvDataSize1,
pingpong_callback,
NULL);
After:
// Signal already set at init: sl_dma_channel_set_peripheral_signal(&dma_handle, SL_DMA_SIGNAL_EUSART0_RXFL)
sl_dma_channel_submit_ping_pong_transfer_p2m(
&dma_handle,
(void *)&(EUSART0->RXDATA),
rx_buf0,
rx_buf1,
BUFFER_SIZE, // byte count
SL_DMA_CTRL_SIZE_BYTE,
NULL); // auto-allocate descriptors
To stop ping-pong, call sl_dma_channel_abort(&handle) from within the callback (or any other context) instead of returning false.
API Mapping Summary
| DMADRV API | DMA 2.0 API |
|---|---|
DMADRV_Init() |
(remove — DMA Manager auto-inits via SL Main) |
DMADRV_AllocateChannel(&ch, NULL) |
sl_dma_manager_allocate_channel(NULL, &ch) |
DMADRV_AllocateChannelById(ch, NULL) |
sl_dma_manager_reserve_channel(NULL, ch) |
DMADRV_FreeChannel(ch) |
sl_dma_channel_deinit(&handle) + sl_dma_manager_free_channel(NULL, ch) |
DMADRV_DeInit() |
(remove — no global deinit) |
DMADRV_MemoryPeripheral(...) |
sl_dma_channel_submit_transfer_m2p(...) |
DMADRV_PeripheralMemory(...) |
sl_dma_channel_submit_transfer_p2m(...) |
DMADRV_MemoryPeripheralPingPong(...) |
sl_dma_channel_submit_ping_pong_transfer_m2p(...) |
DMADRV_PeripheralMemoryPingPong(...) |
sl_dma_channel_submit_ping_pong_transfer_p2m(...) |
| (no equivalent) | sl_dma_channel_submit_transfer_m2m(...) |
| (no equivalent) | sl_dma_channel_submit_transfer_list(...) (also covers triple-buffer) |
DMADRV_TransferActive(ch, &active) |
sl_dma_channel_get_status(&handle, &status) → status.enabled |
DMADRV_TransferDone(ch, &done) |
sl_dma_channel_get_status(&handle, &status) → !status.active |
DMADRV_TransferRemainingCount(ch, &remaining) |
sl_dma_channel_get_status(&handle, &status) → compute total - status.bytes_completed |
DMADRV_StopTransfer(ch) |
sl_dma_channel_abort(&handle) (invokes callbacks with aborted=true) |
DMADRV_PauseTransfer(ch) |
sl_dma_channel_suspend(&handle) (suspends peripheral handshake only) |
DMADRV_ResumeTransfer(ch) |
sl_dma_channel_resume(&handle) (resumes peripheral handshake) |
Step 7 — Update Callbacks
Callbacks in DMADRV are passed per-transfer and return bool. In DMA 2.0, the callback is registered once at sl_dma_channel_init and returns void.
Before:
static bool rx_callback(unsigned int channel,
unsigned int sequence_no,
void *user_param)
{
(void)channel;
(void)sequence_no;
(void)user_param;
rx_transfer_complete = true;
return true; // unused for simple transfers; controls ping-pong continuation
}
After:
static void rx_callback(sl_dma_channel_handle_t *handle,
void *user_data,
bool error,
bool aborted)
{
(void)handle;
(void)user_data;
if (error) {
// Channel is now disabled after a hardware error.
return;
}
if (aborted) {
// Transfer was stopped via sl_dma_channel_abort().
return;
}
rx_transfer_complete = true;
}
For ping-pong transfers, the callback fires on each buffer completion (matching DMADRV behavior). There is no return value to control continuation; call sl_dma_channel_abort(handle) to stop.
For RTOS applications using semaphores from ISR context, the FreeRTOS pattern becomes:
Before:
static bool rx_callback(unsigned int channel, unsigned int sequence_no,
void *user_param)
{
BaseType_t task_woken = pdFALSE;
xSemaphoreGiveFromISR(dma_rx_complete, &task_woken);
return task_woken == pdTRUE;
}
After:
static void rx_callback(sl_dma_channel_handle_t *handle, void *user_data,
bool error, bool aborted)
{
(void)handle;
(void)user_data;
if (error || aborted) {
return;
}
BaseType_t task_woken = pdFALSE;
xSemaphoreGiveFromISR(dma_rx_complete, &task_woken);
portYIELD_FROM_ISR(task_woken);
}
Step 8 — Update Transfer Status Checks
Before:
bool active;
DMADRV_TransferActive(tx_channel, &active);
while (active) {
sl_sleeptimer_delay_millisecond(1);
DMADRV_TransferActive(tx_channel, &active);
}
After:
sl_dma_channel_status_t status;
sl_dma_channel_get_status(&tx_handle, &status);
while (status.active) {
sl_sleeptimer_delay_millisecond(1);
sl_dma_channel_get_status(&tx_handle, &status);
}
sl_dma_channel_status_t exposes three fields:
enabled— channel is enabled (equivalent to DMADRV "transfer active" — channel is enabled, may be waiting for peripheral signal)active— data is actively moving (no peripheral signal wait);!activeis equivalent to DMADRV "transfer done"bytes_completed— bytes completed in the current active descriptor (DMADRV returned items remaining; calculate remaining astotal_bytes - bytes_completed)
Step 9 — Update Cleanup
Before:
void app_cleanup(void)
{
DMADRV_StopTransfer(tx_channel);
DMADRV_StopTransfer(rx_channel);
DMADRV_FreeChannel(tx_channel);
DMADRV_FreeChannel(rx_channel);
DMADRV_DeInit();
}
After:
void app_cleanup(void)
{
// Abort any active transfers — triggers callbacks with aborted=true.
sl_dma_channel_abort(&tx_handle);
sl_dma_channel_abort(&rx_handle);
// Deinit the channel driver handle (returns SL_STATUS_BUSY if still enabled).
sl_dma_channel_deinit(&tx_handle);
sl_dma_channel_deinit(&rx_handle);
// Return channels to the DMA Manager.
sl_dma_manager_free_channel(NULL, tx_channel);
sl_dma_manager_free_channel(NULL, rx_channel);
// There is no global deinit for the DMA Manager.
}
Key differences:
sl_dma_channel_abortstops any active transfer and invokes callbacks withaborted=truefor each pending descriptor.sl_dma_channel_deinitreturnsSL_STATUS_BUSYif the channel is still enabled. Always abort before deiniting.- There is no
DMADRV_DeInit()equivalent. The DMA Manager remains initialized for the lifetime of the application.
Common Pitfalls
1. Channel ID type
unsigned int → uint8_t. Update all declarations, struct fields, and function parameters that store a channel number.
2. Error code type and value
Ecode_t / ECODE_EMDRV_DMADRV_OK → sl_status_t / SL_STATUS_OK.
3. Data size enum rename and unit change
| DMADRV | DMA 2.0 constant | Transfer size |
|---|---|---|
dmadrvDataSize1 (1 byte) |
SL_DMA_CTRL_SIZE_BYTE |
size = len × 1 |
dmadrvDataSize2 (2 bytes) |
SL_DMA_CTRL_SIZE_HALF |
size = len × 2 |
dmadrvDataSize4 (4 bytes) |
SL_DMA_CTRL_SIZE_WORD |
size = len × 4 |
DMADRV took an item count; DMA 2.0 takes a byte count. If you used dmadrvDataSize1, the numeric value is unchanged. For halfword or word transfers, multiply the item count accordingly.
4. Peripheral signal rename and type change
DMADRV signal names follow dmadrvPeripheralSignal_<PERIPH>_<SIGNAL>. DMA 2.0 uses SL_DMA_SIGNAL_<PERIPH>_<SIGNAL>. Note specific renames for EUSART:
| Legacy Signal | New Signal |
|---|---|
dmadrvPeripheralSignal_USART0_TXBL |
SL_DMA_SIGNAL_USART0_TXBL |
dmadrvPeripheralSignal_EUSART0_TXBL |
SL_DMA_SIGNAL_EUSART0_TXFL (TXBL → TXFL) |
dmadrvPeripheralSignal_EUSART0_RXDATAV |
SL_DMA_SIGNAL_EUSART0_RXFL (RXDATAV → RXFL) |
The type also changes: DMADRV_PeripheralSignal_t (uint32_t, by value) → sl_dma_signal_t (const uint32_t*, by pointer). Signal constants are defined in sl_device_dma.h (auto-included via sl_dma_manager.h).
Verify the exact signal name for the target peripheral in sl_dma_signals.h.
5. Peripheral signal is set per-handle, not per-transfer
Call sl_dma_channel_set_peripheral_signal() once during initialization. Do not call it repeatedly before each transfer unless the peripheral is actually changing. The function returns SL_STATUS_BUSY if there are pending transfers on the channel.
6. Callback is registered at init, not per-transfer
Remove the callback and user-parameter arguments from every transfer submit call. Pass them to sl_dma_channel_init instead.
7. Callback return value and ping-pong stop
DMADRV callbacks return bool (returning false stopped ping-pong). DMA 2.0 callbacks return void. To stop a ping-pong transfer, call sl_dma_channel_abort(handle) from within the callback or from any other context.
8. DMADRV_StopTransfer now triggers callbacks
DMADRV_StopTransfer() silently stopped the channel. The new sl_dma_channel_abort() stops the channel and invokes callbacks for each pending descriptor with aborted=true. Ensure your callbacks handle the aborted=true case before calling abort.
9. Channel teardown sequence
To tear down a channel completely:
- Call
sl_dma_channel_abort(&handle)to stop any active transfer. - Call
sl_dma_channel_deinit(&handle)— returnsSL_STATUS_BUSYif channel is still enabled. - Call
sl_dma_manager_free_channel(NULL, channel_nbr)to return the channel to the pool.
10. No global DMA Manager deinit
DMADRV_DeInit() has no DMA 2.0 equivalent. Remove it. The DMA Manager remains initialized for the lifetime of the application.
Advanced: Transfer Status Polling Without a Callback
For TX-only channels where the application polls completion, skip the callback entirely:
sl_dma_channel_init(&tx_handle, SL_PERIPHERAL_LDMA0, tx_channel, NULL, NULL);
// ... submit transfer ...
sl_dma_channel_status_t status;
sl_dma_channel_get_status(&tx_handle, &status);
while (status.active) {
sl_sleeptimer_delay_millisecond(1);
sl_dma_channel_get_status(&tx_handle, &status);
}
Advanced: Channel Allocation with Properties
If an application requires specific channel capabilities, use the extended allocator instead of the basic one:
// Request a high-priority channel that supports interleaving.
sl_status_t status = sl_dma_manager_allocate_channel_with_properties(
NULL,
SL_DMA_CHANNEL_HIGH_PRIORITY | SL_DMA_CHANNEL_SUPPORTS_INTERLEAVING,
&channel_nbr
);
Available property flags:
| Flag | Effect |
|---|---|
SL_DMA_CHANNEL_HIGH_PRIORITY |
Allocates from low-numbered (high-priority) fixed-priority channels first |
SL_DMA_CHANNEL_USES_ROUND_ROBIN |
Allocates from the round-robin range |
SL_DMA_CHANNEL_SUPPORTS_INTERLEAVING |
Allocates only channels that support interleaving (MMLDMA/XDMA); fails if none available |
SL_DMA_CHANNEL_SUPPORTS_DUAL_DESTINATION |
Allocates only channels that support dual-destination (XDMA only); fails if none available |
SL_DMA_CHANNEL_HIGH_PRIORITY and SL_DMA_CHANNEL_USES_ROUND_ROBIN are mutually exclusive. Combining them triggers an assert.
Advanced: Transfer Lists
DMA 2.0 introduces a high-level API for arbitrary linked transfer chains — not supported by DMADRV. Transfer lists are also usable for triple-buffer patterns.
Key constraints:
- The
nextpointer of the last element must beNULL. Transfer lists must not form loops. - To simulate continuous ping-pong, re-submit the list from the callback.
sl_dma_channel_transfer_t transfers[3];
transfers[0].source = src1;
transfers[0].destination = dst1;
transfers[0].size = size1;
transfers[0].unit_size = SL_DMA_CTRL_SIZE_BYTE;
transfers[0].increment_source = true;
transfers[0].increment_destination = true;
transfers[0].block_handshake_mode = false;
transfers[0].callback_on_complete = false;
transfers[0].descriptor = NULL;
transfers[0].next = &transfers[1];
transfers[1].source = src2;
transfers[1].destination = dst2;
transfers[1].size = size2;
transfers[1].unit_size = SL_DMA_CTRL_SIZE_BYTE;
transfers[1].increment_source = true;
transfers[1].increment_destination = true;
transfers[1].block_handshake_mode = false;
transfers[1].callback_on_complete = true; // fire callback after this transfer
transfers[1].descriptor = NULL;
transfers[1].next = &transfers[2];
transfers[2].source = src3;
transfers[2].destination = dst3;
transfers[2].size = size3;
transfers[2].unit_size = SL_DMA_CTRL_SIZE_BYTE;
transfers[2].increment_source = true;
transfers[2].increment_destination = true;
transfers[2].block_handshake_mode = false;
transfers[2].callback_on_complete = true;
transfers[2].descriptor = NULL;
transfers[2].next = NULL; // end of list
sl_dma_channel_submit_transfer_list(&handle, &transfers[0]);