DMADRV to DMA Channel Driver and DMA Manager Migration Guide#
Table of Contents#
1. Overview#
Why Migrate?#
DMADRV was a monolithic, all-in-one DMA driver from the EMDRV (Energy Micro Driver) family. While it served well for basic DMA operations, it combined resource management and transfer operations into a single component, limiting flexibility and extensibility.
The new architecture separates DMA functionality into two focused, independent components:
DMA Manager — System-level DMA resource management (initialization, channel allocation, SYNC bit management, round-robin configuration)
DMA Channel Driver — Per-channel transfer operations (transfer submission, ping-pong, triple-buffered, transfer lists, suspend/resume/abort, status query)
This separation provides a cleaner architecture, better error handling, new transfer modes, and a consistent API style aligned with the modern Silicon Labs platform SDK conventions.
Benefits of Migration#
Benefit | Description |
|---|---|
Improved error handling | Callbacks receive explicit |
New transfer modes | Triple-buffered looping transfers, linked transfer lists with auto-segmentation, and dynamic transfer size updates are now available. |
Consistent API style | Uses standard |
Automatic initialization | DMA Manager initializes automatically through SL Main — no manual |
Auto-segmentation | Transfers larger than the hardware descriptor limit are automatically segmented into linked descriptors. |
Better descriptor management | Descriptors are auto-allocated when passing |
Handle-based API | Channel handles provide type safety and encapsulate channel state, replacing raw integer channel IDs. |
Deprecation Timeline#
Milestone | Release | Description |
|---|---|---|
New components available | 2026.6.0 | DMA Manager and DMA Channel Driver released as production-quality replacements |
DMADRV deprecated | 2026.6.0 | DMADRV marked as deprecated; no new features will be added |
DMADRV removed | 2027.6.0 | DMADRV completely removed from the SDK |
Important: You have a one-year migration window from 2026.6.0 to 2027.6.0. During this period, both DMADRV and the new components coexist in the SDK, and existing DMADRV code continues to work without changes. However, Silicon Labs strongly recommends migrating early to take advantage of improved error handling and new capabilities.
Starting with 2026.6.0, all SiSDK components use the new DMA Manager and DMA Channel Driver. Keeping DMADRV in your application increases code size because the application still pulls in DMADRV in addition to the new DMA components already used by the rest of the SDK.
Migration Effort Estimate#
Project Size | Estimated Effort | Description |
|---|---|---|
Small (< 5 DMA channels, basic transfers only) | 1–2 hours | Straightforward API replacement |
Medium (5–10 DMA channels, mixed transfer types) | 2–3 hours | Requires callback refactoring |
Large (10+ channels or ping-pong-heavy) | 3–4 hours | Ping-pong callback changes require stop-logic review |
2. Prerequisites#
SDK Version Requirements#
Minimum SDK Version: 2026.6.0
Recommended SDK Version: 2026.6.0 or later
Required Components#
The new implementation requires these SLCC components in your .slcp file:
component:
- id: dma_manager
- id: dma_channelThe
dma_manager_initanddma_descriptor_allocatorcomponents are automatically included as dependencies. You do not need to add them explicitly.
Components to Remove#
Remove the legacy DMADRV component from your .slcp file:
# Remove these from your .slcp:
# - id: dmadrvTool Requirements#
Simplicity Studio 6 (2026.6.0 release or later), or
SLC CLI (2026.6.0 release or later)
3. Feature Comparison#
Feature Matrix#
Feature | DMADRV | DMA Manager + DMA Channel Driver | Notes |
|---|---|---|---|
DMA initialization | ✅ | ✅ Auto-init via SL Main | No manual init call needed |
Channel allocation (any) | ✅ | ✅ | Same concept, different API |
Channel reservation (specific) | ✅ | ✅ | Same concept, different API |
Channel free | ✅ | ✅ | Requires deinit + free in new API |
Memory-to-peripheral transfer | ✅ | ✅ | Simplified — no |
Peripheral-to-memory transfer | ✅ | ✅ | Simplified — no |
Memory-to-memory transfer | ✅ (via native LDMA) | ✅ (dedicated API) | No longer requires raw LDMA descriptors |
Ping-pong transfers | ✅ | ✅ | Callback change — callback is still invoked but cannot stop the transfer by returning |
Triple-buffered transfers | ❌ | ✅ | New capability |
Transfer lists (linked) | ❌ (raw LDMA only) | ✅ | With auto-segmentation |
Dynamic transfer size update | ❌ | ✅ | New capability |
Suspend / Resume | ✅ | ✅ | Same behavior |
Abort (Stop) | ✅ | ✅ | Behavioral change — abort now triggers callbacks |
Transfer status query | ✅ (multiple functions) | ✅ (single status struct) | Unified query |
Remaining/completed count | ✅ (items remaining) | ✅ (bytes completed) | Unit changed |
Polled completion | ✅ | ✅ via | Inferred from |
Callback completion | ✅ | ✅ | Signature changed |
SYNC bit management | ❌ | ✅ | New capability |
Channel property allocation | ❌ | ✅ | Allocate by priority, interleaving, etc. |
New Capabilities#
The new DMA Channel Driver introduces capabilities not available in DMADRV:
Triple-Buffered Transfers
Description: Continuous looping transfers across three buffers instead of two
Benefit: Reduces buffer underrun risk in high-throughput scenarios by providing an extra buffer slot
Transfer Lists
Description: Submit a linked list of heterogeneous transfers that execute sequentially
Benefit: Complex multi-step DMA operations without CPU intervention; auto-segmentation handles transfers exceeding the hardware descriptor limit
Dynamic Transfer Size Update
Description: Modify the size of an active transfer while it is in progress
Benefit: Useful for protocol parsing where the total transfer length is determined from an initial header
SYNC Bit Management
Description: Allocate and free LDMA SYNC bits for hardware synchronization
Benefit: Proper resource management for SYNC-based DMA coordination
Channel Property Allocation
Description: Request channels with specific properties (high priority, round-robin, interleaving, dual-destination)
Benefit: Deterministic channel assignment for hardware-constrained features
Removed/Changed Features#
Callback-controlled ping-pong stop
Legacy behavior: Returning
falsefrom the callback stopped the ping-pong transferNew behavior: The callback is still invoked on each buffer completion, but its return type is
void— it cannot stop the transferMigration path: Call
sl_dma_channel_abort()from within the callback or from other application logic to stop the transfer — see Phase 6
sequenceNoin callbacksLegacy behavior: Each callback invocation received an incrementing sequence number
Migration path: Maintain an application-level counter if needed
DMADRV_TransferCompletePending()Legacy behavior: Checked if a transfer-complete interrupt is pending (useful when ISRs are disabled)
Migration path: Use
sl_dma_channel_get_status()or rely on callbacks
DMADRV_DeInit()Legacy behavior: Deinitializes the DMA hardware and disallows further operations
Migration path: No global deinit needed. Use
sl_dma_channel_deinit()+sl_dma_manager_free_channel()for per-channel cleanup.
4. Breaking Changes#
Summary of Breaking Changes#
Change Type | Count | Impact |
|---|---|---|
API signature changes | All 16 functions | High — every call site needs updating |
Behavioral changes | 3 | High — ping-pong, abort, and error handling |
Return type change | 1 | Medium — |
Configuration changes | 3 | Low — simplified configuration |
Return Type Change#
All functions change from Ecode_t to sl_status_t:
Legacy:
Ecode_t result = DMADRV_MemoryPeripheral(ch, signal, dst, src, true, len,
dmadrvDataSize1, callback, user);
if (result != ECODE_EMDRV_DMADRV_OK) {
// Error
}New:
sl_status_t status = sl_dma_channel_submit_transfer_m2p(&handle, src, dst, len,
SL_DMA_CTRL_SIZE_BYTE, NULL);
if (status != SL_STATUS_OK) {
// Error
}Callback Signature Change#
Legacy callback:
typedef bool (*DMADRV_Callback_t)(unsigned int channel,
unsigned int sequenceNo,
void *userParam);New callback:
typedef void (*sl_dma_channel_callback_t)(sl_dma_channel_handle_t *handle,
void *user_data,
bool error,
bool aborted);Key differences:
Return type changes from
booltovoid— callbacks are still invoked during ping-pong, but cannot stop it by returningfalse; callsl_dma_channel_abort()insteadchannel(integer) replaced byhandle(pointer to handle struct)sequenceNoremoved — use application-level counter if neededExplicit
errorandabortedboolean flags added for clear status reportinguserParamrenamed touser_data
Behavioral Changes#
Legacy Behavior | New Behavior | Migration Action |
|---|---|---|
Ping-pong callback returns | Ping-pong callback is | Change callback return type to |
|
| Add abort handling in callbacks |
DMA error causes infinite loop in IRQ handler ( | DMA error invokes callback with | Add error handling in callbacks |
Transfer Size Unit Change#
DMADRV uses item count (int len). DMA Channel Driver uses byte count (size_t size).
DMADRV Data Size | Byte Multiplier | Conversion Formula |
|---|---|---|
| ×1 |
|
| ×2 |
|
| ×4 |
|
Note: If you always used
dmadrvDataSize1(byte transfers), the numeric value stays the same. For halfword or word transfers, multiply the item count accordingly.
Peripheral Signal Change#
Legacy: Passed as a parameter on every transfer call.
New: Set once via sl_dma_channel_set_peripheral_signal() before submitting transfers.
Legacy Signal | New Signal | Notes |
|---|---|---|
|
| Naming convention change |
|
| Name change: |
|
| Name change: |
Signal constants are defined in
sl_device_dma.h(auto-included). The type also changes fromuint32_t(by value) tosl_dma_signal_t(const uint32_t*, by pointer).
Configuration Changes#
Legacy Configuration | New Configuration | Notes |
|---|---|---|
| Removed | Interrupt Manager can be used to change interrupt priority at runtime |
| Removed | DMA Manager uses all available hardware channels |
|
| Add the component; configure the channel count. Applied automatically at init. |
5. API Mapping Reference#
Function Mapping#
Initialization / Deinitialization#
Legacy API | New API | Mapping Type | Notes |
|---|---|---|---|
| Auto-init via SL Main | Removed | DMA Manager initializes automatically through |
|
| Changed | No global deinit. Use per-channel deinit + free instead. |
Channel Allocation / Deallocation#
Legacy API | New API | Mapping Type | Notes |
|---|---|---|---|
|
| Direct | Channel type: |
|
| Renamed | Same behavior |
|
| Split | Deinit the channel driver handle first, then free the channel in DMA Manager |
Transfer APIs#
Legacy API | New API | Mapping Type | Notes |
|---|---|---|---|
|
| Split | Signal set once; |
|
| Split | Signal set once; |
|
| Changed | No raw LDMA descriptors needed |
|
| Split + Behavioral | Callback still invoked per buffer, but |
|
| Split + Behavioral | Callback still invoked per buffer, but |
Transfer Control#
Legacy API | New API | Mapping Type | Notes |
|---|---|---|---|
|
| Renamed | Same behavior |
|
| Renamed | Same behavior |
|
| Changed | Now invokes callbacks with |
Transfer Status#
Legacy API | New API | Mapping Type | Notes |
|---|---|---|---|
|
| Changed | Single unified status struct |
|
| Changed | Infer done from |
| No direct equivalent | Removed | Use |
|
| Changed | Returns bytes completed (not items remaining). Calculate remaining as |
Mapping Type Legend:
Direct — 1:1 replacement with same behavior
Renamed — Same function, different name
Changed — Signature or behavior changed
Split — One legacy function maps to multiple new functions
Removed — No longer available/needed
Behavioral — Significant behavioral change requires code restructuring
Data Structure Mapping#
Legacy Type | New Type | Notes |
|---|---|---|
|
| Standard platform status type |
|
| Handle-based API; contains |
|
| Macro constants instead of enum values |
|
| Value → pointer type |
N/A |
| New: |
N/A |
| New: transfer list element |
Constant/Macro Mapping#
Legacy Constant | New Constant | Notes |
|---|---|---|
|
| Byte transfer |
|
| Halfword (2-byte) transfer |
|
| Word (4-byte) transfer |
|
| Max units per descriptor. New driver auto-segments larger transfers. |
Error Code Mapping#
Legacy DMADRV Error | New | Notes |
|---|---|---|
|
| Success |
|
| Invalid parameter |
| N/A | Auto-initialized; asserts used internally |
|
| DMA Manager |
|
| No channels available |
|
| Channel enabled or has active transfers |
| N/A | Handled gracefully by DMA Manager |
|
| Channel not allocated |
6. Step-by-Step Migration#
Phase 1: Preparation#
Inventory your DMADRV usage
Search your codebase for all DMADRV references:
#include "dmadrv.h"— files that use DMADRVDMADRV_— all API callsECODE_EMDRV_DMADRV— all error code checksdmadrvDataSize— data size constantsdmadrvPeripheralSignal— peripheral signal constants
Identify your transfer patterns
Classify each DMA usage into one of these categories:
Basic M2P/P2M —
DMADRV_MemoryPeripheral()/DMADRV_PeripheralMemory()→ straightforward migrationPing-pong —
DMADRV_MemoryPeripheralPingPong()/DMADRV_PeripheralMemoryPingPong()→ requires behavioral change reviewNative LDMA —
DMADRV_LdmaStartTransfer()→ may require restructuringPolled —
DMADRV_TransferDone()/DMADRV_TransferRemainingCount()→ API change
Assess ping-pong callback complexity
Check if any callbacks return
falseto stop ping-pong — this requires the most significant redesignCheck if any callbacks use the
sequenceNoparameter — you will need application-level tracking
Note custom configuration
Review
dmadrv_config.hfor any non-default settings, especiallyEMDRV_DMADRV_DMA_CH_PRIORITY(round-robin)
Backup your project
Phase 2: Component Changes#
Update your
.slcpfileRemove the legacy component and add the new ones:
# Remove: # - id: dmadrv # Add: component: - id: dma_manager - id: dma_channelConfigure round-robin (if applicable)
If your
dmadrv_config.hhadEMDRV_DMADRV_DMA_CH_PRIORITYset to a value less thanEMDRV_DMADRV_DMA_CH_COUNT(meaning some channels used round-robin scheduling), add thedma_manager_round_robincomponent to your.slcp:component: - id: dma_manager - id: dma_manager_round_robin - id: dma_channelThen set the number of round-robin channels in
sl_dma_manager_round_robin_config.h:// Number of round-robin channels = EMDRV_DMADRV_DMA_CH_COUNT - EMDRV_DMADRV_DMA_CH_PRIORITY #define SL_DMA_MANAGER_ROUND_ROBIN_CHANNEL_COUNT 8Round-robin channels are automatically configured during DMA Manager initialization — no runtime API call is needed.
Regenerate your project
Using Simplicity Studio or SLC CLI:
slc generate my_project.slcp
Phase 3: Header Changes#
Replace legacy includes with new ones in all affected files:
Remove | Add |
|---|---|
|
|
| Removed — round-robin is configured via the |
|
|
Phase 4: Initialization Migration#
Remove
DMADRV_Init()callsDMA Manager is auto-initialized by SL Main via
sl_platform_init()→sl_dma_manager_instances_init(). Simply delete allDMADRV_Init()calls and any associated error checking.Remove
DMADRV_DeInit()callsThere is no global deinit equivalent. If you need per-channel cleanup, use
sl_dma_channel_deinit()followed bysl_dma_manager_free_channel().Add channel handle declarations
For each DMA channel your code uses, declare a handle:
static sl_dma_channel_handle_t my_dma_handle;Initialize channel handles after allocation
Immediately after allocating a channel, initialize the DMA Channel Driver handle:
uint8_t channel; sl_status_t status; status = sl_dma_manager_allocate_channel(NULL, &channel); if (status != SL_STATUS_OK) { // Handle error return status; } status = sl_dma_channel_init(&my_dma_handle, NULL, channel, my_callback, my_user_data); if (status != SL_STATUS_OK) { sl_dma_manager_free_channel(NULL, channel); return status; }
Phase 5: API Call Migration#
Work through your code systematically, replacing each DMADRV API call with its new equivalent. Refer to the API Mapping Reference and the Code Transformation Examples for detailed guidance.
General pattern for each call site:
Find the legacy DMADRV API call
Look up the mapping in the API Mapping Reference
Apply the transformation — see Code Transformation Examples
Update error handling from
Ecode_ttosl_status_tCompile and fix any errors
Phase 6: Ping-Pong Migration#
If you do not use ping-pong transfers, skip this phase.
The callback is still invoked on each buffer completion during ping-pong transfers, so buffer processing logic can remain in the callback. The key behavioral change is that the callback return type is void — you can no longer return false to stop the ping-pong. Instead, call sl_dma_channel_abort() to stop.
Migration strategies depending on your legacy usage:
Legacy Pattern | Recommended New Approach |
|---|---|
Callback always returns | Direct migration — change callback to |
Callback processes buffers between iterations | Direct migration — keep buffer processing in the callback, change signature to |
Callback returns | Keep counter and buffer processing in callback; call |
Callback returns | Check the condition in callback; call |
See Code Transformation Examples — Ping-Pong Transfer for detailed before/after code.
Phase 7: Testing and Validation#
Compile and fix all errors and warnings
Search for remaining legacy symbols — none of these should remain in your code:
DMADRV_— function callsECODE_EMDRV_DMADRV— error codesdmadrv— includesdmadrvDataSize— data size enumsdmadrvPeripheralSignal— peripheral signal constants
Run basic functional tests:
Verify DMA auto-initialization succeeds (no manual init call needed)
Test basic M2P and P2M transfers
Verify callback invocation on transfer completion
Test error handling paths (invalid parameters, busy channels)
Validate ping-pong transfers (if applicable):
Verify continuous looping operation
Verify
sl_dma_channel_abort()stops the transfer correctlyVerify callback receives
aborted=trueafter abort
Performance validation:
Verify transfer throughput is comparable to DMADRV
Check interrupt latency if timing-sensitive
7. Code Transformation Examples#
Basic Initialization and Channel Setup#
Before (DMADRV):
#include "dmadrv.h"
static unsigned int dma_channel;
void app_dma_init(void)
{
Ecode_t result;
result = DMADRV_Init();
if (result != ECODE_EMDRV_DMADRV_OK
&& result != ECODE_EMDRV_DMADRV_ALREADY_INITIALIZED) {
// Handle error
return;
}
result = DMADRV_AllocateChannel(&dma_channel, NULL);
if (result != ECODE_EMDRV_DMADRV_OK) {
// Handle error
return;
}
}After (DMA Manager + DMA Channel Driver):
#include "sl_dma_manager.h"
#include "sl_dma_channel.h"
static sl_dma_channel_handle_t dma_handle;
static uint8_t dma_channel;
// Forward declaration
static void dma_callback(sl_dma_channel_handle_t *handle,
void *user_data,
bool error,
bool aborted);
void app_dma_init(void)
{
sl_status_t status;
// No DMADRV_Init() needed — DMA Manager auto-initializes via SL Main
// Allocate a channel
status = sl_dma_manager_allocate_channel(NULL, &dma_channel);
if (status != SL_STATUS_OK) {
// Handle error — e.g., SL_STATUS_NO_MORE_RESOURCE
return;
}
// Initialize the DMA Channel Driver handle
status = sl_dma_channel_init(&dma_handle, NULL, dma_channel,
dma_callback, NULL);
if (status != SL_STATUS_OK) {
sl_dma_manager_free_channel(NULL, dma_channel);
return;
}
}Key changes:
DMADRV_Init()removed — auto-initialization via SL MainDMADRV_AllocateChannel()→sl_dma_manager_allocate_channel()+sl_dma_channel_init()Ecode_t→sl_status_tChannel ID (
unsigned int) → channel number (uint8_t) + handle (sl_dma_channel_handle_t)Callback is registered during
sl_dma_channel_init(), not at transfer time
Memory-to-Peripheral Transfer (UART TX)#
Before (DMADRV):
#include "dmadrv.h"
static bool tx_callback(unsigned int channel,
unsigned int sequenceNo,
void *userParam)
{
(void)channel;
(void)sequenceNo;
// Signal application that TX is complete
tx_complete = true;
return true;
}
void send_data(const uint8_t *data, size_t length)
{
DMADRV_MemoryPeripheral(dma_channel,
dmadrvPeripheralSignal_EUSART0_TXBL,
(void *)&(EUSART0->TXDATA),
(void *)data,
true, // srcInc
(int)length, // item count
dmadrvDataSize1, // byte transfers
tx_callback,
NULL);
}After (DMA Manager + DMA Channel Driver):
#include "sl_dma_manager.h"
#include "sl_dma_channel.h"
static void tx_callback(sl_dma_channel_handle_t *handle,
void *user_data,
bool error,
bool aborted)
{
(void)handle;
(void)user_data;
if (!error && !aborted) {
// Signal application that TX is complete
tx_complete = true;
} else if (error) {
// Handle DMA error — new capability: recoverable error handling
tx_error = true;
}
}
void send_data(const uint8_t *data, size_t length)
{
// Set peripheral signal once (can be done during init if signal doesn't change)
sl_dma_channel_set_peripheral_signal(&dma_handle,
SL_DMA_SIGNAL_EUSART0_TXFL);
// Submit transfer — callback registered at init time
sl_status_t status;
status = sl_dma_channel_submit_transfer_m2p(&dma_handle,
(void *)data,
(void *)&(EUSART0->TXDATA),
length, // bytes (same value for byte transfers)
SL_DMA_CTRL_SIZE_BYTE,
NULL); // auto-allocate descriptor
if (status != SL_STATUS_OK) {
// Handle submission error
}
}Key changes:
Callback:
boolreturn →void; adderror/abortedchecksPeripheral signal: passed per-call → set once via
sl_dma_channel_set_peripheral_signal()Signal name:
dmadrvPeripheralSignal_EUSART0_TXBL→SL_DMA_SIGNAL_EUSART0_TXFLsrcIncparameter removed — M2P always increments sourceTransfer size already in bytes (for
dmadrvDataSize1), so value stays the sameCallback/user not passed at transfer time — registered during
sl_dma_channel_init()Pass
NULLas descriptor → auto-allocated by driver
Peripheral-to-Memory Transfer (UART RX)#
Before (DMADRV):
void receive_data(uint8_t *buffer, size_t length)
{
DMADRV_PeripheralMemory(dma_channel,
dmadrvPeripheralSignal_EUSART0_RXDATAV,
(void *)buffer,
(void *)&(EUSART0->RXDATA),
true, // dstInc
(int)length,
dmadrvDataSize1,
rx_callback,
NULL);
}After (DMA Manager + DMA Channel Driver):
void receive_data(uint8_t *buffer, size_t length)
{
sl_dma_channel_set_peripheral_signal(&dma_handle,
SL_DMA_SIGNAL_EUSART0_RXFL);
sl_status_t status;
status = sl_dma_channel_submit_transfer_p2m(&dma_handle,
(void *)&(EUSART0->RXDATA),
(void *)buffer,
length,
SL_DMA_CTRL_SIZE_BYTE,
NULL);
if (status != SL_STATUS_OK) {
// Handle error
}
}Key changes:
Signal:
dmadrvPeripheralSignal_EUSART0_RXDATAV→SL_DMA_SIGNAL_EUSART0_RXFLdstIncparameter removed — P2M always increments destinationParameter order: in DMADRV it was
(dst, src); in new API it is(reg, destination)— note the order
Memory-to-Memory Transfer#
Before (DMADRV):
#include "dmadrv.h"
void copy_memory(void *dst, const void *src, size_t length)
{
LDMA_TransferCfg_t xfer_cfg = LDMA_TRANSFER_CFG_MEMORY();
LDMA_Descriptor_t desc = LDMA_DESCRIPTOR_SINGLE_M2M_BYTE(src, dst, length);
DMADRV_LdmaStartTransfer(dma_channel, &xfer_cfg, &desc,
m2m_callback, NULL);
}After (DMA Manager + DMA Channel Driver):
#include "sl_dma_channel.h"
void copy_memory(void *dst, const void *src, size_t length)
{
sl_status_t status;
status = sl_dma_channel_submit_transfer_m2m(&dma_handle,
(void *)src,
dst,
length, // bytes
NULL); // auto-allocate descriptor
if (status != SL_STATUS_OK) {
// Handle error
}
}Key changes:
No raw LDMA descriptors (
LDMA_TransferCfg_t,LDMA_Descriptor_t) neededM2M auto-selects optimal unit size based on source/destination alignment
No
unit_sizeparameter for M2M (auto-detected)Auto-segmentation handles transfers larger than the hardware descriptor limit
Ping-Pong Transfer#
The callback is still invoked on each buffer completion during ping-pong, so buffer processing logic stays in the callback. The main change is that the callback returns void instead of bool — to stop, call sl_dma_channel_abort() instead of returning false.
Before (DMADRV) — Callback returns bool to control ping-pong:
#include "dmadrv.h"
static uint8_t rx_buf0[BUFFER_SIZE];
static uint8_t rx_buf1[BUFFER_SIZE];
static volatile uint32_t buffers_received = 0;
static bool pingpong_callback(unsigned int channel,
unsigned int sequenceNo,
void *userParam)
{
(void)channel;
(void)userParam;
// Process the completed buffer
if (sequenceNo % 2 == 0) {
process_buffer(rx_buf0, BUFFER_SIZE);
} else {
process_buffer(rx_buf1, BUFFER_SIZE);
}
buffers_received++;
// Stop after receiving 10 buffers
if (buffers_received >= 10) {
return false; // Stop ping-pong
}
return true; // Continue ping-pong
}
void start_continuous_rx(void)
{
DMADRV_PeripheralMemoryPingPong(dma_channel,
dmadrvPeripheralSignal_EUSART0_RXDATAV,
rx_buf0,
rx_buf1,
(void *)&(EUSART0->RXDATA),
true,
BUFFER_SIZE,
dmadrvDataSize1,
pingpong_callback,
NULL);
}After (DMA Manager + DMA Channel Driver) — Callback is void, abort to stop:
#include "sl_dma_manager.h"
#include "sl_dma_channel.h"
static uint8_t rx_buf0[BUFFER_SIZE];
static uint8_t rx_buf1[BUFFER_SIZE];
static volatile uint32_t buffers_received = 0;
static volatile bool current_buffer = false; // Track which buffer completed
static void pingpong_callback(sl_dma_channel_handle_t *handle,
void *user_data,
bool error,
bool aborted)
{
(void)user_data;
if (error) {
// Handle DMA error — new capability: recoverable error handling
return;
}
if (aborted) {
// Ping-pong was stopped via sl_dma_channel_abort()
return;
}
// Callback IS invoked on each buffer completion — process the buffer
// (no sequenceNo, use application-level tracking instead)
if (current_buffer) {
process_buffer(rx_buf1, BUFFER_SIZE);
} else {
process_buffer(rx_buf0, BUFFER_SIZE);
}
current_buffer = !current_buffer;
buffers_received++;
// Stop after receiving 10 buffers — call abort instead of returning false
if (buffers_received >= 10) {
sl_dma_channel_abort(handle); // Replaces "return false"
}
}
void start_continuous_rx(void)
{
sl_dma_channel_set_peripheral_signal(&dma_handle,
SL_DMA_SIGNAL_EUSART0_RXFL);
sl_status_t status;
status = sl_dma_channel_submit_ping_pong_transfer_p2m(
&dma_handle,
(void *)&(EUSART0->RXDATA),
rx_buf0,
rx_buf1,
BUFFER_SIZE, // bytes
SL_DMA_CTRL_SIZE_BYTE,
NULL); // auto-allocate descriptors
if (status != SL_STATUS_OK) {
// Handle error
}
}Key changes:
Callback return type:
bool→void; callback is still called on each buffer completionStop mechanism:
return false→sl_dma_channel_abort(handle)from within the callbacksequenceNoremoved — use application-level tracking (current_buffertoggle) to determine which buffer completedBuffer processing stays in the callback — no need to move it to a separate monitoring mechanism
Explicit
errorandabortedflag handling addedSignal name:
RXDATAV→RXFL; set once before transferdstIncparameter removed
Alternative approach for finite-count transfers: If you only needed a fixed number of ping-pong iterations (like the 10-buffer example above), consider using
sl_dma_channel_submit_transfer_list()with a linear list of transfers instead.
Polled Transfer Completion#
Before (DMADRV):
DMADRV_MemoryPeripheral(channel, signal, dst, src, true, len,
dmadrvDataSize1, NULL, NULL);
bool done;
do {
DMADRV_TransferDone(channel, &done);
} while (!done);
int remaining;
DMADRV_TransferRemainingCount(channel, &remaining);After (DMA Manager + DMA Channel Driver):
sl_dma_channel_submit_transfer_m2p(&handle, src, dst, len,
SL_DMA_CTRL_SIZE_BYTE, NULL);
sl_dma_channel_status_t status;
do {
sl_dma_channel_get_status(&handle, &status);
} while (status.active);
// Bytes completed (not items remaining)
uint32_t completed = status.bytes_completed;
uint32_t remaining_bytes = len - completed;Key changes:
TransferDone()→ check!status.activefromget_status()TransferRemainingCount()→ compute frombytes_completedUnits: items remaining → bytes completed
Transfer Control (Pause/Resume/Stop)#
Before (DMADRV):
DMADRV_PauseTransfer(channel);
// ... do something ...
DMADRV_ResumeTransfer(channel);
// ... later ...
DMADRV_StopTransfer(channel);After (DMA Manager + DMA Channel Driver):
sl_dma_channel_suspend(&handle);
// ... do something ...
sl_dma_channel_resume(&handle);
// ... later ...
sl_dma_channel_abort(&handle);
// Note: abort triggers callback with aborted=true for each pending descriptorKey change: sl_dma_channel_abort() triggers callbacks — make sure your callback handles the aborted=true case.
Channel Cleanup and Deallocation#
Before (DMADRV):
DMADRV_FreeChannel(channel);
DMADRV_DeInit();After (DMA Manager + DMA Channel Driver):
// Abort any active transfer first (if needed)
sl_dma_channel_abort(&handle);
// Deinit the DMA Channel Driver handle
sl_dma_channel_deinit(&handle);
// Free the channel in DMA Manager
sl_dma_manager_free_channel(NULL, dma_channel);
// No DeInit needed — DMA Manager stays initializedKey changes:
Must deinit the DMA Channel Driver handle before freeing the channel
No global
DeInit()— DMA Manager persists for the lifetime of the application
Error Handling#
Before (DMADRV):
Ecode_t result;
result = DMADRV_AllocateChannel(&channel, NULL);
if (result == ECODE_EMDRV_DMADRV_CHANNELS_EXHAUSTED) {
// No channels available
} else if (result == ECODE_EMDRV_DMADRV_NOT_INITIALIZED) {
// Not initialized
} else if (result != ECODE_EMDRV_DMADRV_OK) {
// Other error
}After (DMA Manager + DMA Channel Driver):
sl_status_t status;
status = sl_dma_manager_allocate_channel(NULL, &channel);
switch (status) {
case SL_STATUS_OK:
// Success
break;
case SL_STATUS_NO_MORE_RESOURCE:
// No channels available
break;
case SL_STATUS_NOT_INITIALIZED:
// Not initialized (should not happen with auto-init)
break;
default:
// Other error
break;
}Key change: Use standard sl_status_t codes — consistent with all other Silicon Labs platform components.
Halfword Transfer (Size Conversion Example)#
Before (DMADRV):
// Transfer 100 halfword (16-bit) items from memory to peripheral
DMADRV_MemoryPeripheral(channel, signal, dst, src, true,
100, // 100 items
dmadrvDataSize2, // halfword
callback, user);After (DMA Manager + DMA Channel Driver):
// Transfer 200 bytes (100 halfwords × 2 bytes each)
sl_dma_channel_submit_transfer_m2p(&handle, src, dst,
200, // 100 * 2 = 200 bytes
SL_DMA_CTRL_SIZE_HALF,
NULL);Key change: Convert item count to byte count: 200 = 100 items × 2 bytes/halfword.
Complete Application Example#
Before (DMADRV) — UART TX/RX with DMA:
#include "dmadrv.h"
static unsigned int tx_channel;
static unsigned int rx_channel;
static volatile bool tx_done = false;
static volatile bool rx_done = false;
static bool tx_cb(unsigned int ch, unsigned int seq, void *user)
{
(void)ch; (void)seq; (void)user;
tx_done = true;
return true;
}
static bool rx_cb(unsigned int ch, unsigned int seq, void *user)
{
(void)ch; (void)seq; (void)user;
rx_done = true;
return true;
}
void uart_dma_init(void)
{
DMADRV_Init();
DMADRV_AllocateChannel(&tx_channel, NULL);
DMADRV_AllocateChannel(&rx_channel, NULL);
}
void uart_dma_send(const uint8_t *data, size_t len)
{
tx_done = false;
DMADRV_MemoryPeripheral(tx_channel,
dmadrvPeripheralSignal_EUSART0_TXBL,
(void *)&EUSART0->TXDATA,
(void *)data, true, (int)len,
dmadrvDataSize1, tx_cb, NULL);
}
void uart_dma_receive(uint8_t *buf, size_t len)
{
rx_done = false;
DMADRV_PeripheralMemory(rx_channel,
dmadrvPeripheralSignal_EUSART0_RXDATAV,
(void *)buf,
(void *)&EUSART0->RXDATA,
true, (int)len,
dmadrvDataSize1, rx_cb, NULL);
}
void uart_dma_cleanup(void)
{
DMADRV_FreeChannel(tx_channel);
DMADRV_FreeChannel(rx_channel);
DMADRV_DeInit();
}After (DMA Manager + DMA Channel Driver) — UART TX/RX with DMA:
#include "sl_dma_manager.h"
#include "sl_dma_channel.h"
static sl_dma_channel_handle_t tx_handle;
static sl_dma_channel_handle_t rx_handle;
static uint8_t tx_channel;
static uint8_t rx_channel;
static volatile bool tx_done = false;
static volatile bool rx_done = false;
static volatile bool dma_error = false;
static void tx_cb(sl_dma_channel_handle_t *handle, void *user_data,
bool error, bool aborted)
{
(void)handle;
(void)user_data;
if (!error && !aborted) {
tx_done = true;
} else {
dma_error = true;
}
}
static void rx_cb(sl_dma_channel_handle_t *handle, void *user_data,
bool error, bool aborted)
{
(void)handle;
(void)user_data;
if (!error && !aborted) {
rx_done = true;
} else {
dma_error = true;
}
}
void uart_dma_init(void)
{
sl_status_t status;
// No DMADRV_Init() — DMA Manager auto-initialized by SL Main
// Allocate and initialize TX channel
status = sl_dma_manager_allocate_channel(NULL, &tx_channel);
if (status != SL_STATUS_OK) { return; }
status = sl_dma_channel_init(&tx_handle, NULL, tx_channel, tx_cb, NULL);
if (status != SL_STATUS_OK) {
sl_dma_manager_free_channel(NULL, tx_channel);
return;
}
sl_dma_channel_set_peripheral_signal(&tx_handle,
SL_DMA_SIGNAL_EUSART0_TXFL);
// Allocate and initialize RX channel
status = sl_dma_manager_allocate_channel(NULL, &rx_channel);
if (status != SL_STATUS_OK) { return; }
status = sl_dma_channel_init(&rx_handle, NULL, rx_channel, rx_cb, NULL);
if (status != SL_STATUS_OK) {
sl_dma_manager_free_channel(NULL, rx_channel);
return;
}
sl_dma_channel_set_peripheral_signal(&rx_handle,
SL_DMA_SIGNAL_EUSART0_RXFL);
}
void uart_dma_send(const uint8_t *data, size_t len)
{
tx_done = false;
sl_status_t status;
status = sl_dma_channel_submit_transfer_m2p(&tx_handle,
(void *)data,
(void *)&EUSART0->TXDATA,
len,
SL_DMA_CTRL_SIZE_BYTE,
NULL);
if (status != SL_STATUS_OK) {
dma_error = true;
}
}
void uart_dma_receive(uint8_t *buf, size_t len)
{
rx_done = false;
sl_status_t status;
status = sl_dma_channel_submit_transfer_p2m(&rx_handle,
(void *)&EUSART0->RXDATA,
(void *)buf,
len,
SL_DMA_CTRL_SIZE_BYTE,
NULL);
if (status != SL_STATUS_OK) {
dma_error = true;
}
}
void uart_dma_cleanup(void)
{
// Deinit channel driver handles, then free channels
sl_dma_channel_deinit(&tx_handle);
sl_dma_manager_free_channel(NULL, tx_channel);
sl_dma_channel_deinit(&rx_handle);
sl_dma_manager_free_channel(NULL, rx_channel);
// No DMADRV_DeInit() equivalent needed
}Key changes in the complete example:
No
DMADRV_Init()/DMADRV_DeInit()Each channel requires: allocate → init handle → set signal (once)
Callbacks use
voidreturn witherror/abortedflagsSignal names updated:
TXBL→TXFL,RXDATAV→RXFLCleanup: deinit handle first, then free channel
Full
sl_status_terror handling throughout
8. Testing Your Migration#
Migration Validation Checklist#
Project compiles without errors or warnings
No legacy
#include "dmadrv.h"includes remainNo
DMADRV_API calls remain in codeNo
ECODE_EMDRV_DMADRVerror codes remainNo
dmadrvDataSizeordmadrvPeripheralSignalconstants remainAll callbacks use the new
voidsignature witherror/abortedparametersError handling uses
sl_status_tBasic M2P and P2M transfers complete successfully
Ping-pong transfers operate correctly (if applicable)
Abort/stop behavior triggers callbacks as expected
Performance (throughput, latency) is acceptable
Test Scenarios#
Test Case | Description | Expected Result |
|---|---|---|
Auto-init | SL Main initializes DMA Manager | No errors on first channel allocation |
Channel allocate | Allocate a DMA channel |
|
Channel allocate (exhausted) | Allocate more channels than available |
|
Channel reserve (specific) | Reserve a specific channel number |
|
M2P transfer | Memory-to-peripheral byte transfer | Transfer completes; callback fires with |
P2M transfer | Peripheral-to-memory transfer | Transfer completes; callback fires correctly |
M2M transfer | Memory-to-memory copy | Data correctly copied; callback fires |
Transfer abort | Abort an active transfer | Channel stops; callback fires with |
Suspend/Resume | Pause then resume a transfer | Transfer pauses and resumes correctly |
Ping-pong start | Start continuous ping-pong | Transfer loops between two buffers |
Ping-pong abort | Abort a running ping-pong | Looping stops; callback fires with |
Error callback | Simulate a DMA error | Callback fires with |
Channel cleanup | Deinit handle and free channel | Resources released cleanly |
Multiple channels | Two or more channels operating simultaneously | No interference between channels |
9. Troubleshooting#
Common Migration Issues#
Issue: Compilation error — undefined reference to DMADRV_*#
Symptom:
undefined reference to `DMADRV_Init'
undefined reference to `DMADRV_AllocateChannel'Cause: DMADRV component was removed but API calls were not migrated.
Solution: Replace all DMADRV_* calls with their new equivalents — see API Mapping Reference.
Issue: Compilation error — ECODE_EMDRV_DMADRV_OK undeclared#
Symptom:
error: 'ECODE_EMDRV_DMADRV_OK' undeclaredCause: Legacy error codes are not defined when using the new components.
Solution: Replace with sl_status_t codes — see Error Code Mapping.
Issue: Compilation error — implicit declaration of dmadrvPeripheralSignal_*#
Symptom:
error: 'dmadrvPeripheralSignal_EUSART0_TXBL' undeclaredCause: Legacy peripheral signal constants no longer exist.
Solution: Use the new SL_DMA_SIGNAL_* constants from sl_device_dma.h. Note the naming changes, especially for EUSART signals (TXBL → TXFL, RXDATAV → RXFL).
Issue: Ping-pong transfer does not stop when expected#
Symptom: Ping-pong transfer continues running even though the stop condition was reached.
Cause: In DMADRV, you could return false from the callback to stop the ping-pong. In the new DMA Channel Driver, the callback returns void and cannot control the transfer lifecycle.
Solution: Call sl_dma_channel_abort() from within the callback (or from another context) when you want to stop the ping-pong transfer:
void my_callback(sl_dma_channel_handle_t *handle, void *user_data,
bool error, bool aborted) {
if (!error && !aborted) {
if (should_stop()) {
sl_dma_channel_abort(handle); // Replaces "return false"
}
}
}Issue: sl_dma_channel_deinit() returns SL_STATUS_BUSY#
Symptom: Cannot deinit the channel handle.
Cause: The DMA channel is still enabled or has an active transfer.
Solution: Call sl_dma_channel_abort() before sl_dma_channel_deinit():
sl_dma_channel_abort(&handle); // Stop any active transfer
sl_dma_channel_deinit(&handle); // Now safe to deinit
sl_dma_manager_free_channel(NULL, ch);Issue: Transfer size mismatch for halfword/word transfers#
Symptom: Transfer completes but data is truncated or only partially transferred.
Cause: The legacy DMADRV used item count for len, while the new DMA Channel Driver uses byte count for size. If you passed 100 for 100 halfword items, you now need 200 (100 × 2 bytes).
Solution: Convert item count to byte count using the multiplier for the data size:
dmadrvDataSize1(byte):size = len × 1dmadrvDataSize2(halfword):size = len × 2dmadrvDataSize4(word):size = len × 4
Issue: Assertion failure during sl_dma_channel_init()#
Symptom: Assertion triggers inside sl_dma_channel_init().
Cause: The channel number passed may not have been allocated, or the handle pointer is NULL.
Solution: Ensure you allocate the channel via sl_dma_manager_allocate_channel() or sl_dma_manager_reserve_channel() before calling sl_dma_channel_init().
Error Code Quick Reference#
Legacy Error | New Error | When It Occurs |
|---|---|---|
|
| Operation succeeded |
|
| Invalid argument (NULL pointer, bad alignment, zero size) |
| N/A — auto-initialized | Not applicable with auto-init |
|
|
|
|
| All DMA channels are allocated |
|
| Channel is enabled or has active transfers |
| N/A | Not explicitly returned by DMA Manager |
|
| Trying to free/use an unallocated channel |
FAQ#
Q: Can I use both DMADRV and the new DMA Channel Driver simultaneously?
A: It is not recommended to mix the two for the same DMA channel. However, during the deprecation period (2026.6.0–2027.6.0), both components exist in the SDK and can coexist if they operate on different channels. The recommended approach is to migrate all DMADRV usage at once.
Q: Do I need to call sl_dma_manager_init() manually?
A: No. The dma_manager_init component (automatically included when you add dma_manager to your .slcp) contributes sl_dma_manager_instances_init() to the generated sl_platform_init() function, which is called during the SL Main initialization process. The default LDMA peripheral instance is initialized automatically.
Q: What if I used DMADRV_LdmaStartTransfer() with complex linked descriptors?
A: Use sl_dma_channel_submit_transfer_list() which provides a higher-level abstraction. Define your transfers as a linked list of sl_dma_channel_transfer_t structures. The driver handles descriptor allocation and linking automatically. For transfers larger than the hardware descriptor capacity, auto-segmentation splits them into multiple descriptors.
Q: My ping-pong callback processed buffers — how do I replicate this?
A: The callback is still invoked on each buffer completion during ping-pong transfers, so you can keep your buffer processing logic in the callback. The only change is that the callback returns void instead of bool. Use an application-level variable to track which buffer completed (since sequenceNo is no longer provided), and call sl_dma_channel_abort() instead of returning false when you want to stop.
Q: Will my existing dmadrv_config.h settings carry over?
A: Only the round-robin configuration has a new equivalent. Add the dma_manager_round_robin component to your .slcp and set SL_DMA_MANAGER_ROUND_ROBIN_CHANNEL_COUNT in sl_dma_manager_round_robin_config.h. Round-robin channels are configured automatically during DMA Manager initialization — no runtime API call is needed. The IRQ priority and channel count settings are no longer needed.
10. Additional Resources#
Related Documentation
DMA Manager API Reference — Complete DMA Manager header documentation
DMA Channel Driver API Reference — Complete DMA Channel Driver header documentation
DMA Descriptor Allocator API Reference — Descriptor allocator header
Support#
Migration guide for DMADRV to DMA Manager + DMA Channel Driver — Silicon Labs Gecko Platform SDK 2026.6.0