From cb55abfbb0ad9ce15b3b1863f05ba209356ad69c Mon Sep 17 00:00:00 2001 From: patacongo Date: Fri, 2 Apr 2010 00:45:28 +0000 Subject: DMA updates git-svn-id: svn://svn.code.sf.net/p/nuttx/code/trunk@2568 42af7a65-404d-4744-a932-0658087f49c3 --- nuttx/arch/arm/src/sam3u/sam3u_dmac.c | 909 +++++++++++++++++++++++++++++----- 1 file changed, 789 insertions(+), 120 deletions(-) (limited to 'nuttx/arch/arm/src/sam3u/sam3u_dmac.c') diff --git a/nuttx/arch/arm/src/sam3u/sam3u_dmac.c b/nuttx/arch/arm/src/sam3u/sam3u_dmac.c index b5ed3e887..2a5253c8f 100755 --- a/nuttx/arch/arm/src/sam3u/sam3u_dmac.c +++ b/nuttx/arch/arm/src/sam3u/sam3u_dmac.c @@ -1,7 +1,7 @@ /**************************************************************************** * arch/arm/src/sam3u-ek/sam3u_dmac.c * - * Copyright (C) 2009 Gregory Nutt. All rights reserved. + * Copyright (C) 2010 Gregory Nutt. All rights reserved. * Author: Gregory Nutt * * Redistribution and use in source and binary forms, with or without @@ -60,6 +60,31 @@ * Pre-processor Definitions ****************************************************************************/ +/* Configuration ************************************************************/ + +/* Condition out the whole file unless DMA is selected in the configuration */ + +#ifdef CONFIG_SAM3U_DMA + +/* Check the number of link list descriptors to allocate */ + +#ifndef CONFIG_SAM3U_NLLDESC +# define CONFIG_SAM3U_NLLDESC CONFIG_SAM3U_NDMACHAN +#endif + +#if CONFIG_SAM3U_NLLDESC < CONFIG_SAM3U_NDMACHAN +# warning "At least CONFIG_SAM3U_NDMACHAN descriptors must be allocated */ + +# undef CONFIG_SAM3U_NLLDESC +# define CONFIG_SAM3U_NLLDESC CONFIG_SAM3U_NDMACHAN +#endif + +/* Register values **********************************************************/ + +#define DMACHAN_CTRLB_BOTHDSCR \ + (DMACHAN_CTRLB_SRCDSCR | DMACHAN_CTRLB_DSTDSCR) + + /**************************************************************************** * Private Types ****************************************************************************/ @@ -68,24 +93,27 @@ struct sam3u_dma_s { - uint8_t chan; /* DMA channel number (0-6) */ - bool inuse; /* TRUE: The DMA channel is in use */ - uint32_t flags; /* DMA channel flags */ - uint32_t base; /* DMA register channel base address */ - dma_callback_t callback; /* Callback invoked when the DMA completes */ - void *arg; /* Argument passed to callback function */ - uint16_t bufsize; /* Transfer buffer size in bytes */ - volatile uint16_t remaining; /* Total number of bytes remaining to be transferred */ - int result; /* Transfer result (OK or negated errno) */ + uint8_t chan; /* DMA channel number (0-6) */ + bool inuse; /* TRUE: The DMA channel is in use */ + uint32_t flags; /* DMA channel flags */ + uint32_t base; /* DMA register channel base address */ + dma_callback_t callback; /* Callback invoked when the DMA completes */ + void *arg; /* Argument passed to callback function */ + uint16_t bufsize; /* Transfer buffer size in bytes */ + struct dma_linklist_s *llhead; /* DMA link list head */ + struct dma_linklist_s *lltail; /* DMA link list head */ + volatile uint16_t remaining; /* Total number of bytes remaining to be transferred */ + int result; /* Transfer result (OK or negated errno) */ }; /**************************************************************************** * Private Data ****************************************************************************/ -/* This semaphore protects the DMA channel table */ +/* These semaphores protect the DMA channel and descriptor tables */ -static sem_t g_dmasem; +static sem_t g_chsem; +static sem_t g_dsem; /* CTRLA field lookups */ @@ -103,6 +131,17 @@ static const uint32_t g_destwidth[3] = DMACHAN_CTRLA_DSTWIDTH_WORD }; +static const uint32_t g_fifocfg[3] = +{ + DMACHAN_CFG_FIFOCFG_LARGEST, + DMACHAN_CFG_FIFOCFG_HALF, + DMACHAN_CFG_FIFOCFG_SINGLE +}; + +/* This array describes the available link list descriptors */ + +static struct dma_linklist_s g_linklist[CONFIG_SAM3U_NLLDESC]; + /* This array describes the state of each DMA */ static struct sam3u_dma_s g_dma[CONFIG_SAM3U_NDMACHAN] = @@ -145,40 +184,67 @@ static struct sam3u_dma_s g_dma[CONFIG_SAM3U_NDMACHAN] = * Private Functions ****************************************************************************/ -/************************************************************************************ - * Name: sam3u_dmatake() and sam3u_dmagive() +/**************************************************************************** + * Name: sam3u_takechsem() and sam3u_givechsem() * * Description: - * Used to get exclusive access to a DMA channel. + * Used to get exclusive access to the DMA channel table * - ************************************************************************************/ + ****************************************************************************/ -static void sam3u_dmatake(void) +static void sam3u_takechsem(void) { /* Take the semaphore (perhaps waiting) */ - while (sem_wait(&g_dmasem) != 0) + while (sem_wait(&g_chsem) != 0) { - /* The only case that an error should occur here is if the wait was awakened - * by a signal. + /* The only case that an error should occur here is if the wait was + * awakened by a signal. */ ASSERT(errno == EINTR); } } -static inline void sam3u_dmagive(void) +static inline void sam3u_givechsem(void) { - (void)sem_post(&g_dmasem); + (void)sem_post(&g_chsem); } -/************************************************************************************ +**************************************************************************** + * Name: sam3u_takedsem() and sam3u_givedsem() + * + * Description: + * Used to wait for availability of descriptors in the descriptor table. + * + ****************************************************************************/ + +static void sam3u_takedsem(void) +{ + /* Take the semaphore (perhaps waiting) */ + + while (sem_wait(&g_dsem) != 0) + { + /* The only case that an error should occur here is if the wait was + * awakened by a signal. + */ + + ASSERT(errno == EINTR); + } +} + +static inline void sam3u_givedsem(void) +{ + (void)sem_post(&g_dsem); +} + +/**************************************************************************** * Name: sam3u_fifosize * * Description: * Decode the FIFO size from the flags * - ************************************************************************************/ + ****************************************************************************/ static unsigned int sam3u_fifosize(uint8_t dmach_flags) { @@ -193,38 +259,52 @@ static unsigned int sam3u_fifosize(uint8_t dmach_flags) } } -/************************************************************************************ +/**************************************************************************** * Name: sam3u_flowcontrol * * Description: * Decode the FIFO flow control from the flags * - ************************************************************************************/ + ****************************************************************************/ static inline boolean sam3u_flowcontrol(uint8_t dmach_flags) { return ((dmach_flags & DMACH_FLAG_FLOWCONTROL) != 0); } -/************************************************************************************ +/**************************************************************************** + * Name: sam3u_flowcontrol + * + * Description: + * Decode the FIFO config from the flags + * + ****************************************************************************/ + +static uint32_t sam3u_fifocfg(struct sam3u_dma_s *dmach) +{ + unsigned int ndx = (dmach->flags & DMACH_FLAG_FIFOCFG_MASK) >> DMACH_FLAG_FIFOCFG_SHIFT; + DEBUGASSERT(ndx < 3); + return sam3u_fifocfg[ndx]; +} + +/**************************************************************************** * Name: sam3u_txctrlabits * * Description: - * Decode the the flags to get the correct CTRLA register bit settings for a transmit - * (memory to peripheral) transfer. These are only the "fixed" CTRLA values and - * need to be updated with the actual transfer size before being written to CTRLA - * sam3u_txctrla). + * Decode the the flags to get the correct CTRLA register bit settings for + * a transmit (memory to peripheral) transfer. These are only the "fixed" + * CTRLA values and need to be updated with the actual transfer size before + * being written to CTRLA sam3u_txctrla). * - ************************************************************************************/ + ****************************************************************************/ static inline uint32_t -sam3u_txctrlabits(struct sam3u_dma_s *dmach, uint32_t otherbits) +sam3u_txctrlabits(struct sam3u_dma_s *dmach) { uint32_t regval; unsigned int ndx; DEBUGASSERT(dmach); - regval = otherbits; /* Since this is a transmit, the source is described by the memory selections. * Set the source width (memory width). @@ -232,7 +312,7 @@ sam3u_txctrlabits(struct sam3u_dma_s *dmach, uint32_t otherbits) ndx = (dmach->flags & DMACH_FLAG_MEMWIDTH_MASK) >> DMACH_FLAG_MEMWIDTH_SHIFT; DEBUGASSERT(ndx < 3); - regval |= g_srcwidth[ndx]; + regval = g_srcwidth[ndx]; /* Set the source chuck size (memory chunk size) */ @@ -271,21 +351,23 @@ sam3u_txctrlabits(struct sam3u_dma_s *dmach, uint32_t otherbits) return regval; } -/************************************************************************************ +/**************************************************************************** * Name: sam3u_txctrla * * Description: * Or in the variable CTRLA bits * - ************************************************************************************/ + ****************************************************************************/ static inline uint32_t sam3u_txctrla(uint32_t dmasize, uint32_t txctrlabits) { - /* Set the buffer transfer size field. This is the number of transfers to be - * performed, that is, the number of source width transfers to perform. + /* Set the buffer transfer size field. This is the number of transfers to + * be performed, that is, the number of source width transfers to perform. */ - /* Adjust the the source transfer size for the source chunk size (memory chunk size) */ + /* Adjust the the source transfer size for the source chunk size (memory + * chunk size) + */ if ((dmach->flags & DMACH_FLAG_MEMCHUNKSIZE) == DMACH_FLAG_MEMCHUNKSIZE_4) { @@ -296,33 +378,32 @@ static inline uint32_t sam3u_txctrla(uint32_t dmasize, uint32_t txctrlabits) return (txctrlabits & ~DMACHAN_CTRLA_BTSIZE_MASK) | (dmasize << DMACHAN_CTRLA_BTSIZE_SHIFT); } -/************************************************************************************ +/**************************************************************************** * Name: sam3u_setrxctrla * * Description: - * Decode the the flags to get the correct CTRLA register bit settings for a read - * (peripheral to memory) transfer. These are only the "fixed" CTRLA values and - * need to be updated with the actual transfer size before being written to CTRLA - * sam3u_rxctrla). + * Decode the the flags to get the correct CTRLA register bit settings for + * a read (peripheral to memory) transfer. These are only the "fixed" CTRLA + * values and need to be updated with the actual transfer size before being + * written to CTRLA sam3u_rxctrla). * - ************************************************************************************/ + ****************************************************************************/ static inline uint32_t -sam3u_setrxctrla(struct sam3u_dma_s *dmach, uint32_t otherbits) +sam3u_setrxctrla(struct sam3u_dma_s *dmach) { uint32_t regval; unsigned int ndx; DEBUGASSERT(dmach && dmasize <= DMACHAN_CTRLA_BTSIZE_MAX); - regval = otherbits; - /* Since this is a receive, the source is described by the peripheral selections. - * Set the source width (peripheral width). + /* Since this is a receive, the source is described by the peripheral + * selections. Set the source width (peripheral width). */ ndx = (dmach->flags & DMACH_FLAG_PERIPHWIDTH_MASK) >> DMACH_FLAG_PERIPHWIDTH_SHIFT; DEBUGASSERT(ndx < 3); - regval |= g_srcwidth[ndx]; + regval = g_srcwidth[ndx]; /* Set the source chuck size (peripheral chunk size) */ @@ -361,21 +442,23 @@ sam3u_setrxctrla(struct sam3u_dma_s *dmach, uint32_t otherbits) return regval; } -/************************************************************************************ +/**************************************************************************** * Name: sam3u_rxctrla * * Description: - * Or in the variable CTRLA bits + * 'OR' in the variable CTRLA bits * - ************************************************************************************/ + ****************************************************************************/ static inline uint32_t sam3u_rxctrla(uint32_t dmasize, uint32_t txctrlabits) { - /* Set the buffer transfer size field. This is the number of transfers to be - * performed, that is, the number of source width transfers to perform. + /* Set the buffer transfer size field. This is the number of transfers to + * be performed, that is, the number of source width transfers to perform. */ - /* Adjust the the source transfer size for the source chunk size (peripheral chunk size) */ + /* Adjust the the source transfer size for the source chunk size (peripheral + * chunk size) + */ if ((dmach->flags & DMACH_FLAG_PERIPHCHUNKSIZE) == DMACH_FLAG_PERIPHCHUNKSIZE_4) { @@ -386,79 +469,171 @@ static inline uint32_t sam3u_rxctrla(uint32_t dmasize, uint32_t txctrlabits) return (txctrlabits & ~DMACHAN_CTRLA_BTSIZE_MASK) | (dmasize << DMACHAN_CTRLA_BTSIZE_SHIFT); } -/************************************************************************************ - * Name: sam3u_srcctrlb +/**************************************************************************** + * Name: sam3u_txctrlb * * Description: - * Set source related CTRLB fields + * Decode the the flags to get the correct CTRLB register bit settings for + * a transmit (memory to peripheral) transfer. * - ************************************************************************************/ + ****************************************************************************/ -static void sam3u_srcctrlb(struct sam3u_dma_s *dmach, bool lli, bool autoincr) +static inline void sam3u_txctrlb(struct sam3u_dma_s *dmach) { uint32_t regval; + + /* Assume that we will not be using the link list and disable the source + * and destination descriptors. The default will be single transfer mode. + */ - /* Fetch CTRLB and clear the configurable bits */ + regval = DMACHAN_CTRLB_BOTHDSCR; - regval = getreg32(dmach->base + SAM3U_DMACHAN_CTRLB_OFFSET); - regval &= ~ (DMACHAN_CTRLB_SRCDSCR | DMACHAN_CTRLB_SRCINCR_MASK | 1<<31); + /* Select flow control (even if the channel doesn't support it). The + * naming convention from TX is memory to peripheral, but that is really be + * determined by bits in the DMA flags. + */ + + /* Is the memory source really a peripheral? */ + + if ((dmach->flags & DMACH_FLAG_MEMISPERIPH) != 0) + { + /* Yes.. is the peripheral destination also a peripheral? */ - /* Disable the source descriptor if we are not using the LLI transfer mode */ + if ((dmach->flags & DMACH_FLAG_PERIPHISPERIPH) != 0) + { + /* Yes.. Use peripheral-to-peripheral flow control */ - if (lli) + regval |= DMACHAN_CTRLB_FC_P2P; + } + else + { + /* No.. Use peripheral-to-memory flow control */ + + regval |= DMACHAN_CTRLB_FC_P2M; + } + } + else { - regval |= DMACHAN_CTRLB_SRCDSCR; + /* No, the source is memory. Is the peripheral destination a + * peripheral + */ + + if ((dmach->flags & DMACH_FLAG_PERIPHISPERIPH) != 0) + { + /* Yes.. Use memory-to-peripheral flow control */ + + regval |= DMACHAN_CTRLB_FC_M2P; + } + else + { + /* No.. Use memory-to-memory flow control */ + + regval |= DMACHAN_CTRLB_FC_M2M; + } } - /* Select address incrementing */ + /* Select source address incrementing */ - regval |= autoincr ? DMACHAN_CTRLB_SRCINCR_INCR ? DMACHAN_CTRLB_SRCINCR_FIXED; + if ((dmach->flags & DMACH_FLAG_MEMINCREMENT) == 0) + { + regval |= DMACHAN_CTRLB_SRCINCR_FIXED; + } - /* Save the updated CTRLB value */ + /* Select destination address incrementing */ - putreg32(regval, dmach->base + SAM3U_DMACHAN_CTRLB_OFFSET) + if ((dmach->flags & DMACH_FLAG_PERIPHINCREMENT) == 0) + { + regval |= DMACHAN_CTRLB_DESTINCR_FIXED; + } + return regval; } -/************************************************************************************ - * Name: sam3u_destctrlb +/**************************************************************************** + * Name: sam3u_rxctrlb * * Description: - * Set destination related CTRLB fields + * Decode the the flags to get the correct CTRLB register bit settings for + * a receive (peripheral to memory) transfer. * - ************************************************************************************/ + ****************************************************************************/ -static void sam3u_destctrlb(struct sam3u_dma_s *dmach, bool lli, bool autoincr) +static inline void sam3u_rxctrlb(struct sam3u_dma_s *dmach) { uint32_t regval; + + /* Assume that we will not be using the link list and disable the source and + * destination descriptors. The default will be single transfer mode. + */ - /* Fetch CTRLB and clear the configurable bits */ + regval = DMACHAN_CTRLB_BOTHDSCR; - regval = getreg32(dmach->base + SAM3U_DMACHAN_CTRLB_OFFSET); - regval &= ~ (DMACHAN_CTRLB_DSTDSCR | DMACHAN_CTRLB_DSTINCR_MASK); + /* Select flow control (even if the channel doesn't support it). The + * naming convention from RX is peripheral to memory, but that is really be + * determined by bits in the DMA flags. + */ - /* Disable the source descriptor if we are not using the LLI transfer mode */ + /* Is the peripheral source really a peripheral? */ - if (lli) + if ((dmach->flags & DMACH_FLAG_PERIPHISPERIPH) != 0) { - regval |= DMACHAN_CTRLB_DSTDSCR; + /* Yes.. is the memory destination also a peripheral? */ + + if ((dmach->flags & DMACH_FLAG_MEMISPERIPH) != 0) + { + /* Yes.. Use peripheral-to-peripheral flow control */ + + regval |= DMACHAN_CTRLB_FC_P2P; + } + else + { + /* No.. Use peripheral-to-memory flow control */ + + regval |= DMACHAN_CTRLB_FC_P2M; + } } + else + { + /* No, the peripheral source is memory. Is the memory destination + * a peripheral + */ - /* Select address incrementing */ + if ((dmach->flags & DMACH_FLAG_MEMISPERIPH) != 0) + { + /* Yes.. Use memory-to-peripheral flow control */ - regval |= autoincr ? DMACHAN_CTRLB_DESTINCR_INCR ? DMACHAN_CTRLB_DESTINCR_FIXED; - - /* Save the updated CTRLB value */ + regval |= DMACHAN_CTRLB_FC_M2P; + } + else + { + /* No.. Use memory-to-memory flow control */ - putreg32(regval, dmach->base + SAM3U_DMACHAN_CTRLB_OFFSET) + regval |= DMACHAN_CTRLB_FC_M2M; + } + } + + /* Select source address incrementing */ + + if (dmach->flags & DMACH_FLAG_PERIPHINCREMENT) == 0) + { + regval |= DMACHAN_CTRLB_SRCINCR_FIXED; + } + + /* Select address incrementing */ + + if (dmach->flags & DMACH_FLAG_MEMINCREMENT) 0= 0) + { + regval |= DMACHAN_CTRLB_DESTINCR_FIXED; + } + return regval; } -/************************************************************************************ +/**************************************************************************** * Name: sam3u_flowcontrol * * Description: * Select flow control * - ************************************************************************************/ + ****************************************************************************/ static inline void sam3u_flowcontrol(struct sam3u_dma_s *dmach, uint32_t setting) { @@ -470,13 +645,396 @@ static inline void sam3u_flowcontrol(struct sam3u_dma_s *dmach, uint32_t setting putreg(regval, dmach->base + SAM3U_DMACHAN_CTRLB_OFFSET); } -/************************************************************************************ +/**************************************************************************** + * Name: sam3u_allocdesc + * + * Description: + * Allocate and add one descriptor to the DMA channel's link list. + * + * NOTE: link list entries are freed by the DMA interrupt handler. However, + * since the setting/clearing of the 'in use' indication is atomic, no + * special actions need be performed. It would be a good thing to add logic + * to handle the case where all of the entries are exhausted and we could + * wait for some to be freed by the interrupt handler. + * + ****************************************************************************/ + +static struct dma_linklist_s * +sam3u_allocdesc(struct sam3u_dma_s *dmach, struct dma_linklist_s *prev, + uint23_t src, uint32_t dest, uint32_t ctrla, uint32_t ctrlb) +{ + struct dma_linklist_s *desc = NULL; + int i; + + /* Sanity check -- src == 0 is the indication that the link is unused. + * Obviously setting it to zero would break that usage. + */ + +#ifdef CONFIG_DEBUG + if (src != 0) +#endif + { + /* Table a descriptor table semaphore count. When we get one, then there + * is at least one free descriptor in the table and it is ours. + */ + + sam3u_takedsem(); + + /* Examine each link list entry to find an available one -- i.e., one + * with src == 0. That src field is set to zero by the DMA transfer + * complete interrupt handler. The following should be safe because + * that is an atomic operation. + */ + + sam3u_takechsem(); + for (i = 0; i < CONFIG_SAM3U_NLLDESC; i++) + { + if (g_linklist[i].src == 0) + { + /* We have it. Initialize the new link list entry */ + + desc = &g_linklist[i]; + desc->src = src; /* Source address */ + desc->dest = dest; /* Destination address */ + desc->ctrla = ctrla; /* Control A value */ + desc->ctrlb = ctrlb; /* Control B value */ + desc->desc = 0; /* Descriptor address */ + + /* And then hook it at the tail of the link list */ + + if (!prev) + { + /* There is no previous link. This is the new head of + * the list + */ + + DEBUGASSERT(dmach->llhead == NULL && dmach->lltail == NULL); + dmach->llhead = desc; + } + else + { + DEBUGASSERT(dmach->llhead != NULL && dmach->tail == prev); + + /* When the second link is added to the list, that is the + * cue that we are going to do the link list transfer. + * + * Enable the source and destination descriptor in the link + * list entry just before this one. We assume that both + * source and destination buffers are non-continuous, but + * this should work even if that is not the case. + */ + + prev->ctrlb &= ~DMACHAN_CTRLB_BOTHDSCR; + + /* Link the previous tail to the new tail */ + + prev->next = (uint32_t)desc; + } + + /* In any event, this is the new tail of the list. The source + * and destination descriptors must be disabled for the last entry + * in the link list. */ + + desc->ctrlb |= DMACHAN_CTRLB_BOTHDSCR; + dmach->lltail = desc; + + /* Increment the total number of bytes to be transferred */ + + dmach->bufsize += nbytes; + break; + } + } + + /* Because we hold a count from the counting semaphore, the above + * search loop should always be successful. + */ + + sam3u_givechsem(); + DEBUGASSERT(desc != NULL); + } + return desc; +} + +/**************************************************************************** + * Name: sam3u_freelinklist + * + * Description: + * Free all descriptors in the DMA channel's link list. + * + * NOTE: Called from the DMA interrupt handler. + * + ****************************************************************************/ + +static void sam3u_freelinklist(struct sam3u_dma_s *dmach) +{ + struct dma_linklist_s *desc; + struct dma_linklist_s *next; + + /* Get the head of the link list and detach the link list from the DMA + * channel + */ + + desc = dmach->llhead + dmach->llhead = NULL; + dmach->lltail = NULL; + + /* No transfer in progress */ + + dmach->remaining = 0; + dmach->bufsize = 0; + + /* Reset each descriptor in the link list (thereby freeing them) */ + + while (desc != NULL) + { + next = (struct dma_linklist_s *)desc->next; + DEBUGASSERT(desc->src != 0); + memset(desc, 0, sizeof(struct dma_linklist_s)); + sam3u_givedsem(); + desc = next; + } +} + +/**************************************************************************** + * Name: sam3u_txbuffer + * + * Description: + * Configure DMA for transmit of one buffer (memory to peripheral). This + * function may be called multiple times to handle large and/or dis- + * continuous transfers. + * + ****************************************************************************/ + +static int sam3u_txbuffer(struct sam3u_dma_s *dmach, uint32_t paddr, + uint32_t maddr, size_t nbytes) +{ + uint32_t regval; + uint32_t ctrla; + uint32_t ctrlb; + + /* If we are appending a buffer to a linklist, then re-use the CTRLA/B + * values. Otherwise, create them from the properties of the transfer. + */ + + if (dmach->llhead) + { + regval = dmach->llhead.ctrla; + ctrlb = dmach->llhead.ctrlb; + } + else + { + regval = sam3u_txctrlabits(dmach); + ctrla = sam3u_txctrlb(dmach); + } + ctrla = sam3u_txctrla(regval, nbytes); + + /* Add the new link list entry */ + + if (!sam3u_allocdesc(dmach, dmach->lltail, maddr, paddr, ctrla, ctrlb)) + { + return -ENOMEM; + } + return OK; +} + +/**************************************************************************** + * Name: sam3u_rxbuffer + * + * Description: + * Configure DMA for receipt of one buffer (peripheral to memory). This + * function may be called multiple times to handle large and/or dis- + * continuous transfers. + * + ****************************************************************************/ + +static int sam3u_rxbuffer(struct sam3u_dma_s *dmach, uint32_t paddr, + uint32_t maddr, size_t nbytes) +{ + uint32_t regval; + uint32_t ctrla; + uint32_t ctrlb; + + /* If we are appending a buffer to a linklist, then re-use the CTRLA/B + * values. Otherwise, create them from the properties of the transfer. + */ + + if (dmach->llhead) + { + regval = dmach->llhead.ctrla; + ctrlb = dmach->llhead.ctrlb; + } + else + { + regval = sam3u_rxctrlabits(dmach); + ctrlb = sam3u_rxctrlb(dmach); + } + ctrla = sam3u_rxctrla(regval, nbytes); + + /* Add the new link list entry */ + + if (!sam3u_allocdesc(dmach, dmach->lltail, maddr, paddr, ctrla, ctrlb)) + { + return -ENOMEM; + } + return OK; +} + +/**************************************************************************** + * Name: sam3u_single + * + * Description: + * Start a single buffer DMA. + * + ****************************************************************************/ + +static inline int sam3u_single(struct sam3u_dma_s *dmach) +{ + +Transfer Type AUTO SRC_REP DST_REP SRC_DSCR DST_DSCR BTSIZE SADDR DADDR Other + 1) Single Buffer or Last 0 – – 1 1 USR USR USR USR +buffer of a multiple buffer +transfer + + struct dma_linklist_s *llhead = dmach->llhead; + + /* Clear any pending interrupts from any previous DMAC transfer by reading + * the interrupt status register. + */ + + (void)getreg32(SAM3U_DMAC_EBCISR); + + /* Write the starting source address in the SADDR register */ + + DEBUGASSERT(llhead != NULL && llhead->src != 0); + putreg32(llhead->src, dmach->base + SAM3U_DMACHAN_SADDR_OFFSET); + + /* Write the starting destination address in the DADDR register */ + + putreg32(llhead->dest, dmach->base + SAM3U_DMACHAN_DADDR_OFFSET); + + /* Set up the CTRLA register */ + + putreg32(llhead->ctrla, dmach->base + SAM3U_DMACHAN_CTRLA_OFFSET); + + /* Set up the CTRLB register */ + + putreg32(llhead->ctrlb, dmach->base + SAM3U_DMACHAN_CTRLA_OFFSET); + + /* Both the DST and SRC DSCR bits should be '1' in CTRLB */ + + DEBUGASSERT((llhead->ctrlb & DMACHAN_CTRLB_BOTHDSCR) == DMACHAN_CTRLB_BOTHDSCR); + + /* Set up the CFG register */ + #error missing logic + /* Enable the channel by writing a ‘1’ to the CHER enable bit */ + #error missing logic + + /* Make sure that bit 0 of the EN register is enabled */ + +5. Source and destination request single and chunk DMAC transactions to transfer the + buffer of data (assuming non-memory peripherals). The DMAC acknowledges at the + completion of every transaction (chunk and single) in the buffer and carry out the buffer + transfer. +6. Once the transfer completes, hardware sets the interrupts and disables the channel. At + this time you can either respond to the buffer Complete or Transfer Complete interrupts, + or poll for the Channel Handler Status Register (DMAC_CHSR.ENABLE[n]) bit until it is + cleared by hardware, to detect when the transfer is complete. +} + + +/**************************************************************************** + * Name: sam3u_multiple + * + * Description: + * Start a multiple buffer DMA. + * + ****************************************************************************/ + +static inline int sam3u_multiple(struct sam3u_dma_s *dmach) +{ +Transfer Type AUTO SRC_REP DST_REP SRC_DSCR DST_DSCR BTSIZE SADDR DADDR Other + 1) Single Buffer or Last 0 – – 1 1 USR USR USR USR +buffer of a multiple buffer +transfer + 4) Multi Buffer transfer 0 – – 0 0 LLI LLI LLI LLI +with LLI support + + struct dma_linklist_s *llhead = dmach->llhead; + + DEBUGASSERT(llhead != NULL && llhead->src != 0); + + /* Check the first and last CTRLB values */ + + DEBUGASSERT((llhead->ctrlb & DMACHAN_CTRLB_BOTHDSCR) == 0); + DEBUGASSERT((lltail->ctrlb & DMACHAN_CTRLB_BOTHDSCR) == DMACHAN_CTRLB_BOTHDSCR); + + /* Write the channel configuration information into the CFG register */ + +#error missing logic + + /* Clear any pending interrupts from any previous DMAC transfer by reading the + * status register + */ + + (void)getreg32(SAM3U_DMAC_EBCISR); + + /* Set up the initial CTRLB register (to enable descriptors) */ + + putreg32(llhead->ctrlb, dmach->base + SAM3U_DMACHAN_CTRLA_OFFSET); + +/* Program the DMAC_CTRLBx, DMAC_CFGx registers according to Row 4 as shown in + Table 40-1 on page 1060. */ + #error missing logic + + /* Program the DSCR register with the pointer to the firstlink list entry. */ + + putreg32((uint32_t)dmach->llhead, dmac->base + SAM3U_DMACHAN_DSCR_OFFSET); + + #error missing logic + +/* Finally, enable the channel by writing a ‘1’ to the CHER enable */ + #error missing logic + +14. The DMAC fetches the first LLI from the location pointed to by DMAC_DSCRx(0). + Note: The LLI.DMAC_SADDRx, LLI. DMAC_DADDRx, LLI.DMAC_DSCRx, LLI.DMAC_CTRLAx and + LLI.DMAC_CTRLBx registers are fetched. The DMAC automatically reprograms the + DMAC_SADDRx, DMAC_DADDRx, DMAC_DSCRx, DMAC_CTRLBx and DMAC_CTRLAx channel + registers from the DMAC_DSCRx(0). + +15. Source and destination request single and chunk DMAC transactions to transfer the + buffer of data (assuming non-memory peripheral). The DMAC acknowledges at the + completion of every transaction (chunk and single) in the buffer and carry out the buffer + transfer. + +16. Once the buffer of data is transferred, the DMAC_CTRLAx register is written out to system + memory at the same location and on the same layer where it was originally + fetched, that is, the location of the DMAC_CTRLAx register of the linked list item + fetched prior to the start of the buffer transfer. Only DMAC_CTRLAx register is written + out because only the DMAC_CTRLAx.BTSIZE and DMAC_CTRLAX.DONE bits have + been updated by DMAC hardware. Additionally, the DMAC_CTRLAx.DONE bit is + asserted when the buffer transfer has completed. + Note: Do not poll the DMAC_CTRLAx.DONE bit in the DMAC memory map. Instead, poll the + LLI.DMAC_CTRLAx.DONE bit in the LLI for that buffer. If the poll LLI.DMAC_CTRLAx.DONE bit is + asserted, then this buffer transfer has completed. This LLI.DMAC_CTRLAx.DONE bit was cleared + at the start of the transfer. +17. The DMAC does not wait for the buffer interrupt to be cleared, but continues fetching + the next LLI from the memory location pointed to by current DMAC_DSCRx register + and automatically reprograms the DMAC_SADDRx, DMAC_DADDRx, DMAC_DSCRx, + DMAC_CTRLAx and DMAC_CTRLBx channel registers. The DMAC transfer continues + until the DMAC determines that the DMAC_CTRLBx and DMAC_DSCRx registers at + the end of a buffer transfer match described in Row 1 of Table 40-1 on page 1060. The + DMAC then knows that the previous buffer transferred was the last buffer in the DMAC + transfer. The DMAC transfer might look like that shown in Figure 40-5 on page 1064 + +} + +/**************************************************************************** * Name: sam3u_dmainterrupt * * Description: * DMA interrupt handler * - ************************************************************************************/ + ****************************************************************************/ static int sam3u_dmainterrupt(int irq, void *context) { @@ -502,7 +1060,7 @@ static int sam3u_dmainterrupt(int irq, void *context) { /* Subtract the number of bytes transferred so far */ - dmach->remaining -= dmach->bufsize; + dmach->remaining -= dmach->bufsize ???????; /* Is the transfer finished? */ @@ -517,11 +1075,15 @@ static int sam3u_dmainterrupt(int irq, void *context) putreg32(DMAC_CHDR_DIS(dmach->chan), SAM3U_DMAC_CHDR); + /* Free the linklist */ + + sam3u_freelinklist(dmach); + /* Perform the DMA complete callback */ if (dmach->callback) { - dmach->callback(dmach->arg); + dmach->callback(dmach->arg, dmach->result); } } else @@ -575,7 +1137,12 @@ void weak_function up_dmainitialize(void) /* Enable the DMA controller */ - putreg32(DMAC_EN_ENABLE, SAM3U_DMAC_EN); + putreg32(DMAC_EN_ENABLE, SAM3U_DMAC_EN); + + /* Initialize semaphores */ + + sem_init(g_chsem, 0, 1); + sem_init(g_chsem, 0, CONFIG_SAM3U_NDMACHAN); } /**************************************************************************** @@ -614,7 +1181,7 @@ DMA_HANDLE sam3u_dmachannel(uint16_t dmach_flags) */ dmach = NULL; - sam3u_dmatake(); + sam3u_takechsem(); for (chndx = 0; chndx < CONFIG_SAM3U_NDMACHAN; chndx++) { struct sam3u_dma_s *candidate = &g_dma[chndx]; @@ -625,16 +1192,21 @@ DMA_HANDLE sam3u_dmachannel(uint16_t dmach_flags) dmach = candidate; dmach->inuse = true; - /* Read the status register to clear any pending interrupts on the channel */ + /* Read the status register to clear any pending interrupts on the + * channel + */ (void)getreg32(SAM3U_DMAC_EBCISR); - /* Disable the channel by writing one to the write-only channel disable register */ + /* Disable the channel by writing one to the write-only channel + * disable register + */ putreg32(DMAC_CHDR_DIS(chndx), SAM3U_DMAC_CHDR); - /* See the DMA channel flags, retaining the fifo size and flow control - * settings which are inherent properties of the FIFO and cannot be changed. + /* See the DMA channel flags, retaining the fifo size and flow + * control settings which are inherent properties of the FIFO + * and cannot be changed. */ dmach->flags &= (DMACH_FLAG_FLOWCONTROL | DMACH_FLAG_FIFOSIZE_MASK); @@ -646,7 +1218,7 @@ DMA_HANDLE sam3u_dmachannel(uint16_t dmach_flags) break; } } - sam3u_dmagive(); + sam3u_givechsem(); return (DMA_HANDLE)dmach; } @@ -680,30 +1252,113 @@ void sam3u_dmafree(DMA_HANDLE handle) * Name: sam3u_dmatxsetup * * Description: - * Configure DMA for transmit (memory to peripheral) before using + * Configure DMA for transmit of one buffer (memory to peripheral). This + * function may be called multiple times to handle large and/or dis- + * continuous transfers. Calls to sam3u_dmatxsetup() and sam3u_dmatxsetup() + * must not be intermixed on the same transfer, however. * ****************************************************************************/ -void sam3u_dmarxsetup(DMA_HANDLE handle, uint32_t paddr, uint32_t maddr, size_t nbytes) +int sam3u_dmarxsetup(DMA_HANDLE handle, uint32_t paddr, uint32_t maddr, size_t nbytes) { struct sam3u_dma_s *dmach = (struct sam3u_dma_s *)handle; uint32_t regval; -# warning "Missing logic" + int ret = OK; + + DEBUGASSERT(dmach && dmach->llhead != NULL && dmach->lltail != 0); + DEBUGASSERT(dmach->remaining == 0); + + /* If this is a large transfer, break it up into smaller buffers */ + + while (nbytes > DMACHAN_CTRLA_BTSIZE_MAX) + { + /* Set up the maximum size transfer */ + + ret = sam3u_txbuffer(dmach, paddr, maddr, DMACHAN_CTRLA_BTSIZE_MAX); + if (ret == OK); + { + /* Decrement the number of bytes left to transfer */ + + nbytes -= DMACHAN_CTRLA_BTSIZE_MAX; + + /* Increment the memory & peripheral address (if it is appropriate to + * do do). + */ + + if (dmach->flags & DMACH_FLAG_PERIPHINCREMENT) != 0) + { + paddr += DMACHAN_CTRLA_BTSIZE_MAX; + } + + if (dmach->flags & DMACH_FLAG_MEMINCREMENT) != 0) + { + maddr += DMACHAN_CTRLA_BTSIZE_MAX; + } + } + } + + /* Then set up the final buffer transfer */ + + if (ret == OK && nbytes > 0) + { + ret = sam3u_txbuffer(dmach, paddr, maddr, nbytes); + } } /**************************************************************************** * Name: sam3u_dmarxsetup * * Description: - * Configure DMA for receuve (peripheral to memory) before using + * Configure DMA for receipt of one buffer (peripheral to memory). This + * function may be called multiple times to handle large and/or dis- + * continuous transfers. Calls to sam3u_dmatxsetup() and sam3u_dmatxsetup() + * must not be intermixed on the same transfer, however. * ****************************************************************************/ -void sam3u_dmarxsetup(DMA_HANDLE handle, uint32_t paddr, uint32_t maddr, size_t nbytes) +int sam3u_dmarxsetup(DMA_HANDLE handle, uint32_t paddr, uint32_t maddr, size_t nbytes) { struct sam3u_dma_s *dmach = (struct sam3u_dma_s *)handle; uint32_t regval; -# warning "Missing logic" + int ret = OK; + + DEBUGASSERT(dmach && dmach->llhead != NULL && dmach->lltail != 0); + + /* If this is a large transfer, break it up into smaller buffers */ + + while (nbytes > DMACHAN_CTRLA_BTSIZE_MAX) + { + /* Set up the maximum size transfer */ + + ret = sam3u_rxbuffer(dmach, paddr, maddr, DMACHAN_CTRLA_BTSIZE_MAX); + if (ret == OK); + { + /* Decrement the number of bytes left to transfer */ + + nbytes -= DMACHAN_CTRLA_BTSIZE_MAX; + + /* Increment the memory & peripheral address (if it is appropriate to + * do do). + */ + + if (dmach->flags & DMACH_FLAG_PERIPHINCREMENT) != 0) + { + paddr += DMACHAN_CTRLA_BTSIZE_MAX; + } + + if (dmach->flags & DMACH_FLAG_MEMINCREMENT) != 0) + { + maddr += DMACHAN_CTRLA_BTSIZE_MAX; + } + } + } + + /* Then set up the final buffer transfer */ + + if (ret == OK && nbytes > 0) + { + ret = sam3u_rxbuffer(dmach, paddr, maddr, nbytes); + } } /**************************************************************************** @@ -712,23 +1367,37 @@ void sam3u_dmarxsetup(DMA_HANDLE handle, uint32_t paddr, uint32_t maddr, size_t * Description: * Start the DMA transfer * - * Assumptions: - * - DMA handle allocated by sam3u_dmachannel() - * - No DMA in progress - * ****************************************************************************/ -void sam3u_dmastart(DMA_HANDLE handle, dma_callback_t callback, void *arg, bool half) +int sam3u_dmastart(DMA_HANDLE handle, dma_callback_t callback, void *arg) { struct sam3u_dma_s *dmach = (struct sam3u_dma_s *)handle; + int ret = -EINVAL; + + /* Verify that the DMA has been setup (i.e., at least one entry in the + * link list). + */ - DEBUGASSERT(handle != NULL); + DEBUGASSERT(dmach != NULL); + if (dmach->llhead) + { + /* Save the callback info. This will be invoked whent the DMA commpletes */ + + dmach->callback = callback; + dmach->arg = arg; - /* Save the callback info. This will be invoked whent the DMA commpletes */ + /* Is this a single block transfer? Or a multiple block tranfer? */ - dmach->callback = callback; - dmach->arg = arg; -# warning "Missing logic" + if (dmach->llhead == dmach->lltail) + { + ret = sam3u_single(dmach); + } + else + { + ret = sam3u_multiple(dmach); + } + } + return ret; } /**************************************************************************** @@ -797,7 +1466,7 @@ void sam3u_dmasample(DMA_HANDLE handle, struct sam3u_dmaregs_s *regs) regs->cfg = getreg32(dmach->base + SAM3U_DMACHAN_CFG_OFFSET); irqrestore(flags); } -#endif +#endif /* CONFIG_DEBUG_DMA */ /**************************************************************************** * Name: sam3u_dmadump @@ -834,5 +1503,5 @@ void sam3u_dmadump(DMA_HANDLE handle, const struct sam3u_dmaregs_s *regs, dmadbg(" CTRLB[%08x]: %08x\n", dmach->base + SAM3U_DMACHAN_CTRLB_OFFSET, regs->ctrlb); dmadbg(" CFG[%08x]: %08x\n", dmach->base + SAM3U_DMACHAN_CFG_OFFSET, regs->cfg); } -#endif - +#endif /* CONFIG_DEBUG_DMA */ +#endif /* CONFIG_SAM3U_DMA */ \ No newline at end of file -- cgit v1.2.3