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 <gnutt@nuttx.org>
This commit is contained in:
Juha Niskanen 2017-10-03 16:39:51 +00:00 committed by Gregory Nutt
parent 5c4d45a331
commit 2997a49e51
9 changed files with 165 additions and 89 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -57,6 +57,7 @@
#include <nuttx/irq.h>
#include <nuttx/arch.h>
#include <nuttx/fs/ioctl.h>
#include <nuttx/power/pm.h>
#include <nuttx/analog/adc.h>
#include <nuttx/analog/ioctl.h>
@ -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;
}

View File

@ -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

View File

@ -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 */

View File

@ -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 */

View File

@ -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

View File

@ -43,12 +43,12 @@
#include <stdbool.h>
#include <sched.h>
#include <time.h>
#include <errno.h>
#include <debug.h>
#include <nuttx/arch.h>
#include <nuttx/irq.h>
#include <nuttx/time.h>
#include <arch/board/board.h>
@ -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.