/*! * \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" /*------------------------------------------------------------------------------------ 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)) { switch (x) { case 0: dma_mask_tfr_set(reg); break; case 1: dma_mask_block_set(reg); break; case 2: dma_mask_src_tran_set(reg); break; case 3: dma_mask_dst_tran_set(reg); break; case 4: dma_mask_err_set(reg); break; } } } } return errorCode; } /** * @brief xc_dma_unmask_irq * @details * * @param * @param * @return void * @retval void * @retval void * @note */ 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)) { switch (x) { case 0: dma_mask_tfr_set(reg); break; case 1: dma_mask_block_set(reg); break; case 2: dma_mask_src_tran_set(reg); break; case 3: dma_mask_dst_tran_set(reg); break; case 4: dma_mask_err_set(reg); break; } } } } 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)) { switch (x) { case 0: dma_clear_tfr_set(reg); break; case 1: dma_clear_block_set(reg); break; case 2: dma_clear_src_tran_set(reg); break; case 3: dma_clear_dst_tran_set(reg); break; case 4: dma_clear_err_set(reg); break; } } } } 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); }