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 #include #include +#include #include #include #include @@ -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 #include #include +#include /**************************************************************************** * 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()