/* Copyright (c) 2020 Kneron, Inc. All Rights Reserved. * * The information contained herein is property of Kneron, Inc. * Terms and conditions of usage are described in detail in Kneron * STANDARD SOFTWARE LICENSE AGREEMENT. * * Licensees are granted free, non-transferable use of the information. * NO WARRANTY of ANY KIND is provided. This heading must NOT be removed * from the file. */ /****************************************************************************** * Filename: * --------- * kdrv_gdma.c * * Project: * -------- * KL520 * * Description: * ------------ * This GDMA driver is for Generic Direct Memory Access * HW: Faraday FTDMAC020 (DMAC32), connect to AHB BUS 0 only, AHB BUS 1 is not in use * * Author: * ------- * Hans Yang ** ******************************************************************************/ /****************************************************************************** Head Block of The File ******************************************************************************/ //#define GDMA_DBG //#define GDMA_ERR //#define GDMA_FEWER_CODE_SIZE // Sec 0: Comment block of the file // Sec 1: Include File #include "kdrv_cmsis_core.h" #include "regbase.h" #include "kdrv_gdma.h" #include #if defined(GDMA_DBG) | defined(GDMA_ERR) #include "kmdw_console.h" #endif // Sec 2: Constant Definitions, Imported Symbols, miscellaneous // some fixed numbers #define DMA_FIFO_SIZE 4 // 16 entries, from HW designer #define NUM_DMA_CHANNELS 8 #define NUM_CH_FOR_MEMCPY 3 #define DMA_CHANNEL_BIT_MASK ((0x1 << NUM_DMA_CHANNELS) - 1) // register bit : DMA enable #define DMACEN 0x1 // thread flag for transfer done #define GDMA_FLAG_XFER_DONE 0x100 typedef volatile union { struct { uint32_t INT; // 0x00 uint32_t INT_TC; // 0x04 uint32_t INT_TC_CLR; // 0x08 uint32_t INT_ERR_ABT; // 0x0C uint32_t INT_ERR_ABT_CLR; // 0x10 uint32_t TC; // 0x14 uint32_t ERR_ABT; // 0x18 uint32_t CH_EN; // 0x1C uint32_t CH_BUSY; // 0x20 uint32_t CSR; // 0x24 uint32_t SYNC; // 0x28 uint32_t DMAC_REVISION; // 0x30 uint32_t DMAC_FEATURE; // 0x34 } dw; //double word } U_regGDMA; typedef volatile union { struct { uint32_t CSR; // 0x100 + 0x20*n uint32_t CFG; // 0x104 + 0x20*n uint32_t SrcAddr; // 0x108 + 0x20*n uint32_t DstAddr; // 0x10C + 0x20*n uint32_t LLP; // 0x110 + 0x20*n uint32_t SIZE; // 0x114 + 0x20*n uint32_t reserved[2]; // 0x118 + 0x20*n } dw; //double word struct { // CSR uint32_t CSR_CH_EN : 1; // channel enable uint32_t CSR_DST_SEL : 1; // dst select AHB 0/1 uint32_t CSR_SRC_SEL : 1; // src select AHB 0/1 uint32_t CSR_DSTAD_CTL : 2; // dst address control: inc/dec/fixed uint32_t CSR_SRCAD_CTL : 2; // src address control: inc/dec/fixed uint32_t CSR_MODE : 1; // normal/HW handshake mode uint32_t CSR_DST_WIDTH : 3; // dst transfer width, use gdma_transfer_width_t uint32_t CSR_SRC_WIDTH : 3; // src transfer width, use gdma_transfer_width_t uint32_t CSR_reserved0 : 1; // uint32_t CSR_ABT : 1; // abort current transaction uint32_t CSR_SRC_SIZE : 3; // src busrt size, use gdma_burst_size_t uint32_t CSR_PROT1 : 1; // we dont use it uint32_t CSR_PROT2 : 1; // we dont use it uint32_t CSR_PROT3 : 1; // we dont use it uint32_t CSR_PRIORITY : 2; // priority ? //uint32_t CSR_reserved1 : 2; // uint32_t CSR_DMA_FF_TH : 3; // DMA FIFO threshold value, fix it to '3'(8) as our FIFO size is 16 uint32_t CSR_reserved2 : 4; // uint32_t CSR_TC_MSK : 1; // terminal count status mask // CFG uint32_t CFG_INT_TC_MSK : 1; // channel terminal count interrupt mask uint32_t CFG_INT_ERR_MSK : 1; // channel error interrupt mask uint32_t CFG_INT_ABT_MSK : 1; // channel abort interrupt mask uint32_t CFG_SRC_RS : 4; // src DMA request select (for HW handshake mode) uint32_t CFG_SRC_HE : 1; // src hardware handshake mode enable (for HW handshake mode) uint32_t CFG_BUSY : 1; // DMA channel is busy uint32_t CFG_DST_RS : 4; // dst DMA request select (for HW handshake mode) uint32_t CFG_DST_HE : 1; // dst hardware handshake mode enable (for HW handshake mode) uint32_t CFG_reserved1 : 2; // uint32_t CFG_LLP_CNT : 4; // chain transfer counter uint32_t CFG_reserved2 : 12; // } bf; //bit-field } U_regGDMA_CH; #define regGDMA ((U_regGDMA *)DMAC_FTDMAC020_PA_BASE) // global setting registers #define regGDMA_ch ((U_regGDMA_CH *)(DMAC_FTDMAC020_PA_BASE + 0x100)) // per-channel registers in array form /****************************************************************************** Declaration of External Variables & Functions ******************************************************************************/ // Sec 3: declaration of external variable // Sec 4: declaration of external function prototype /****************************************************************************** Declaration of data structure ******************************************************************************/ // Sec 5: structure, uniou, enum, linked list // this struct is for internal use only typedef struct { gdma_setting_t settings; uint8_t in_use; uint8_t running; osThreadId_t user_tid; // user thread ID, when 0 means non-os context gdma_xfer_callback_t user_cb; // user callback void *user_arg; // user argument uint32_t src_end; // workaround for ending content uint32_t dst_end; // workaround for ending content kdrv_status_t ret_sts; } _GDMA_CH_Ctrl_t; /****************************************************************************** Declaration of Global Variables & Functions ******************************************************************************/ // Sec 6: declaration of global variable // Sec 7: declaration of global function prototype /****************************************************************************** Declaration of static Global Variables & Functions ******************************************************************************/ // Sec 8: declaration of static global variable static uint8_t g_dma_inited = 0; static _GDMA_CH_Ctrl_t *gDmaChCtrl = 0; // Sec 9: declaration of static function prototype /****************************************************************************** // Sec 10: C Functions ******************************************************************************/ static gdma_transfer_width_t _get_width_from_align(uint8_t align) { if (align == 0) return GDMA_TXFER_WIDTH_32_BITS; else if (align == 2) return GDMA_TXFER_WIDTH_16_BITS; else return GDMA_TXFER_WIDTH_8_BITS; } static void _fill_up_last_few_bytes(int ch) { // doing this is because the GDMA does not complete the last few bytes in some cases (address/num is not divisiable by 4) for (int i = 0; i < 3; i++) *((uint8_t *)(gDmaChCtrl[ch].dst_end - i)) = *((uint8_t *)(gDmaChCtrl[ch].src_end - i)); } static void gdma_isr(void) { uint32_t int_sts = regGDMA->dw.INT; uint32_t handled = 0; if (int_sts & DMA_CHANNEL_BIT_MASK) // check INT register low 8 bits (8 channels) { uint32_t int_tc = regGDMA->dw.INT_TC; uint32_t int_err_abt = regGDMA->dw.INT_ERR_ABT; // clear all interrupt status as early as possible regGDMA->dw.INT_TC_CLR = int_tc; regGDMA->dw.INT_ERR_ABT_CLR = int_err_abt; #ifdef GDMA_DBG kmdw_printf("GDMA ISR: int_sts 0x%x, int_tc 0x%x, int_err_abt 0x%x\n", int_sts, int_tc, int_err_abt); #endif uint32_t int_abt = (int_err_abt >> 16); // scanl all channels for (int ch = 0; ch < NUM_DMA_CHANNELS; ch++) { int32_t xferStatus = -100; if (int_tc & (0x1 << ch)) { xferStatus = KDRV_STATUS_OK; } else if ((int_err_abt & (0x1 << ch)) || (int_abt & (0x1 << ch))) { xferStatus = KDRV_STATUS_ERROR; } if (xferStatus == -100) // no status change for this ch continue; if (gDmaChCtrl[ch].user_cb) { _fill_up_last_few_bytes(ch); gDmaChCtrl[ch].running = 0; gDmaChCtrl[ch].user_cb((kdrv_status_t)xferStatus, gDmaChCtrl[ch].user_arg); // NOTE: this callback is from ISR context } else { gDmaChCtrl[ch].ret_sts = (kdrv_status_t)xferStatus; if (gDmaChCtrl[ch].user_tid) osThreadFlagsSet(gDmaChCtrl[ch].user_tid, GDMA_FLAG_XFER_DONE); else gDmaChCtrl[ch].running = 0; } } ++handled; } #ifdef GDMA_ERR if (handled == 0) { kmdw_printf("GDMA: interrupt is handled\n"); } #endif } kdrv_status_t kdrv_gdma_initialize(void) { if (g_dma_inited) return KDRV_STATUS_ERROR; // FIXME: enable DMAC0 clock (hclk_en[3]) with better API/Macro { uint32_t ahb_clock_reg = (*(volatile unsigned int *)(SCU_FTSCU100_PA_BASE + 0x50)); ahb_clock_reg |= 0x8; (*(volatile unsigned int *)(SCU_FTSCU100_PA_BASE + 0x50)) = ahb_clock_reg; } // global dma register initialization { regGDMA->dw.CSR = DMACEN; // enable the DMA controller } // allocate per-channel control blocks gDmaChCtrl = malloc(NUM_DMA_CHANNELS * sizeof(_GDMA_CH_Ctrl_t)); // initialize per-channel dma register settings to default values for (int i = 0; i < NUM_DMA_CHANNELS; i++) { gDmaChCtrl[i].in_use = 0; gDmaChCtrl[i].running = 0; #ifdef GDMA_FEWER_CODE_SIZE regGDMA_ch[i].dw.CSR = 0x3001200; regGDMA_ch[i].dw.CFG = 0x0; #else // CSR regGDMA_ch[i].bf.CSR_CH_EN = 0; // disabled at initialization time regGDMA_ch[i].bf.CSR_DST_SEL = 0; // AHB 0 regGDMA_ch[i].bf.CSR_SRC_SEL = 0; // AHB 0 regGDMA_ch[i].bf.CSR_DSTAD_CTL = GDMA_INCREMENT_ADDRESS; regGDMA_ch[i].bf.CSR_SRCAD_CTL = GDMA_INCREMENT_ADDRESS; regGDMA_ch[i].bf.CSR_MODE = GDMA_NORMAL_MODE; regGDMA_ch[i].bf.CSR_DST_WIDTH = GDMA_TXFER_WIDTH_32_BITS; regGDMA_ch[i].bf.CSR_SRC_WIDTH = GDMA_TXFER_WIDTH_32_BITS; regGDMA_ch[i].bf.CSR_ABT = 0; regGDMA_ch[i].bf.CSR_SRC_SIZE = GDMA_BURST_SIZE_16; //regGDMA_ch[i].bf.CSR_PRIORITY = 0; regGDMA_ch[i].bf.CSR_DMA_FF_TH = (DMA_FIFO_SIZE - 1); // threshold value = 8 due to our FIFO size is 16 regGDMA_ch[i].bf.CSR_TC_MSK = 0; // CFG regGDMA_ch[i].bf.CFG_INT_TC_MSK = 0; // enable terminal count interrupt regGDMA_ch[i].bf.CFG_INT_ERR_MSK = 0; // enable error interrupt regGDMA_ch[i].bf.CFG_INT_ABT_MSK = 0; // enable abort interrupt regGDMA_ch[i].bf.CFG_SRC_RS = 0; // dma request, not use when in normal mode regGDMA_ch[i].bf.CFG_SRC_HE = 0; // dma request, not use when in normal mode regGDMA_ch[i].bf.CFG_DST_RS = 0; // dma request, not use when in normal mode regGDMA_ch[i].bf.CFG_DST_HE = 0; // dma request, not use when in normal mode #endif // others we use the reset default } NVIC_SetVector((IRQn_Type)DMA_FTDMAC020_0_IRQ, (uint32_t)gdma_isr); NVIC_ClearPendingIRQ(DMA_FTDMAC020_0_IRQ); NVIC_EnableIRQ(DMA_FTDMAC020_0_IRQ); g_dma_inited = 1; return KDRV_STATUS_OK; } kdrv_status_t kdrv_gdma_uninitialize(void) { if (!g_dma_inited) return KDRV_STATUS_ERROR; // FIXME: disable DMAC0 clock (hclk_en[3]) with better API/Macro { uint32_t ahb_clock_reg = (*(volatile unsigned int *)(SCU_FTSCU100_PA_BASE + 0x50)); ahb_clock_reg &= ~0x8; (*(volatile unsigned int *)(SCU_FTSCU100_PA_BASE + 0x50)) = ahb_clock_reg; } free(gDmaChCtrl); NVIC_DisableIRQ(DMA_FTDMAC020_0_IRQ); g_dma_inited = 0; return KDRV_STATUS_OK; } kdrv_status_t kdrv_gdma_acquire_handle(kdrv_gdma_handle_t *handle) { int ch = 0; // try to find an unused dma channel for memcpy for (ch = NUM_CH_FOR_MEMCPY; ch < NUM_DMA_CHANNELS; ch++) { if (!gDmaChCtrl[ch].in_use) { gDmaChCtrl[ch].in_use = 1; // claim it is in use now *handle = ch; return KDRV_STATUS_OK; } } return KDRV_STATUS_ERROR; } kdrv_status_t kdrv_gdma_configure_setting(kdrv_gdma_handle_t handle, gdma_setting_t *dma_setting) { int ch = handle; regGDMA_ch[ch].bf.CSR_DST_WIDTH = dma_setting->dst_width; regGDMA_ch[ch].bf.CSR_SRC_WIDTH = dma_setting->src_width; regGDMA_ch[ch].bf.CSR_SRC_SIZE = dma_setting->burst_size; regGDMA_ch[ch].bf.CSR_DSTAD_CTL = dma_setting->dst_addr_ctrl; regGDMA_ch[ch].bf.CSR_SRCAD_CTL = dma_setting->src_addr_ctrl; regGDMA_ch[ch].bf.CSR_MODE = dma_setting->dma_mode; if (dma_setting->dma_mode == GDMA_HW_HANDSHAKE_MODE) { regGDMA_ch[ch].bf.CFG_DST_RS = dma_setting->dma_dst_req; regGDMA_ch[ch].bf.CFG_DST_HE = (dma_setting->dma_dst_req != 0) ? 1 : 0; regGDMA_ch[ch].bf.CFG_SRC_RS = dma_setting->dma_src_req; regGDMA_ch[ch].bf.CFG_SRC_HE = (dma_setting->dma_src_req != 0) ? 1 : 0; } return KDRV_STATUS_OK; } kdrv_status_t kdrv_gdma_release_handle(kdrv_gdma_handle_t handle) { gDmaChCtrl[handle].in_use = 0; return KDRV_STATUS_OK; } kdrv_status_t kdrv_gdma_transfer_async(kdrv_gdma_handle_t handle, uint32_t dst_addr, uint32_t src_addr, uint32_t num_bytes, gdma_xfer_callback_t xfer_isr_cb, void *usr_arg) { int ch = handle; gDmaChCtrl[ch].running = 1; // in case the DMA ch is still busy while (regGDMA->dw.CH_BUSY & (0x1 << ch)) ; gDmaChCtrl[ch].user_cb = xfer_isr_cb; gDmaChCtrl[ch].user_arg = usr_arg; gDmaChCtrl[ch].src_end = src_addr + num_bytes - 1; gDmaChCtrl[ch].dst_end = dst_addr + num_bytes - 1; // for memcpy we dont have to set up all register setting because many are done at initialization regGDMA_ch[ch].dw.DstAddr = dst_addr; regGDMA_ch[ch].dw.SrcAddr = src_addr; regGDMA_ch[ch].dw.SIZE = (num_bytes >> regGDMA_ch[ch].bf.CSR_SRC_WIDTH); kdrv_status_t ret; if (xfer_isr_cb == 0) { // check execution context gDmaChCtrl[ch].user_tid = osThreadGetId(); // trigger the DMA channel regGDMA_ch[ch].bf.CSR_CH_EN = 1; if (gDmaChCtrl[ch].user_tid != 0) { // OS thread context // blocking here until transfer is done uint32_t flags = osThreadFlagsWait(GDMA_FLAG_XFER_DONE, osFlagsWaitAny, osWaitForever); gDmaChCtrl[ch].running = 0; } else { // Non-OS context do { __WFE(); } while (gDmaChCtrl[ch].running == 1); } _fill_up_last_few_bytes(ch); // workaround last few bytes if needed ret = gDmaChCtrl[ch].ret_sts; } else { // trigger the DMA channel regGDMA_ch[ch].bf.CSR_CH_EN = 1; ret = KDRV_STATUS_OK; } return ret; } kdrv_status_t kdrv_gdma_transfer(kdrv_gdma_handle_t handle, uint32_t dst_addr, uint32_t src_addr, uint32_t num_bytes) { return kdrv_gdma_transfer_async(handle, dst_addr, src_addr, num_bytes, NULL, NULL); } kdrv_status_t kdrv_gdma_memcpy_async(uint32_t dst_addr, uint32_t src_addr, uint32_t num_bytes, gdma_xfer_callback_t xfer_isr_cb, void *usr_arg) { int ch = 0; // try to find an unused dma channel for memcpy for (ch = 0; ch < NUM_CH_FOR_MEMCPY; ch++) { if (!gDmaChCtrl[ch].running) { gDmaChCtrl[ch].running = 1; break; } } // at present no available dma channel if (ch >= NUM_CH_FOR_MEMCPY) return KDRV_STATUS_GDMA_ERROR_NO_RESOURCE; // below src & dst width should vary with address alignment regGDMA_ch[ch].bf.CSR_DST_WIDTH = _get_width_from_align(dst_addr & 0x3); regGDMA_ch[ch].bf.CSR_SRC_WIDTH = _get_width_from_align(src_addr & 0x3); return kdrv_gdma_transfer_async(ch, dst_addr, src_addr, num_bytes, xfer_isr_cb, usr_arg); } kdrv_status_t kdrv_gdma_memcpy(uint32_t dst_addr, uint32_t src_addr, uint32_t num_bytes) { return kdrv_gdma_memcpy_async(dst_addr, src_addr, num_bytes, NULL, NULL); }