From 2997a49e5163ed16bcc2bf1a4dd493a205b59f8e Mon Sep 17 00:00:00 2001 From: Juha Niskanen Date: Tue, 3 Oct 2017 16:39:51 +0000 Subject: [PATCH] Merged in juniskane/nuttx_stm32l4/stm32l4_rtc_pm_fixes_pr (pull request #502) STM32L4 RTC, PM: small fixes to subseconds handling, ADC power-management hooks * STM32L4 ADC: add PM hooks from Motorola MDK * STM32L4 RTC: add up_rtc_getdatetime_with_subseconds * STM32 RTC: workaround for potential subseconds race condition In all recent STM32 chips reading either RTC_SSR or RTC_TR is supposed to lock the values in the higher-order calendar shadow registers until RTC_DR is read. However many old chips have in their errata this silicon bug (at least F401xB/C, F42xx, F43xx, L15xxE, L15xVD and likely others): "When reading the calendar registers with BYPSHAD=0, the RTC_TR and RTC_DR registers may not be locked after reading the RTC_SSR register. This happens if the read operation is initiated one APB clock period before the shadow registers are updated. This can result in a non-consistency of the three registers. Similarly, RTC_DR register can be updated after reading the RTC_TR register instead of being locked." * STM32L4 RTC: correct RTC_SSR and RTC_TR read ordering In all recent STM32 chips reading either RTC_SSR or RTC_TR is supposed to lock the values in the higher-order calendar shadow registers until RTC_DR is read. Change the register read ordering to match this and don't keep a workaround for a hypothetical race condition (not in any L4 errata, lets for once assume ST's silicon works as it is documented...) * STM32L4 PM: remove useless #ifdefs and old non-L4 STM32 code Approved-by: Gregory Nutt --- arch/arm/src/stm32/stm32_rtcc.c | 25 +++++--- arch/arm/src/stm32/stm32f40xxx_rtcc.c | 27 ++++++--- arch/arm/src/stm32l4/Kconfig | 5 ++ arch/arm/src/stm32l4/stm32l4_adc.c | 68 +++++++++++++++++++++ arch/arm/src/stm32l4/stm32l4_pm.h | 6 -- arch/arm/src/stm32l4/stm32l4_pmlpr.c | 5 -- arch/arm/src/stm32l4/stm32l4_pmstandby.c | 17 +----- arch/arm/src/stm32l4/stm32l4_pmstop.c | 26 +------- arch/arm/src/stm32l4/stm32l4_rtcc.c | 75 +++++++++++++++++------- 9 files changed, 165 insertions(+), 89 deletions(-) diff --git a/arch/arm/src/stm32/stm32_rtcc.c b/arch/arm/src/stm32/stm32_rtcc.c index 1e03d89ba18..b8e1c1a59e9 100644 --- a/arch/arm/src/stm32/stm32_rtcc.c +++ b/arch/arm/src/stm32/stm32_rtcc.c @@ -832,9 +832,12 @@ int up_rtc_getdatetime(FAR struct tm *tp) uint32_t tr; uint32_t tmp; - /* Sample the data time registers. There is a race condition here... If we sample - * the time just before midnight on December 31, the date could be wrong because - * the day rolled over while were sampling. + /* Sample the data time registers. There is a race condition here... If + * we sample the time just before midnight on December 31, the date could + * be wrong because the day rolled over while were sampling. Thus loop for + * checking overflow here is needed. There is a race condition with + * subseconds too. If we sample TR register just before second rolling + * and subseconds are read at wrong second, we get wrong time. */ do @@ -843,16 +846,24 @@ int up_rtc_getdatetime(FAR struct tm *tp) tr = getreg32(STM32_RTC_TR); #ifdef CONFIG_STM32_HAVE_RTC_SUBSECONDS ssr = getreg32(STM32_RTC_SSR); + tmp = getreg32(STM32_RTC_TR); + if (tmp != tr) + { + continue; + } #endif tmp = getreg32(STM32_RTC_DR); + if (tmp == dr) + { + break; + } } - while (tmp != dr); + while (1); rtc_dumpregs("Reading Time"); /* Convert the RTC time to fields in struct tm format. All of the STM32 - * All of the ranges of values correspond between struct tm and the time - * register. + * ranges of values correspond between struct tm and the time register. */ tmp = (tr & (RTC_TR_SU_MASK | RTC_TR_ST_MASK)) >> RTC_TR_SU_SHIFT; @@ -887,7 +898,7 @@ int up_rtc_getdatetime(FAR struct tm *tp) tmp = (dr & RTC_DR_WDU_MASK) >> RTC_DR_WDU_SHIFT; tp->tm_wday = tmp % 7; tp->tm_yday = tp->tm_mday + clock_daysbeforemonth(tp->tm_mon, clock_isleapyear(tp->tm_year + 1900)); - tp->tm_isdst = 0 + tp->tm_isdst = 0; #endif #ifdef CONFIG_STM32_HAVE_RTC_SUBSECONDS diff --git a/arch/arm/src/stm32/stm32f40xxx_rtcc.c b/arch/arm/src/stm32/stm32f40xxx_rtcc.c index 89098bbd13f..b41cbf65fd1 100644 --- a/arch/arm/src/stm32/stm32f40xxx_rtcc.c +++ b/arch/arm/src/stm32/stm32f40xxx_rtcc.c @@ -770,8 +770,7 @@ static int rtchw_set_alrmar(rtc_alarmreg_t alarmreg) /* Set the RTC Alarm register */ putreg32(alarmreg, STM32_RTC_ALRMAR); - rtcinfo(" TR: %08x ALRMAR: %08x\n", - getreg32(STM32_RTC_TR), getreg32(STM32_RTC_ALRMAR)); + rtcinfo(" ALRMAR: %08x\n", getreg32(STM32_RTC_ALRMAR)); /* Enable RTC alarm */ @@ -807,8 +806,7 @@ static int rtchw_set_alrmbr(rtc_alarmreg_t alarmreg) /* Set the RTC Alarm register */ putreg32(alarmreg, STM32_RTC_ALRMBR); - rtcinfo(" TR: %08x ALRMBR: %08x\n", - getreg32(STM32_RTC_TR), getreg32(STM32_RTC_ALRMBR)); + rtcinfo(" ALRMBR: %08x\n", getreg32(STM32_RTC_ALRMBR)); /* Enable RTC alarm B */ @@ -1168,7 +1166,10 @@ int up_rtc_getdatetime(FAR struct tm *tp) /* Sample the data time registers. There is a race condition here... If * we sample the time just before midnight on December 31, the date could - * be wrong because the day rolled over while were sampling. + * be wrong because the day rolled over while were sampling. Thus loop for + * checking overflow here is needed. There is a race condition with + * subseconds too. If we sample TR register just before second rolling + * and subseconds are read at wrong second, we get wrong time. */ do @@ -1177,16 +1178,24 @@ int up_rtc_getdatetime(FAR struct tm *tp) tr = getreg32(STM32_RTC_TR); #ifdef CONFIG_STM32_HAVE_RTC_SUBSECONDS ssr = getreg32(STM32_RTC_SSR); + tmp = getreg32(STM32_RTC_TR); + if (tmp != tr) + { + continue; + } #endif tmp = getreg32(STM32_RTC_DR); + if (tmp == dr) + { + break; + } } - while (tmp != dr); + while (1); rtc_dumpregs("Reading Time"); /* Convert the RTC time to fields in struct tm format. All of the STM32 - * All of the ranges of values correspond between struct tm and the time - * register. + * ranges of values correspond between struct tm and the time register. */ tmp = (tr & (RTC_TR_SU_MASK | RTC_TR_ST_MASK)) >> RTC_TR_SU_SHIFT; @@ -1221,7 +1230,7 @@ int up_rtc_getdatetime(FAR struct tm *tp) tmp = (dr & RTC_DR_WDU_MASK) >> RTC_DR_WDU_SHIFT; tp->tm_wday = tmp % 7; tp->tm_yday = tp->tm_mday + clock_daysbeforemonth(tp->tm_mon, clock_isleapyear(tp->tm_year + 1900)); - tp->tm_isdst = 0 + tp->tm_isdst = 0; #endif #ifdef CONFIG_STM32_HAVE_RTC_SUBSECONDS diff --git a/arch/arm/src/stm32l4/Kconfig b/arch/arm/src/stm32l4/Kconfig index 54235044958..95de59ea977 100644 --- a/arch/arm/src/stm32l4/Kconfig +++ b/arch/arm/src/stm32l4/Kconfig @@ -1405,6 +1405,11 @@ config ARCH_BOARD_STM32L4_CUSTOM_CLOCKCONFIG ---help--- Enables special, board-specific STM32 clock configuration. +config STM32L4_HAVE_RTC_SUBSECONDS + bool + select ARCH_HAVE_RTC_SUBSECONDS + default y + choice prompt "RTC clock source" default STM32L4_RTC_LSECLOCK diff --git a/arch/arm/src/stm32l4/stm32l4_adc.c b/arch/arm/src/stm32l4/stm32l4_adc.c index ff9511550ca..a689587dda2 100644 --- a/arch/arm/src/stm32l4/stm32l4_adc.c +++ b/arch/arm/src/stm32l4/stm32l4_adc.c @@ -57,6 +57,7 @@ #include #include #include +#include #include #include @@ -88,6 +89,12 @@ /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ + +#ifndef container_of +# define container_of(ptr, type, member) \ + ((type *)((intptr_t)(ptr) - offsetof(type, member))) +#endif + /* RCC reset ****************************************************************/ #define STM32L4_RCC_RSTR STM32L4_RCC_AHB2RSTR @@ -195,6 +202,10 @@ struct stm32_dev_s uint32_t freq; /* The desired frequency of conversions */ #endif +#ifdef CONFIG_PM + struct pm_callback_s pm_callback; +#endif + #ifdef ADC_HAVE_DMA DMA_HANDLE dma; /* Allocated DMA channel */ @@ -258,6 +269,11 @@ static int adc_setoffset(FAR struct stm32_dev_s *priv, uint8_t ch, uint8_t i, static void adc_startconv(FAR struct stm32_dev_s *priv, bool enable); +#ifdef CONFIG_PM +static int adc_pm_prepare(struct pm_callback_s *cb, int domain, + enum pm_state_e state); +#endif + /* ADC Interrupt Handler */ static int adc_interrupt(FAR struct adc_dev_s *dev, uint32_t regval); @@ -317,6 +333,12 @@ static struct stm32_dev_s g_adcpriv1 = #ifdef ADC1_HAVE_DFSDM .hasdfsdm = true, #endif +#ifdef CONFIG_PM + .pm_callback = + { + .prepare = adc_pm_prepare, + } +#endif }; static struct adc_dev_s g_adcdev1 = @@ -349,6 +371,12 @@ static struct stm32_dev_s g_adcpriv2 = #ifdef ADC2_HAVE_DFSDM .hasdfsdm = true, #endif +#ifdef CONFIG_PM + .pm_callback = + { + .prepare = adc_pm_prepare, + } +#endif }; static struct adc_dev_s g_adcdev2 = @@ -381,6 +409,12 @@ static struct stm32_dev_s g_adcpriv3 = #ifdef ADC3_HAVE_DFSDM .hasdfsdm = true, #endif +#ifdef CONFIG_PM + .pm_callback = + { + .prepare = adc_pm_prepare, + } +#endif }; static struct adc_dev_s g_adcdev3 = @@ -974,6 +1008,32 @@ static int adc_timinit(FAR struct stm32_dev_s *priv) } #endif +/**************************************************************************** + * Name: adc_pm_prepare + * + * Description: + * Called by power management framework when it wants to enter low power + * states. Check if ADC is in progress and if so prevent from entering STOP. + * + ****************************************************************************/ +#if CONFIG_PM +static int adc_pm_prepare(struct pm_callback_s *cb, int domain, + enum pm_state_e state) +{ + FAR struct stm32_dev_s *priv = + container_of(cb, struct stm32_dev_s, pm_callback); + uint32_t regval; + + regval = adc_getreg(priv, STM32L4_ADC_CR_OFFSET); + if ((state >= PM_IDLE) && (regval & ADC_CR_ADSTART)) + { + return -EBUSY; + } + + return OK; +} +#endif + /**************************************************************************** * Name: adc_wdog_enable * @@ -1962,6 +2022,14 @@ struct adc_dev_s *stm32l4_adc_initialize(int intf, FAR const uint8_t *chanlist, priv->cchannels = cchannels; memcpy(priv->chanlist, chanlist, cchannels); +#ifdef CONFIG_PM + if (pm_register(&priv->pm_callback) != OK) + { + aerr("Power management registration failed\n"); + return NULL; + } +#endif + return dev; } diff --git a/arch/arm/src/stm32l4/stm32l4_pm.h b/arch/arm/src/stm32l4/stm32l4_pm.h index 40c06398c7f..fd94299389c 100644 --- a/arch/arm/src/stm32l4/stm32l4_pm.h +++ b/arch/arm/src/stm32l4/stm32l4_pm.h @@ -97,10 +97,7 @@ int stm32l4_pmstop(bool lpds); * ****************************************************************************/ -#if defined(CONFIG_STM32L4_STM32L4X3) || defined(CONFIG_STM32L4_STM32L4X5) || \ - defined(CONFIG_STM32L4_STM32L4X6) int stm32l4_pmstop2(void); -#endif /**************************************************************************** * Name: stm32l4_pmstandby @@ -159,10 +156,7 @@ void stm32l4_pmsleep(bool sleeponexit); * ****************************************************************************/ -#if defined(CONFIG_STM32L4_STM32L4X3) || defined(CONFIG_STM32L4_STM32L4X5) || \ - defined(CONFIG_STM32L4_STM32L4X6) int stm32l4_pmlpr(void); -#endif #undef EXTERN #ifdef __cplusplus diff --git a/arch/arm/src/stm32l4/stm32l4_pmlpr.c b/arch/arm/src/stm32l4/stm32l4_pmlpr.c index 78237750bee..faebe8215dd 100644 --- a/arch/arm/src/stm32l4/stm32l4_pmlpr.c +++ b/arch/arm/src/stm32l4/stm32l4_pmlpr.c @@ -48,9 +48,6 @@ #include "stm32l4_pm.h" #include "stm32l4_rcc.h" -#if defined(CONFIG_STM32L4_STM32L4X3) || defined(CONFIG_STM32L4_STM32L4X5) || \ - defined(CONFIG_STM32L4_STM32L4X6) - /**************************************************************************** * Public Functions ****************************************************************************/ @@ -108,5 +105,3 @@ int stm32l4_pmlpr(void) return OK; } - -#endif /* CONFIG_STM32L4_STM32L4X3 || CONFIG_STM32L4_STM32L4X5 || CONFIG_STM32L4_STM32L4X6 */ diff --git a/arch/arm/src/stm32l4/stm32l4_pmstandby.c b/arch/arm/src/stm32l4/stm32l4_pmstandby.c index bf068767a59..c8730c14f9d 100644 --- a/arch/arm/src/stm32l4/stm32l4_pmstandby.c +++ b/arch/arm/src/stm32l4/stm32l4_pmstandby.c @@ -72,8 +72,6 @@ int stm32l4_pmstandby(void) { uint32_t regval; -#if defined(CONFIG_STM32L4_STM32L4X3) || defined(CONFIG_STM32L4_STM32L4X5) || \ - defined(CONFIG_STM32L4_STM32L4X6) /* Clear the Wake-Up Flags by setting the CWUFx bits in the power status * clear register */ @@ -82,25 +80,12 @@ int stm32l4_pmstandby(void) putreg32(regval, STM32L4_PWR_SCR); /* Select Standby mode */ + regval = getreg32(STM32L4_PWR_CR1); regval &= ~PWR_CR1_LPMS_MASK; regval |= PWR_CR1_LPMS_STANDBY; putreg32(regval, STM32L4_PWR_CR1); -#else - /* Clear the Wake-Up Flag by setting the CWUF bit in the power control - * register. - */ - - regval = getreg32(STM32L4_PWR_CR); - regval |= PWR_CR_CWUF; - putreg32(regval, STM32L4_PWR_CR); - - /* Set the Power Down Deep Sleep (PDDS) bit in the power control register. */ - - regval |= PWR_CR_PDDS; - putreg32(regval, STM32L4_PWR_CR); -#endif /* Set SLEEPDEEP bit of Cortex System Control Register */ diff --git a/arch/arm/src/stm32l4/stm32l4_pmstop.c b/arch/arm/src/stm32l4/stm32l4_pmstop.c index df07a2b7f0c..d69ad47bd79 100644 --- a/arch/arm/src/stm32l4/stm32l4_pmstop.c +++ b/arch/arm/src/stm32l4/stm32l4_pmstop.c @@ -109,38 +109,19 @@ int stm32l4_pmstop(bool lpds) { uint32_t regval; -#if defined(CONFIG_STM32L4_STM32L4X3) || defined(CONFIG_STM32L4_STM32L4X5) || \ - defined(CONFIG_STM32L4_STM32L4X6) /* Clear Low-Power Mode Selection (LPMS) bits in power control register 1. */ + regval = getreg32(STM32L4_PWR_CR1); regval &= ~PWR_CR1_LPMS_MASK; /* Select Stop 1 mode with low-power regulator if so requested */ + if (lpds) { regval |= PWR_CR1_LPMS_STOP1LPR; } putreg32(regval, STM32L4_PWR_CR1); -#else - /* Clear the Power Down Deep Sleep (PDDS), Low Power Deep Sleep (LPDS), and - * Low Power regulator Low Voltage in Deep Sleep (LPLVDS) bits in the power - * control register. - */ - - regval = getreg32(STM32L4_PWR_CR); - regval &= ~(PWR_CR_LPDS | PWR_CR_PDDS | PWR_CR_LPLVDS); - - /* Set the Low Power Deep Sleep (LPDS) and Low Power regulator Low Voltage - * in Deep Sleep (LPLVDS) bits if so requested */ - - if (lpds) - { - regval |= PWR_CR_LPDS | PWR_CR_LPLVDS; - } - - putreg32(regval, STM32L4_PWR_CR); -#endif return do_stop(); } @@ -161,8 +142,6 @@ int stm32l4_pmstop(bool lpds) * ****************************************************************************/ -#if defined(CONFIG_STM32L4_STM32L4X3) || defined(CONFIG_STM32L4_STM32L4X5) || \ - defined(CONFIG_STM32L4_STM32L4X6) int stm32l4_pmstop2(void) { uint32_t regval; @@ -176,4 +155,3 @@ int stm32l4_pmstop2(void) return do_stop(); } -#endif diff --git a/arch/arm/src/stm32l4/stm32l4_rtcc.c b/arch/arm/src/stm32l4/stm32l4_rtcc.c index b7b4239dc70..6a23e40dcd1 100644 --- a/arch/arm/src/stm32l4/stm32l4_rtcc.c +++ b/arch/arm/src/stm32l4/stm32l4_rtcc.c @@ -43,12 +43,12 @@ #include #include -#include #include #include #include #include +#include #include @@ -717,8 +717,7 @@ static int rtchw_set_alrmar(rtc_alarmreg_t alarmreg) putreg32(alarmreg, STM32L4_RTC_ALRMAR); putreg32(0, STM32L4_RTC_ALRMASSR); - rtcinfo(" TR: %08x ALRMAR: %08x\n", - getreg32(STM32L4_RTC_TR), getreg32(STM32L4_RTC_ALRMAR)); + rtcinfo(" ALRMAR: %08x\n", getreg32(STM32L4_RTC_ALRMAR)); /* Enable RTC alarm A */ @@ -763,8 +762,7 @@ static int rtchw_set_alrmbr(rtc_alarmreg_t alarmreg) putreg32(alarmreg, STM32L4_RTC_ALRMBR); putreg32(0, STM32L4_RTC_ALRMBSSR); - rtcinfo(" TR: %08x ALRMBR: %08x\n", - getreg32(STM32L4_RTC_TR), getreg32(STM32L4_RTC_ALRMBR)); + rtcinfo(" ALRMBR: %08x\n", getreg32(STM32L4_RTC_ALRMBR)); /* Enable RTC alarm B */ @@ -1092,30 +1090,25 @@ int stm32_rtc_irqinitialize(void) int stm32l4_rtc_getdatetime_with_subseconds(FAR struct tm *tp, FAR long *nsec) { +#ifdef CONFIG_STM32L4_HAVE_RTC_SUBSECONDS uint32_t ssr; +#endif uint32_t dr; uint32_t tr; uint32_t tmp; - /* Sample the data time registers. There is a race condition here... If we sample - * the time just before midnight on December 31, the date could be wrong because - * the day rolled over while were sampling. - */ + /* Sample the date time registers. Must read in this order. */ - do - { - dr = getreg32(STM32L4_RTC_DR); - tr = getreg32(STM32L4_RTC_TR); - ssr = getreg32(STM32L4_RTC_SSR); - tmp = getreg32(STM32L4_RTC_DR); - } - while (tmp != dr); +#ifdef CONFIG_STM32L4_HAVE_RTC_SUBSECONDS + ssr = getreg32(STM32L4_RTC_SSR); +#endif + tr = getreg32(STM32L4_RTC_TR); + dr = getreg32(STM32L4_RTC_DR); rtc_dumpregs("Reading Time"); - /* Convert the RTC time to fields in struct tm format. All of the STM32 - * All of the ranges of values correspond between struct tm and the time - * register. + /* Convert the RTC time to fields in struct tm format. All of the STM32 + * ranges of values correspond between struct tm and the time register. */ tmp = (tr & (RTC_TR_SU_MASK | RTC_TR_ST_MASK)) >> RTC_TR_SU_SHIFT; @@ -1150,13 +1143,14 @@ int stm32l4_rtc_getdatetime_with_subseconds(FAR struct tm *tp, FAR long *nsec) tmp = (dr & RTC_DR_WDU_MASK) >> RTC_DR_WDU_SHIFT; tp->tm_wday = tmp % 7; tp->tm_yday = tp->tm_mday + clock_daysbeforemonth(tp->tm_mon, clock_isleapyear(tp->tm_year + 1900)); - tp->tm_isdst = 0 + tp->tm_isdst = 0; #endif /* Return RTC sub-seconds if a non-NULL value * of nsec has been provided to receive the sub-second value. */ +#ifdef CONFIG_STM32L4_HAVE_RTC_SUBSECONDS if (nsec) { uint32_t prediv_s; @@ -1174,6 +1168,9 @@ int stm32l4_rtc_getdatetime_with_subseconds(FAR struct tm *tp, FAR long *nsec) usecs = (((prediv_s - ssr) * 100000) / (prediv_s + 1)) * 10; *nsec = usecs * 1000; } +#else + DEBUGASSERT(nsec == NULL); +#endif rtc_dumptime(tp, "Returning"); return OK; @@ -1207,6 +1204,40 @@ int up_rtc_getdatetime(FAR struct tm *tp) return stm32l4_rtc_getdatetime_with_subseconds(tp, NULL); } +/************************************************************************************ + * Name: up_rtc_getdatetime_with_subseconds + * + * Description: + * Get the current date and time from the date/time RTC. This interface + * is only supported by the date/time RTC hardware implementation. + * It is used to replace the system timer. It is only used by the RTOS during + * initialization to set up the system time when CONFIG_RTC and CONFIG_RTC_DATETIME + * are selected (and CONFIG_RTC_HIRES is not). + * + * NOTE: This interface exposes sub-second accuracy capability of RTC hardware. + * This interface allow maintaining timing accuracy when system time needs constant + * resynchronization with RTC, for example with board level power-save mode utilizing + * deep-sleep modes such as STOP on STM32 MCUs. + * + * Input Parameters: + * tp - The location to return the high resolution time value. + * nsec - The location to return the subsecond time value. + * + * Returned Value: + * Zero (OK) on success; a negated errno on failure + * + ************************************************************************************/ + +#ifdef CONFIG_ARCH_HAVE_RTC_SUBSECONDS +# ifndef CONFIG_STM32L4_HAVE_RTC_SUBSECONDS +# error "Invalid config, enable CONFIG_STM32L4_HAVE_RTC_SUBSECONDS." +# endif +int up_rtc_getdatetime_with_subseconds(FAR struct tm *tp, FAR long *nsec) +{ + return stm32l4_rtc_getdatetime_with_subseconds(tp, nsec); +} +#endif + /************************************************************************************ * Name: stm32l4_rtc_setdatetime * @@ -1300,7 +1331,7 @@ int stm32l4_rtc_setdatetime(FAR const struct tm *tp) } /************************************************************************************ - * Name: stm32l4_rtc_setdatetime + * Name: stm32l4_rtc_havesettime * * Description: * Check if RTC time has been set.