mirror of
https://github.com/apache/nuttx.git
synced 2026-08-01 20:28:58 +00:00
stm32h7/dac: add DMA stream mode with ioctl-driven double-buffering
Add DMA support for DAC output with configurable double-buffering via ioctl interface: - ANIOC_DAC_DMABUFF_INIT: Copy full buffer into DMA buffer (memcpy) - ANIOC_DAC_DMA_START: Start DMA with optional half-transfer interrupts - ANIOC_DAC_DMA_STOP: Stop DMA and timer - ANIOC_DAC_DMA_GET_EVENT: Wait for half-transfer complete event - ANIOC_DAC_DMA_WRITE_HBUF: Write half-buffer into DMA buffer - ANIOC_DAC_INFO: Query DAC capabilities (resolution, DMA, buffer size) Stream mode: when halfint=1, both HTIF and TCIF generate events via a ring buffer and semaphore. User writes the completed half while DMA fills the other half. TCIF indicates h=1, HTIF h=0. DMA priority is configurable per-channel via Kconfig choice (Low/Medium/High/VeryHigh), defaulting to Medium. Signed-off-by: Andrey Sobol <andrey.sobol.nn@gmail.com>
This commit is contained in:
parent
85041e1699
commit
9bf825fd4f
4 changed files with 267 additions and 53 deletions
|
|
@ -101,6 +101,26 @@ config STM32_DAC1CH1_DMA_EXTERNAL
|
|||
bool "DAC1CH1 DMA External Trigger"
|
||||
default n
|
||||
|
||||
choice
|
||||
prompt "DAC1CH1 DMA priority"
|
||||
default STM32_DAC1CH1_DMA_PRIORITY_MEDIUM
|
||||
---help---
|
||||
DMA stream priority for DAC1 channel 1.
|
||||
|
||||
config STM32_DAC1CH1_DMA_PRIORITY_LOW
|
||||
bool "Low"
|
||||
|
||||
config STM32_DAC1CH1_DMA_PRIORITY_MEDIUM
|
||||
bool "Medium"
|
||||
|
||||
config STM32_DAC1CH1_DMA_PRIORITY_HIGH
|
||||
bool "High"
|
||||
|
||||
config STM32_DAC1CH1_DMA_PRIORITY_VERYHIGH
|
||||
bool "Very High"
|
||||
|
||||
endchoice
|
||||
|
||||
if STM32_HRTIM_DAC
|
||||
|
||||
config STM32_DAC1CH1_HRTIM_TRG1
|
||||
|
|
@ -163,6 +183,26 @@ config STM32_DAC1CH2_DMA_EXTERNAL
|
|||
bool "DAC1CH2 DMA External Trigger"
|
||||
default n
|
||||
|
||||
choice
|
||||
prompt "DAC1CH2 DMA priority"
|
||||
default STM32_DAC1CH2_DMA_PRIORITY_MEDIUM
|
||||
---help---
|
||||
DMA stream priority for DAC1 channel 2.
|
||||
|
||||
config STM32_DAC1CH2_DMA_PRIORITY_LOW
|
||||
bool "Low"
|
||||
|
||||
config STM32_DAC1CH2_DMA_PRIORITY_MEDIUM
|
||||
bool "Medium"
|
||||
|
||||
config STM32_DAC1CH2_DMA_PRIORITY_HIGH
|
||||
bool "High"
|
||||
|
||||
config STM32_DAC1CH2_DMA_PRIORITY_VERYHIGH
|
||||
bool "Very High"
|
||||
|
||||
endchoice
|
||||
|
||||
if STM32_HRTIM_DAC
|
||||
|
||||
config STM32_DAC1CH2_HRTIM_TRG1
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@
|
|||
#include <sys/types.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <debug.h>
|
||||
|
|
@ -125,6 +126,32 @@
|
|||
# define HAVE_DMA 1
|
||||
#endif
|
||||
|
||||
/* DMA priority macros from per-channel Kconfig choices */
|
||||
|
||||
#ifdef CONFIG_STM32_DAC1CH1_DMA
|
||||
# if defined(CONFIG_STM32_DAC1CH1_DMA_PRIORITY_LOW)
|
||||
# define DAC1CH1_DMA_PRI 0
|
||||
# elif defined(CONFIG_STM32_DAC1CH1_DMA_PRIORITY_MEDIUM)
|
||||
# define DAC1CH1_DMA_PRI 1
|
||||
# elif defined(CONFIG_STM32_DAC1CH1_DMA_PRIORITY_HIGH)
|
||||
# define DAC1CH1_DMA_PRI 2
|
||||
# else
|
||||
# define DAC1CH1_DMA_PRI 3
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_STM32_DAC1CH2_DMA
|
||||
# if defined(CONFIG_STM32_DAC1CH2_DMA_PRIORITY_LOW)
|
||||
# define DAC1CH2_DMA_PRI 0
|
||||
# elif defined(CONFIG_STM32_DAC1CH2_DMA_PRIORITY_MEDIUM)
|
||||
# define DAC1CH2_DMA_PRI 1
|
||||
# elif defined(CONFIG_STM32_DAC1CH2_DMA_PRIORITY_HIGH)
|
||||
# define DAC1CH2_DMA_PRI 2
|
||||
# else
|
||||
# define DAC1CH2_DMA_PRI 3
|
||||
# endif
|
||||
#endif
|
||||
|
||||
/* Timer trigger selection for DAC1 channels.
|
||||
* On STM32H7, TSEL[3:0] encoding (from stm32h7xx_ll_dac.h):
|
||||
* 0001: TIM1_TRGO (DAC_CR_TSEL1_0)
|
||||
|
|
@ -346,17 +373,23 @@ struct stm32_chan_s
|
|||
uint32_t dro; /* Data output register address */
|
||||
uint32_t tsel; /* CR trigger select value */
|
||||
#ifdef HAVE_DMA
|
||||
uint8_t hasdma : 1; /* True, this channel supports DMA */
|
||||
uint8_t dma_active : 1; /* True, DMA transfer is running */
|
||||
uint8_t timer; /* Timer number for DMA trigger */
|
||||
uint16_t dmachan; /* DMA channel */
|
||||
uint16_t buffer_len; /* DMA buffer length */
|
||||
DMA_HANDLE dma; /* Allocated DMA channel */
|
||||
uint32_t tbase; /* Timer base address */
|
||||
uint32_t tfrequency; /* Desired timer frequency */
|
||||
int result; /* DMA result */
|
||||
uint16_t buffer_pos; /* Position in dmabuffer */
|
||||
uint16_t *dmabuffer; /* DMA transfer buffer */
|
||||
uint8_t hasdma : 1; /* True, this channel supports DMA */
|
||||
uint8_t dma_active : 1; /* True, DMA transfer is running */
|
||||
uint8_t halfint : 1; /* True, HT interrupt enabled */
|
||||
uint8_t dma_priority : 2; /* DMA PL field (DMA_SCR_PRI*) */
|
||||
uint8_t timer; /* Timer number for DMA trigger */
|
||||
uint16_t dmachan; /* DMA channel */
|
||||
uint16_t buffer_len; /* DMA buffer length */
|
||||
DMA_HANDLE dma; /* Allocated DMA channel */
|
||||
uint32_t tbase; /* Timer base address */
|
||||
uint32_t tfrequency; /* Desired timer frequency */
|
||||
int result; /* DMA result */
|
||||
uint16_t buffer_pos; /* Position in dmabuffer */
|
||||
uint16_t *dmabuffer; /* DMA transfer buffer */
|
||||
sem_t dma_halfsem; /* Stream half-completion sem */
|
||||
uint8_t dma_half_q[16]; /* Ring of completed half indices */
|
||||
uint8_t dma_half_q_head;
|
||||
uint8_t dma_half_q_tail;
|
||||
#endif
|
||||
};
|
||||
|
||||
|
|
@ -385,7 +418,7 @@ static int stm32_dac_ioctl(struct dac_dev_s *dev, int cmd,
|
|||
#ifdef HAVE_DMA
|
||||
static int stm32_dac_timinit(struct stm32_chan_s *chan);
|
||||
static void stm32_dac_timstart(struct stm32_chan_s *chan);
|
||||
static void stm32_dac_dma_start(struct stm32_chan_s *chan);
|
||||
static void stm32_dac_dma_start(struct stm32_chan_s *chan, bool halfint);
|
||||
static void stm32_dac_dma_stop(struct stm32_chan_s *chan);
|
||||
static int stm32_dac_dmainit(struct stm32_chan_s *chan);
|
||||
#endif
|
||||
|
|
@ -459,6 +492,7 @@ static struct stm32_chan_s g_dac1ch1priv =
|
|||
.timer = CONFIG_STM32_DAC1CH1_TIMER,
|
||||
.tbase = DAC1_CH1_TIMER_BASE,
|
||||
.tfrequency = CONFIG_STM32_DAC1CH1_TIMER_FREQUENCY,
|
||||
.dma_priority = DAC1CH1_DMA_PRI,
|
||||
#endif
|
||||
};
|
||||
|
||||
|
|
@ -497,6 +531,7 @@ static struct stm32_chan_s g_dac1ch2priv =
|
|||
.timer = CONFIG_STM32_DAC1CH2_TIMER,
|
||||
.tbase = DAC1_CH2_TIMER_BASE,
|
||||
.tfrequency = CONFIG_STM32_DAC1CH2_TIMER_FREQUENCY,
|
||||
.dma_priority = DAC1CH2_DMA_PRI,
|
||||
#endif
|
||||
};
|
||||
|
||||
|
|
@ -629,6 +664,10 @@ static int stm32_dac_setup(struct dac_dev_s *dev)
|
|||
|
||||
#ifdef HAVE_DMA
|
||||
chan->buffer_pos = 0;
|
||||
chan->halfint = 0;
|
||||
nxsem_init(&chan->dma_halfsem, 0, 0);
|
||||
chan->dma_half_q_head = 0;
|
||||
chan->dma_half_q_tail = 0;
|
||||
#endif
|
||||
|
||||
return OK;
|
||||
|
|
@ -644,6 +683,7 @@ static void stm32_dac_shutdown(struct dac_dev_s *dev)
|
|||
|
||||
#ifdef HAVE_DMA
|
||||
stm32_dac_dma_stop(chan);
|
||||
nxsem_destroy(&chan->dma_halfsem);
|
||||
#endif
|
||||
|
||||
stm32_dac_modify_cr(chan, DAC_CR_EN(chan->ch), 0);
|
||||
|
|
@ -667,28 +707,37 @@ static void stm32_dac_dmatxcallback(DMA_HANDLE handle, uint8_t isr,
|
|||
void *arg)
|
||||
{
|
||||
struct stm32_chan_s *chan = (struct stm32_chan_s *)arg;
|
||||
struct dac_dev_s *dev;
|
||||
|
||||
DEBUGASSERT(chan);
|
||||
|
||||
#ifdef CONFIG_STM32_DAC1CH1
|
||||
if (chan->ch == 1)
|
||||
dev = &g_dac1ch1dev;
|
||||
else
|
||||
#endif
|
||||
#ifdef CONFIG_STM32_DAC1CH2
|
||||
if (chan->ch == 2)
|
||||
dev = &g_dac1ch2dev;
|
||||
else
|
||||
#endif
|
||||
DEBUGPANIC();
|
||||
|
||||
DEBUGASSERT(dev->ad_priv == chan);
|
||||
|
||||
if (chan->result == -EBUSY)
|
||||
{
|
||||
chan->result = (isr & DMA_STREAM_TEIF_BIT) ? -EIO : OK;
|
||||
dac_txdone(dev);
|
||||
if (isr & DMA_STREAM_TEIF_BIT)
|
||||
{
|
||||
chan->result = -EIO;
|
||||
}
|
||||
else if (chan->halfint)
|
||||
{
|
||||
uint8_t h;
|
||||
|
||||
if (isr & DMA_STREAM_HTIF_BIT)
|
||||
{
|
||||
h = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
h = 1;
|
||||
}
|
||||
|
||||
chan->dma_half_q[chan->dma_half_q_head] = h;
|
||||
chan->dma_half_q_head =
|
||||
(chan->dma_half_q_head + 1) & 15;
|
||||
nxsem_post(&chan->dma_halfsem);
|
||||
}
|
||||
else
|
||||
{
|
||||
chan->result = OK;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
@ -701,24 +750,9 @@ static int stm32_dac_send(struct dac_dev_s *dev, struct dac_msg_s *msg)
|
|||
{
|
||||
struct stm32_chan_s *chan = dev->ad_priv;
|
||||
|
||||
#ifdef HAVE_DMA
|
||||
if (chan->hasdma)
|
||||
{
|
||||
chan->dmabuffer[chan->buffer_pos++] = (uint16_t)msg->am_data;
|
||||
dac_txdone(dev);
|
||||
|
||||
if (chan->buffer_pos >= chan->buffer_len && !chan->dma_active)
|
||||
{
|
||||
stm32_dac_dma_start(chan);
|
||||
}
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
stm32_dac_modify_cr(chan, 0, DAC_CR_EN(chan->ch));
|
||||
putreg16(msg->am_data, chan->dro);
|
||||
dac_txdone(dev);
|
||||
}
|
||||
stm32_dac_modify_cr(chan, 0, DAC_CR_EN(chan->ch));
|
||||
putreg16(msg->am_data, chan->dro);
|
||||
dac_txdone(dev);
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
|
@ -729,7 +763,105 @@ static int stm32_dac_send(struct dac_dev_s *dev, struct dac_msg_s *msg)
|
|||
|
||||
static int stm32_dac_ioctl(struct dac_dev_s *dev, int cmd, unsigned long arg)
|
||||
{
|
||||
return -ENOTTY;
|
||||
int ret = -ENOTTY;
|
||||
struct stm32_chan_s *chan = dev->ad_priv;
|
||||
|
||||
switch (cmd)
|
||||
{
|
||||
#ifdef HAVE_DMA
|
||||
case ANIOC_DAC_DMABUFF_INIT:
|
||||
{
|
||||
uint16_t *buffer = (uint16_t *)arg;
|
||||
|
||||
/* The caller is responsible for providing buffer with
|
||||
* suitable length equal to CONFIG_STM32_DACxCHy_DMA_BUFFER_SIZE
|
||||
*/
|
||||
|
||||
uint32_t len = chan->buffer_len * sizeof(uint16_t);
|
||||
memcpy(chan->dmabuffer, buffer, len);
|
||||
#ifdef CONFIG_ARMV7M_DCACHE
|
||||
up_clean_dcache((uintptr_t)chan->dmabuffer,
|
||||
(uintptr_t)chan->dmabuffer + len);
|
||||
#endif
|
||||
ret = OK;
|
||||
}
|
||||
break;
|
||||
|
||||
case ANIOC_DAC_DMA_START:
|
||||
{
|
||||
struct dac_dma_start_s *req =
|
||||
(struct dac_dma_start_s *)arg;
|
||||
|
||||
chan->halfint = req->halfint;
|
||||
stm32_dac_dma_start(chan, req->halfint);
|
||||
ret = OK;
|
||||
}
|
||||
break;
|
||||
|
||||
case ANIOC_DAC_DMA_STOP:
|
||||
stm32_dac_dma_stop(chan);
|
||||
chan->halfint = 0;
|
||||
ret = OK;
|
||||
break;
|
||||
|
||||
case ANIOC_DAC_DMA_GET_EVENT:
|
||||
{
|
||||
struct dac_dma_event_s *req =
|
||||
(struct dac_dma_event_s *)arg;
|
||||
|
||||
ret = nxsem_wait(&chan->dma_halfsem);
|
||||
if (ret == OK)
|
||||
{
|
||||
req->half =
|
||||
chan->dma_half_q[chan->dma_half_q_tail];
|
||||
chan->dma_half_q_tail =
|
||||
(chan->dma_half_q_tail + 1) & 15;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case ANIOC_DAC_DMAHBUF_WRITE:
|
||||
{
|
||||
struct dac_dma_event_s *req =
|
||||
(struct dac_dma_event_s *)arg;
|
||||
uint32_t half_len = chan->buffer_len / 2;
|
||||
uint16_t *dst = chan->dmabuffer + req->half * half_len;
|
||||
|
||||
memcpy(dst, req->buffer, half_len * sizeof(uint16_t));
|
||||
#ifdef CONFIG_ARMV7M_DCACHE
|
||||
up_clean_dcache((uintptr_t)dst,
|
||||
(uintptr_t)dst + half_len * sizeof(uint16_t));
|
||||
#endif
|
||||
ret = OK;
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
|
||||
case ANIOC_DAC_INFO:
|
||||
{
|
||||
struct dac_info_s *info = (struct dac_info_s *)arg;
|
||||
|
||||
info->sample_bits = 12;
|
||||
#ifdef HAVE_DMA
|
||||
info->dma_enabled = chan->dma_active;
|
||||
info->halfint_enabled = chan->halfint;
|
||||
info->dma_buffer_size = chan->buffer_len;
|
||||
info->dma_timer_frequency = chan->tfrequency;
|
||||
#else
|
||||
info->dma_enabled = false;
|
||||
info->halfint_enabled = 0;
|
||||
info->dma_buffer_size = 0;
|
||||
info->dma_timer_frequency = 0;
|
||||
#endif
|
||||
ret = OK;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
|
|
@ -849,7 +981,7 @@ static void stm32_dac_timstart(struct stm32_chan_s *chan)
|
|||
****************************************************************************/
|
||||
|
||||
#ifdef HAVE_DMA
|
||||
static void stm32_dac_dma_start(struct stm32_chan_s *chan)
|
||||
static void stm32_dac_dma_start(struct stm32_chan_s *chan, bool halfint)
|
||||
{
|
||||
if (chan->dma_active)
|
||||
return;
|
||||
|
|
@ -862,7 +994,7 @@ static void stm32_dac_dma_start(struct stm32_chan_s *chan)
|
|||
chan->buffer_len * sizeof(uint16_t));
|
||||
#endif
|
||||
|
||||
stm32_dmastart(chan->dma, stm32_dac_dmatxcallback, chan, false);
|
||||
stm32_dmastart(chan->dma, stm32_dac_dmatxcallback, chan, halfint);
|
||||
stm32_dac_modify_cr(chan, 0, DAC_CR_EN(chan->ch) |
|
||||
DAC_CR_TEN(chan->ch) |
|
||||
DAC_CR_DMAEN(chan->ch));
|
||||
|
|
@ -908,7 +1040,8 @@ static int stm32_dac_dmainit(struct stm32_chan_s *chan)
|
|||
dmacfg.paddr = chan->dro;
|
||||
dmacfg.maddr = (uint32_t)chan->dmabuffer;
|
||||
dmacfg.ndata = chan->buffer_len;
|
||||
dmacfg.cfg1 = DAC_DMA_CONTROL_WORD;
|
||||
dmacfg.cfg1 = DAC_DMA_CONTROL_WORD |
|
||||
(chan->dma_priority << DMA_SCR_PL_SHIFT);
|
||||
dmacfg.cfg2 = 0;
|
||||
stm32_dmasetup(chan->dma, &dmacfg);
|
||||
|
||||
|
|
@ -1064,7 +1197,7 @@ static void stm32_dac_llops_writedro(struct stm32_dac_dev_s *dev,
|
|||
static void stm32_dac_llops_startdma(struct stm32_dac_dev_s *dev)
|
||||
{
|
||||
struct stm32_chan_s *priv = (struct stm32_chan_s *)dev;
|
||||
stm32_dac_dma_start(priv);
|
||||
stm32_dac_dma_start(priv, false);
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@
|
|||
#include <nuttx/mutex.h>
|
||||
#include <nuttx/semaphore.h>
|
||||
#include <nuttx/spi/spi.h>
|
||||
#include <nuttx/analog/ioctl.h>
|
||||
|
||||
/****************************************************************************
|
||||
* Pre-processor Definitions
|
||||
|
|
@ -60,6 +61,26 @@
|
|||
* Public Types
|
||||
****************************************************************************/
|
||||
|
||||
struct dac_dma_start_s
|
||||
{
|
||||
uint8_t halfint; /* 0 = transfer complete only, 1 = half + transfer complete */
|
||||
};
|
||||
|
||||
struct dac_dma_event_s
|
||||
{
|
||||
FAR uint16_t *buffer; /* data to write (WRITE_BUF) */
|
||||
int half; /* 0 = first half, 1 = second half */
|
||||
};
|
||||
|
||||
struct dac_info_s
|
||||
{
|
||||
uint8_t sample_bits; /* DAC resolution (12 bits) */
|
||||
uint8_t dma_enabled; /* 1 if DMA is running */
|
||||
uint8_t halfint_enabled; /* current HTIT state */
|
||||
uint32_t dma_buffer_size; /* DMA buffer length in samples */
|
||||
uint32_t dma_timer_frequency; /* timer output frequency in Hz */
|
||||
};
|
||||
|
||||
begin_packed_struct struct dac_msg_s
|
||||
{
|
||||
uint8_t am_channel; /* The 8-bit DAC Channel */
|
||||
|
|
|
|||
|
|
@ -65,9 +65,29 @@
|
|||
* IN: None
|
||||
* OUT: Number of samples
|
||||
* waiting to be read */
|
||||
#define ANIOC_DAC_DMABUFF_INIT _ANIOC(0x0007) /* Copy full buffer to DAC DMA
|
||||
* IN: uint16_t * (buffer to copy)
|
||||
* Caller must provide buffer of
|
||||
* CONFIG_STM32_DACxCHy_DMA_BUFFER_SIZE
|
||||
* OUT: None */
|
||||
#define ANIOC_DAC_DMA_START _ANIOC(0x0008) /* Start DAC DMA transfer
|
||||
* IN: struct dac_dma_start_s *
|
||||
* OUT: None */
|
||||
#define ANIOC_DAC_DMA_STOP _ANIOC(0x0009) /* Stop DAC DMA transfer
|
||||
* IN: None
|
||||
* OUT: None */
|
||||
#define ANIOC_DAC_DMA_GET_EVENT _ANIOC(0x000a) /* Wait for half-transfer DMA event
|
||||
* IN: None
|
||||
* OUT: struct dac_dma_event_s * */
|
||||
#define ANIOC_DAC_DMAHBUF_WRITE _ANIOC(0x000b) /* Write half-buffer to DMA
|
||||
* IN: struct dac_dma_event_s *
|
||||
* OUT: None */
|
||||
#define ANIOC_DAC_INFO _ANIOC(0x000c) /* Get DAC info
|
||||
* IN: None
|
||||
* OUT: struct dac_info_s * */
|
||||
|
||||
#define AN_FIRST 0x0001 /* First common command */
|
||||
#define AN_NCMDS 6 /* Number of common commands */
|
||||
#define AN_NCMDS 12 /* Number of common commands */
|
||||
|
||||
/* User defined ioctl commands are also supported. These will be forwarded
|
||||
* by the upper-half driver to the lower-half driver via the ioctl()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue