Something went wrong on our end
-
patacongo authored
git-svn-id: svn://svn.code.sf.net/p/nuttx/code/trunk@4142 42af7a65-404d-4744-a932-0658087f49c3
patacongo authoredgit-svn-id: svn://svn.code.sf.net/p/nuttx/code/trunk@4142 42af7a65-404d-4744-a932-0658087f49c3
stm32f40xxx_dma.c 20.15 KiB
/****************************************************************************
* arch/arm/src/stm32/stm32f40xxx_dma.c
*
* Copyright (C) 2011 Gregory Nutt. All rights reserved.
* Author: Gregory Nutt <gnutt@nuttx.org>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* 3. Neither the name NuttX nor the names of its contributors may be
* used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <stdint.h>
#include <stdbool.h>
#include <semaphore.h>
#include <debug.h>
#include <errno.h>
#include <nuttx/irq.h>
#include <nuttx/arch.h>
#include <arch/irq.h>
#include "up_arch.h"
#include "up_internal.h"
#include "os_internal.h"
#include "chip.h"
#include "stm32_dma.h"
#include "stm32_internal.h"
/* This file supports only the STM32 F4 family (an probably the F2 family
* as well?)
*/
#if defined(CONFIG_STM32_STM32F40XX)
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
#define DMA1_NSTREAMS 8
#if STM32_NDMA > 1
# define DMA2_NSTREAMS 8
# define DMA_NSTREAMS (DMA1_NSTREAMS+DMA2_NSTREAMS)
#else
# define DMA_NSTREAMS DMA1_NSTREAMS
#endif
#ifndef CONFIG_DMA_PRI
# define CONFIG_DMA_PRI NVIC_SYSH_PRIORITY_DEFAULT
#endif
/* Convert the DMA stream base address to the DMA register block address */
#define DMA_BASE(ch) (ch & 0xfffffc00)
/****************************************************************************
* Private Types
****************************************************************************/
/* This structure descibes one DMA channel */
struct stm32_dma_s
{
uint8_t stream; /* DMA stream number (0-7) */
uint8_t irq; /* DMA stream IRQ number */
uint8_t shift; /* ISR/IFCR bit shift value */
uint8_t pad; /* Unused */
sem_t sem; /* Used to wait for DMA channel to become available */
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 */
};
/****************************************************************************
* Private Data
****************************************************************************/
/* This array describes the state of each DMA */
static struct stm32_dma_s g_dma[DMA_NSTREAMS] =
{
{
.stream = 0,
.irq = STM32_IRQ_DMA1S0,
.shift = DMA_INT_STREAM0_SHIFT,
.base = STM32_DMA1_BASE + STM32_DMA_OFFSET(0),
},
{
.stream = 1,
.irq = STM32_IRQ_DMA1S1,
.shift = DMA_INT_STREAM1_SHIFT,
.base = STM32_DMA1_BASE + STM32_DMA_OFFSET(1),
},
{
.stream = 2,
.irq = STM32_IRQ_DMA1S2,
.shift = DMA_INT_STREAM2_SHIFT,
.base = STM32_DMA1_BASE + STM32_DMA_OFFSET(2),
},
{
.stream = 3,
.irq = STM32_IRQ_DMA1S3,
.shift = DMA_INT_STREAM3_SHIFT,
.base = STM32_DMA1_BASE + STM32_DMA_OFFSET(4),
},
{
.stream = 4,
.irq = STM32_IRQ_DMA1S4,
.shift = DMA_INT_STREAM4_SHIFT,
.base = STM32_DMA1_BASE + STM32_DMA_OFFSET(4),
},
{
.stream = 5,
.irq = STM32_IRQ_DMA1S5,
.shift = DMA_INT_STREAM5_SHIFT,
.base = STM32_DMA1_BASE + STM32_DMA_OFFSET(5),
},
{
.stream = 6,
.irq = STM32_IRQ_DMA1S6,
.shift = DMA_INT_STREAM6_SHIFT,
.base = STM32_DMA1_BASE + STM32_DMA_OFFSET(6),
},
{
.stream = 7,
.irq = STM32_IRQ_DMA1S7,
.shift = DMA_INT_STREAM7_SHIFT,
.base = STM32_DMA1_BASE + STM32_DMA_OFFSET(7),
},
#if STM32_NDMA > 1
{
.stream = 0,
.irq = STM32_IRQ_DMA2S0,
.shift = DMA_INT_STREAM0_SHIFT,
.base = STM32_DMA2_BASE + STM32_DMA_OFFSET(0),
},
{
.stream = 1,
.irq = STM32_IRQ_DMA2S1,
.shift = DMA_INT_STREAM1_SHIFT,
.base = STM32_DMA2_BASE + STM32_DMA_OFFSET(1),
},
{
.stream = 2,
.irq = STM32_IRQ_DMA2S2,
.shift = DMA_INT_STREAM2_SHIFT,
.base = STM32_DMA2_BASE + STM32_DMA_OFFSET(2),
},
{
.stream = 3,
.irq = STM32_IRQ_DMA2S3,
.shift = DMA_INT_STREAM3_SHIFT,
.base = STM32_DMA2_BASE + STM32_DMA_OFFSET(3),
},
{
.stream = 4,
.irq = STM32_IRQ_DMA2S4,
.base = STM32_DMA2_BASE + STM32_DMA_OFFSET(4),
},
{
.stream = 5,
.irq = STM32_IRQ_DMA2S5,
.shift = DMA_INT_STREAM5_SHIFT,
.base = STM32_DMA2_BASE + STM32_DMA_OFFSET(5),
},
{
.stream = 6,
.irq = STM32_IRQ_DMA2S6,
.shift = DMA_INT_STREAM6_SHIFT,
.base = STM32_DMA2_BASE + STM32_DMA_OFFSET(6),
},
{
.stream = 7,
.irq = STM32_IRQ_DMA2S7,
.shift = DMA_INT_STREAM7_SHIFT,
.base = STM32_DMA2_BASE + STM32_DMA_OFFSET(7),
},
#endif
};
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* DMA register access functions
****************************************************************************/
/* Get non-channel register from DMA1 or DMA2 */
static inline uint32_t dmabase_getreg(struct stm32_dma_s *dmast, uint32_t offset)
{
return getreg32(DMA_BASE(dmast->base) + offset);
}
/* Write to non-channel register in DMA1 or DMA2 */
static inline void dmabase_putreg(struct stm32_dma_s *dmast, uint32_t offset, uint32_t value)
{
putreg32(value, DMA_BASE(dmast->base) + offset);
}
/* Get channel register from DMA1 or DMA2 */
static inline uint32_t dmast_getreg(struct stm32_dma_s *dmast, uint32_t offset)
{
return getreg32(dmast->base + offset);
}
/* Write to channel register in DMA1 or DMA2 */
static inline void dmast_putreg(struct stm32_dma_s *dmast, uint32_t offset, uint32_t value)
{
putreg32(value, dmast->base + offset);
}
/************************************************************************************
* Name: stm32_dmatake() and stm32_dmagive()
*
* Description:
* Used to get exclusive access to a DMA channel.
*
************************************************************************************/
static void stm32_dmatake(FAR struct stm32_dma_s *dmast)
{
/* Take the semaphore (perhaps waiting) */
while (sem_wait(&dmast->sem) != 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 stm32_dmagive(FAR struct stm32_dma_s *dmast)
{
(void)sem_post(&dmast->sem);
}
/************************************************************************************
* Name: stm32_dmastreamdisable
*
* Description:
* Disable the DMA stream
*
************************************************************************************/
static void stm32_dmastreamdisable(struct stm32_dma_s *dmast)
{
uint32_t regoffset;
uint32_t regval;
/* Disable all interrupts at the DMA controller */
regval = dmast_getreg(dmast, STM32_DMA_SCR_OFFSET);
regval &= ~DMA_SCR_ALLINTS;
/* Disable the DMA stream */
regval &= ~DMA_SCR_EN;
dmast_putreg(dmast, STM32_DMA_SCR_OFFSET, regval);
/* Clear pending stream interrupts by setting bits in the upper or lower IFCR
* register
*/
if (dmast->stream < 4)
{
regoffset = STM32_DMA_LIFCR_OFFSET;
}
else
{
regoffset = STM32_DMA_HIFCR_OFFSET;
}
dmabase_putreg(dmast, regoffset, (DMA_STREAM_MASK << dmast->shift));
}
/************************************************************************************
* Name: stm32_dmainterrupt
*
* Description:
* DMA interrupt handler
*
************************************************************************************/
static int stm32_dmainterrupt(int irq, void *context)
{
struct stm32_dma_s *dmast;
uint32_t isr;
uint32_t regoffset = 0;
int stndx = 0;
/* Get the stream structure from the interrupt number */
if (irq >= STM32_IRQ_DMA1S0 && irq <= STM32_IRQ_DMA1S7)
{
stndx = irq - STM32_IRQ_DMA1S0;
regoffset = STM32_DMA_LISR_OFFSET;
}
else
#if STM32_NDMA > 1
if (irq >= STM32_IRQ_DMA2S0 && irq <= STM32_IRQ_DMA2S7)
{
stndx = irq - STM32_IRQ_DMA2S0 + DMA1_NSTREAMS;
regoffset = STM32_DMA_HISR_OFFSET;
}
else
#endif
{
PANIC(OSERR_INTERNAL);
}
dmast = &g_dma[stndx];
/* Get the interrupt status for this stream */
isr = (dmabase_getreg(dmast, regoffset) >> dmast->shift) & DMA_STREAM_MASK;
/* Disable the DMA stream */
stm32_dmastreamdisable(dmast);
/* Invoke the callback */
if (dmast->callback)
{
dmast->callback(dmast, isr, dmast->arg);
}
return OK;
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: stm32_dmainitialize
*
* Description:
* Initialize the DMA subsystem
*
* Returned Value:
* None
*
****************************************************************************/
void weak_function up_dmainitialize(void)
{
struct stm32_dma_s *dmast;
int stndx;
/* Initialize each DMA stream */
for (stndx = 0; stndx < DMA_NSTREAMS; stndx++)
{
dmast = &g_dma[stndx];
sem_init(&dmast->sem, 0, 1);
/* Attach DMA interrupt vectors */
(void)irq_attach(dmast->irq, stm32_dmainterrupt);
/* Disable the DMA stream */
stm32_dmastreamdisable(dmast);
/* Enable the IRQ at the NVIC (still disabled at the DMA controller) */
up_enable_irq(dmast->irq);
/* Set the interrrupt priority */
up_prioritize_irq(dmast->irq, CONFIG_DMA_PRI);
}
}
/****************************************************************************
* Name: stm32_dmachannel
*
* Description:
* Allocate a DMA channel. This function gives the caller mutually
* exclusive access to the DMA channel specified by the 'stndx' argument.
* DMA channels are shared on the STM32: Devices sharing the same DMA
* channel cannot do DMA concurrently! See the DMACHAN_* definitions in
* stm32_dma.h.
*
* If the DMA channel is not available, then stm32_dmachannel() will wait
* until the holder of the channel relinquishes the channel by calling
* stm32_dmafree(). WARNING: If you have two devices sharing a DMA
* channel and the code never releases the channel, the stm32_dmachannel
* call for the other will hang forever in this function! Don't let your
* design do that!
*
* Hmm.. I suppose this interface could be extended to make a non-blocking
* version. Feel free to do that if that is what you need.
*
* Returned Value:
* Provided that 'stndx' is valid, this function ALWAYS returns a non-NULL,
* void* DMA channel handle. (If 'stndx' is invalid, the function will
* assert if debug is enabled or do something ignorant otherwise).
*
* Assumptions:
* - The caller does not hold he DMA channel.
* - The caller can wait for the DMA channel to be freed if it is no
* available.
*
****************************************************************************/
DMA_HANDLE stm32_dmachannel(int stndx)
{
struct stm32_dma_s *dmast = &g_dma[stndx];
DEBUGASSERT(stndx < DMA_NSTREAMS);
/* Get exclusive access to the DMA channel -- OR wait until the channel
* is available if it is currently being used by another driver
*/
stm32_dmatake(dmast);
/* The caller now has exclusive use of the DMA channel */
return (DMA_HANDLE)dmast;
}
/****************************************************************************
* Name: stm32_dmafree
*
* Description:
* Release a DMA channel. If another thread is waiting for this DMA channel
* in a call to stm32_dmachannel, then this function will re-assign the
* DMA channel to that thread and wake it up. NOTE: The 'handle' used
* in this argument must NEVER be used again until stm32_dmachannel() is
* called again to re-gain access to the channel.
*
* Returned Value:
* None
*
* Assumptions:
* - The caller holds the DMA channel.
* - There is no DMA in progress
*
****************************************************************************/
void stm32_dmafree(DMA_HANDLE handle)
{
struct stm32_dma_s *dmast = (struct stm32_dma_s *)handle;
DEBUGASSERT(handle != NULL);
/* Release the channel */
stm32_dmagive(dmast);
}
/****************************************************************************
* Name: stm32_dmasetup
*
* Description:
* Configure DMA before using
*
****************************************************************************/
void stm32_dmasetup(DMA_HANDLE handle, uint32_t paddr, uint32_t maddr, size_t ntransfers, uint32_t scr)
{
struct stm32_dma_s *dmast = (struct stm32_dma_s *)handle;
uint32_t regval;
/* Set the peripheral register address in the DMA_SPARx register. The data
* will be moved from/to this address to/from the memory after the
* peripheral event.
*/
dmast_putreg(dmast, STM32_DMA_SPAR_OFFSET, paddr);
/* Set the memory address in the DMA_SM0ARx register. The data will be
* written to or read from this memory after the peripheral event.
* Note that only single-buffer mode is currently supported so SM1ARx
* is not used.
*/
dmast_putreg(dmast, STM32_DMA_SM0AR_OFFSET, maddr);
/* Configure the total number of data items to be transferred in the DMA_SNDTRx
* register. After each peripheral event, this value will be decremented.
*/
dmast_putreg(dmast, STM32_DMA_SNDTR_OFFSET, ntransfers);
/* Configure the stream priority using the PL[1:0] bits in the DMA_SCRx
* register. Configure data transfer direction, circular mode, peripheral & memory
* incremented mode, peripheral & memory data size, and interrupt after
* half and/or full transfer in the DMA_CCRx register.
*/
regval = dmast_getreg(dmast, STM32_DMA_SCR_OFFSET);
regval &= ~(DMA_SCR_DIR_MASK|DMA_SCR_PINC|DMA_SCR_MINC|DMA_SCR_PSIZE_MASK|
DMA_SCR_MSIZE_MASK|DMA_SCR_PINCOS|DMA_SCR_PL_MASK|DMA_SCR_PBURST_MASK|
DMA_SCR_MBURST_MASK);
scr &= (DMA_SCR_DIR_MASK|DMA_SCR_PINC|DMA_SCR_MINC|DMA_SCR_PSIZE_MASK|
DMA_SCR_MSIZE_MASK|DMA_SCR_PINCOS|DMA_SCR_PL_MASK|DMA_SCR_PBURST_MASK|
DMA_SCR_MBURST_MASK);
regval |= scr;
dmast_putreg(dmast, STM32_DMA_SCR_OFFSET, regval);
}
/****************************************************************************
* Name: stm32_dmastart
*
* Description:
* Start the DMA transfer
*
* Assumptions:
* - DMA handle allocated by stm32_dmachannel()
* - No DMA in progress
*
****************************************************************************/
void stm32_dmastart(DMA_HANDLE handle, dma_callback_t callback, void *arg, bool half)
{
struct stm32_dma_s *dmast = (struct stm32_dma_s *)handle;
uint32_t scr;
DEBUGASSERT(handle != NULL);
/* Save the callback info. This will be invoked whent the DMA commpletes */
dmast->callback = callback;
dmast->arg = arg;
/* Activate the stream by setting the ENABLE bit in the DMA_SCRx register.
* As soon as the stream is enabled, it can serve any DMA request from the
* peripheral connected on the stream.
*/
scr = dmast_getreg(dmast, STM32_DMA_SCR_OFFSET);
scr |= DMA_SCR_EN;
/* Once half of the bytes are transferred, the half-transfer flag (HTIF) is
* set and an interrupt is generated if the Half-Transfer Interrupt Enable
* bit (HTIE) is set. At the end of the transfer, the Transfer Complete Flag
* (TCIF) is set and an interrupt is generated if the Transfer Complete
* Interrupt Enable bit (TCIE) is set.
*/
scr |= (half ? (DMA_SCR_HTIE|DMA_SCR_TEIE) : (DMA_SCR_TCIE|DMA_SCR_TEIE));
dmast_putreg(dmast, STM32_DMA_SCR_OFFSET, scr);
}
/****************************************************************************
* Name: stm32_dmastop
*
* Description:
* Cancel the DMA. After stm32_dmastop() is called, the DMA channel is
* reset and stm32_dmasetup() must be called before stm32_dmastart() can be
* called again
*
* Assumptions:
* - DMA handle allocated by stm32_dmachannel()
*
****************************************************************************/
void stm32_dmastop(DMA_HANDLE handle)
{
struct stm32_dma_s *dmast = (struct stm32_dma_s *)handle;
stm32_dmastreamdisable(dmast);
}
/****************************************************************************
* Name: stm32_dmasample
*
* Description:
* Sample DMA register contents
*
* Assumptions:
* - DMA handle allocated by stm32_dmachannel()
*
****************************************************************************/
#ifdef CONFIG_DEBUG_DMA
void stm32_dmasample(DMA_HANDLE handle, struct stm32_dmaregs_s *regs)
{
struct stm32_dma_s *dmast = (struct stm32_dma_s *)handle;
irqstate_t flags;
flags = irqsave();
regs->lisr = dmabase_getreg(dmast, STM32_DMA_LISR_OFFSET);
regs->hisr = dmabase_getreg(dmast, STM32_DMA_LISR_OFFSET);
regs->scr = dmast_getreg(dmast, STM32_DMA_HISR_OFFSET);
regs->sndtr = dmast_getreg(dmast, STM32_DMA_SNDTR_OFFSET);
regs->spar = dmast_getreg(dmast, STM32_DMA_SPAR_OFFSET);
regs->sm0ar = dmast_getreg(dmast, STM32_DMA_SM0AR_OFFSET);
regs->sm1ar = dmast_getreg(dmast, STM32_DMA_SM1AR_OFFSET);
regs->sfcr = dmast_getreg(dmast, STM32_DMA_SFCR_OFFSET);
irqrestore(flags);
}
#endif
/****************************************************************************
* Name: stm32_dmadump
*
* Description:
* Dump previously sampled DMA register contents
*
* Assumptions:
* - DMA handle allocated by stm32_dmachannel()
*
****************************************************************************/
#ifdef CONFIG_DEBUG_DMA
void stm32_dmadump(DMA_HANDLE handle, const struct stm32_dmaregs_s *regs,
const char *msg)
{
struct stm32_dma_s *dmast = (struct stm32_dma_s *)handle;
uint32_t dmabase = DMA_BASE(dmast->base);
dmadbg("DMA Registers: %s\n", msg);
dmadbg(" LISR[%08x]: %08x\n", dmabase + STM32_DMA_LISR_OFFSET, regs->lisr);
dmadbg(" HISR[%08x]: %08x\n", dmabase + STM32_DMA_HISR_OFFSET, regs->hisr);
dmadbg(" SCR[%08x]: %08x\n", dmast->base + STM32_DMA_SCR_OFFSET, regs->scr);
dmadbg(" SNDTR[%08x]: %08x\n", dmast->base + STM32_DMA_SNDTR_OFFSET, regs->sndtr);
dmadbg(" SPAR[%08x]: %08x\n", dmast->base + STM32_DMA_SPAR_OFFSET, regs->spar);
dmadbg(" SM0AR[%08x]: %08x\n", dmast->base + STM32_DMA_SM0AR_OFFSET, regs->sm0ar);
dmadbg(" SM1AR[%08x]: %08x\n", dmast->base + STM32_DMA_SM1AR_OFFSET, regs->sm1ar);
dmadbg(" SFCRF[%08x]: %08x\n", dmast->base + STM32_DMA_SFCR_OFFSET, regs->sfcr);
}
#endif
#endif /* CONFIG_STM32_STM32F10XX */