| /**************************************************************************** |
| * arch/arm/src/lc823450/lc823450_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/kmalloc.h> |
| #include <nuttx/semaphore.h> |
| |
| #include <sys/param.h> |
| #include <sys/types.h> |
| #include <stdint.h> |
| #include <stdbool.h> |
| #include <errno.h> |
| #include <assert.h> |
| #include <debug.h> |
| #include <stdlib.h> |
| |
| #include <nuttx/arch.h> |
| #include <nuttx/spinlock.h> |
| #include <nuttx/mutex.h> |
| |
| #include "arm_internal.h" |
| #include "lc823450_dma.h" |
| #include "lc823450_syscontrol.h" |
| #include <arch/chip/clk.h> |
| |
| /**************************************************************************** |
| * Pre-processor Definitions |
| ****************************************************************************/ |
| |
| #define DMACBASE 0x40004000 |
| #define DMACINTSTAT (DMACBASE + 0x00) |
| #define DMACINTTCSTAT (DMACBASE + 0x04) |
| #define DMACINTTCCLR (DMACBASE + 0x08) |
| #define DMACINTERRSTAT (DMACBASE + 0x10) |
| #define DMACCHENABLE (DMACBASE + 0x1c) |
| #define DMACINTERRCLR (DMACBASE + 0x08) |
| #define DMACCONFIG (DMACBASE + 0x30) |
| #define DMACCONFIG_EN (1 << 0) |
| #define DMACSRCADDR(ch) (DMACBASE + ((ch) * 0x20) + 0x100) |
| #define DMACDSTADDR(ch) (DMACBASE + ((ch) * 0x20) + 0x104) |
| #define DMACLLI(ch) (DMACBASE + ((ch) * 0x20) + 0x108) |
| #define DMACCTL(ch) (DMACBASE + ((ch) * 0x20) + 0x10c) |
| #define DMACCTL_INT (1 << 31) |
| #define DMACCFG(ch) (DMACBASE + ((ch) * 0x20) + 0x110) |
| #define DMACCFG_H (1 << 18) |
| #define DMACCFG_ACT (1 << 17) |
| #define DMACCFG_ITC (1 << 15) |
| #define DMACCFG_FLOWCTRL_MASK (7 << 11) |
| #define DMACCFG_FLOWCTRL_M2M_DMA (0 << 11) |
| #define DMACCFG_FLOWCTRL_M2P_DMA (1 << 11) |
| #define DMACCFG_FLOWCTRL_P2M_DMA (2 << 11) |
| #define DMACCFG_FLOWCTRL_M2P_PER (5 << 11) |
| #define DMACCFG_FLOWCTRL_P2M_PER (6 << 11) |
| #define DMACCFG_DSTPERI_SHIFT 6 |
| #define DMACCFG_SRCPERI_SHIFT 1 |
| #define DMACCFG_E (1 << 0) |
| |
| #define LC823450_DMA_EN (1 << 26) |
| |
| /**************************************************************************** |
| * Private Types |
| ****************************************************************************/ |
| |
| struct lc823450_phydmach_s |
| { |
| sq_queue_t req_q; /* DMA Request queue */ |
| bool inprogress; /* True: DMA is in progress on this channel */ |
| }; |
| |
| struct lc823450_dmach_s |
| { |
| sq_entry_t q_ent; |
| uint8_t chn; |
| struct lc823450_phydmach_s *phych; |
| dma_callback_t callback; /* DMA completion callback function */ |
| void *arg; /* Argument to pass to the callback function */ |
| uint32_t srcaddr; |
| uint32_t destaddr; |
| uint32_t ctrl; |
| size_t nxfrs; |
| uint32_t llist; |
| }; |
| |
| struct lc823450_dma_s |
| { |
| mutex_t lock; /* For exclusive access to the DMA channel list */ |
| |
| /* This is the state of each DMA channel */ |
| |
| int count; |
| struct lc823450_phydmach_s phydmach[DMA_CHANNEL_NUM]; |
| uint32_t reqline_use; |
| }; |
| |
| static int phydmastart(struct lc823450_phydmach_s *pdmach); |
| |
| /**************************************************************************** |
| * Private Data |
| ****************************************************************************/ |
| |
| static struct lc823450_dma_s g_dma = |
| { |
| .lock = NXMUTEX_INITIALIZER, |
| }; |
| volatile uint8_t g_dma_inprogress; |
| |
| /**************************************************************************** |
| * Private Functions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: dma_interrupt_core |
| ****************************************************************************/ |
| |
| static int dma_interrupt_core(void *context) |
| { |
| struct lc823450_phydmach_s *pdmach; |
| struct lc823450_dmach_s *dmach; |
| sq_entry_t *q_ent; |
| irqstate_t flags; |
| |
| pdmach = (struct lc823450_phydmach_s *)context; |
| |
| flags = spin_lock_irqsave(NULL); |
| q_ent = pdmach->req_q.tail; |
| DEBUGASSERT(q_ent != NULL); |
| dmach = (struct lc823450_dmach_s *)q_ent; |
| |
| if (dmach->nxfrs == 0) |
| { |
| /* finish one transfer */ |
| |
| sq_remlast(&pdmach->req_q); |
| spin_unlock_irqrestore(NULL, flags); |
| |
| if (dmach->callback) |
| dmach->callback((DMA_HANDLE)dmach, dmach->arg, 0); |
| } |
| else |
| { |
| spin_unlock_irqrestore(NULL, flags); |
| } |
| |
| up_disable_clk(LC823450_CLOCK_DMA); |
| phydmastart(pdmach); |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: dma_interrupt |
| ****************************************************************************/ |
| |
| static int dma_interrupt(int irq, void *context, void *arg) |
| { |
| int i; |
| uint32_t stat; |
| uint32_t err; |
| |
| up_enable_clk(LC823450_CLOCK_DMA); |
| |
| stat = getreg32(DMACINTTCSTAT); |
| err = getreg32(DMACINTERRSTAT); |
| |
| putreg32(stat, DMACINTTCCLR); |
| putreg32(err, DMACINTERRCLR); |
| |
| for (i = 0; i < DMA_CHANNEL_NUM; i++) |
| { |
| if (stat & (1 << i)) |
| { |
| dma_interrupt_core((void *)&g_dma.phydmach[i]); |
| } |
| |
| if (err & (1 << i)) |
| { |
| dmaerr("ERROR %d\n", i); |
| } |
| } |
| |
| up_disable_clk(LC823450_CLOCK_DMA); |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: phydmastart |
| ****************************************************************************/ |
| |
| static int phydmastart(struct lc823450_phydmach_s *pdmach) |
| { |
| irqstate_t flags; |
| int trnum; |
| |
| struct lc823450_dmach_s *dmach; |
| sq_entry_t *q_ent; |
| |
| flags = spin_lock_irqsave(NULL); |
| |
| q_ent = pdmach->req_q.tail; |
| |
| if (!q_ent) |
| { |
| pdmach->inprogress = 0; |
| spin_unlock_irqrestore(NULL, flags); |
| return 0; |
| } |
| |
| dmach = (struct lc823450_dmach_s *)q_ent; |
| |
| trnum = MIN(dmach->nxfrs, LC823450_DMA_MAX_TRANSSIZE); |
| |
| pdmach->inprogress = 1; |
| up_enable_clk(LC823450_CLOCK_DMA); |
| |
| /* start DMA */ |
| |
| putreg32(dmach->srcaddr, DMACSRCADDR(dmach->chn)); |
| putreg32(dmach->destaddr, DMACDSTADDR(dmach->chn)); |
| |
| putreg32(dmach->ctrl | (dmach->llist ? 0 : LC823450_DMA_ITC) | trnum, |
| DMACCTL(dmach->chn)); |
| putreg32(dmach->llist, DMACLLI(dmach->chn)); |
| |
| dmach->nxfrs -= trnum; |
| |
| if (dmach->nxfrs) |
| { |
| if (dmach->ctrl & LC823450_DMA_SRCINC) |
| { |
| if (dmach->ctrl & LC823450_DMA_SRCWIDTH_WORD) |
| { |
| dmach->srcaddr += trnum * 4; |
| } |
| else if (dmach->ctrl & LC823450_DMA_SRCWIDTH_HWORD) |
| { |
| dmach->srcaddr += trnum * 2; |
| } |
| else |
| { |
| dmach->srcaddr += trnum; |
| } |
| } |
| |
| if (dmach->ctrl & LC823450_DMA_DSTINC) |
| { |
| if (dmach->ctrl & LC823450_DMA_DSTWIDTH_WORD) |
| { |
| dmach->destaddr += trnum * 4; |
| } |
| else if (dmach->ctrl & LC823450_DMA_DSTWIDTH_HWORD) |
| { |
| dmach->destaddr += trnum * 2; |
| } |
| else |
| { |
| dmach->destaddr += trnum; |
| } |
| } |
| } |
| |
| while (getreg32(DMACCHENABLE) & (1 << dmach->chn)); |
| |
| while (getreg32(DMACCFG(dmach->chn)) & (DMACCFG_E | DMACCFG_ACT)); |
| |
| modifyreg32(DMACCFG(dmach->chn), 0, DMACCFG_ITC | DMACCFG_E); |
| |
| spin_unlock_irqrestore(NULL, flags); |
| return 0; |
| } |
| |
| #ifdef DMA_TEST |
| int test_buf1[4096]; |
| int test_buf2[4096]; |
| static volatile int test_done; |
| |
| static void dma_done(DMA_HANDLE handle, void *arg, int result) |
| { |
| int i; |
| for (i = 0; i < 256; i++) |
| llinfo("test_buf2[%d] = %d\n", i, test_buf2[i]); |
| test_done = 1; |
| } |
| |
| void lc823450_dma_test(void) |
| { |
| int i; |
| for (i = 0; i < 256; i++) |
| { |
| test_buf1[i] = i; |
| test_buf2[i] = 0; |
| } |
| |
| DMA_HANDLE hdma; |
| hdma = lc823450_dmachannel(DMA_CHANNEL_VIRTUAL); |
| lc823450_dmasetup(hdma, LC823450_DMA_SRCWIDTH_WORD | |
| LC823450_DMA_DSTWIDTH_WORD | |
| LC823450_DMA_DSTINC | |
| LC823450_DMA_SRCINC, |
| test_buf1, test_buf2, 4084); |
| lc823450_dmastart(hdma, dma_done, NULL); |
| while (!test_done); |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Public Functions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: arm_dma_initialize |
| ****************************************************************************/ |
| |
| void arm_dma_initialize(void) |
| { |
| int i; |
| |
| for (i = 0; i < DMA_CHANNEL_NUM; i++) |
| { |
| g_dma.phydmach[i].inprogress = 0; |
| sq_init(&g_dma.phydmach[i].req_q); |
| } |
| |
| if (irq_attach(LC823450_IRQ_DMAC, dma_interrupt, NULL) != 0) |
| { |
| return; |
| } |
| |
| up_enable_irq(LC823450_IRQ_DMAC); |
| |
| /* Clock & Reset */ |
| |
| modifyreg32(MCLKCNTBASIC, 0, MCLKCNTBASIC_DMAC_CLKEN); |
| modifyreg32(MRSTCNTBASIC, 0, MRSTCNTBASIC_DMAC_RSTB); |
| |
| /* DMAC enable */ |
| |
| modifyreg32(DMACCONFIG, 0, DMACCONFIG_EN); |
| |
| #ifdef DMA_TEST |
| lc823450_dma_test(); |
| #endif |
| |
| /* clock disable */ |
| |
| modifyreg32(MCLKCNTBASIC, MCLKCNTBASIC_DMAC_CLKEN, 0); |
| } |
| |
| /**************************************************************************** |
| * Name: lc823450_dmaconfigure |
| * |
| * Description: |
| * Configure a DMA request. Each DMA request may have two different DMA |
| * request sources. This associates one of the sources with a DMA request. |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| void lc823450_dmaconfigure(uint8_t dmarequest, bool alternate) |
| { |
| } |
| |
| /**************************************************************************** |
| * Name: lc823450_dmarequest |
| * |
| * Description: |
| * Configure a DMA request. |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| void lc823450_dmarequest(DMA_HANDLE handle, uint8_t dmarequest) |
| { |
| struct lc823450_dmach_s *dmach = (struct lc823450_dmach_s *)handle; |
| uint32_t val; |
| int req_line; |
| |
| /* search for free request line */ |
| |
| for (req_line = 0; req_line < 16; req_line++) |
| { |
| if ((g_dma.reqline_use & (1 << req_line)) == 0) |
| { |
| g_dma.reqline_use |= 1 << req_line; |
| break; |
| } |
| } |
| |
| DEBUGASSERT(req_line != 16); |
| |
| up_enable_clk(LC823450_CLOCK_DMA); |
| |
| /* DREQ Select */ |
| |
| val = getreg32(DREQ0_3 + (req_line / 4) * 4); |
| val &= ~(0xff << ((req_line % 4) * 8)); |
| val |= dmarequest << ((req_line % 4) * 8); |
| putreg32(val, (DREQ0_3 + (req_line / 4) * 4)); |
| |
| /* source or dest peri request ? */ |
| |
| val = getreg32(DMACCFG(dmach->chn)); |
| switch (dmarequest) |
| { |
| case DMA_REQUEST_UART0RX: |
| case DMA_REQUEST_UART1RX: |
| case DMA_REQUEST_UART2RX: |
| case DMA_REQUEST_SIORX: |
| val &= ~(0xf << DMACCFG_SRCPERI_SHIFT); |
| val |= req_line << DMACCFG_SRCPERI_SHIFT; |
| val |= DMACCFG_FLOWCTRL_P2M_DMA; |
| break; |
| case DMA_REQUEST_UART0TX: |
| case DMA_REQUEST_UART1TX: |
| case DMA_REQUEST_UART2TX: |
| case DMA_REQUEST_SIOTX: |
| val &= ~(0xf << DMACCFG_DSTPERI_SHIFT); |
| val |= req_line << DMACCFG_DSTPERI_SHIFT; |
| val |= DMACCFG_FLOWCTRL_M2P_DMA; |
| break; |
| case DMA_REQUEST_USBDEV: /* ??? */ |
| val &= ~(0xf << DMACCFG_DSTPERI_SHIFT); |
| val |= req_line << DMACCFG_DSTPERI_SHIFT; |
| val |= req_line << DMACCFG_SRCPERI_SHIFT; |
| val |= DMACCFG_FLOWCTRL_M2P_DMA; |
| break; |
| default: |
| dmaerr("ERROR: Not implemetned\n"); |
| DEBUGPANIC(); |
| } |
| |
| putreg32(val, DMACCFG(dmach->chn)); |
| up_disable_clk(LC823450_CLOCK_DMA); |
| } |
| |
| /**************************************************************************** |
| * Name: lc823450_dmareauest_dir |
| ****************************************************************************/ |
| |
| void lc823450_dmareauest_dir(DMA_HANDLE handle, uint8_t dmarequest, int m2p) |
| { |
| struct lc823450_dmach_s *dmach = (struct lc823450_dmach_s *)handle; |
| uint32_t val; |
| |
| up_enable_clk(LC823450_CLOCK_DMA); |
| |
| val = getreg32(DMACCFG(dmach->chn)); |
| |
| val &= ~DMACCFG_FLOWCTRL_MASK; |
| if (m2p) |
| { |
| val |= DMACCFG_FLOWCTRL_M2P_DMA; |
| } |
| else |
| { |
| val |= DMACCFG_FLOWCTRL_P2M_DMA; |
| } |
| |
| putreg32(val, DMACCFG(dmach->chn)); |
| up_disable_clk(LC823450_CLOCK_DMA); |
| } |
| |
| /**************************************************************************** |
| * Name: lc823450_dmachannel |
| * |
| * Description: |
| * Allocate a DMA channel. This function sets aside a DMA channel and |
| * gives the caller exclusive access to the DMA channel. |
| * |
| * Returned Value: |
| * On success, this function returns a non-NULL, void* DMA channel handle. |
| * NULL is returned on any failure. This function can fail only if no DMA |
| * channel is available. |
| * |
| ****************************************************************************/ |
| |
| DMA_HANDLE lc823450_dmachannel(int ch) |
| { |
| struct lc823450_dmach_s *dmach = NULL; |
| |
| /* Get exclusive access to the GPDMA state structure */ |
| |
| dmach = (struct lc823450_dmach_s *) |
| kmm_zalloc(sizeof(struct lc823450_dmach_s)); |
| |
| if (dmach) |
| { |
| dmach->chn = ch; |
| } |
| |
| return (DMA_HANDLE)dmach; |
| } |
| |
| /**************************************************************************** |
| * Name: lc823450_dmafree |
| * |
| * Description: |
| * Release a DMA channel. NOTE: The 'handle' used in this argument must |
| * NEVER be used again until lc823450_dmachannel() is called again to |
| * re-gain a valid handle. |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| void lc823450_dmafree(DMA_HANDLE handle) |
| { |
| struct lc823450_dmach_s *dmach = (DMA_HANDLE)handle; |
| |
| DEBUGASSERT(dmach != NULL); |
| UNUSED(dmach); |
| |
| /* Make sure that the DMA channel was properly stopped */ |
| |
| lc823450_dmastop(handle); |
| |
| /* Mark the channel available. This is an atomic operation and needs no |
| * special protection. |
| */ |
| |
| kmm_free(handle); |
| } |
| |
| /**************************************************************************** |
| * Name: lc823450_dmasetup |
| * |
| * Description: |
| * Configure DMA for one transfer. |
| * |
| ****************************************************************************/ |
| |
| int lc823450_dmasetup(DMA_HANDLE handle, uint32_t control, |
| uint32_t srcaddr, uint32_t destaddr, size_t nxfrs) |
| { |
| struct lc823450_dmach_s *dmach = (DMA_HANDLE)handle; |
| |
| DEBUGASSERT(dmach != NULL); |
| |
| dmach->srcaddr = srcaddr; |
| dmach->destaddr = destaddr; |
| dmach->ctrl = control; |
| dmach->nxfrs = nxfrs; |
| dmach->llist = 0; |
| |
| return OK; |
| } |
| |
| int lc823450_dmallsetup(DMA_HANDLE handle, uint32_t control, |
| uint32_t srcaddr, uint32_t destaddr, |
| size_t nxfrs, uint32_t llist) |
| { |
| struct lc823450_dmach_s *dmach = (DMA_HANDLE)handle; |
| |
| DEBUGASSERT(dmach != NULL); |
| |
| dmach->srcaddr = srcaddr; |
| dmach->destaddr = destaddr; |
| dmach->ctrl = control; |
| dmach->nxfrs = nxfrs; |
| dmach->llist = llist; |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: lc823450_dmaremain |
| ****************************************************************************/ |
| |
| int lc823450_dmaremain(DMA_HANDLE handle) |
| { |
| struct lc823450_dmach_s *dmach = (DMA_HANDLE)handle; |
| |
| return getreg32(DMACCTL(dmach->chn)) & 0xfff; |
| } |
| |
| /**************************************************************************** |
| * Name: lc823450_dmastart |
| ****************************************************************************/ |
| |
| int lc823450_dmastart(DMA_HANDLE handle, dma_callback_t callback, void *arg) |
| { |
| struct lc823450_dmach_s *dmach = (DMA_HANDLE)handle; |
| irqstate_t flags; |
| |
| DEBUGASSERT(dmach != NULL); |
| |
| /* select physical channel */ |
| |
| flags = spin_lock_irqsave(NULL); |
| |
| sq_addfirst(&dmach->q_ent, &g_dma.phydmach[dmach->chn].req_q); |
| |
| dmach->callback = callback; |
| dmach->arg = arg; |
| |
| /* Kick DMAC, if not active */ |
| |
| if (!g_dma.phydmach[dmach->chn].inprogress) |
| { |
| phydmastart(&g_dma.phydmach[dmach->chn]); |
| } |
| |
| spin_unlock_irqrestore(NULL, flags); |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: lc823450_dmastop |
| ****************************************************************************/ |
| |
| void lc823450_dmastop(DMA_HANDLE handle) |
| { |
| struct lc823450_dmach_s *dmach = (DMA_HANDLE)handle; |
| struct lc823450_phydmach_s *pdmach; |
| irqstate_t flags; |
| |
| DEBUGASSERT(dmach != NULL); |
| |
| flags = spin_lock_irqsave(NULL); |
| |
| modifyreg32(DMACCFG(dmach->chn), DMACCFG_ITC | DMACCFG_E, 0); |
| |
| pdmach = &g_dma.phydmach[dmach->chn]; |
| if (pdmach) |
| { |
| if (pdmach->inprogress) |
| { |
| up_disable_clk(LC823450_CLOCK_DMA); |
| } |
| |
| pdmach->inprogress = 0; |
| sq_rem(&dmach->q_ent, &pdmach->req_q); |
| } |
| |
| spin_unlock_irqrestore(NULL, flags); |
| } |