Files
hpw421/component/xc6xx_drivers/Drivers/xc_driver/xc_drv_dma.c
T
2026-07-03 18:08:25 +08:00

824 lines
20 KiB
C

/*!
* \file xc_drv_dma.c
*
* \brief Target xinchip dma driver implementation
*
* \copyright Revised BSD License, see section \ref LICENSE.
*
* \code
*
* _ __ _ ________ _
* | |/ /(_)___ / ____/ /_ (_)___
* | // / __ \/ / / __ \/ / __ \
* / |/ / / / / /___/ / / / / /_/ /
* /_/|_/_/_/ /_/\____/_/ /_/_/ .___/
* /_/
* (C) 2022-2025 XinChip
*
* \endcode
*
* \author ( XinChip ) Alex-J
*
* \author ( XinChip )
*/
/*-----------------------------------------------------------------------------------
INCLUDE HEADE FILES
------------------------------------------------------------------------------------*/
#include "xc_drv_dma.h"
#if XC_CHECK(XC_DMA_ENABLED)
/*------------------------------------------------------------------------------------
Global Variables
-------------------------------------------------------------------------------------*/
DMA_Instance_t DMA_Instance;
/*------------------------------------------------------------------------------------
Functions
-------------------------------------------------------------------------------------*/
/**
* @brief xc_dma_set_test_mode
* @details DMAC test mode set
*
* @param uint8_t - mode
* @return void
* @retval void
* @retval void
* @note
*/
__RAM_CODE void xc_dma_set_test_mode(uint8_t mode)
{
dma_test_reg_set(mode);
}
/**
* @brief xc_dma_set_channel_priority_order
* @details
*
* @param
* @param
* @return void
* @retval void
* @retval void
* @note
*/
__RAM_CODE void xc_dma_set_channel_priority_order(DMA_Instance_t *inst)
{
uint8_t priority[DMA_MAX_CHANNELS];
uint8_t i, ch_priority, order;
for (i = 0; i < DMA_MAX_CHANNELS; i++) {
priority[i] = 0;
inst->Ch_Order[i] = i;
}
for (uint8_t ch_num = 0; ch_num < DMA_MAX_CHANNELS; ch_num++) {
// read the priority of the current channel
ch_priority = dma_cfg_l__ch_prior__getf(ch_num);
// Check the channel order array to see what position
// this channel comes in the priority list.
for (i = 0; i <= DMA_MAX_CHANNELS; i++) {
if (A_MAXEQ_B(ch_priority, priority[i]) || i == DMA_MAX_CHANNELS) {
order = i;
break;
}
}
// Now we know the order for this channel insert in the correct
// array position and shift current priority ordering to suite.
for (i = ch_num; i > order; i--) {
priority[i] = priority[i - 1];
inst->Ch_Order[i] = inst->Ch_Order[i - 1];
}
priority[order] = ch_priority;
inst->Ch_Order[order] = ch_num;
}
}
/**
* @brief xc_dma_reset_instance
* @details
*
* @param
* @param
* @return void
* @retval void
* @retval void
* @note
*/
__RAM_CODE void xc_dma_reset_instance(DMA_Instance_t *inst)
{
for (uint8_t i = 0; i < DMA_MAX_CHANNELS; i++) {
switch (i) {
case 0:
inst->Ch[i].ch_num = DMA_CHANNEL0;
break;
case 1:
inst->Ch[i].ch_num = DMA_CHANNEL1;
break;
case 2:
inst->Ch[i].ch_num = DMA_CHANNEL2;
break;
case 3:
inst->Ch[i].ch_num = DMA_CHANNEL3;
break;
}
inst->Ch[i].userCallback = NULL;
inst->Ch[i].userListener = NULL;
}
// Set the channel priority order
xc_dma_set_channel_priority_order(inst);
}
/**
* @brief xc_dma_check_channel_range
* @details
*
* @param
* @param
* @return void
* @retval void
* @retval void
* @note
*/
__RAM_CODE uint8_t xc_dma_check_channel_range(eDMA_Ch_Num ch_num)
{
uint8_t errorCode = DMA_OK;
if (ch_num == DMA_NO_CHANNEL || ch_num > DMA_ALL_CHANNELS) {
errorCode = DMA_ECHRNG;
}
return errorCode;
}
/**
* @brief xc_dma_check_channel_busy
* @details
*
* @param
* @param
* @return void
* @retval void
* @retval void
* @note
*/
__RAM_CODE uint8_t xc_dma_check_channel_busy(eDMA_Ch_Num ch_num)
{
uint8_t errorCode = DMA_OK;
if (ch_num == DMA_NO_CHANNEL || ch_num & (~DMA_ALL_CHANNELS)) {
errorCode = DMA_ECHRNG;
} else {
if (((uint16_t)dma_ch_en_reg_get( )) & ch_num) {
errorCode = DMA_EBUSY;
}
}
return errorCode;
}
/**
* @brief xc_dma_get_channel_index
* @details
*
* @param
* @param
* @return void
* @retval void
* @retval void
* @note
*/
__RAM_CODE uint8_t xc_dma_get_channel_index(eDMA_Ch_Num ch_num)
{
uint8_t ch_enum = 1;
uint8_t ch_index = 0;
ch_num &= (DMA_ALL_CHANNELS >> 8);
while (ch_index < DMA_MAX_CHANNELS) {
if (ch_enum == ch_num) {
break;
}
ch_enum *= 2;
ch_index++;
}
return ch_index;
}
/**
* @brief xc_dma_enable
* @details
*
* @param
* @param
* @return void
* @retval void
* @retval void
* @note
*/
__RAM_CODE void xc_dma_enable(void) { dma_cfg_reg_set(ENABLE); }
/**
* @brief xc_dma_disable
* @details
*
* @param
* @param
* @return void
* @retval void
* @retval void
* @note
*/
__RAM_CODE uint8_t xc_dma_disable(void)
{
uint8_t errorCode = 0;
uint8_t dma_en = dma_cfg_reg__dma_en__getf( );
if (dma_en != 0) {
dma_cfg_reg_set(DISABLE);
// Ensure that the DMA was disabled
// May not disable due to split response on one
// of the DMA channels
if (dma_cfg_reg_get( )) {
errorCode = DMA_EBUSY;
}
}
return errorCode;
}
/**
* @brief xc_dma_is_enable
* @details
*
* @param
* @param
* @return void
* @retval void
* @retval void
* @note
*/
bool xc_dma_is_enable(void)
{
bool ret;
ret = (bool)dma_cfg_reg__dma_en__getf( );
return ret;
}
/**
* @brief xc_dma_enable_channel
* @details
*
* @param
* @param
* @return void
* @retval void
* @retval void
* @note
*/
__RAM_CODE uint8_t xc_dma_enable_channel(eDMA_Ch_Num ch_num)
{
uint8_t errorCode = 0;
// Check for valid channel number and not busy
errorCode = xc_dma_check_channel_busy(ch_num);
if (errorCode == 0) {
// The dw_dmac_channel_number enum is declared such that
// the enumerated value maps exactly to the value that
// needs to be written into the ChEnReg for enabling.
dma_ch_en_reg_set(ch_num);
}
return errorCode;
}
/**
* @brief xc_dma_disable_channel
* @details
*
* @param
* @param
* @return void
* @retval void
* @retval void
* @note
*/
__RAM_CODE uint8_t xc_dma_disable_channel(eDMA_Ch_Num ch_num)
{
uint8_t errorCode = 0;
uint32_t enabled_ch;
// Check for valid channel number
errorCode = xc_dma_check_channel_range(ch_num);
if (errorCode == 0) {
enabled_ch = (dma_ch_en_reg_get( ) & ch_num);
if (enabled_ch != 0) {
dma_ch_en_reg_set((ch_num & (DMA_ALL_CHANNELS << 8)));
// Ensure that the channel(s) was disabled.
// Channel may not disable due to a split response.
if (dma_ch_en_reg_get( ) & ch_num) {
errorCode = DMA_EBUSY;
}
}
}
return errorCode;
}
/**
* @brief xc_dma_is_channel_enabled
* @details
*
* @param
* @param
* @return void
* @retval void
* @retval void
* @note
*/
bool xc_dma_is_channel_enabled(eDMA_Ch_Num ch_num)
{
uint8_t ch_index;
bool ret;
ch_index = xc_dma_get_channel_index(ch_num);
// Allow only ONE channel to be specified
if (ch_index == DMA_MAX_CHANNELS)
return false;
// Check that the specified channel is in range
if (ch_num & (~DMA_ALL_CHANNELS))
return false;
ret = (bool)REG_BIT_VAL_GET(dma_ch_en_reg__ch_en__getf( ), 1 << ch_index, ch_index);
return ret;
}
/**
* @brief xc_dma_enable_channel_irq
* @details
*
* @param
* @param
* @return void
* @retval void
* @retval void
* @note
*/
__RAM_CODE uint8_t xc_dma_enable_channel_irq(eDMA_Ch_Num ch_num)
{
uint8_t errorCode = DMA_OK;
// Check for valid channel number and not busy
errorCode = xc_dma_check_channel_busy(ch_num);
if (errorCode == 0) {
// Loop through each channel in turn and disable
// the channel Irq for the selected channels.
for (uint8_t x = 0; x < DMA_MAX_CHANNELS; x++) {
if (ch_num & (1 << x)) {
if (dma_ctl_l__int_en__getf(x) != 0x1) {
dma_ctl_l__int_en__setf(x, ENABLE);
}
}
}
}
return errorCode;
}
/**
* @brief xc_dma_disable_channel_irq
* @details
*
* @param
* @param
* @return void
* @retval void
* @retval void
* @note
*/
__RAM_CODE uint8_t xc_dma_disable_channel_irq(eDMA_Ch_Num ch_num)
{
uint8_t errorCode = DMA_OK;
// Check for valid channel number and not busy
errorCode = xc_dma_check_channel_busy(ch_num);
if (errorCode == 0) {
// Loop through each channel in turn and disable
// the channel Irq for the selected channels.
for (uint8_t x = 0; x < DMA_MAX_CHANNELS; x++) {
if (ch_num & (1 << x)) {
if (dma_ctl_l__int_en__getf(x) != 0x0) {
dma_ctl_l__int_en__setf(x, DISABLE);
}
}
}
}
return errorCode;
}
/**
* @brief xc_dma_mask_irq
* @details
*
* @param
* @param
* @return void
* @retval void
* @retval void
* @note
*/
__RAM_CODE uint8_t xc_dma_mask_irq(eDMA_Ch_Num ch_num, eDMA_Irq ch_irq)
{
uint8_t errorCode = DMA_OK;
uint16_t reg;
// Check for valid channel number
errorCode = xc_dma_check_channel_range(ch_num);
if (errorCode == 0) {
// Loop through and clear the selected channel Irq
// for the targeted channels.
reg = (ch_num & (DMA_ALL_CHANNELS << 8));
for (uint8_t x = 0; x < DMA_MAX_INTERRUPTS; x++) {
if (ch_irq & (1 << x)) {
if(x == 0){
dma_mask_tfr_set(reg);
}else if(x == 1){
dma_mask_block_set(reg);
}else if(x == 2){
dma_mask_src_tran_set(reg);
}else if(x == 3){
dma_mask_dst_tran_set(reg);
}else if(x == 4){
dma_mask_err_set(reg);
}
}
}
}
return errorCode;
}
/**
* @brief xc_dma_unmask_irq
* @details
*
* @param
* @param
* @return void
* @retval void
* @retval void
* @note
*/
__RAM_CODE uint8_t xc_dma_unmask_irq(eDMA_Ch_Num ch_num, eDMA_Irq ch_irq)
{
uint8_t errorCode = DMA_OK;
uint16_t reg;
// Check for valid channel number
errorCode = xc_dma_check_channel_range(ch_num);
if (errorCode == 0) {
// Loop through and clear the selected channel Irq
// for the targeted channels.
reg = ch_num;
for (uint8_t x = 0; x < DMA_MAX_INTERRUPTS; x++) {
if (ch_irq & (1 << x)) {
if(x == 0){
dma_mask_tfr_set(reg);
}else if(x == 1){
dma_mask_block_set(reg);
}else if(x == 2){
dma_mask_src_tran_set(reg);
}else if(x == 3){
dma_mask_dst_tran_set(reg);
}else if(x == 4){
dma_mask_err_set(reg);
}
}
}
}
return errorCode;
}
/**
* @brief xc_dma_clear_irq
* @details
*
* @param
* @param
* @return void
* @retval void
* @retval void
* @note
*/
__RAM_CODE uint8_t xc_dma_clear_irq(eDMA_Ch_Num ch_num, eDMA_Irq ch_irq)
{
uint8_t errorCode = DMA_OK;
uint16_t reg;
// Check for valid channel number
errorCode = xc_dma_check_channel_range(ch_num);
if (errorCode == 0) {
// Loop through and clear the selected channel Irq
// for the targeted channels.
reg = (ch_num & (DMA_ALL_CHANNELS >> 8));
for (uint8_t x = 0; x < DMA_MAX_INTERRUPTS; x++) {
if (ch_irq & (1 << x)) {
if(x == 0){
dma_clear_tfr_set(reg);
}else if(x == 1){
dma_clear_block_set(reg);
}else if(x == 2){
dma_clear_src_tran_set(reg);
}else if(x == 3){
dma_clear_dst_tran_set(reg);
}else if(x == 4){
dma_clear_err_set(reg);
}
}
}
}
return errorCode;
}
/**
* @brief xc_dma_init
* @details
*
* @param
* @param
* @return void
* @retval void
* @retval void
* @note
*/
__RAM_CODE uint8_t xc_dma_init(void)
{
// Remove from test mode
xc_dma_set_test_mode(DISABLE);
// Reset the DMA instance structure
xc_dma_reset_instance(&DMA_Instance);
// Disable the DMA controller
uint8_t errorCode = xc_dma_disable( );
if (errorCode == 0) {
// Disable all DMA channels
errorCode = xc_dma_disable_channel(DMA_ALL_CHANNELS);
}
if (errorCode == 0) {
// Disable all channel interrupts
errorCode = xc_dma_disable_channel_irq(DMA_ALL_CHANNELS);
}
if (errorCode == 0) {
// Mask all channel interrupts
errorCode = xc_dma_mask_irq(DMA_ALL_CHANNELS, DMA_IRQ_ALL);
}
if (errorCode == 0) {
// Clear any pending interrupts
errorCode = xc_dma_clear_irq(DMA_ALL_CHANNELS, DMA_IRQ_ALL);
}
return errorCode;
}
/**
* @brief xc_dma_set_channel_config
* @details
*
* @param
* @param
* @return void
* @retval void
* @retval void
* @note
*/
__RAM_CODE uint8_t xc_dma_set_channel_config(eDMA_Ch_Num ch_num,
DMA_Chx_Cfg_t *ch)
{
uint8_t errorCode;
uint8_t ch_index;
ch_index = xc_dma_get_channel_index(ch_num);
// Check for valid channel number and not busy
errorCode = xc_dma_check_channel_busy(ch_num);
if (errorCode == 0) {
// Set the control register
dma_ctl_l_pack(ch_index, ch->ctl_tt_fc, ch->ctl_src_msize, ch->ctl_dst_msize,
ch->ctl_sinc, ch->ctl_dinc, ch->ctl_src_tr_width, ch->ctl_dst_tr_width,
0);
dma_ctl_h__block_ts__setf(ch_index, ch->ctl_block_ts);
// Set the config register
dma_cfg_l__hs_sel_src__setf(ch_index, ch->cfg_hs_sel_src);
dma_cfg_l__hs_sel_dst__setf(ch_index, ch->cfg_hs_sel_dst);
dma_cfg_h_pack(ch_index, (ch->cfg_dst_per & 0xf), (ch->cfg_src_per & 0xf), (ch->cfg_fifo_mode & 0x1));
// set the SAR/DAR registers
dma_sar_set(ch_index, ch->sar);
dma_dar_set(ch_index, ch->dar);
};
return errorCode;
}
/**
* @brief xc_dma_set_listener
* @details
*
* @param
* @param
* @return void
* @retval void
* @retval void
* @note
*/
__RAM_CODE bool xc_dma_set_listener(eDMA_Ch_Num ch_num,
DMA_Callback userFunction)
{
uint8_t ch_index;
DMA_Instance_t *inst = &DMA_Instance;
ch_index = xc_dma_get_channel_index(ch_num);
// Allow only ONE channel to be specified
if (ch_index == DMA_MAX_CHANNELS)
return false;
// Check that the specified channel is in range
if (ch_num & (~DMA_ALL_CHANNELS))
return false;
inst->Ch[ch_index].userListener = userFunction;
return true;
}
/**
* @brief xc_dma_start_transfer
* @details
*
* @param
* @param
* @return void
* @retval void
* @retval void
* @note
*/
__RAM_CODE uint8_t xc_dma_start_transfer(eDMA_Ch_Num ch_num, DMA_Callback cb_func)
{
uint8_t errorCode;
uint8_t ch_index;
DMA_Instance_t *inst = &DMA_Instance;
// Update the channel instance
ch_index = xc_dma_get_channel_index(ch_num);
// Allow only ONE channel to be specified
if (ch_index == DMA_MAX_CHANNELS) {
errorCode = DMA_ECHRNG;
} else {
// Check for valid channel number and not busy
errorCode = xc_dma_check_channel_busy(ch_num);
}
if (errorCode == 0) {
// Disable the channels interrupts
errorCode = xc_dma_disable_channel_irq(ch_num);
}
if (errorCode == 0) {
// set the call back function, the number of blocks
// in the transfer and the source and destination states.
inst->Ch[ch_index].userCallback = cb_func;
// always want to unmask the tfr, block and err interrupts
xc_dma_unmask_irq(ch_num, DMA_IRQ_TRF);
xc_dma_unmask_irq(ch_num, DMA_IRQ_ERR);
// Enable the channel interrupts for the type of transfer
errorCode = xc_dma_enable_channel_irq(ch_num);
if (errorCode == 0) {
// Enable the DMA channel
errorCode = xc_dma_enable_channel(ch_num);
}
}
return errorCode;
}
/**
* @brief dma_irq_handler
* @details
*
* @param
* @param
* @return void
* @retval void
* @retval void
* @note
*/
__RAM_CODE bool dma_irq_handler(DMA_Instance_t *inst)
{
bool retval;
uint8_t i, ch_index, callbackArg;
uint32_t reg, mask;
DMA_Callback userCallback;
// Assume an interrupt will be processed. The return value will be
// set to false if an active interrupt is not found.
retval = true;
// ERR INTERRUPT
if (dma_status_int__err__getf( )) {
reg = dma_status_err_get( );
// Loop through the channels until we find
// active interrupt. We start at the highest priority
// channel and work down to the lowest priority.
for (i = 0; i < DMA_MAX_CHANNELS; i++) {
mask = 1 << inst->Ch_Order[i];
if (reg & mask) {
ch_index = inst->Ch_Order[i];
break;
}
}
// run the listener function
if (inst->Ch[ch_index].userListener != NULL) {
userCallback = inst->Ch[ch_index].userListener;
callbackArg = DMA_IRQ_ERR;
userCallback(callbackArg);
}
// clear the interrupt
dma_clear_err_set(mask);
}
// TFR INTERRUPT
else if (dma_status_int__tfr__getf( )) {
reg = dma_status_tfr_get( );
// Loop through the channels until we find
// active interrupt. We start at the highest priority
// channel and work down to the lowest priority.
for (i = 0; i < DMA_MAX_CHANNELS; i++) {
mask = 1 << inst->Ch_Order[i];
if (reg & mask) {
ch_index = inst->Ch_Order[i];
break;
}
}
// Disable all channel interrupts
xc_dma_disable_channel_irq(inst->Ch[ch_index].ch_num);
// clear any pending block/srcTran/dstTran interrupts
dma_clear_block_set(mask);
dma_clear_src_tran_set(mask);
dma_clear_dst_tran_set(mask);
xc_dma_clear_irq(inst->Ch[ch_index].ch_num, DMA_IRQ_TRF);
// Mask all channel interrupts
xc_dma_mask_irq(inst->Ch[ch_index].ch_num, DMA_IRQ_ALL);
// run the callback function
if (inst->Ch[ch_index].userCallback != NULL) {
userCallback = inst->Ch[ch_index].userCallback;
callbackArg = 0;
userCallback(callbackArg);
}
} else {
// If we've reached this point, either the enabling and
// disabling of DW_ahb_dmac interrupts is not being handled
// properly or this function is being called unnecessarily.
retval = false;
}
return retval;
}
__RAM_CODE void DMA_Handler(void) { dma_irq_handler(&DMA_Instance); }
#endif // XC_CHECK(XC_DMA_ENABLED)