#include "FreeRTOS.h" #include "chip.h" #define DMA_CH_NUM 4 #define DMA_BLOCK_SIZE 0xfff #define rDMACIntStatus *((volatile unsigned int *)(REGS_DMAC_BASE + 0x000)) #define rDMACIntTCStatus *((volatile unsigned int *)(REGS_DMAC_BASE + 0x004)) #define rDMACIntTCClear *((volatile unsigned int *)(REGS_DMAC_BASE + 0x008)) #define rDMACIntErrorStatus *((volatile unsigned int *)(REGS_DMAC_BASE + 0x00C)) #define rDMACIntErrClr *((volatile unsigned int *)(REGS_DMAC_BASE + 0x010)) #define rDMACRawIntTCStatus *((volatile unsigned int *)(REGS_DMAC_BASE + 0x014)) #define rDMACRawIntErrorStatus *((volatile unsigned int *)(REGS_DMAC_BASE + 0x018)) #define rDMACEnbldChns *((volatile unsigned int *)(REGS_DMAC_BASE + 0x01C)) #define rDMACSoftBReq *((volatile unsigned int *)(REGS_DMAC_BASE + 0x020)) #define rDMACSoftSReq *((volatile unsigned int *)(REGS_DMAC_BASE + 0x024)) #define rDMACSoftLBReq *((volatile unsigned int *)(REGS_DMAC_BASE + 0x028)) #define rDMACSoftLSReq *((volatile unsigned int *)(REGS_DMAC_BASE + 0x02C)) #define rDMACConfiguration *((volatile unsigned int *)(REGS_DMAC_BASE + 0x030)) #define rDMACSync *((volatile unsigned int *)(REGS_DMAC_BASE + 0x034)) #define rDMACCxSrcAddr(x) *((volatile unsigned int *)(REGS_DMAC_BASE + 0x100 + 0x00 + (x)*0x20)) #define rDMACCxDestAddr(x) *((volatile unsigned int *)(REGS_DMAC_BASE + 0x100 + 0x04 + (x)*0x20)) #define rDMACCxLLI(x) *((volatile unsigned int *)(REGS_DMAC_BASE + 0x100 + 0x08 + (x)*0x20)) #define rDMACCxControl(x) *((volatile unsigned int *)(REGS_DMAC_BASE + 0x100 + 0x0C + (x)*0x20)) #define rDMACCxConfiguration(x) *((volatile unsigned int *)(REGS_DMAC_BASE + 0x100 + 0x10 + (x)*0x20)) static struct dma_chan dma_ch[DMA_CH_NUM] = {0}; static SemaphoreHandle_t dma_mutex; static QueueHandle_t dma_m2m_done = NULL; struct dma_chan *dma_request_channel(int favorite_ch) { int i; configASSERT (favorite_ch >= 0 && favorite_ch < DMA_CH_NUM) xSemaphoreTake(dma_mutex, portMAX_DELAY); if (!dma_ch[favorite_ch].in_use) { dma_ch[favorite_ch].chan_id = favorite_ch; dma_ch[favorite_ch].in_use = 1; xSemaphoreGive(dma_mutex); return &dma_ch[favorite_ch]; } for (i = 0; i < DMA_CH_NUM; i++) { if (!dma_ch[i].in_use) { dma_ch[i].chan_id = i; dma_ch[i].in_use = 1; xSemaphoreGive(dma_mutex); return &dma_ch[i]; } } xSemaphoreGive(dma_mutex); return NULL; } void dma_release_channel(struct dma_chan *chan) { /* This channel is not in use, bail out */ if (!chan->in_use) return; dma_stop_channel(chan); xSemaphoreTake(dma_mutex, portMAX_DELAY); /* This channel is not in use anymore, free it */ chan->irq_callback = NULL; chan->callback_param = NULL; chan->in_use = 0; xSemaphoreGive(dma_mutex); } /* * Fix sconfig's burst size according to dw_dmac. We need to convert them as: * 1 -> 0, 4 -> 1, 8 -> 2, 16 -> 3. * * NOTE: burst size 2 is not supported by controller. * * This can be done by finding least significant bit set: n & (n - 1) */ static void convert_burst(u32 *maxburst) { if (*maxburst > 1) *maxburst = fls(*maxburst) - 2; else *maxburst = 0; } int dma_config_channel(struct dma_chan *chan, struct dma_config *config) { unsigned int ctl; unsigned int cfg; unsigned int src_width, dst_width; unsigned int src_id = 0, dst_id = 0; unsigned int di = 0, si = 0; unsigned int data_width = (1 << DMA_BUSWIDTH_4_BYTES); convert_burst(&config->src_maxburst); convert_burst(&config->dst_maxburst); if (config->direction == DMA_MEM_TO_DEV) { src_width = __ffs(data_width | config->src_addr | config->transfer_size); dst_width = config->dst_addr_width; dst_id = config->dst_id; si = 1; } else if (config->direction == DMA_DEV_TO_MEM) { src_width = config->src_addr_width; dst_width = __ffs(data_width | config->dst_addr | config->transfer_size); src_id = config->src_id; di = 1; } else if (config->direction == DMA_MEM_TO_MEM) { src_width = __ffs(data_width | config->src_addr | config->transfer_size); dst_width = __ffs(data_width | config->dst_addr | config->transfer_size); si = 1; di = 1; } ctl = (1 << 31) | /* [31] I Read/write Terminal count interrupt enable bit */ (0 << 28) | /* [30:28] Prot Read/write Protection */ (di << 27) | /* [27] DI Read/write Destination increment */ (si << 26) | /* [26] SI Read/write Source increment */ (0 << 25) | /* [25] D Read/write Destination AHB master select */ (1 << 24) | /* [24] S Read/write Source AHB master select */ (dst_width << 21) | /* [23:21] DWidth Read/write Destination transfer width */ (src_width << 18) | /* [20:18] SWidth Read/write Source transfer width */ (config->dst_maxburst << 15) | /* [17:15] DBSize Read/write Destination burst size */ (config->src_maxburst << 12) | /* [14:12] SBSize Read/write Source burst size */ 0; /* [11:0] TransferSize Read/write Transfer size */ cfg = (0 << 18) | /* [18] H Read/write Halt */ (0 << 16) | /* [16] L Read/write Lock */ (1 << 15) | /* [15] ITC Read/write Terminal count interrupt mask */ (1 << 14) | /* [14] IE Read/write Interrupt error mask */ (config->direction << 11) | /* [13:11] FlowCntrl Read/write Flow control and transfer type */ (dst_id << 6) | /* [9:6] DestPeripheral Read/write Destination peripheral */ (src_id << 1) | /* [4:1] SrcPeripheral Read/write Source peripheral */ 0; /* [0] Channel enable */ if ((config->transfer_size >> src_width) > DMA_BLOCK_SIZE) { unsigned int blk_size = config->transfer_size >> src_width; int lli_num; int i; lli_num = (blk_size + DMA_BLOCK_SIZE - 1) / DMA_BLOCK_SIZE - 1; if (chan->lli) { vPortFree(chan->lli); chan->lli = NULL; } chan->lli = pvPortMalloc(sizeof(struct dma_lli) * lli_num); if (!chan->lli) return -ENOMEM; for (i = 0; i < lli_num - 1; i++) { chan->lli[i].src_addr = config->src_addr + (si ? (i + 1) : 0) * (DMA_BLOCK_SIZE << src_width); chan->lli[i].dst_addr = config->dst_addr + (di ? (i + 1) : 0) * (DMA_BLOCK_SIZE << src_width); chan->lli[i].next_lli = (unsigned int)&chan->lli[i + 1]; chan->lli[i].control = ctl | DMA_BLOCK_SIZE; if (!config->blkint_en) chan->lli[i].control &= ~(1 << 31); } chan->lli[i].src_addr = config->src_addr + (si ? (i + 1) : 0) * (DMA_BLOCK_SIZE << src_width); chan->lli[i].dst_addr = config->dst_addr + (di ? (i + 1) : 0) * (DMA_BLOCK_SIZE << src_width); chan->lli[i].next_lli = 0; chan->lli[i].control = ctl | (blk_size - DMA_BLOCK_SIZE * lli_num); CP15_clean_dcache_for_dma((unsigned int)chan->lli, (unsigned int)chan->lli + sizeof(struct dma_lli) * lli_num); rDMACCxSrcAddr(chan->chan_id) = config->src_addr; rDMACCxDestAddr(chan->chan_id) = config->dst_addr; rDMACCxLLI(chan->chan_id) = (unsigned int)chan->lli | 1; rDMACCxControl(chan->chan_id) = ctl & ~(1 << 31) | DMA_BLOCK_SIZE; rDMACCxConfiguration(chan->chan_id) = cfg; } else { rDMACCxSrcAddr(chan->chan_id) = config->src_addr; rDMACCxDestAddr(chan->chan_id) = config->dst_addr; rDMACCxLLI(chan->chan_id) = 0; rDMACCxControl(chan->chan_id) = ctl | (config->transfer_size >> src_width); rDMACCxConfiguration(chan->chan_id) = cfg; } return 0; } int dma_register_complete_callback(struct dma_chan *chan, void (*callback)(void *param, unsigned int mask), void *callback_param) { chan->irq_callback = callback; chan->callback_param = callback_param; return 0; } int dma_start_channel(struct dma_chan *chan) { configASSERT(chan && chan->chan_id < DMA_CH_NUM); rDMACCxConfiguration(chan->chan_id) |= (1 << 0); return 0; } int dma_stop_channel(struct dma_chan *chan) { unsigned int timeout = xTaskGetTickCount() + 1000; configASSERT(chan && chan->chan_id < DMA_CH_NUM); xSemaphoreTake(dma_mutex, portMAX_DELAY); if(!(rDMACEnbldChns & (1 << chan->chan_id))) { xSemaphoreGive(dma_mutex); return 0; } // A channel can be disabled by clearing the Enable bit. rDMACCxConfiguration(chan->chan_id) &= ~1; // waiting while(rDMACEnbldChns & (1 << chan->chan_id)) { if(xTaskGetTickCount() >= timeout) { printf ("dma_stop_channel %d timeout\n", chan->chan_id); xSemaphoreGive(dma_mutex); return -1; } vTaskDelay(pdMS_TO_TICKS(10)); } if (chan->lli) { vPortFree(chan->lli); chan->lli = NULL; } xSemaphoreGive(dma_mutex); return 0; } static void dma_m2m_callback(void *param, unsigned int mask) { if(dma_m2m_done) xQueueSendFromISR(dma_m2m_done, NULL, 0); } int dma_m2mcpy(unsigned int dst_addr, unsigned int src_addr, int size) { struct dma_config cfg = {0}; int ret = -1; struct dma_chan *dma_ch = dma_request_channel(0); if (!dma_ch) { printf("%s() dma_request_channel fail.\n", __func__); return -1; } cfg.dst_addr_width = DMA_BUSWIDTH_4_BYTES; cfg.dst_maxburst = 256; cfg.src_addr_width = DMA_BUSWIDTH_4_BYTES; cfg.src_maxburst = 256; cfg.transfer_size = size; cfg.src_addr = src_addr; cfg.dst_addr = dst_addr; cfg.direction = DMA_MEM_TO_MEM; dma_clean_range(src_addr, src_addr + size); dma_inv_range(dst_addr, dst_addr + size); ret = dma_config_channel(dma_ch, &cfg); if (ret) { printf("%s, dma_config_channel failed.\n", __func__); goto exit; } dma_register_complete_callback(dma_ch, dma_m2m_callback, NULL); xQueueReset(dma_m2m_done); dma_start_channel(dma_ch); if (xQueueReceive(dma_m2m_done, NULL, pdMS_TO_TICKS(1000)) != pdTRUE) { printf("dma_m2mcpy wait timeout.\n"); ret = -ETIMEDOUT; goto exit; } dma_stop_channel(dma_ch); ret = 0; exit: if(dma_ch) dma_release_channel(dma_ch); return ret; } static void dma_int_handler(void *param) { unsigned int err_status, tfr_status; struct dma_chan *chan; unsigned int irqmask = 0; int i; err_status = rDMACIntErrorStatus; tfr_status = rDMACIntTCStatus; rDMACIntTCClear = tfr_status; rDMACIntErrClr = err_status; for(i= 0; i< DMA_CH_NUM; i++) { irqmask = 0; if (err_status & (1 << i)) { irqmask |= DMA_INT_ERR; } if (tfr_status & (1 << i)) { irqmask |= DMA_INT_TC; } if (!irqmask) continue; chan = &dma_ch[i]; if (chan->irq_callback) chan->irq_callback(chan->callback_param, irqmask); } } int dma_init(void) { dma_mutex = xSemaphoreCreateMutex(); dma_m2m_done = xQueueCreate(1, 0); sys_soft_reset(softreset_dma); request_irq(DMA_IRQn, 0, dma_int_handler, NULL); /* Clear all interrupts on all channels. */ rDMACIntTCClear = 0xff; rDMACIntErrClr = 0xff; rDMACConfiguration |= (1<<0); // [0] E Read/write PrimeCell DMAC enable return 0; }