2025-12-17 15:55:25 +08:00

496 lines
17 KiB
C

/* 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 <stdlib.h>
#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);
}