This is an automated email from the ASF dual-hosted git repository.

acassis pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/nuttx.git

commit 9bf825fd4f5d9515d67d6c66d89668080778eefb
Author: Andrey Sobol <[email protected]>
AuthorDate: Tue Jul 14 23:06:48 2026 +0300

    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 <[email protected]>
---
 arch/arm/src/common/stm32/Kconfig.dac |  40 ++++++
 arch/arm/src/stm32h7/stm32_dac.c      | 237 ++++++++++++++++++++++++++--------
 include/nuttx/analog/dac.h            |  21 +++
 include/nuttx/analog/ioctl.h          |  22 +++-
 4 files changed, 267 insertions(+), 53 deletions(-)

diff --git a/arch/arm/src/common/stm32/Kconfig.dac 
b/arch/arm/src/common/stm32/Kconfig.dac
index fecbcaf23c8..5dc6d55ea30 100644
--- a/arch/arm/src/common/stm32/Kconfig.dac
+++ b/arch/arm/src/common/stm32/Kconfig.dac
@@ -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
diff --git a/arch/arm/src/stm32h7/stm32_dac.c b/arch/arm/src/stm32h7/stm32_dac.c
index 4b4805cc1e7..2c58a93cb8f 100644
--- a/arch/arm/src/stm32h7/stm32_dac.c
+++ b/arch/arm/src/stm32h7/stm32_dac.c
@@ -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);
 }
 
 /****************************************************************************
diff --git a/include/nuttx/analog/dac.h b/include/nuttx/analog/dac.h
index 3f7cfd58744..4e3bf8531ea 100644
--- a/include/nuttx/analog/dac.h
+++ b/include/nuttx/analog/dac.h
@@ -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 */
diff --git a/include/nuttx/analog/ioctl.h b/include/nuttx/analog/ioctl.h
index 19bcaad40e2..abe46ec90ee 100644
--- a/include/nuttx/analog/ioctl.h
+++ b/include/nuttx/analog/ioctl.h
@@ -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()

Reply via email to