From 0d47ae4fca3fc66ee0872756eb645ce88c5844cf Mon Sep 17 00:00:00 2001 From: Kiril Zyapkov Date: Wed, 21 Mar 2018 15:29:59 +0100 Subject: [PATCH] drivers: rtc: add support for STM32 RTC Add support for the STM32 Real-Time-Clock leveraging the LL APIs Signed-off-by: Johannes Hutter Signed-off-by: Kiril Zyapkov --- .../st_stm32/stm32l4/Kconfig.defconfig.series | 7 + arch/arm/soc/st_stm32/stm32l4/soc.h | 6 + drivers/rtc/CMakeLists.txt | 1 + drivers/rtc/Kconfig | 1 + drivers/rtc/Kconfig.stm32_rtc | 67 ++++ drivers/rtc/rtc_ll_stm32.c | 300 ++++++++++++++++++ 6 files changed, 382 insertions(+) create mode 100644 drivers/rtc/Kconfig.stm32_rtc create mode 100644 drivers/rtc/rtc_ll_stm32.c diff --git a/arch/arm/soc/st_stm32/stm32l4/Kconfig.defconfig.series b/arch/arm/soc/st_stm32/stm32l4/Kconfig.defconfig.series index f502913eefc..74df81fd509 100644 --- a/arch/arm/soc/st_stm32/stm32l4/Kconfig.defconfig.series +++ b/arch/arm/soc/st_stm32/stm32l4/Kconfig.defconfig.series @@ -27,4 +27,11 @@ config ENTROPY_STM32_RNG endif # ENTROPY_GENERATOR +if RTC + +config RTC_STM32 + default y + +endif # RTC + endif # SOC_SERIES_STM32L4X diff --git a/arch/arm/soc/st_stm32/stm32l4/soc.h b/arch/arm/soc/st_stm32/stm32l4/soc.h index a952560ed9a..d744898bbe8 100644 --- a/arch/arm/soc/st_stm32/stm32l4/soc.h +++ b/arch/arm/soc/st_stm32/stm32l4/soc.h @@ -62,6 +62,12 @@ #include #endif +#ifdef CONFIG_RTC_STM32 +#include +#include +#include +#endif + #ifdef CONFIG_USB /* Required to remove USB transceiver supply isolation */ #include diff --git a/drivers/rtc/CMakeLists.txt b/drivers/rtc/CMakeLists.txt index e1db65c0334..cece6c255a0 100644 --- a/drivers/rtc/CMakeLists.txt +++ b/drivers/rtc/CMakeLists.txt @@ -2,4 +2,5 @@ zephyr_library() zephyr_library_sources_ifdef(CONFIG_RTC_QMSI rtc_qmsi.c) zephyr_library_sources_ifdef(CONFIG_RTC_MCUX rtc_mcux.c) +zephyr_library_sources_ifdef(CONFIG_RTC_STM32 rtc_ll_stm32.c) zephyr_library_sources_ifdef(CONFIG_USERSPACE rtc_handlers.c) diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index 723a16aa088..7f7f1a85480 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -27,5 +27,6 @@ comment "RTC drivers" source "drivers/rtc/Kconfig.qmsi" source "drivers/rtc/Kconfig.mcux_rtc" +source "drivers/rtc/Kconfig.stm32_rtc" endif # RTC diff --git a/drivers/rtc/Kconfig.stm32_rtc b/drivers/rtc/Kconfig.stm32_rtc new file mode 100644 index 00000000000..64b89fd8c4f --- /dev/null +++ b/drivers/rtc/Kconfig.stm32_rtc @@ -0,0 +1,67 @@ +# Kconfig - STM32 RTC configuration options +# +# Copyright (c) 2018 Workaround GmbH +# Copyright (c) 2018 Allterco Robotics +# +# SPDX-License-Identifier: Apache-2.0 +# + +config RTC_STM32 + bool "STM32 RTC Driver" + depends on SOC_FAMILY_STM32 + select USE_STM32_LL_RTC + select USE_STM32_LL_PWR + select USE_STM32_LL_RCC + select USE_STM32_LL_EXTI + select NEWLIB_LIBC + help + Build RTC driver for STM32x4 SoCs. Tested on STM32L4 series. + +choice RTC_STM32_CLOCK_SRC + bool "RTC clock source" + depends on RTC_STM32 + +config RTC_STM32_CLOCK_LSI + bool "LSI" + help + Use LSI as RTC clock + +config RTC_STM32_CLOCK_LSE + bool "LSE" + help + Use LSE as RTC clock + +endchoice #RTC_STM32_CLOCK_SRC + +choice RTC_STM32_LSE_DRIVE + prompt "LSE oscillator drive capability" + depends on RTC_STM32_CLOCK_LSE + +config RTC_STM32_LSE_DRIVE_LOW + bool "Low" + help + Xtal mode lower driving capability + +config RTC_STM32_LSE_DRIVE_MEDIUMLOW + bool "Medium Low" + help + Xtal mode medium low driving capability + +config RTC_STM32_LSE_DRIVE_MEDIUMHIGH + bool "Medium High" + help + Xtal mode medium high driving capability + +config RTC_STM32_LSE_DRIVE_HIGH + bool "High" + help + Xtal mode higher driving capability + +endchoice + +config RTC_STM32_LSE_DRIVE_STRENGTH + hex + default 0x00000000 if RTC_STM32_LSE_DRIVE_LOW + default 0x00000008 if RTC_STM32_LSE_DRIVE_MEDIUMLOW + default 0x00000010 if RTC_STM32_LSE_DRIVE_MEDIUMHIGH + default 0x00000018 if RTC_STM32_LSE_DRIVE_HIGH diff --git a/drivers/rtc/rtc_ll_stm32.c b/drivers/rtc/rtc_ll_stm32.c new file mode 100644 index 00000000000..d88ef24978f --- /dev/null +++ b/drivers/rtc/rtc_ll_stm32.c @@ -0,0 +1,300 @@ +/* + * Copyright (c) 2018 Workaround GmbH + * Copyright (c) 2018 Allterco Robotics + * + * SPDX-License-Identifier: Apache-2.0 + * + * Source file for the STM32 RTC driver + * + */ + +#include + +#include +#include +#include +#include +#include +#include + +#define EPOCH_OFFSET 946684800 + +struct rtc_stm32_config { + struct stm32_pclken pclken; + LL_RTC_InitTypeDef ll_rtc_config; +}; + +struct rtc_stm32_data { + void (*cb_fn)(struct device *dev); + struct k_sem sem; +}; + + +#define DEV_DATA(dev) ((struct rtc_stm32_data *const)(dev)->driver_data) +#define DEV_SEM(dev) (&DEV_DATA(dev)->sem) +#define DEV_CFG(dev) \ +((const struct rtc_stm32_config * const)(dev)->config->config_info) + +static int rtc_stm32_set_alarm(struct device *dev, const u32_t alarm_val); +static void rtc_stm32_irq_config(struct device *dev); + +static void rtc_stm32_enable(struct device *dev) +{ + LL_RCC_EnableRTC(); +} + +static void rtc_stm32_disable(struct device *dev) +{ + LL_RCC_DisableRTC(); +} + +static u32_t rtc_stm32_read(struct device *dev) +{ + struct tm now = { 0 }; + time_t ts; + u32_t rtc_date, rtc_time; + + /* Read time and date registers */ + rtc_time = LL_RTC_TIME_Get(RTC); + rtc_date = LL_RTC_DATE_Get(RTC); + + /* Convert calendar datetime to UNIX timestamp */ + now.tm_year = 100 + __LL_RTC_CONVERT_BCD2BIN( + __LL_RTC_GET_YEAR(rtc_date)); + now.tm_mon = __LL_RTC_CONVERT_BCD2BIN(__LL_RTC_GET_MONTH(rtc_date)); + now.tm_mday = __LL_RTC_CONVERT_BCD2BIN(__LL_RTC_GET_DAY(rtc_date)); + + now.tm_hour = __LL_RTC_CONVERT_BCD2BIN(__LL_RTC_GET_HOUR(rtc_time)); + now.tm_min = __LL_RTC_CONVERT_BCD2BIN(__LL_RTC_GET_MINUTE(rtc_time)); + now.tm_sec = __LL_RTC_CONVERT_BCD2BIN(__LL_RTC_GET_SECOND(rtc_time)); + + ts = mktime(&now); + + /* Return number of seconds since 2000-01-01 00:00:00 */ + ts -= EPOCH_OFFSET; + + return (u32_t)ts; +} + +static int rtc_stm32_set_alarm(struct device *dev, const u32_t alarm_val) +{ + struct tm alarm_tm; + time_t alarm_ts; + LL_RTC_AlarmTypeDef rtc_alarm; + + u32_t now = rtc_stm32_read(dev); + + /* The longest period we can match for universally is the + duration of the shortest month */ + if ((alarm_val - now) > (RTC_ALARM_DAY * 28)) { + return -ENOTSUP; + } + + /* Convert seconds since 2000-01-01 00:00:00 to calendar datetime */ + alarm_ts = alarm_val; + alarm_ts += EPOCH_OFFSET; + gmtime_r(&alarm_ts, &alarm_tm); + + /* Apply ALARM_A */ + rtc_alarm.AlarmTime.TimeFormat = LL_RTC_TIME_FORMAT_AM_OR_24; + rtc_alarm.AlarmTime.Hours = alarm_tm.tm_hour; + rtc_alarm.AlarmTime.Minutes = alarm_tm.tm_min; + rtc_alarm.AlarmTime.Seconds = alarm_tm.tm_sec; + + rtc_alarm.AlarmMask = LL_RTC_ALMA_MASK_NONE; + rtc_alarm.AlarmDateWeekDaySel = LL_RTC_ALMA_DATEWEEKDAYSEL_DATE; + rtc_alarm.AlarmDateWeekDay = alarm_tm.tm_mday; + + LL_RTC_DisableWriteProtection(RTC); + LL_RTC_ALMA_Disable(RTC); + + if (LL_RTC_ALMA_Init(RTC, LL_RTC_FORMAT_BIN, &rtc_alarm) != SUCCESS) { + return -EIO; + } + + LL_RTC_DisableWriteProtection(RTC); + + LL_RTC_ALMA_Enable(RTC); + LL_RTC_ClearFlag_ALRA(RTC); + LL_RTC_EnableIT_ALRA(RTC); + + LL_RTC_EnableWriteProtection(RTC); + + return 0; +} + +static int rtc_stm32_set_config(struct device *dev, struct rtc_config *cfg) +{ + int result = 0; + time_t init_ts = 0; + struct tm init_tm = { 0 }; + + LL_RTC_DateTypeDef rtc_date = { 0 }; + LL_RTC_TimeTypeDef rtc_time = { 0 }; + + /* Convert seconds since 2000-01-01 00:00:00 to calendar datetime */ + init_ts = cfg->init_val; + init_ts += EPOCH_OFFSET; + + gmtime_r(&init_ts, &init_tm); + + rtc_date.Year = init_tm.tm_year % 100; + rtc_date.Month = init_tm.tm_mon; + rtc_date.Day = init_tm.tm_mday; + rtc_date.WeekDay = init_tm.tm_wday + 1; + + rtc_time.TimeFormat = LL_RTC_TIME_FORMAT_AM_OR_24; + rtc_time.Hours = init_tm.tm_hour; + rtc_time.Minutes = init_tm.tm_min; + rtc_time.Seconds = init_tm.tm_sec; + + k_sem_take(DEV_SEM(dev), K_FOREVER); + + if (cfg->cb_fn != NULL) { + DEV_DATA(dev)->cb_fn = cfg->cb_fn; + } + + if (LL_RTC_DATE_Init(RTC, LL_RTC_FORMAT_BIN, &rtc_date) != SUCCESS) { + result = -EIO; + goto fin; + } + + if (LL_RTC_TIME_Init(RTC, LL_RTC_FORMAT_BIN, &rtc_time) != SUCCESS) { + result = -EIO; + goto fin; + } + + if (cfg->alarm_enable) { + rtc_stm32_set_alarm(dev, cfg->alarm_val); + } + +fin: + k_sem_give(DEV_SEM(dev)); + + return result; +} + +static u32_t rtc_stm32_get_pending_int(struct device *dev) +{ + return LL_RTC_IsActiveFlag_ALRA(RTC) != 0; +} + +void rtc_stm32_isr(void *arg) +{ + struct device *const dev = (struct device *)arg; + + if (LL_RTC_IsActiveFlag_ALRA(RTC) != 0) { + + if (DEV_DATA(dev)->cb_fn != NULL) { + DEV_DATA(dev)->cb_fn(dev); + } + + LL_RTC_ClearFlag_ALRA(RTC); + LL_RTC_DisableIT_ALRA(RTC); + } + + LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_18); +} + +static int rtc_stm32_init(struct device *dev) +{ + struct device *clk = device_get_binding(STM32_CLOCK_CONTROL_NAME); + const struct rtc_stm32_config *cfg = DEV_CFG(dev); + + __ASSERT_NO_MSG(clk); + + k_sem_init(DEV_SEM(dev), 1, UINT_MAX); + DEV_DATA(dev)->cb_fn = NULL; + + clock_control_on(clk, (clock_control_subsys_t *) &cfg->pclken); + + LL_PWR_EnableBkUpAccess(); + LL_RCC_ForceBackupDomainReset(); + LL_RCC_ReleaseBackupDomainReset(); + +#if defined(CONFIG_RTC_STM32_CLOCK_LSI) + + LL_RCC_LSI_Enable(); + while (LL_RCC_LSI_IsReady() != 1) + ; + LL_RCC_SetRTCClockSource(LL_RCC_RTC_CLKSOURCE_LSI); + +#else /* CONFIG_RTC_STM32_CLOCK_LSE */ + + LL_RCC_LSE_SetDriveCapability(CONFIG_RTC_STM32_LSE_DRIVE_STRENGTH); + LL_RCC_LSE_Enable(); + + /* Wait untill LSE is ready */ + while (LL_RCC_LSE_IsReady() != 1) + ; + + LL_RCC_SetRTCClockSource(LL_RCC_RTC_CLKSOURCE_LSE); + +#endif /* CONFIG_RTC_STM32_CLOCK_SRC */ + + LL_RCC_EnableRTC(); + + if (LL_RTC_DeInit(RTC) != SUCCESS) { + return -EIO; + } + + if (LL_RTC_Init(RTC, ((LL_RTC_InitTypeDef *) + &cfg->ll_rtc_config)) != SUCCESS) { + return -EIO; + } + + LL_RTC_EnableShadowRegBypass(RTC); + + LL_EXTI_EnableIT_0_31(LL_EXTI_LINE_18); + LL_EXTI_EnableRisingTrig_0_31(LL_EXTI_LINE_18); + + rtc_stm32_irq_config(dev); + + return 0; +} + +static struct rtc_stm32_data rtc_data; + +static const struct rtc_stm32_config rtc_config = { + .pclken = { + .enr = LL_APB1_GRP1_PERIPH_PWR, + .bus = STM32_CLOCK_BUS_APB1, + }, + .ll_rtc_config = { + .HourFormat = LL_RTC_HOURFORMAT_24HOUR, + +#if defined(CONFIG_RTC_STM32_CLOCK_LSI) + + /* prescaler values for LSI @ 32 KHz */ + .AsynchPrescaler = 0x7F, + .SynchPrescaler = 0x00F9, + +#else /* CONFIG_RTC_STM32_CLOCK_LSE */ + + /* prescaler values for LSE @ 32768 Hz */ + .AsynchPrescaler = 0x7F, + .SynchPrescaler = 0x00FF, +#endif + + }, +}; + +static const struct rtc_driver_api rtc_api = { + .enable = rtc_stm32_enable, + .disable = rtc_stm32_disable, + .read = rtc_stm32_read, + .set_config = rtc_stm32_set_config, + .set_alarm = rtc_stm32_set_alarm, + .get_pending_int = rtc_stm32_get_pending_int, +}; + +DEVICE_AND_API_INIT(rtc_stm32, CONFIG_RTC_0_NAME, &rtc_stm32_init, + &rtc_data, &rtc_config, PRE_KERNEL_1, + CONFIG_KERNEL_INIT_PRIORITY_DEVICE, &rtc_api); + +static void rtc_stm32_irq_config(struct device *dev) +{ + IRQ_CONNECT(CONFIG_RTC_0_IRQ, CONFIG_RTC_0_IRQ_PRI, + rtc_stm32_isr, DEVICE_GET(rtc_stm32), 0); + irq_enable(CONFIG_RTC_0_IRQ); +}