/* * Copyright (c) 2022 Winner Microelectronics Co., Ltd. All rights reserved. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * @file wm_dma.c * * @brief DMA Driver Module * * @author dave * * Copyright (c) 2014 Winner Microelectronics Co., Ltd. */ #include #include #include #include "wm_regs.h" #include "wm_irq.h" #include "wm_osal.h" #include "core_804.h" #include "wm_pmu.h" #include "wm_dma.h" static u16 dma_used_bit = 0; struct tls_dma_channels { unsigned char channels[8]; /* list of channels */ }; typedef void (*dma_irq_callback)(void *p); struct dma_irq_context { u8 flags; dma_irq_callback burst_done_pf; void *burst_done_priv; dma_irq_callback transfer_done_pf; void *transfer_done_priv; }; static struct dma_irq_context dma_context[8]; static struct tls_dma_channels channels; extern void wm_delay_ticks(uint32_t ticks); extern void tls_irq_priority(u8 vec_no, u32 prio); static void dma_irq_proc(void *p) { unsigned char ch; unsigned int int_src; ch = (unsigned char)(unsigned long)p; int_src = tls_reg_read32(HR_DMA_INT_SRC); if (ch > 3) { for (ch = 4; ch < 8; ch++) { if (int_src & (TLS_DMA_IRQ_BOTH_DONE << (ch * 2))) { // 2:byte alignment break; } } if (ch == 8) { return; } } if (DMA_CTRL_REG(ch) & 0x01) { uint32_t temp = 0, cur_len = 0; static uint32_t len[8] = {0, 0, 0, 0, 0, 0, 0, 0}; temp = DMA_CTRL_REG(ch); if (len[ch] == 0) { len[ch] = (temp & 0xFFFF00) >> 8; } cur_len = (temp & 0xFFFF00) >> 8; if ((cur_len + len[ch]) > 0xFFFF) { cur_len = 0; DMA_CHNLCTRL_REG(ch) |= (1 << 1); while (DMA_CHNLCTRL_REG(ch) & (1 << 0)) { } DMA_CHNLCTRL_REG(ch) |= (1 << 0); } temp &= ~(0xFFFF << 8); temp |= ((cur_len + len[ch]) << 8); DMA_CTRL_REG(ch) = temp; } if ((int_src & (TLS_DMA_IRQ_BOTH_DONE << (ch * 2))) && (TLS_DMA_IRQ_BOTH_DONE == dma_context[ch].flags)) { tls_dma_irq_clr(ch, TLS_DMA_IRQ_BOTH_DONE); if (dma_context[ch].burst_done_pf) { dma_context[ch].burst_done_pf(dma_context[ch].burst_done_priv); } } else if ((int_src & (TLS_DMA_IRQ_BURST_DONE << (ch * 2))) && (TLS_DMA_IRQ_BURST_DONE == dma_context[ch].flags)) { tls_dma_irq_clr(ch, TLS_DMA_IRQ_BOTH_DONE); if (dma_context[ch].burst_done_pf) { dma_context[ch].burst_done_pf(dma_context[ch].burst_done_priv); } } else if ((int_src & (TLS_DMA_IRQ_TRANSFER_DONE << (ch * 2))) && (TLS_DMA_IRQ_TRANSFER_DONE == dma_context[ch].flags)) { tls_dma_irq_clr(ch, TLS_DMA_IRQ_BOTH_DONE); if (dma_context[ch].transfer_done_pf) { dma_context[ch].transfer_done_pf(dma_context[ch].transfer_done_priv); } } return; } ATTRIBUTE_ISR void DMA_Channel0_IRQHandler(void) { csi_kernel_intrpt_enter(); dma_irq_proc((void *)0); csi_kernel_intrpt_exit(); } ATTRIBUTE_ISR void DMA_Channel1_IRQHandler(void) { csi_kernel_intrpt_enter(); dma_irq_proc((void *)1); csi_kernel_intrpt_exit(); } ATTRIBUTE_ISR void DMA_Channel2_IRQHandler(void) { csi_kernel_intrpt_enter(); dma_irq_proc((void *)2); csi_kernel_intrpt_exit(); } ATTRIBUTE_ISR void DMA_Channel3_IRQHandler(void) { csi_kernel_intrpt_enter(); dma_irq_proc((void *)3); csi_kernel_intrpt_exit(); } ATTRIBUTE_ISR void DMA_Channel4_7_IRQHandler(void) { csi_kernel_intrpt_enter(); dma_irq_proc((void *)4); csi_kernel_intrpt_exit(); } /** * @brief This function is used to clear dma interrupt flag. * * @param[in] ch Channel no.[0~7] * @param[in] flags Flags setted to TLS_DMA_IRQ_BURST_DONE, TLS_DMA_IRQ_TRANSFER_DONE, TLS_DMA_IRQ_BOTH_DONE. * * @return None * * @note None */ void tls_dma_irq_clr(unsigned char ch, unsigned char flags) { unsigned int int_src = 0; int_src |= (flags << (2 * ch)); // 2:byte alignment tls_reg_write32(HR_DMA_INT_SRC, int_src); return; } /** * @brief This function is used to register dma interrupt callback function. * * @param[in] ch Channel no.[0~7] * @param[in] callback is the dma interrupt call back function. * @param[in] arg the param of the callback function. * @param[in] flags Flags setted to TLS_DMA_IRQ_BURST_DONE, TLS_DMA_IRQ_TRANSFER_DONE, TLS_DMA_IRQ_BOTH_DONE. * * @return None * * @note None */void tls_dma_irq_register(unsigned char ch, void (*callback)(void *p), void *arg, unsigned char flags) { unsigned int mask; unsigned char Channel = ch; mask = tls_reg_read32(HR_DMA_INT_MASK); mask |= (TLS_DMA_IRQ_BOTH_DONE << (2 * Channel)); mask &= ~(flags << (2 * Channel)); // 2:byte alignment tls_reg_write32(HR_DMA_INT_MASK, mask); dma_context[Channel].flags = flags; if (flags & TLS_DMA_IRQ_BURST_DONE) { dma_context[Channel].burst_done_pf = callback; dma_context[Channel].burst_done_priv = arg; } if (flags & TLS_DMA_IRQ_TRANSFER_DONE) { dma_context[Channel].transfer_done_pf = callback; dma_context[Channel].transfer_done_priv = arg; } if (Channel > 3) { Channel = 4; // 4:byte alignment } tls_irq_priority(DMA_Channel0_IRQn + Channel, Channel / 2); // 2:byte alignment tls_irq_enable(DMA_Channel0_IRQn + Channel); } /** * @brief This function is used to Wait until DMA operation completes * * @param[in] ch channel no * * @retval 0 completed * @retval -1 failed * * @note None */ int tls_dma_wait_complt(unsigned char ch) { unsigned long timeout = 0; while (DMA_CHNLCTRL_REG(ch) & DMA_CHNL_CTRL_CHNL_ON) { tls_os_time_delay(1); timeout ++; if (timeout > 500) return -1; } return 0; } /** * @brief This function is used to Start the DMA controller by Wrap * * @param[in] ch channel no * @param[in] dma_desc pointer to DMA channel descriptor structure * @param[in] auto_reload does restart when current transfer complete * @param[in] src_zize dource address size * @param[in] dest_zize destination address size * * @retval 1 success * @retval 0 failed * * @note * DMA Descriptor: +--------------------------------------------------------------+ * |Vld[31] | RSV | * +--------------------------------------------------------------+ * | RSV | Dma_Ctrl[16:0] | * +--------------------------------------------------------------+ * | Src_Addr[31:0] | * +--------------------------------------------------------------+ * | Dest_Addr[31:0] | * +--------------------------------------------------------------+ * | Next_Desc_Add[31:0] | * +--------------------------------------------------------------+ */ unsigned char tls_dma_start_by_wrap(unsigned char ch, struct tls_dma_descriptor *dma_desc, unsigned char auto_reload, unsigned short src_zize, unsigned short dest_zize) { if ((ch > 7) && !dma_desc) { // 7:byte alignment return 1; } DMA_SRCWRAPADDR_REG(ch) = dma_desc->src_addr; DMA_DESTWRAPADDR_REG(ch) = dma_desc->dest_addr; DMA_WRAPSIZE_REG(ch) = (dest_zize << 16) | src_zize; DMA_CTRL_REG(ch) = ((dma_desc->dma_ctrl & 0x1ffff) << 1) | (auto_reload ? 0x1: 0x0); DMA_CHNLCTRL_REG(ch) |= DMA_CHNL_CTRL_CHNL_ON; return 0; } /** * @brief This function is used to Start the DMA controller * * @param[in] ch channel no * @param[in] dma_desc pointer to DMA channel descriptor structure * @param[in] auto_reload does restart when current transfer complete * * @retval 1 success * @retval 0 failed * * @note * DMA Descriptor: +--------------------------------------------------------------+ * |Vld[31] | RSV | * +--------------------------------------------------------------+ * | RSV | Dma_Ctrl[16:0] | * +--------------------------------------------------------------+ * | Src_Addr[31:0] | * +--------------------------------------------------------------+ * | Dest_Addr[31:0] | * +--------------------------------------------------------------+ * | Next_Desc_Add[31:0] | * +--------------------------------------------------------------+ */ unsigned char tls_dma_start(unsigned char ch, struct tls_dma_descriptor *dma_desc, unsigned char auto_reload) { if ((ch > 7) && !dma_desc) { // 7:byte alignment return 1; } if ((dma_used_bit &(1<src_addr; DMA_DESTADDR_REG(ch) = dma_desc->dest_addr; DMA_CTRL_REG(ch) = ((dma_desc->dma_ctrl & 0x7fffff) << 1) | (auto_reload ? 0x1: 0x0); DMA_CHNLCTRL_REG(ch) |= DMA_CHNL_CTRL_CHNL_ON; return 0; } /** * @brief This function is used to To stop current DMA channel transfer * * @param[in] ch channel no. to be stopped * * @retval 0 success * @retval 1 failed * * @note * If channel stop, DMA_CHNL_CTRL_CHNL_ON bit in DMA_CHNLCTRL_REG is cleared. */ unsigned char tls_dma_stop(unsigned char ch) { if (ch > 7) { // 7:byte alignment return 1; } if (DMA_CHNLCTRL_REG(ch) & DMA_CHNL_CTRL_CHNL_ON) { DMA_CHNLCTRL_REG(ch) |= DMA_CHNL_CTRL_CHNL_OFF; while (DMA_CHNLCTRL_REG(ch) & DMA_CHNL_CTRL_CHNL_ON); } return 0; } /** * @brief This function is used to Request a free dma channel * * @param[in] ch specified channel when ch is valid and not used. * @param[in] flags flags setted to selected channel * * @return Real DMA Channel No. * * @note * If ch is invalid or valid but used, the function will select a random free channel. * else return the selected channel no. */ unsigned char tls_dma_request(unsigned char ch, unsigned char flags) { unsigned char freeCh = 0xFF; /* If channel is valid, try to use specified DMA channel! */ if ((ch < 8)) { if (!(channels.channels[ch] & TLS_DMA_FLAGS_CHANNEL_VALID)) { freeCh = ch; } } /* If ch is not valid, or ch has been used, try to select another free channel for the caller */ if (freeCh == 0xFF) { int i = 0; for (i = 0; i < 8; i++) { if (!(channels.channels[i] & TLS_DMA_FLAGS_CHANNEL_VALID)) { freeCh = i; break; } } if (i == 8) { printf("!!!There is no free DMA channel.!!!\n"); } } if ((freeCh < 8)) { if (dma_used_bit == 0) { tls_open_peripheral_clock(TLS_PERIPHERAL_TYPE_DMA); } dma_used_bit |= (1<