972a260391
Signed-off-by: Xiang Xiao <xiaoxiang@xiaomi.com>
1089 lines
33 KiB
C
1089 lines
33 KiB
C
/****************************************************************************
|
|
* arch/arm/src/stm32f7/stm32_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 <inttypes.h>
|
|
#include <stdint.h>
|
|
#include <stdbool.h>
|
|
#include <assert.h>
|
|
#include <debug.h>
|
|
#include <errno.h>
|
|
|
|
#include <nuttx/irq.h>
|
|
#include <nuttx/arch.h>
|
|
#include <nuttx/semaphore.h>
|
|
#include <arch/stm32f7/chip.h>
|
|
|
|
#include "arm_internal.h"
|
|
#include "sched/sched.h"
|
|
#include "stm32_dma.h"
|
|
|
|
/* Content of this file requires verification before it is used with other
|
|
* families
|
|
*/
|
|
|
|
#if defined(CONFIG_STM32F7_STM32F72XX) || defined(CONFIG_STM32F7_STM33F75XX) \
|
|
|| defined(CONFIG_STM32F7_STM32F74XX) || defined(CONFIG_STM32F7_STM32F75XX) \
|
|
|| defined(CONFIG_STM32F7_STM32F76XX) || defined(CONFIG_STM32F7_STM32F77XX)
|
|
|
|
/****************************************************************************
|
|
* Pre-processor Definitions
|
|
****************************************************************************/
|
|
|
|
#define DMA1_NSTREAMS 8
|
|
#if STM32F7_NDMA > 1
|
|
# define DMA2_NSTREAMS 8
|
|
# define DMA_NSTREAMS (DMA1_NSTREAMS+DMA2_NSTREAMS)
|
|
#else
|
|
# define DMA_NSTREAMS DMA1_NSTREAMS
|
|
#endif
|
|
|
|
/* Convert the DMA stream base address to the DMA register block address */
|
|
|
|
#define DMA_BASE(ch) ((ch) & 0xfffffc00)
|
|
|
|
/****************************************************************************
|
|
* Private Types
|
|
****************************************************************************/
|
|
|
|
/* This structure describes 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 channel; /* DMA channel number (0-7) */
|
|
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(3),
|
|
},
|
|
{
|
|
.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 STM32F7_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 int stm32_dmatake(struct stm32_dma_s *dmast)
|
|
{
|
|
return nxsem_wait_uninterruptible(&dmast->sem);
|
|
}
|
|
|
|
static inline void stm32_dmagive(struct stm32_dma_s *dmast)
|
|
{
|
|
nxsem_post(&dmast->sem);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: stm32_dmastream
|
|
*
|
|
* Description:
|
|
* Get the g_dma table entry associated with a DMA controller and a stream
|
|
* number
|
|
*
|
|
****************************************************************************/
|
|
|
|
static inline struct stm32_dma_s *stm32_dmastream(unsigned int stream,
|
|
unsigned int controller)
|
|
{
|
|
int index;
|
|
|
|
DEBUGASSERT(stream < DMA_NSTREAMS && controller < STM32F7_NDMA);
|
|
|
|
/* Convert the controller + stream based on the fact that there are
|
|
* 8 streams per controller.
|
|
*/
|
|
|
|
#if STM32F7_NDMA > 1
|
|
index = controller << 3 | stream;
|
|
#else
|
|
index = stream;
|
|
#endif
|
|
|
|
/* Then return the stream structure associated with the stream index */
|
|
|
|
return &g_dma[index];
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: stm32_dmamap
|
|
*
|
|
* Description:
|
|
* Get the g_dma table entry associated with a bit-encoded DMA selection
|
|
*
|
|
****************************************************************************/
|
|
|
|
static inline struct stm32_dma_s *stm32_dmamap(unsigned long dmamap)
|
|
{
|
|
/* Extract the DMA controller number from the bit encoded value */
|
|
|
|
unsigned int controller = STM32_DMA_CONTROLLER(dmamap);
|
|
|
|
/* Extract the stream number from the bit encoded value */
|
|
|
|
unsigned int stream = STM32_DMA_STREAM(dmamap);
|
|
|
|
/* Return the table entry associated with the controller + stream */
|
|
|
|
return stm32_dmastream(stream, controller);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* 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, void *arg)
|
|
{
|
|
struct stm32_dma_s *dmast;
|
|
uint32_t status;
|
|
uint32_t regoffset = 0;
|
|
unsigned int stream = 0;
|
|
unsigned int controller = 0;
|
|
|
|
/* Get the stream and the controller that generated the interrupt */
|
|
|
|
if (irq >= STM32_IRQ_DMA1S0 && irq <= STM32_IRQ_DMA1S6)
|
|
{
|
|
stream = irq - STM32_IRQ_DMA1S0;
|
|
controller = DMA1;
|
|
}
|
|
else if (irq == STM32_IRQ_DMA1S7)
|
|
{
|
|
stream = 7;
|
|
controller = DMA1;
|
|
}
|
|
else
|
|
#if STM32F7_NDMA > 1
|
|
if (irq >= STM32_IRQ_DMA2S0 && irq <= STM32_IRQ_DMA2S4)
|
|
{
|
|
stream = irq - STM32_IRQ_DMA2S0;
|
|
controller = DMA2;
|
|
}
|
|
else if (irq >= STM32_IRQ_DMA2S5 && irq <= STM32_IRQ_DMA2S7)
|
|
{
|
|
stream = irq - STM32_IRQ_DMA2S5 + 5;
|
|
controller = DMA2;
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
DEBUGPANIC();
|
|
}
|
|
|
|
/* Get the stream structure from the stream and controller numbers */
|
|
|
|
dmast = stm32_dmastream(stream, controller);
|
|
|
|
/* Select the interrupt status register (either the LISR or HISR)
|
|
* based on the stream number that caused the interrupt.
|
|
*/
|
|
|
|
if (stream < 4)
|
|
{
|
|
regoffset = STM32_DMA_LISR_OFFSET;
|
|
}
|
|
else
|
|
{
|
|
regoffset = STM32_DMA_HISR_OFFSET;
|
|
}
|
|
|
|
/* Get the interrupt status for this stream */
|
|
|
|
status = (dmabase_getreg(dmast, regoffset) >> dmast->shift) &
|
|
DMA_STREAM_MASK;
|
|
|
|
/* Clear fetched stream interrupts by setting bits in the upper or lower
|
|
* IFCR register
|
|
*/
|
|
|
|
if (stream < 4)
|
|
{
|
|
regoffset = STM32_DMA_LIFCR_OFFSET;
|
|
}
|
|
else
|
|
{
|
|
regoffset = STM32_DMA_HIFCR_OFFSET;
|
|
}
|
|
|
|
dmabase_putreg(dmast, regoffset, (status << dmast->shift));
|
|
|
|
/* Invoke the callback */
|
|
|
|
if (dmast->callback)
|
|
{
|
|
dmast->callback(dmast, status, dmast->arg);
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Public Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: stm32_dmainitialize
|
|
*
|
|
* Description:
|
|
* Initialize the DMA subsystem
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
****************************************************************************/
|
|
|
|
void weak_function arm_dma_initialize(void)
|
|
{
|
|
struct stm32_dma_s *dmast;
|
|
int stream;
|
|
|
|
/* Initialize each DMA stream */
|
|
|
|
for (stream = 0; stream < DMA_NSTREAMS; stream++)
|
|
{
|
|
dmast = &g_dma[stream];
|
|
nxsem_init(&dmast->sem, 0, 1);
|
|
|
|
/* Attach DMA interrupt vectors */
|
|
|
|
irq_attach(dmast->irq, stm32_dmainterrupt, dmast);
|
|
|
|
/* Disable the DMA stream */
|
|
|
|
stm32_dmastreamdisable(dmast);
|
|
|
|
/* Enable the IRQ at the NVIC (still disabled at the DMA controller) */
|
|
|
|
up_enable_irq(dmast->irq);
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: stm32_dmachannel
|
|
*
|
|
* Description:
|
|
* Allocate a DMA channel. This function gives the caller mutually
|
|
* exclusive access to the DMA channel specified by the 'dmamap' 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.
|
|
*
|
|
* Input Parameters:
|
|
* dmamap - Identifies the stream/channel resource. For the STM32 F7, this
|
|
* is a bit-encoded value as provided by the DMAMAP_* definitions
|
|
* in chip/stm32f7xxxxxxx_dma.h
|
|
*
|
|
* Returned Value:
|
|
* Provided that 'dmamap' is valid, this function ALWAYS returns a non-NULL
|
|
* void* DMA channel handle. (If 'dmamap' 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(unsigned int dmamap)
|
|
{
|
|
struct stm32_dma_s *dmast;
|
|
int ret;
|
|
|
|
/* Get the stream index from the bit-encoded channel value */
|
|
|
|
dmast = stm32_dmamap(dmamap);
|
|
DEBUGASSERT(dmast != NULL);
|
|
|
|
/* Get exclusive access to the DMA channel -- OR wait until the channel
|
|
* is available if it is currently being used by another driver
|
|
*/
|
|
|
|
ret = stm32_dmatake(dmast);
|
|
if (ret < 0)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
/* The caller now has exclusive use of the DMA channel. Assign the
|
|
* channel to the stream and return an opaque reference to the stream
|
|
* structure.
|
|
*/
|
|
|
|
dmast->channel = STM32_DMA_CHANNEL(dmamap);
|
|
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 regoffset;
|
|
uint32_t regval;
|
|
|
|
dmainfo("paddr: %08" PRIx32 " maddr: %08" PRIx32
|
|
" ntransfers: %zu scr: %08" PRIx32 "\n",
|
|
paddr, maddr, ntransfers, scr);
|
|
|
|
#ifdef CONFIG_STM32F7_DMACAPABLE
|
|
DEBUGASSERT(stm32_dmacapable(maddr, ntransfers, scr));
|
|
#endif
|
|
|
|
/* "If the stream is enabled, disable it by resetting the EN bit in the
|
|
* DMA_SxCR register, then read this bit in order to confirm that there
|
|
* is no ongoing stream operation. Writing this bit to 0 is not immediately
|
|
* effective since it is actually written to 0 once all the current
|
|
* transfers have finished. When the EN bit is read as 0, this means that
|
|
* the stream is ready to be configured. It is therefore necessary to wait
|
|
* for the EN bit to be cleared before starting any stream
|
|
* configuration..."
|
|
*/
|
|
|
|
while ((dmast_getreg(dmast, STM32_DMA_SCR_OFFSET) & DMA_SCR_EN) != 0);
|
|
|
|
/* "... All the stream dedicated bits set in the status register (DMA_LISR
|
|
* and DMA_HISR) from the previous data block DMA transfer should be
|
|
* cleared before the stream can be re-enabled."
|
|
*
|
|
* 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));
|
|
|
|
/* "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 in double-buffered mode it is explicitly assumed that the
|
|
* second buffer immediately follows the first.
|
|
*/
|
|
|
|
dmast_putreg(dmast, STM32_DMA_SM0AR_OFFSET, maddr);
|
|
if (scr & DMA_SCR_DBM)
|
|
{
|
|
dmast_putreg(dmast, STM32_DMA_SM1AR_OFFSET, maddr + ntransfers);
|
|
}
|
|
|
|
/* "Configure the total number of data items to be transferred in the
|
|
* DMA_SNDTRx register. After each peripheral event, this value will be
|
|
* decremented."
|
|
*
|
|
* "When the peripheral flow controller is used for a given stream, the
|
|
* value written into the DMA_SxNDTR has no effect on the DMA transfer.
|
|
* Actually, whatever the value written, it will be forced by hardware
|
|
* to 0xFFFF as soon as the stream is enabled..."
|
|
*/
|
|
|
|
dmast_putreg(dmast, STM32_DMA_SNDTR_OFFSET, ntransfers);
|
|
|
|
/* "Select the DMA channel (request) using CHSEL[2:0] in the DMA_SxCR
|
|
* register."
|
|
*
|
|
* "Configure the stream priority using the PL[1:0] bits in the DMA_SCRx"
|
|
* register."
|
|
*/
|
|
|
|
regval = dmast_getreg(dmast, STM32_DMA_SCR_OFFSET);
|
|
regval &= ~(DMA_SCR_PL_MASK | DMA_SCR_CHSEL_MASK);
|
|
regval |= scr & DMA_SCR_PL_MASK;
|
|
regval |= (uint32_t)dmast->channel << DMA_SCR_CHSEL_SHIFT;
|
|
dmast_putreg(dmast, STM32_DMA_SCR_OFFSET, regval);
|
|
|
|
/* "Configure the FIFO usage (enable or disable, threshold in transmission
|
|
* and reception)"
|
|
*
|
|
* "Caution is required when choosing the FIFO threshold (bits FTH[1:0]
|
|
* of the DMA_SxFCR register) and the size of the memory burst
|
|
* (MBURST[1:0] of the DMA_SxCR register): The content pointed by the FIFO
|
|
* threshold must exactly match to an integer number of memory burst
|
|
* transfers. If this is not in the case, a FIFO error (flag FEIFx of the
|
|
* DMA_HISR or DMA_LISR register) will be generated when the stream is
|
|
* enabled, then the stream will be automatically disabled."
|
|
*
|
|
* The FIFO is disabled in circular mode when transferring data from a
|
|
* peripheral to memory, as in this case it is usually desirable to know
|
|
* that every byte from the peripheral is transferred immediately to
|
|
* memory. It is not practical to flush the DMA FIFO, as this requires
|
|
* disabling the channel which triggers the transfer-complete interrupt.
|
|
*
|
|
* NOTE: The FEIFx error interrupt is not enabled because the FEIFx seems
|
|
* to be reported spuriously causing good transfers to be marked as
|
|
* failures.
|
|
*/
|
|
|
|
regval = dmast_getreg(dmast, STM32_DMA_SFCR_OFFSET);
|
|
regval &= ~(DMA_SFCR_FTH_MASK | DMA_SFCR_FS_MASK | DMA_SFCR_FEIE);
|
|
if (!((scr & (DMA_SCR_CIRC | DMA_SCR_DIR_MASK)) ==
|
|
(DMA_SCR_CIRC | DMA_SCR_DIR_P2M)))
|
|
{
|
|
regval |= (DMA_SFCR_FTH_FULL | DMA_SFCR_DMDIS);
|
|
}
|
|
|
|
dmast_putreg(dmast, STM32_DMA_SFCR_OFFSET, regval);
|
|
|
|
/* "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."
|
|
*
|
|
* Note: The CT bit is always reset.
|
|
*/
|
|
|
|
regval = dmast_getreg(dmast, STM32_DMA_SCR_OFFSET);
|
|
regval &= ~(DMA_SCR_PFCTRL | DMA_SCR_DIR_MASK | DMA_SCR_PINC |
|
|
DMA_SCR_MINC | DMA_SCR_PSIZE_MASK | DMA_SCR_MSIZE_MASK |
|
|
DMA_SCR_PINCOS | DMA_SCR_CIRC | DMA_SCR_DBM | DMA_SCR_CT |
|
|
DMA_SCR_PBURST_MASK | DMA_SCR_MBURST_MASK);
|
|
scr &= (DMA_SCR_PFCTRL | DMA_SCR_DIR_MASK | DMA_SCR_PINC |
|
|
DMA_SCR_MINC | DMA_SCR_PSIZE_MASK | DMA_SCR_MSIZE_MASK |
|
|
DMA_SCR_PINCOS | DMA_SCR_DBM | DMA_SCR_CIRC |
|
|
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 when the DMA completes */
|
|
|
|
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;
|
|
|
|
/* In normal mode, interrupt at either half or full completion. In circular
|
|
* and double-buffered modes, always interrupt on buffer wrap, and
|
|
* optionally interrupt at the halfway point.
|
|
*/
|
|
|
|
if ((scr & (DMA_SCR_DBM | DMA_SCR_CIRC)) == 0)
|
|
{
|
|
/* 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));
|
|
}
|
|
else
|
|
{
|
|
/* In non-stop modes, when the transfer completes it immediately resets
|
|
* and starts again. The transfer-complete interrupt is thus always
|
|
* enabled, and the half-complete interrupt can be used in circular
|
|
* mode to determine when the buffer is half-full or in double-buffered
|
|
* mode to determine when one of the two buffers is full.
|
|
*/
|
|
|
|
scr |= (half ? DMA_SCR_HTIE : 0) | 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_dmaresidual
|
|
*
|
|
* Description:
|
|
* Read the DMA bytes-remaining register.
|
|
*
|
|
* Assumptions:
|
|
* - DMA handle allocated by stm32_dmachannel()
|
|
*
|
|
****************************************************************************/
|
|
|
|
size_t stm32_dmaresidual(DMA_HANDLE handle)
|
|
{
|
|
struct stm32_dma_s *dmast = (struct stm32_dma_s *)handle;
|
|
uint32_t residual;
|
|
|
|
/* Fetch the count of bytes remaining to be transferred.
|
|
*
|
|
* If the FIFO is enabled, this count may be inaccurate. ST don't
|
|
* appear to document whether this counts the peripheral or the memory
|
|
* side of the channel, and they don't make the memory pointer
|
|
* available either.
|
|
*
|
|
* For reception in circular mode the FIFO is disabled in order that
|
|
* this value can be useful.
|
|
*/
|
|
|
|
residual = dmast_getreg(dmast, STM32_DMA_SNDTR_OFFSET);
|
|
|
|
return (size_t)residual;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: stm32_dmacapable
|
|
*
|
|
* Description:
|
|
* Check if the DMA controller can transfer data to/from given memory
|
|
* address. This depends on the internal connections in the ARM bus matrix
|
|
* of the processor. Note that this only applies to memory addresses, it
|
|
* will return false for any peripheral address.
|
|
*
|
|
* Input Parameters:
|
|
*
|
|
* maddr - starting memory address
|
|
* count - number of unit8 or uint16 or uint32 items as defined by MSIZE of
|
|
* ccr.
|
|
* ccr - DMA stream configuration register
|
|
*
|
|
* Returned Value:
|
|
* True, if transfer is possible.
|
|
*
|
|
****************************************************************************/
|
|
|
|
#ifdef CONFIG_STM32F7_DMACAPABLE
|
|
bool stm32_dmacapable(uintptr_t maddr, uint32_t count, uint32_t ccr)
|
|
{
|
|
uint32_t transfer_size;
|
|
uint32_t burst_length;
|
|
uint32_t mend;
|
|
|
|
dmainfo("stm32_dmacapable: 0x%08" PRIxPTR
|
|
"/%" PRIu32 " 0x%08" PRIx32 "\n", maddr, count, ccr);
|
|
|
|
/* Verify that the address conforms to the memory transfer size.
|
|
* Transfers to/from memory performed by the DMA controller are
|
|
* required to be aligned to their size.
|
|
*
|
|
* See ST RM0410 DocID028270 Rev 2, section 8.3.11 Single and burst
|
|
* transfers
|
|
*
|
|
* Compute mend inline to avoid a possible non-constant integer
|
|
* multiply.
|
|
*/
|
|
|
|
switch (ccr & DMA_SCR_MSIZE_MASK)
|
|
{
|
|
case DMA_SCR_MSIZE_8BITS:
|
|
transfer_size = 1;
|
|
mend = maddr + count - 1;
|
|
break;
|
|
|
|
case DMA_SCR_MSIZE_16BITS:
|
|
transfer_size = 2;
|
|
mend = maddr + (count << 1) - 1;
|
|
break;
|
|
|
|
case DMA_SCR_MSIZE_32BITS:
|
|
transfer_size = 4;
|
|
mend = maddr + (count << 2) - 1;
|
|
break;
|
|
|
|
default:
|
|
dmainfo("stm32_dmacapable: bad transfer size in CCR\n");
|
|
return false;
|
|
}
|
|
|
|
if ((maddr & (transfer_size - 1)) != 0)
|
|
{
|
|
dmainfo("stm32_dmacapable: transfer unaligned\n");
|
|
return false;
|
|
}
|
|
|
|
# if defined(CONFIG_ARMV7M_DCACHE) && !defined(CONFIG_ARMV7M_DCACHE_WRITETHROUGH)
|
|
/* buffer alignment is required for DMA transfers with dcache in buffered
|
|
* mode (not write-through) because a) up_invalidate_dcache could lose
|
|
* buffered writes and b) up_flush_dcache could corrupt adjacent memory if
|
|
* the maddr and the mend+1, the next next address are not on
|
|
* ARMV7M_DCACHE_LINESIZE boundaries.
|
|
*/
|
|
|
|
if ((maddr & (ARMV7M_DCACHE_LINESIZE - 1)) != 0 ||
|
|
((mend + 1) & (ARMV7M_DCACHE_LINESIZE - 1)) != 0)
|
|
{
|
|
dmawarn("stm32_dmacapable:"
|
|
" dcache unaligned maddr:0x%08" PRIxPTR " mend:0x%08"
|
|
PRIx32 "\n", maddr, mend);
|
|
#if !defined(CONFIG_STM32F7_DMACAPABLE_ASSUME_CACHE_ALIGNED)
|
|
return false;
|
|
#endif
|
|
}
|
|
# endif
|
|
|
|
/* Verify that burst transfers do not cross a 1KiB boundary. */
|
|
|
|
if ((maddr / 1024) != (mend / 1024))
|
|
{
|
|
/* The transfer as a whole crosses a 1KiB boundary.
|
|
* Verify that no burst does by asserting that the address
|
|
* is aligned to the burst length.
|
|
*/
|
|
|
|
switch (ccr & DMA_SCR_MBURST_MASK)
|
|
{
|
|
case DMA_SCR_MBURST_SINGLE:
|
|
burst_length = transfer_size;
|
|
break;
|
|
|
|
case DMA_SCR_MBURST_INCR4:
|
|
burst_length = transfer_size << 2;
|
|
break;
|
|
|
|
case DMA_SCR_MBURST_INCR8:
|
|
burst_length = transfer_size << 3;
|
|
break;
|
|
|
|
case DMA_SCR_MBURST_INCR16:
|
|
burst_length = transfer_size << 4;
|
|
break;
|
|
|
|
default:
|
|
dmainfo("stm32_dmacapable: bad burst size in CCR\n");
|
|
return false;
|
|
}
|
|
|
|
if ((maddr & (burst_length - 1)) != 0)
|
|
{
|
|
dmainfo("stm32_dmacapable: burst crosses 1KiB\n");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/* Verify that the transfer is to a memory region that supports DMA. */
|
|
|
|
if ((maddr & STM32_REGION_MASK) != (mend & STM32_REGION_MASK))
|
|
{
|
|
dmainfo("stm32_dmacapable: transfer crosses memory region\n");
|
|
return false;
|
|
}
|
|
|
|
switch (maddr & STM32_REGION_MASK)
|
|
{
|
|
case STM32_FMC_BANK1:
|
|
case STM32_FMC_BANK2:
|
|
case STM32_FMC_BANK3:
|
|
case STM32_FMC_BANK4:
|
|
case STM32_SRAM_BASE:
|
|
|
|
/* All RAM is supported */
|
|
|
|
break;
|
|
|
|
case STM32_CODE_BASE:
|
|
|
|
/* Everything except the ITCM ram is supported per the manual
|
|
* The ITCM bus is not accessible on AHBS. So the DMA data transfer
|
|
* to/from ITCM RAM is not supported.
|
|
*/
|
|
|
|
if (maddr >= STM32_INSTRAM_BASE &&
|
|
(maddr - STM32_INSTRAM_BASE) < 0x3fff)
|
|
{
|
|
dmainfo("stm32_dmacapable: transfer targets ITCM RAM\n");
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
|
|
/* Everything else is unsupported by DMA */
|
|
|
|
dmainfo("stm32_dmacapable: transfer targets unknown/unsupported"
|
|
" region\n");
|
|
|
|
return false;
|
|
}
|
|
|
|
dmainfo("stm32_dmacapable: transfer OK\n");
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Name: stm32_dmasample
|
|
*
|
|
* Description:
|
|
* Sample DMA register contents
|
|
*
|
|
* Assumptions:
|
|
* - DMA handle allocated by stm32_dmachannel()
|
|
*
|
|
****************************************************************************/
|
|
|
|
#ifdef CONFIG_DEBUG_DMA_INFO
|
|
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 = enter_critical_section();
|
|
regs->lisr = dmabase_getreg(dmast, STM32_DMA_LISR_OFFSET);
|
|
regs->hisr = dmabase_getreg(dmast, STM32_DMA_HISR_OFFSET);
|
|
regs->scr = dmast_getreg(dmast, STM32_DMA_SCR_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);
|
|
leave_critical_section(flags);
|
|
}
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Name: stm32_dmadump
|
|
*
|
|
* Description:
|
|
* Dump previously sampled DMA register contents
|
|
*
|
|
* Assumptions:
|
|
* - DMA handle allocated by stm32_dmachannel()
|
|
*
|
|
****************************************************************************/
|
|
|
|
#ifdef CONFIG_DEBUG_DMA_INFO
|
|
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);
|
|
|
|
dmainfo("DMA Registers: %s\n", msg);
|
|
dmainfo(" LISR[%08x]: %08x\n",
|
|
dmabase + STM32_DMA_LISR_OFFSET, regs->lisr);
|
|
dmainfo(" HISR[%08x]: %08x\n",
|
|
dmabase + STM32_DMA_HISR_OFFSET, regs->hisr);
|
|
dmainfo(" SCR[%08x]: %08x\n",
|
|
dmast->base + STM32_DMA_SCR_OFFSET, regs->scr);
|
|
dmainfo(" SNDTR[%08x]: %08x\n",
|
|
dmast->base + STM32_DMA_SNDTR_OFFSET, regs->sndtr);
|
|
dmainfo(" SPAR[%08x]: %08x\n",
|
|
dmast->base + STM32_DMA_SPAR_OFFSET, regs->spar);
|
|
dmainfo(" SM0AR[%08x]: %08x\n",
|
|
dmast->base + STM32_DMA_SM0AR_OFFSET, regs->sm0ar);
|
|
dmainfo(" SM1AR[%08x]: %08x\n",
|
|
dmast->base + STM32_DMA_SM1AR_OFFSET, regs->sm1ar);
|
|
dmainfo(" SFCR[%08x]: %08x\n",
|
|
dmast->base + STM32_DMA_SFCR_OFFSET, regs->sfcr);
|
|
}
|
|
#endif
|
|
|
|
#endif /* CONFIG_STM32F7_STM32F74XX || CONFIG_STM32F7_STM32F75XX */
|