From 798d03cb3dae9608bdc980fc69b3d2382f13af9e Mon Sep 17 00:00:00 2001 From: Juha Niskanen Date: Mon, 9 Oct 2017 16:20:24 +0000 Subject: [PATCH] Merged in juniskane/nuttx_stm32l4/stm32_serial_patches_pr (pull request #504) Stm32, stm32l4 serial patches * stm32: serial: add interface to get uart_dev_t by USART number, stm32_serial_get_uart * stm32: serial: do not stop processing input in SW flow-control mode * stm32l4: serial: do not stop processing input in SW flow-control mode * stm32l4: serial: suspend serial for Stop mode Approved-by: Gregory Nutt --- arch/arm/src/stm32/stm32_serial.c | 54 +++++ arch/arm/src/stm32/stm32_uart.h | 11 + arch/arm/src/stm32l4/stm32l4_serial.c | 336 ++++++++++++++++++++++++-- arch/arm/src/stm32l4/stm32l4_uart.h | 20 ++ 4 files changed, 396 insertions(+), 25 deletions(-) diff --git a/arch/arm/src/stm32/stm32_serial.c b/arch/arm/src/stm32/stm32_serial.c index 10d4abec0c..348f56bd61 100644 --- a/arch/arm/src/stm32/stm32_serial.c +++ b/arch/arm/src/stm32/stm32_serial.c @@ -277,6 +277,10 @@ struct up_dev_s uint16_t ie; /* Saved interrupt mask bits value */ uint16_t sr; /* Saved status bits */ + /* Has been initialized and HW is setup. */ + + bool initialized; + /* If termios are supported, then the following fields may vary at * runtime. */ @@ -1559,6 +1563,11 @@ static int up_setup(struct uart_dev_s *dev) /* Set up the cached interrupt enables value */ priv->ie = 0; + + /* Mark device as initialized. */ + + priv->initialized = true; + return OK; } @@ -1638,6 +1647,10 @@ static void up_shutdown(struct uart_dev_s *dev) struct up_dev_s *priv = (struct up_dev_s *)dev->priv; uint32_t regval; + /* Mark device as uninitialized. */ + + priv->initialized = false; + /* Disable all interrupts */ up_disableusartint(priv, NULL); @@ -2287,6 +2300,22 @@ static bool up_rxflowcontrol(struct uart_dev_s *dev, /* Assert/de-assert nRTS set it high resume/stop sending */ stm32_gpiowrite(priv->rts_gpio, upper); + + if (upper) + { + /* With heavy Rx traffic, RXNE might be set and data pending. + * Returning 'true' in such case would cause RXNE left unhandled + * and causing interrupt storm. Sending end might be also be slow + * to react on nRTS, and returning 'true' here would prevent + * processing that data. + * + * Therefore, return 'false' so input data is still being processed + * until sending end reacts on nRTS signal and stops sending more. + */ + + return false; + } + return upper; } @@ -2645,6 +2674,31 @@ static int up_pm_prepare(struct pm_callback_s *cb, int domain, #ifdef USE_SERIALDRIVER + /**************************************************************************** + * Name: stm32_serial_get_uart + * + * Description: + * Get serial driver structure for STM32 USART + * + ****************************************************************************/ + +FAR uart_dev_t *stm32_serial_get_uart(int uart_num) +{ + int uart_idx = uart_num - 1; + + if (uart_idx < 0 || uart_idx >= STM32_NUSART || !uart_devs[uart_idx]) + { + return NULL; + } + + if (!uart_devs[uart_idx]->initialized) + { + return NULL; + } + + return &uart_devs[uart_idx]->dev; +} + /**************************************************************************** * Name: up_earlyserialinit * diff --git a/arch/arm/src/stm32/stm32_uart.h b/arch/arm/src/stm32/stm32_uart.h index e7c058ba6c..02a0528fda 100644 --- a/arch/arm/src/stm32/stm32_uart.h +++ b/arch/arm/src/stm32/stm32_uart.h @@ -41,6 +41,7 @@ ************************************************************************************/ #include +#include #include "chip.h" @@ -412,6 +413,16 @@ extern "C" * Public Functions ************************************************************************************/ +/************************************************************************************ + * Name: stm32_serial_get_uart + * + * Description: + * Get serial driver structure for STM32 USART + * + ************************************************************************************/ + +FAR uart_dev_t *stm32_serial_get_uart(int uart_num); + /************************************************************************************ * Name: stm32_serial_dma_poll * diff --git a/arch/arm/src/stm32l4/stm32l4_serial.c b/arch/arm/src/stm32l4/stm32l4_serial.c index 2c8e9f1ae3..ae243024d8 100644 --- a/arch/arm/src/stm32l4/stm32l4_serial.c +++ b/arch/arm/src/stm32l4/stm32l4_serial.c @@ -213,6 +213,12 @@ struct stm32l4_serial_s bool initialized; + bool suspended; /* UART device has been suspended. */ + + /* Interrupt mask value stored before suspending for stop mode. */ + + uint16_t suspended_ie; + /* If termios are supported, then the following fields may vary at * runtime. */ @@ -262,6 +268,7 @@ struct stm32l4_serial_s #ifdef SERIAL_HAVE_DMA DMA_HANDLE rxdma; /* currently-open receive DMA stream */ bool rxenable; /* DMA-based reception en/disable */ + bool rxdmasusp; /* Rx DMA suspended */ uint32_t rxdmanext; /* Next byte in the DMA buffer to be read */ char *const rxfifo; /* Receive DMA buffer */ #endif @@ -305,6 +312,10 @@ static int stm32l4serial_dmasetup(FAR struct uart_dev_s *dev); static void stm32l4serial_dmashutdown(FAR struct uart_dev_s *dev); static int stm32l4serial_dmareceive(FAR struct uart_dev_s *dev, FAR unsigned int *status); +static void stm32l4serial_dmareenable(struct stm32l4_serial_s *priv); +#ifdef CONFIG_SERIAL_IFLOWCONTROL +static bool stm32l4serial_dmaiflowrestart(struct stm32l4_serial_s *priv); +#endif static void stm32l4serial_dmarxint(FAR struct uart_dev_s *dev, bool enable); static bool stm32l4serial_dmarxavailable(struct uart_dev_s *dev); @@ -749,6 +760,8 @@ static struct pm_callback_s g_serialcb = }; #endif +static bool serial_suspended_for_stop = false; + /**************************************************************************** * Private Functions ****************************************************************************/ @@ -1030,6 +1043,127 @@ static void stm32l4serial_setformat(FAR struct uart_dev_s *dev) } #endif /* CONFIG_SUPPRESS_UART_CONFIG */ +/**************************************************************************** + * Name: stm32l4serial_setsuspend + * + * Description: + * Suspend or resume serial peripheral. + * + ****************************************************************************/ + +static void stm32l4serial_setsuspend(struct uart_dev_s *dev, bool suspend) +{ + FAR struct stm32l4_serial_s *priv = (struct stm32l4_serial_s *)dev->priv; +#ifdef SERIAL_HAVE_DMA + bool dmarestored = false; +#endif + + if (priv->suspended == suspend) + { + return; + } + + priv->suspended = suspend; + + if (suspend) + { +#ifdef CONFIG_SERIAL_IFLOWCONTROL + if (priv->iflow) + { + /* Force RTS high to prevent further Rx. */ + + stm32l4_configgpio((priv->rts_gpio & ~GPIO_MODE_MASK) + | (GPIO_OUTPUT | GPIO_OUTPUT_SET)); + } +#endif + + /* Disable interrupts to prevent Tx. */ + + stm32l4serial_disableusartint(priv, &priv->suspended_ie); + + /* Wait last Tx to complete. */ + + while ((stm32l4serial_getreg(priv, STM32L4_USART_ISR_OFFSET) & USART_ISR_TC) == 0); + +#ifdef SERIAL_HAVE_DMA + if (priv->dev.ops == &g_uart_dma_ops && !priv->rxdmasusp) + { +#ifdef CONFIG_SERIAL_IFLOWCONTROL + if (priv->iflow && priv->rxdmanext == RXDMA_BUFFER_SIZE) + { + /* Rx DMA in non-circular iflow mode and already stopped + * at end of DMA buffer. No need to suspend. */ + } + else +#endif + { + /* Suspend Rx DMA. */ + + stm32l4_dmastop(priv->rxdma); + priv->rxdmasusp = true; + } + } +#endif + } + else + { +#ifdef SERIAL_HAVE_DMA + if (priv->dev.ops == &g_uart_dma_ops && priv->rxdmasusp) + { +#ifdef CONFIG_SERIAL_IFLOWCONTROL + if (priv->iflow) + { + (void)stm32l4serial_dmaiflowrestart(priv); + } + else +#endif + { + /* This USART does not have HW flow-control. Unconditionally + * re-enable DMA (might loss unprocessed bytes received + * to DMA buffer before suspending). */ + + stm32l4serial_dmareenable(priv); + priv->rxdmasusp = false; + } + dmarestored = true; + } +#endif + + /* Re-enable interrupts to resume Tx. */ + + stm32l4serial_restoreusartint(priv, priv->suspended_ie); + +#ifdef CONFIG_SERIAL_IFLOWCONTROL + if (priv->iflow) + { + /* Restore peripheral RTS control. */ + + stm32l4_configgpio(priv->rts_gpio); + } +#endif + } + +#ifdef SERIAL_HAVE_DMA + if (dmarestored) + { + irqstate_t flags; + + flags = enter_critical_section(); + + /* Perform initial Rx DMA buffer fetch to wake-up serial device + * activity. + */ + + if (priv->rxdma != NULL) + { + stm32l4serial_dmarxcallback(priv->rxdma, 0, priv); + } + + leave_critical_section(flags); + } +#endif +} + /**************************************************************************** * Name: stm32l4serial_setapbclock * @@ -1945,6 +2079,22 @@ static bool stm32l4serial_rxflowcontrol(FAR struct uart_dev_s *dev, /* Assert/de-assert nRTS set it high resume/stop sending */ stm32l4_gpiowrite(priv->rts_gpio, upper); + + if (upper) + { + /* With heavy Rx traffic, RXNE might be set and data pending. + * Returning 'true' in such case would cause RXNE left unhandled + * and causing interrupt storm. Sending end might be also be slow + * to react on nRTS, and returning 'true' here would prevent + * processing that data. + * + * Therefore, return 'false' so input data is still being processed + * until sending end reacts on nRTS signal and stops sending more. + */ + + return false; + } + return upper; } @@ -2040,16 +2190,31 @@ static int stm32l4serial_dmareceive(FAR struct uart_dev_s *dev, * ****************************************************************************/ -#if defined(SERIAL_HAVE_DMA) && defined(CONFIG_SERIAL_IFLOWCONTROL) +#if defined(SERIAL_HAVE_DMA) static void stm32l4serial_dmareenable(FAR struct stm32l4_serial_s *priv) { - /* Configure for non-circular DMA reception into the RX fifo */ +#ifdef CONFIG_SERIAL_IFLOWCONTROL + if (priv->iflow) + { + /* Configure for non-circular DMA reception into the RX FIFO */ - stm32l4_dmasetup(priv->rxdma, - priv->usartbase + STM32L4_USART_RDR_OFFSET, - (uint32_t)priv->rxfifo, - RXDMA_BUFFER_SIZE, - SERIAL_DMA_IFLOW_CONTROL_WORD); + stm32l4_dmasetup(priv->rxdma, + priv->usartbase + STM32L4_USART_RDR_OFFSET, + (uint32_t)priv->rxfifo, + RXDMA_BUFFER_SIZE, + SERIAL_DMA_IFLOW_CONTROL_WORD); + } + else +#endif + { + /* Configure for circular DMA reception into the RX FIFO */ + + stm32l4_dmasetup(priv->rxdma, + priv->usartbase + STM32L4_USART_RDR_OFFSET, + (uint32_t)priv->rxfifo, + RXDMA_BUFFER_SIZE, + SERIAL_DMA_CONTROL_WORD); + } /* Reset our DMA shadow pointer to match the address just * programmed above. @@ -2057,16 +2222,79 @@ static void stm32l4serial_dmareenable(FAR struct stm32l4_serial_s *priv) priv->rxdmanext = 0; - /* Start the DMA channel, and arrange for callbacks at the full point in - * the FIFO. After buffer gets full, hardware flow-control kicks in and - * DMA transfer is stopped. - */ +#ifdef CONFIG_SERIAL_IFLOWCONTROL + if (priv->iflow) + { + /* Start the DMA channel, and arrange for callbacks at the full point + * in the FIFO. After buffer gets full, hardware flow-control kicks + * in and DMA transfer is stopped. + */ - stm32l4_dmastart(priv->rxdma, stm32l4serial_dmarxcallback, (FAR void *)priv, - false); + stm32l4_dmastart(priv->rxdma, stm32l4serial_dmarxcallback, (void *)priv, false); + } + else +#endif + { + /* Start the DMA channel, and arrange for callbacks at the half and + * full points in the FIFO. This ensures that we have half a FIFO + * worth of time to claim bytes before they are overwritten. + */ + + stm32l4_dmastart(priv->rxdma, stm32l4serial_dmarxcallback, (void *)priv, true); + } + + /* Clear DMA suspended flag. */ + + priv->rxdmasusp = false; } #endif +/**************************************************************************** + * Name: stm32l4serial_dmaiflowrestart + * + * Description: + * Call to restart RX DMA for input flow-controlled USART + * + ****************************************************************************/ + +#if defined(SERIAL_HAVE_DMA) && defined(CONFIG_SERIAL_IFLOWCONTROL) +static bool stm32l4serial_dmaiflowrestart(struct stm32l4_serial_s *priv) +{ + if (!priv->rxenable) + { + /* Rx not enabled by upper layer. */ + + return false; + } + + if (priv->rxdmanext != RXDMA_BUFFER_SIZE) + { + if (priv->rxdmasusp) + { + /* Rx DMA in suspended state. */ + + if (stm32l4serial_dmarxavailable(&priv->dev)) + { + /* DMA buffer has unprocessed data, do not re-enable yet. */ + + return false; + } + } + else + { + return false; + } + } + + /* DMA is stopped or suspended and DMA buffer does not have pending data, + * re-enabling without data loss is now safe. */ + + stm32l4serial_dmareenable(priv); + + return true; + } +#endif + /**************************************************************************** * Name: stm32l4serial_dmarxint * @@ -2091,11 +2319,11 @@ static void stm32l4serial_dmarxint(FAR struct uart_dev_s *dev, bool enable) priv->rxenable = enable; #ifdef CONFIG_SERIAL_IFLOWCONTROL - if (priv->iflow && priv->rxenable && (priv->rxdmanext == RXDMA_BUFFER_SIZE)) + if (priv->iflow) { /* Re-enable RX DMA. */ - stm32l4serial_dmareenable(priv); + (void)stm32l4serial_dmaiflowrestart(priv); } #endif } @@ -2245,15 +2473,32 @@ static void stm32l4serial_dmarxcallback(DMA_HANDLE handle, uint8_t status, uart_recvchars(&priv->dev); #ifdef CONFIG_SERIAL_IFLOWCONTROL - if (priv->iflow && priv->rxenable && - (priv->rxdmanext == RXDMA_BUFFER_SIZE)) + if (priv->iflow) { /* Re-enable RX DMA. */ - stm32l4serial_dmareenable(priv); + (void)stm32l4serial_dmaiflowrestart(priv); } #endif } + + /* Get the masked USART status word to check and clear error flags. + * + * When wake-up from low power mode was not fast enough, UART is resumed too + * late and sometimes exactly when character was coming over UART, resulting + * to frame error. + + * If error flag is not cleared, Rx DMA will be stuck. Clearing errors + * will release Rx DMA. + */ + + priv->sr = stm32l4serial_getreg(priv, STM32L4_USART_ISR_OFFSET); + + if ((priv->sr & (USART_ISR_ORE | USART_ISR_NF | USART_ISR_FE)) != 0) + { + stm32l4serial_putreg(priv, STM32L4_USART_ICR_OFFSET, + (USART_ICR_NCF | USART_ICR_ORECF | USART_ICR_FECF)); + } } #endif @@ -2287,29 +2532,27 @@ static void stm32l4serial_pmnotify(FAR struct pm_callback_s *cb, int domain, { case(PM_NORMAL): { - /* Logic for PM_NORMAL goes here */ - + stm32l4_serial_set_suspend(false); } break; case(PM_IDLE): { - /* Logic for PM_IDLE goes here */ - + stm32l4_serial_set_suspend(false); } break; case(PM_STANDBY): { - /* Logic for PM_STANDBY goes here */ + /* TODO: logic for enabling serial in Stop 1 mode with HSI16 missing */ + stm32l4_serial_set_suspend(true); } break; case(PM_SLEEP): { - /* Logic for PM_SLEEP goes here */ - + stm32l4_serial_set_suspend(true); } break; @@ -2371,6 +2614,49 @@ static int stm32l4serial_pmprepare(FAR struct pm_callback_s *cb, int domain, #ifdef USE_SERIALDRIVER +/**************************************************************************** + * Name: stm32l4_is_serial_suspended + * + * Description: + * Check if serial peripherals have been suspended for deep-sleep/stop modes. + * + ****************************************************************************/ + +bool stm32l4_is_serial_suspended(void) +{ + return (serial_suspended_for_stop); +} + +/**************************************************************************** + * Name: stm32_serial_set_suspend + * + * Description: + * Suspend or resume serial peripherals for/from deep-sleep/stop modes. + * + ****************************************************************************/ + +void stm32l4_serial_set_suspend(bool suspend) +{ + int n; + + /* Already in desired state? */ + + if (suspend == serial_suspended_for_stop) + return; + + serial_suspended_for_stop = suspend; + + for (n = 0; n < STM32L4_NUSART+STM32L4_NUART; n++) + { + struct stm32l4_serial_s *priv = uart_devs[n]; + + if (!priv || !priv->initialized) + continue; + + stm32l4serial_setsuspend(&priv->dev, suspend); + } +} + /**************************************************************************** * Name: stm32l4_serial_get_uart * diff --git a/arch/arm/src/stm32l4/stm32l4_uart.h b/arch/arm/src/stm32l4/stm32l4_uart.h index c1f53957a5..4f89aff838 100644 --- a/arch/arm/src/stm32l4/stm32l4_uart.h +++ b/arch/arm/src/stm32l4/stm32l4_uart.h @@ -248,6 +248,26 @@ extern "C" FAR uart_dev_t *stm32l4_serial_get_uart(int uart_num); +/**************************************************************************** + * Name: stm32l4_is_serial_suspended + * + * Description: + * Check if serial peripherals have been suspended for deep-sleep/stop modes. + * + ****************************************************************************/ + +bool stm32l4_is_serial_suspended(void); + +/**************************************************************************** + * Name: stm32_serial_set_suspend + * + * Description: + * Suspend or resume serial peripherals for/from deep-sleep/stop modes. + * + ****************************************************************************/ + +void stm32l4_serial_set_suspend(bool suspend); + /************************************************************************************ * Name: stm32l4_serial_dma_poll *