OpenRTX/lib/miosix-kernel/miosix/arch/common/drivers/stm32_rtc.cpp

323 lines
10 KiB
C++

/***************************************************************************
* Copyright (C) 2017 by Terraneo Federico *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* As a special exception, if other files instantiate templates or use *
* macros or inline functions from this file, or you compile this file *
* and link it with other works to produce a work based on this file, *
* this file does not by itself cause the resulting work to be covered *
* by the GNU General Public License. However the source code for this *
* file must still be made available in accordance with the GNU General *
* Public License. This exception does not invalidate any other reasons *
* why a work based on this file might be covered by the GNU General *
* Public License. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, see <http://www.gnu.org/licenses/> *
***************************************************************************/
#include "stm32_rtc.h"
#include <miosix.h>
#include <sys/ioctl.h>
#include <kernel/scheduler/scheduler.h>
using namespace miosix;
namespace {
//
// class ScopedCnf, RAII to allow writing to the RTC alarm and prescaler.
// NOTE: datasheet says that after CNF bit has been cleared, no write is allowed
// to *any* of the RTC registers, not just PRL,CNT,ALR until RTOFF goes to 1.
// We do not wait until RTOFF is 1 in the destructor for performance reasons,
// so the rest of the code must be careful.
//
class ScopedCnf
{
public:
ScopedCnf()
{
while((RTC->CRL & RTC_CRL_RTOFF)==0) ;
RTC->CRL=0b11111;
}
~ScopedCnf()
{
RTC->CRL=0b01111;
}
};
long long swTime=0; //64bit software extension of current time in ticks
long long irqTime=0; //64bit software extension of scheduled irq time in ticks
Thread *waiting=nullptr; //waiting thread
/**
* \return the hardware counter
*/
inline unsigned int IRQgetHwTick()
{
unsigned int h1=RTC->CNTH;
unsigned int l1=RTC->CNTL;
unsigned int h2=RTC->CNTH;
if(h1==h2) return (h1<<16) | l1;
return (h2<<16) | RTC->CNTL;
}
/**
* \return the time in ticks (hardware part + software extension to 64bits)
*/
long long IRQgetTick()
{
//Pending bit trick
unsigned int hwTime=IRQgetHwTick();
if((RTC->CRL & RTC_CRL_OWF) && IRQgetHwTick()>=hwTime)
return (swTime + static_cast<long long>(hwTime)) + (1LL<<32);
return swTime + static_cast<long long>(hwTime);
}
/**
* Sleep the current thread till the specified time
* \param tick absolute time in ticks
* \param dLock used to reenable interrupts while sleeping
* \return true if the wait time was in the past
*/
bool IRQabsoluteWaitTick(long long tick, FastInterruptDisableLock& dLock)
{
irqTime=tick;
unsigned int hwAlarm=(tick & 0xffffffffULL) - (swTime & 0xffffffffULL);
{
ScopedCnf cnf;
RTC->ALRL=hwAlarm;
RTC->ALRH=hwAlarm>>16;
}
//NOTE: We do not wait for the alarm register to be written back to the low
//frequency domain for performance reasons. The datasheet says it takes
//at least 3 cycles of the 32KHz clock, but experiments show that it takes
//from 2 to 3, so perhaps they meant "at most 3". Because of this we
//consider the time in the past if we are more than 2 ticks of the 16KHz
//clock (4 ticks of the 32KHz one) in advance. Sleeps less than 122us are
//thus not supported.
if(IRQgetTick()>=tick-2) return true;
waiting=Thread::IRQgetCurrentThread();
do {
Thread::IRQwait();
{
FastInterruptEnableLock eLock(dLock);
Thread::yield();
}
} while(waiting);
return false;
}
} //anon namespace
/**
* RTC interrupt
*/
void __attribute__((naked)) RTC_IRQHandler()
{
saveContext();
asm volatile("bl _Z10RTCIrqImplv");
restoreContext();
}
/**
* RTC interrupt actual implementation
*/
void __attribute__((used)) RTCIrqImpl()
{
unsigned int crl=RTC->CRL;
if(crl & RTC_CRL_OWF)
{
RTC->CRL=(RTC->CRL | 0xf) & ~RTC_CRL_OWF;
swTime+=1LL<<32;
} else if(crl & RTC_CRL_ALRF) {
RTC->CRL=(RTC->CRL | 0xf) & ~RTC_CRL_ALRF;
if(waiting && IRQgetTick()>=irqTime)
{
waiting->IRQwakeup();
if(waiting->IRQgetPriority()>
Thread::IRQgetCurrentThread()->IRQgetPriority())
Scheduler::IRQfindNextThread();
waiting=nullptr;
}
}
}
namespace miosix {
void absoluteDeepSleep(long long int value)
{
#ifdef RUN_WITH_HSI
const int wakeupAdvance=3; //waking up takes time
#else //RUN_WITH_HSI
const int wakeupAdvance=33; //HSE starup time is 2ms
#endif //RUN_WITH_HSI
Rtc& rtc=Rtc::instance();
ioctl(STDOUT_FILENO,IOCTL_SYNC,0);
FastInterruptDisableLock dLock;
//Set alarm and enable EXTI
long long wkupTick=rtc.tc.ns2tick(value)-wakeupAdvance;
unsigned int hwAlarm=(wkupTick & 0xffffffffULL) - (swTime & 0xffffffffULL);
{
ScopedCnf cnf;
RTC->ALRL=hwAlarm;
RTC->ALRH=hwAlarm>>16;
}
while((RTC->CRL & RTC_CRL_RTOFF)==0) ;
EXTI->RTSR |= EXTI_RTSR_TR17;
EXTI->EMR |= EXTI_EMR_MR17; //enable event for wakeup
long long tick=IRQgetTick();
while(tick<wkupTick)
{
PWR->CR |= PWR_CR_LPDS;
SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;
// Using WFE instead of WFI because if while we are with interrupts
// disabled an interrupt (such as the tick interrupt) occurs, it
// remains pending and the WFI becomes a nop, and the device never goes
// in sleep mode. WFE events are latched in a separate pending register
// so interrupts do not interfere with them
__WFE();
SCB->SCR &= ~SCB_SCR_SLEEPDEEP_Msk;
PWR->CR &= ~PWR_CR_LPDS;
#ifndef SYSCLK_FREQ_24MHz
#error TODO: support more PLL frequencies
#endif
//STOP mode resets the clock to the HSI 8MHz, so restore the 24MHz clock
#ifndef RUN_WITH_HSI
RCC->CR |= RCC_CR_HSEON;
while((RCC->CR & RCC_CR_HSERDY)==0) ;
//PLL = (HSE / 2) * 6 = 24 MHz
RCC->CFGR &= ~(RCC_CFGR_PLLSRC | RCC_CFGR_PLLXTPRE | RCC_CFGR_PLLMULL);
RCC->CFGR |= RCC_CFGR_PLLSRC | RCC_CFGR_PLLXTPRE | RCC_CFGR_PLLMULL6;
#else //RUN_WITH_HSI
//PLL = (HSI / 2) * 6 = 24 MHz
RCC->CFGR &= ~(RCC_CFGR_PLLSRC | RCC_CFGR_PLLXTPRE | RCC_CFGR_PLLMULL);
RCC->CFGR |= RCC_CFGR_PLLXTPRE | RCC_CFGR_PLLMULL6;
#endif //RUN_WITH_HSI
RCC->CR |= RCC_CR_PLLON;
while((RCC->CR & RCC_CR_PLLRDY)==0) ;
RCC->CFGR &= ~RCC_CFGR_SW;
RCC->CFGR |= RCC_CFGR_SW_PLL;
while((RCC->CFGR & RCC_CFGR_SWS)!=0x08) ;
//Wait RSF
RTC->CRL=(RTC->CRL | 0xf) & ~RTC_CRL_RSF;
while((RTC->CRL & RTC_CRL_RSF)==0) ;
//Clear IRQ
unsigned int crl=RTC->CRL;
//NOTE: ST docs say OWF, ALRF are not updated in deep sleep. So, to
//detect counter ovreflow we check for oldTick>newTick. The check for
//flags is still there in case overflow/alarm occurs before/after sleep
if(crl & RTC_CRL_OWF || tick>IRQgetTick())
{
RTC->CRL=(RTC->CRL | 0xf) & ~RTC_CRL_OWF;
swTime+=1LL<<32;
} else if(crl & RTC_CRL_ALRF) {
RTC->CRL=(RTC->CRL | 0xf) & ~RTC_CRL_ALRF;
}
//Update tick, done after checking IRQ flags to avoid race condition
tick=IRQgetTick();
}
EXTI->EMR &=~ EXTI_EMR_MR17; //disable event for wakeup
wkupTick+=wakeupAdvance;
//NOTE: if we use the HSE we may spin for a while, but adding a
//IRQabsoluteWaitTick here makes this function wakeup too late
while(IRQgetTick()<wkupTick) ;
}
//
// class Rtc
//
Rtc& Rtc::instance()
{
static Rtc singleton;
return singleton;
}
long long Rtc::getValue() const
{
//Function takes ~170 clock cycles ~60 cycles IRQgetTick, ~96 cycles tick2ns
long long tick;
{
FastInterruptDisableLock dLock;
tick=IRQgetTick();
}
//tick2ns is reentrant, so can be called with interrupt enabled
return tc.tick2ns(tick);
}
long long Rtc::IRQgetValue() const
{
return tc.tick2ns(IRQgetTick());
}
// May not work due to the way hwAlarm is computed in sleep functions
// void Rtc::setValue(long long value)
// {
// FastInterruptDisableLock dLock;
// swTime=tc.ns2tick(value)-IRQgetHwTick();
// }
void Rtc::wait(long long value)
{
FastInterruptDisableLock dLock;
IRQabsoluteWaitTick(IRQgetTick()+tc.ns2tick(value),dLock);
}
bool Rtc::absoluteWait(long long value)
{
FastInterruptDisableLock dLock;
return IRQabsoluteWaitTick(tc.ns2tick(value),dLock);
}
Rtc::Rtc() : tc(getTickFrequency())
{
{
FastInterruptDisableLock dLock;
RCC->APB1ENR |= RCC_APB1ENR_PWREN | RCC_APB1ENR_BKPEN;
PWR->CR |= PWR_CR_DBP;
RCC->BDCR=RCC_BDCR_RTCEN //RTC enabled
| RCC_BDCR_LSEON //External 32KHz oscillator enabled
| RCC_BDCR_RTCSEL_0; //Select LSE as clock source for RTC
RCC_SYNC();
#ifdef RTC_CLKOUT_ENABLE
BKP->RTCCR=BKP_RTCCR_CCO; //Output RTC clock/64 on pin
#endif
}
while((RCC->BDCR & RCC_BDCR_LSERDY)==0) ; //Wait for LSE to start
RTC->CRH=RTC_CRH_OWIE | RTC_CRH_ALRIE;
{
ScopedCnf cnf;
RTC->PRLH=0;
RTC->PRLL=1;
}
NVIC_SetPriority(RTC_IRQn,5);
NVIC_EnableIRQ(RTC_IRQn);
}
} //namespace miosix