| /**************************************************************************** |
| * arch/risc-v/src/bl602/bl602_dma.c |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. The |
| * ASF licenses this file to you 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. |
| * |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Included Files |
| ****************************************************************************/ |
| |
| #include <nuttx/config.h> |
| #include <nuttx/arch.h> |
| |
| #include <stdint.h> |
| #include <errno.h> |
| #include <assert.h> |
| #include <debug.h> |
| |
| #include "chip.h" |
| #include "riscv_internal.h" |
| #include "hardware/bl602_dma.h" |
| #include "bl602_dma.h" |
| |
| /**************************************************************************** |
| * Pre-processor Definitions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Private Types |
| ****************************************************************************/ |
| |
| /* This structure describes one DMA channel */ |
| |
| struct dma_channel_s |
| { |
| uint8_t chan; /* DMA channel number (0-BL602_DMA_NCHANNELS) */ |
| bool inuse; /* TRUE: The DMA channel is in use */ |
| bl602_dma_callback_t callback; /* Callback invoked when the DMA completes */ |
| void *arg; /* Argument passed to callback function */ |
| }; |
| |
| /* This structure describes the state of the DMA controller */ |
| |
| struct dma_controller_s |
| { |
| mutex_t chanlock; /* Protects channel table */ |
| sem_t chansem; /* Count of free channels */ |
| }; |
| |
| /**************************************************************************** |
| * Private Data |
| ****************************************************************************/ |
| |
| /* This is the overall state of the DMA controller */ |
| |
| static struct dma_controller_s g_dmac = |
| { |
| .chanlock = NXMUTEX_INITIALIZER, |
| .chansem = SEM_INITIALIZER(BL602_DMA_NCHANNELS), |
| }; |
| |
| /* This is the array of all DMA channels */ |
| |
| static struct dma_channel_s g_dmach[BL602_DMA_NCHANNELS]; |
| |
| /**************************************************************************** |
| * Private Functions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: bl602_dma_int_handler |
| * |
| * Description: |
| * DMA interrupt handler. |
| * |
| ****************************************************************************/ |
| |
| static int bl602_dma_int_handler(int irq, void *context, void *arg) |
| { |
| /* We need to ack the IRQ or a mess is made */ |
| |
| /* Itterate over each of the channels checking for and clearing: |
| * DMA_INTTCSTATUS |
| * DMA_INTERRORSTATUS |
| */ |
| |
| uint8_t ch; |
| uint32_t tc_status; |
| uint32_t err_status; |
| |
| tc_status = getreg32(BL602_DMA_INTTCSTATUS) & DMA_INTTCSTATUS_MASK; |
| err_status = getreg32(BL602_DMA_INTERRORSTATUS) & DMA_INTERRORSTATUS_MASK; |
| |
| for (ch = 0; ch < BL602_DMA_NCHANNELS; ch++) |
| { |
| if (tc_status & (1 << ch)) |
| { |
| dmainfo("CH %d TC Int fired\n", ch); |
| putreg32((1 << ch), BL602_DMA_INTTCCLEAR); |
| if (g_dmach[ch].callback != NULL) |
| { |
| g_dmach[ch].callback( |
| ch, |
| BL602_DMA_INT_EVT_TC, |
| g_dmach[ch].arg); |
| } |
| } |
| |
| if (err_status & (1 << ch)) |
| { |
| dmainfo("CH %d Error Int fired\n", ch); |
| putreg32((1 << ch), BL602_DMA_INTERRCLR); |
| if (g_dmach[ch].callback != NULL) |
| { |
| g_dmach[ch].callback( |
| ch, |
| BL602_DMA_INT_EVT_ERR, |
| g_dmach[ch].arg); |
| } |
| } |
| } |
| |
| return 0; |
| } |
| |
| /**************************************************************************** |
| * Public Functions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: bl602_dma_channel_request |
| * |
| * Description: |
| * Allocate a new DMA channel. |
| * |
| * Input Parameters: |
| * None |
| * |
| * Returned Value: |
| * 0-3: DMA channel |
| * -1: Failed |
| * |
| ****************************************************************************/ |
| |
| int8_t bl602_dma_channel_request(bl602_dma_callback_t callback, void *arg) |
| { |
| struct dma_channel_s *dmach; |
| unsigned int ch; |
| int ret; |
| |
| /* Take a count from the channel counting semaphore. We may block |
| * if there are no free channels. When we get the count, then we can |
| * be assured that a channel is available in the channel list and is |
| * reserved for us. |
| */ |
| |
| ret = nxsem_wait_uninterruptible(&g_dmac.chansem); |
| if (ret < 0) |
| { |
| return -1; |
| } |
| |
| /* Get exclusive access to the DMA channel list */ |
| |
| ret = nxmutex_lock(&g_dmac.chanlock); |
| if (ret < 0) |
| { |
| nxsem_post(&g_dmac.chansem); |
| return -1; |
| } |
| |
| /* Search for an available DMA channel */ |
| |
| for (ch = 0, dmach = NULL; ch < BL602_DMA_NCHANNELS; ch++) |
| { |
| struct dma_channel_s *candidate = &g_dmach[ch]; |
| if (!candidate->inuse) |
| { |
| dmainfo("DMA Channel %u assigned.\n", ch); |
| dmach = candidate; |
| dmach->inuse = true; |
| |
| break; |
| } |
| } |
| |
| nxmutex_unlock(&g_dmac.chanlock); |
| |
| /* Since we have reserved a DMA descriptor by taking a count from chansem, |
| * it would be a serious logic failure if we could not find a free channel |
| * for our use. |
| */ |
| |
| DEBUGASSERT(dmach); |
| dmach->callback = callback; |
| dmach->arg = arg; |
| return dmach->chan; |
| } |
| |
| /**************************************************************************** |
| * Name: bl602_dma_channel_release |
| * |
| * Description: |
| * Release a DMA channel. |
| * |
| * Input Parameters: |
| * channel: DMA channel. |
| * |
| * Returned Value: |
| * Zero (OK) is returned on success. Otherwise -1 (ERROR). |
| * |
| ****************************************************************************/ |
| |
| int bl602_dma_channel_release(uint8_t channel_id) |
| { |
| /* Get exclusive access to the DMA channel list */ |
| |
| if (nxmutex_lock(&g_dmac.chanlock) < 0) |
| { |
| return -1; |
| } |
| |
| /* Verify if the channel is actually in use */ |
| |
| if (g_dmach[channel_id].inuse) |
| { |
| /* This channel was infact in use, release it and increment the |
| * count of free channels for use. |
| */ |
| |
| g_dmach[channel_id].inuse = false; |
| nxsem_post(&g_dmac.chansem); |
| } |
| |
| nxmutex_unlock(&g_dmac.chanlock); |
| return 0; |
| } |
| |
| /**************************************************************************** |
| * Name: bl602_dma_channel_start |
| * |
| * Description: |
| * Start a DMA channel. |
| * |
| * Input Parameters: |
| * channel: DMA channel. |
| * |
| * Returned Value: |
| * Zero (OK) is returned on success. Otherwise -1 (ERROR). |
| * |
| ****************************************************************************/ |
| |
| int bl602_dma_channel_start(uint8_t channel_id) |
| { |
| /* Unmask interrupts for: |
| * - DMA_INT_TCOMPLETED |
| * - DMA_INT_ERR |
| * Note it is expected that the TC interrupt to be enabled prior to this |
| * function call if needed as it is nominally controlled via the LLI |
| * mechanism. |
| */ |
| |
| modifyreg32(BL602_DMA_CH_N_REG(BL602_DMA_CONFIG_OFFSET, channel_id), |
| DMA_C0CONFIG_ITC | DMA_C0CONFIG_IE, |
| 0); |
| |
| /* Enable channel */ |
| |
| modifyreg32(BL602_DMA_CH_N_REG(BL602_DMA_CONFIG_OFFSET, channel_id), |
| 0, |
| DMA_C0CONFIG_E); |
| |
| return 0; |
| } |
| |
| /**************************************************************************** |
| * Name: bl602_dma_channel_stop |
| * |
| * Description: |
| * Stop a DMA channel. |
| * |
| * Input Parameters: |
| * channel: DMA channel. |
| * |
| * Returned Value: |
| * Zero (OK) is returned on success. Otherwise -1 (ERROR). |
| * |
| ****************************************************************************/ |
| |
| int bl602_dma_channel_stop(uint8_t channel_id) |
| { |
| /* Disable channel */ |
| |
| modifyreg32(BL602_DMA_CH_N_REG(BL602_DMA_CONFIG_OFFSET, channel_id), |
| DMA_C0CONFIG_E, |
| 0); |
| |
| /* Mask interrupts for: |
| * - DMA_INT_TCOMPLETED |
| * - DMA_INT_ERR |
| */ |
| |
| modifyreg32(BL602_DMA_CH_N_REG(BL602_DMA_CONFIG_OFFSET, channel_id), |
| 0, |
| DMA_C0CONFIG_ITC | DMA_C0CONFIG_IE); |
| |
| /* Clear interrupts for channel in: |
| * - DMA_INTTCCLEAR |
| * - DMA_INTERRORSTATUS |
| */ |
| |
| putreg32((1 << channel_id), BL602_DMA_INTTCCLEAR); |
| putreg32((1 << channel_id), BL602_DMA_INTERRCLR); |
| |
| return 0; |
| } |
| |
| /**************************************************************************** |
| * Name: riscv_dma_initialize |
| * |
| * Description: |
| * Initialize DMA controller. |
| * |
| * Input Parameters: |
| * None |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| void weak_function riscv_dma_initialize(void) |
| { |
| uint8_t ch; |
| |
| #ifdef CONFIG_DEBUG_DMA_INFO |
| struct bl602_dmaregs_s regs; |
| #endif |
| |
| dmainfo("Initialize DMA\n"); |
| |
| /* Note we may want to set EN bits in DMAEN as part of clk_cfg2. |
| * At reset these bits are already set to enabled, and the documentation |
| * is a little thin around this. If we implement more low power |
| * configuration we will want to be more clear about these bits. |
| */ |
| |
| /* Initialize the channel list */ |
| |
| for (ch = 0; ch < BL602_DMA_NCHANNELS; ch++) |
| { |
| g_dmach[ch].chan = ch; |
| |
| /* Disable the DMA channel */ |
| |
| putreg32(0, BL602_DMA_CH_N_REG(BL602_DMA_CONFIG_OFFSET, ch)); |
| } |
| |
| /* Attach DMA transfer complete interrupt handler */ |
| |
| irq_attach(BL602_IRQ_DMA_ALL, bl602_dma_int_handler, NULL); |
| up_enable_irq(BL602_IRQ_DMA_ALL); |
| |
| /* Enable SMDMA controller */ |
| |
| modifyreg32(BL602_DMA_TOP_CONFIG, 0, DMA_TOP_CONFIG_E); |
| |
| /* Dump DMA register state */ |
| |
| bl602_dmasample(®s); |
| bl602_dmadump(®s, "Initialized DMA"); |
| } |
| |
| /**************************************************************************** |
| * Name: bl602_dmasample |
| * |
| * Description: |
| * Sample DMA register contents |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_DEBUG_DMA_INFO |
| void bl602_dmasample(struct bl602_dmaregs_s *regs) |
| { |
| irqstate_t flags; |
| |
| /* Sample DMA registers. */ |
| |
| flags = enter_critical_section(); |
| |
| regs->intstatus = getreg32(BL602_DMA_INTSTATUS); |
| regs->inttcstatus = getreg32(BL602_DMA_INTTCSTATUS); |
| regs->inttcclear = getreg32(BL602_DMA_INTTCCLEAR); |
| regs->interrorstatus = getreg32(BL602_DMA_INTERRORSTATUS); |
| regs->interrclr = getreg32(BL602_DMA_INTERRCLR); |
| regs->rawinttcstatus = getreg32(BL602_DMA_RAWINTTCSTATUS); |
| regs->rawinterrorstatus = getreg32(BL602_DMA_RAWINTERRORSTATUS); |
| regs->enbldchns = getreg32(BL602_DMA_ENBLDCHNS); |
| regs->softbreq = getreg32(BL602_DMA_SOFTBREQ); |
| regs->softsreq = getreg32(BL602_DMA_SOFTSREQ); |
| regs->softlbreq = getreg32(BL602_DMA_SOFTLBREQ); |
| regs->softlsreq = getreg32(BL602_DMA_SOFTLSREQ); |
| regs->top_config = getreg32(BL602_DMA_TOP_CONFIG); |
| regs->sync = getreg32(BL602_DMA_SYNC); |
| regs->c0srcaddr = getreg32(BL602_DMA_C0SRCADDR); |
| regs->c0dstaddr = getreg32(BL602_DMA_C0DSTADDR); |
| regs->c0lli = getreg32(BL602_DMA_C0LLI); |
| regs->c0control = getreg32(BL602_DMA_C0CONTROL); |
| regs->c0config = getreg32(BL602_DMA_C0CONFIG); |
| regs->c1srcaddr = getreg32(BL602_DMA_C1SRCADDR); |
| regs->c1dstaddr = getreg32(BL602_DMA_C1DSTADDR); |
| regs->c1lli = getreg32(BL602_DMA_C1LLI); |
| regs->c1control = getreg32(BL602_DMA_C1CONTROL); |
| regs->c1config = getreg32(BL602_DMA_C1CONFIG); |
| regs->c2srcaddr = getreg32(BL602_DMA_C2SRCADDR); |
| regs->c2dstaddr = getreg32(BL602_DMA_C2DSTADDR); |
| regs->c2lli = getreg32(BL602_DMA_C2LLI); |
| regs->c2control = getreg32(BL602_DMA_C2CONTROL); |
| regs->c2config = getreg32(BL602_DMA_C2CONFIG); |
| regs->c3srcaddr = getreg32(BL602_DMA_C3SRCADDR); |
| regs->c3dstaddr = getreg32(BL602_DMA_C3DSTADDR); |
| regs->c3lli = getreg32(BL602_DMA_C3LLI); |
| regs->c3control = getreg32(BL602_DMA_C3CONTROL); |
| regs->c3config = getreg32(BL602_DMA_C3CONFIG); |
| |
| leave_critical_section(flags); |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Name: bl602_dmadump |
| * |
| * Description: |
| * Dump previously sampled DMA register contents |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_DEBUG_DMA_INFO |
| void bl602_dmadump(const struct bl602_dmaregs_s *regs, |
| const char *msg) |
| { |
| dmainfo("%s\n", msg); |
| dmainfo(" DMA Registers:\n"); |
| dmainfo(" INTSTATUS: %08x\n", regs->intstatus); |
| dmainfo(" INTTCSTATU: %08x\n", regs->inttcstatus); |
| dmainfo(" INTTCCLEAR: %08x\n", regs->inttcclear); |
| dmainfo(" INTERRORST: %08x\n", regs->interrorstatus); |
| dmainfo(" INTERRCLR: %08x\n", regs->interrclr); |
| dmainfo(" RAWINTTCST: %08x\n", regs->rawinttcstatus); |
| dmainfo(" RAWINTERRO: %08x\n", regs->rawinterrorstatus); |
| dmainfo(" ENBLDCHNS: %08x\n", regs->enbldchns); |
| dmainfo(" SOFTBREQ: %08x\n", regs->softbreq); |
| dmainfo(" SOFTSREQ: %08x\n", regs->softsreq); |
| dmainfo(" SOFTLBREQ: %08x\n", regs->softlbreq); |
| dmainfo(" SOFTLSREQ: %08x\n", regs->softlsreq); |
| dmainfo(" TOP_CONFIG: %08x\n", regs->top_config); |
| dmainfo(" SYNC: %08x\n", regs->sync); |
| dmainfo(" === Channel 0 ===\n"); |
| dmainfo(" C0SRCADDR: %08x\n", regs->c0srcaddr); |
| dmainfo(" C0DSTADDR: %08x\n", regs->c0dstaddr); |
| dmainfo(" C0LLI: %08x\n", regs->c0lli); |
| dmainfo(" C0CONTROL: %08x\n", regs->c0control); |
| dmainfo(" C0CONFIG: %08x\n", regs->c0config); |
| dmainfo(" === Channel 1 ===\n"); |
| dmainfo(" C1SRCADDR: %08x\n", regs->c1srcaddr); |
| dmainfo(" C1DSTADDR: %08x\n", regs->c1dstaddr); |
| dmainfo(" C1LLI: %08x\n", regs->c1lli); |
| dmainfo(" C1CONTROL: %08x\n", regs->c1control); |
| dmainfo(" C1CONFIG: %08x\n", regs->c1config); |
| dmainfo(" === Channel 2 ===\n"); |
| dmainfo(" C2SRCADDR: %08x\n", regs->c2srcaddr); |
| dmainfo(" C2DSTADDR: %08x\n", regs->c2dstaddr); |
| dmainfo(" C2LLI: %08x\n", regs->c2lli); |
| dmainfo(" C2CONTROL: %08x\n", regs->c2control); |
| dmainfo(" C2CONFIG: %08x\n", regs->c2config); |
| dmainfo(" === Channel 3 ===\n"); |
| dmainfo(" C3SRCADDR: %08x\n", regs->c3srcaddr); |
| dmainfo(" C3DSTADDR: %08x\n", regs->c3dstaddr); |
| dmainfo(" C3LLI: %08x\n", regs->c3lli); |
| dmainfo(" C3CONTROL: %08x\n", regs->c3control); |
| dmainfo(" C3CONFIG: %08x\n", regs->c3config); |
| } |
| #endif |