| /**************************************************************************** |
| * arch/arm/src/kinetis/kinetis_rtc.c |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. The |
| * ASF licenses this file to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance with the |
| * License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| * License for the specific language governing permissions and limitations |
| * under the License. |
| * |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Included Files |
| ****************************************************************************/ |
| |
| #include <nuttx/config.h> |
| #include <nuttx/arch.h> |
| #include <nuttx/irq.h> |
| #include <nuttx/timers/rtc.h> |
| #include <nuttx/spinlock.h> |
| #include <arch/board/board.h> |
| |
| #include <stdlib.h> |
| #include <stdint.h> |
| #include <stdbool.h> |
| #include <stdio.h> |
| #include <assert.h> |
| #include <debug.h> |
| #include <errno.h> |
| |
| #include "arm_internal.h" |
| #include "kinetis_config.h" |
| #include "chip.h" |
| #include "hardware/kinetis_rtc.h" |
| #include "hardware/kinetis_sim.h" |
| #include "kinetis.h" |
| #include "kinetis_alarm.h" |
| |
| #if defined(CONFIG_RTC) |
| |
| /**************************************************************************** |
| * Pre-processor Definitions |
| ****************************************************************************/ |
| |
| #if !defined(BOARD_RTC_CAP) |
| /* Capacitance values 8pF if not already defined */ |
| |
| # define BOARD_RTC_CAP RTC_CR_SC8P | RTC_CR_SC4P |
| #endif |
| |
| /**************************************************************************** |
| * Private Data |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_RTC_HIRES |
| static spinlock_t g_rtc_lock = SP_UNLOCKED; |
| #endif |
| |
| #ifdef CONFIG_RTC_ALARM |
| static alarmcb_t g_alarmcb; |
| static bool rtc_irq_state = false; |
| #endif |
| |
| /**************************************************************************** |
| * Public Data |
| ****************************************************************************/ |
| |
| volatile bool g_rtc_enabled = false; |
| |
| /**************************************************************************** |
| * Private Functions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: rtc_dumpregs |
| * |
| * Description: |
| * Disable RTC write protection |
| * |
| * Input Parameters: |
| * None |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_DEBUG_RTC_INFO |
| static void rtc_dumpregs(const char *msg) |
| { |
| rtcinfo("%s:\n", msg); |
| rtcinfo(" TSR: %08x\n", getreg32(KINETIS_RTC_TSR)); |
| rtcinfo(" TPR: %08x\n", getreg32(KINETIS_RTC_TPR)); |
| rtcinfo(" TAR: %08x\n", getreg32(KINETIS_RTC_TAR)); |
| rtcinfo(" CR: %08x\n", getreg32(KINETIS_RTC_CR)); |
| rtcinfo(" SR: %08x\n", getreg32(KINETIS_RTC_SR)); |
| rtcinfo(" LR: %08x\n", getreg32(KINETIS_RTC_LR)); |
| rtcinfo(" IER: %08x\n", getreg32(KINETIS_RTC_IER)); |
| #if defined(KINETIS_RTC_GEN2) |
| rtcinfo(" TTSR: %08x\n", getreg32(KINETIS_RTC_TTSR)); |
| rtcinfo(" MER: %08x\n", getreg32(KINETIS_RTC_MER)); |
| rtcinfo(" MCLR: %08x\n", getreg32(KINETIS_RTC_MCLR)); |
| rtcinfo(" MCHR: %08x\n", getreg32(KINETIS_RTC_MCHR)); |
| rtcinfo(" WAR: %08x\n", getreg32(KINETIS_RTC_WAR)); |
| rtcinfo(" RAR: %08x\n", getreg32(KINETIS_RTC_RAR)); |
| #endif |
| } |
| #else |
| # define rtc_dumpregs(msg) |
| #endif |
| |
| /**************************************************************************** |
| * Name: rtc_dumptime |
| * |
| * Description: |
| * Disable RTC write protection |
| * |
| * Input Parameters: |
| * None |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_DEBUG_RTC_INFO |
| static void rtc_dumptime(struct tm *tp, const char *msg) |
| { |
| rtcinfo("%s:\n", msg); |
| rtcinfo(" tm_sec: %08x\n", tp->tm_sec); |
| rtcinfo(" tm_min: %08x\n", tp->tm_min); |
| rtcinfo(" tm_hour: %08x\n", tp->tm_hour); |
| rtcinfo(" tm_mday: %08x\n", tp->tm_mday); |
| rtcinfo(" tm_mon: %08x\n", tp->tm_mon); |
| rtcinfo(" tm_year: %08x\n", tp->tm_year); |
| } |
| #else |
| # define rtc_dumptime(tp, msg) |
| #endif |
| |
| /**************************************************************************** |
| * Name: kinetis_rtc_interrupt |
| * |
| * Description: |
| * RTC interrupt service routine |
| * |
| * Input Parameters: |
| * irq - The IRQ number that generated the interrupt |
| * context - Architecture specific register save information. |
| * |
| * Returned Value: |
| * Zero (OK) on success; A negated errno value on failure. |
| * |
| ****************************************************************************/ |
| |
| #if defined(CONFIG_RTC_ALARM) |
| static int kinetis_rtc_interrupt(int irq, void *context, void *arg) |
| { |
| uint16_t rtc_sr; |
| |
| /* if alarm */ |
| |
| rtc_sr = getreg32(KINETIS_RTC_SR); |
| if (rtc_sr & RTC_SR_TAF) |
| { |
| if (g_alarmcb != NULL) |
| { |
| /* Alarm callback */ |
| |
| g_alarmcb(); |
| g_alarmcb = NULL; |
| } |
| } |
| else |
| { |
| /* other interrupts are serious and should leave a turd |
| * |
| * RTC_SR_TIF _TOF _MOF |
| */ |
| |
| rtcwarn("unexp int src=0x%x, num=", rtc_sr); |
| } |
| |
| /* Clear pending flags, disable alarm */ |
| |
| putreg32(0, KINETIS_RTC_TAR); /* Unset alarm (resets flags) */ |
| putreg32(0, KINETIS_RTC_IER); /* Disable alarm interrupt */ |
| |
| return 0; |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Name: rtc_reset |
| * |
| * Description: |
| * Reset the RTC to known state |
| * |
| * Input Parameters: |
| * none |
| * |
| * Returned Value: |
| * none |
| * |
| ****************************************************************************/ |
| |
| static inline void rtc_reset(void) |
| { |
| putreg32((RTC_CR_SWR | getreg32(KINETIS_RTC_CR)), KINETIS_RTC_CR); |
| putreg32((~RTC_CR_SWR & getreg32(KINETIS_RTC_CR)), KINETIS_RTC_CR); |
| |
| /* Set TSR register to 0x1 to avoid the timer invalid (TIF) bit being |
| * set in the SR register |
| */ |
| |
| putreg32(1, KINETIS_RTC_TSR); |
| } |
| |
| /**************************************************************************** |
| * Public Functions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: up_rtc_irqinit |
| * |
| * Description: |
| * Initialize the hardware RTC irq. |
| * This only needs to be called once when first used. |
| * |
| * Input Parameters: |
| * None |
| * |
| * Returned Value: |
| * Zero (OK) on success; a negated errno on failure |
| * |
| ****************************************************************************/ |
| |
| #if defined(CONFIG_RTC_ALARM) |
| int up_rtc_irq_attach(void) |
| { |
| uint32_t rtc_sr; |
| |
| if (!rtc_irq_state) |
| { |
| rtc_irq_state = true; |
| |
| /* Clear TAF if pending */ |
| |
| rtc_sr = getreg32(KINETIS_RTC_SR); |
| if ((rtc_sr & RTC_SR_TAF) != 0) |
| { |
| putreg32(0, KINETIS_RTC_TAR); |
| } |
| |
| /* Enable alarm interrupts. |
| * This will not work if part of up_rtc_initialize() |
| * as it is called very early in initialization BEFORE the interrupt |
| * system will be enabled. All interrupts will disabled later when |
| * the interrupt system is disabled. This must be done later when the |
| * alarm is first set. |
| * |
| * KINETIS_IRQ_RTCS is a separate interrupt for seconds if needed |
| */ |
| |
| irq_attach(KINETIS_IRQ_RTC, kinetis_rtc_interrupt, NULL); |
| up_enable_irq(KINETIS_IRQ_RTC); |
| } |
| |
| return OK; |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Name: up_rtc_initialize |
| * |
| * Description: |
| * Initialize the hardware RTC per the selected configuration. This |
| * function is called once during the OS initialization sequence |
| * |
| * Input Parameters: |
| * None |
| * |
| * Returned Value: |
| * Zero (OK) on success; a negated errno on failure |
| * |
| ****************************************************************************/ |
| |
| int up_rtc_initialize(void) |
| { |
| uint32_t regval; |
| bool rtc_valid = false; |
| |
| /* Enable RTC module */ |
| |
| regval = getreg32(KINETIS_SIM_SCGC6); |
| regval |= SIM_SCGC6_RTC; |
| putreg32(regval, KINETIS_SIM_SCGC6); |
| |
| regval = getreg32(KINETIS_RTC_SR); |
| if (!(regval & RTC_SR_TIF)) |
| { |
| #ifdef KINETIS_RTC_GEN2 |
| /* Check if the one-time initialization of the RTC has already been |
| * performed. We can determine this by checking if the magic number |
| * has been written to the back-up date register DR0. |
| */ |
| |
| regval = getreg32(KINETIS_RTC_MCLR); |
| if ((CONFIG_RTC_MAGICL == regval) && |
| (CONFIG_RTC_MAGICH == getreg32(KINETIS_RTC_MCHR))) |
| #endif |
| { |
| rtc_valid = true; |
| } |
| } |
| |
| if (rtc_valid) |
| { |
| rtcinfo("Do resume\n"); |
| |
| /* RTC already set-up, just resume normal operation */ |
| |
| rtc_dumpregs("Did resume"); |
| } |
| else |
| { |
| rtcinfo("Do setup\n"); |
| rtc_reset(); |
| |
| #ifdef KINETIS_RTC_GEN2 |
| /* Configure the RTC to be initialized */ |
| |
| putreg32(CONFIG_RTC_MAGICL, KINETIS_RTC_MCLR); |
| putreg32(CONFIG_RTC_MAGICH, KINETIS_RTC_MCHR); |
| #endif |
| |
| /* Setup the update mode and supervisor access mode */ |
| |
| putreg32((~(RTC_CR_UM | RTC_CR_SUP) & getreg32(KINETIS_RTC_CR)), |
| KINETIS_RTC_CR); |
| |
| /* Disable counters (just in case) */ |
| |
| putreg32(0, KINETIS_RTC_SR); |
| |
| /* Enable oscillator - must have Vbat else hard fault */ |
| |
| putreg32((BOARD_RTC_CAP | RTC_CR_OSCE), KINETIS_RTC_CR); |
| |
| /* TODO - add capability to accurately tune RTC |
| * This is a per individual board customization and requires |
| * parameters to be configurable and stored in non-volatile eg flash. |
| */ |
| |
| /* TODO: delay some time (1024 cycles? would be 30ms) */ |
| } |
| |
| /* Disable interrupts */ |
| |
| putreg32(0, KINETIS_RTC_IER); |
| |
| /* Reset flags requires writing the seconds register, the following line |
| * avoids altering any stored time value. |
| */ |
| |
| putreg32(getreg32(KINETIS_RTC_TSR), KINETIS_RTC_TSR); |
| |
| /* Enable counters */ |
| |
| putreg32(RTC_SR_TCE, KINETIS_RTC_SR); |
| |
| /* Mark RTC enabled */ |
| |
| g_rtc_enabled = true; |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: up_rtc_time |
| * |
| * Description: |
| * Get the current time in seconds. This is similar to the standard |
| * time() function. This interface is only required if the low-resolution |
| * RTC/counter hardware implementation selected. It is only used by the |
| * RTOS during initialization to set up the system time when CONFIG_RTC is |
| * set but neither CONFIG_RTC_HIRES nor CONFIG_RTC_DATETIME are set. |
| * |
| * Input Parameters: |
| * None |
| * |
| * Returned Value: |
| * The current time in seconds |
| * |
| ****************************************************************************/ |
| |
| #ifndef CONFIG_RTC_HIRES |
| time_t up_rtc_time(void) |
| { |
| return getreg32(KINETIS_RTC_TSR); |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Name: up_rtc_gettime |
| * |
| * Description: |
| * Get the current time from the high resolution RTC clock/counter. This |
| * interface is only supported by the high-resolution RTC/counter hardware |
| * implementation. It is used to replace the system timer. |
| * |
| * Input Parameters: |
| * tp - The location to return the high resolution time value. |
| * |
| * Returned Value: |
| * Zero (OK) on success; a negated errno on failure |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_RTC_HIRES |
| int up_rtc_gettime(struct timespec *tp) |
| { |
| irqstate_t flags; |
| uint32_t seconds; |
| uint32_t prescaler; |
| uint32_t prescaler2; |
| |
| /* Get prescaler and seconds register. this is in a loop which ensures that |
| * registers will be re-read if during the reads the prescaler has |
| * wrapped-around. |
| */ |
| |
| flags = spin_lock_irqsave(&g_rtc_lock); |
| do |
| { |
| prescaler = getreg32(KINETIS_RTC_TPR); |
| seconds = getreg32(KINETIS_RTC_TSR); |
| prescaler2 = getreg32(KINETIS_RTC_TPR); |
| } |
| while (prescaler > prescaler2); |
| |
| spin_unlock_irqrestore(&g_rtc_lock, flags); |
| |
| /* Build seconds + nanoseconds from seconds and prescaler register */ |
| |
| tp->tv_sec = seconds; |
| tp->tv_nsec = prescaler * (1000000000 / CONFIG_RTC_FREQUENCY); |
| return OK; |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Name: up_rtc_settime |
| * |
| * Description: |
| * Set the RTC to the provided time. All RTC implementations must be able |
| * to set their time based on a standard timespec. |
| * |
| * Input Parameters: |
| * tp - the time to use |
| * |
| * Returned Value: |
| * Zero (OK) on success; a negated errno on failure |
| * |
| ****************************************************************************/ |
| |
| int up_rtc_settime(const struct timespec *tp) |
| { |
| irqstate_t flags; |
| uint32_t seconds; |
| uint32_t prescaler; |
| |
| seconds = tp->tv_sec; |
| prescaler = tp->tv_nsec / (1000000000 / CONFIG_RTC_FREQUENCY); |
| |
| flags = spin_lock_irqsave(&g_rtc_lock); |
| |
| putreg32(0, KINETIS_RTC_SR); /* Disable counter */ |
| |
| putreg32(prescaler, KINETIS_RTC_TPR); /* Always write prescaler first */ |
| putreg32(seconds, KINETIS_RTC_TSR); |
| |
| putreg32(RTC_SR_TCE, KINETIS_RTC_SR); /* Re-enable counter */ |
| |
| spin_unlock_irqrestore(&g_rtc_lock, flags); |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: kinetis_rtc_setalarm |
| * |
| * Description: |
| * Set up an alarm. |
| * |
| * Input Parameters: |
| * tp - the time to set the alarm |
| * callback - the function to call when the alarm expires. |
| * |
| * Returned Value: |
| * Zero (OK) on success; a negated errno on failure |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_RTC_ALARM |
| int kinetis_rtc_setalarm(const struct timespec *tp, alarmcb_t callback) |
| { |
| /* Is there already something waiting on the ALARM? */ |
| |
| if (g_alarmcb == NULL) |
| { |
| /* No.. Save the callback function pointer */ |
| |
| g_alarmcb = callback; |
| |
| /* ensure irq is attached */ |
| |
| up_rtc_irq_attach(); |
| |
| /* Enable and set RTC alarm */ |
| |
| putreg32(tp->tv_sec, KINETIS_RTC_TAR); /* Set alarm (also resets |
| * flags) */ |
| putreg32(RTC_IER_TAIE, KINETIS_RTC_IER); /* Enable alarm interrupt */ |
| |
| rtc_dumpregs("set alarmtime"); |
| |
| return OK; |
| } |
| else |
| { |
| return -EBUSY; |
| } |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Name: kinetis_rtc_cancelalarm |
| * |
| * Description: |
| * Cancel a pending alarm alarm |
| * |
| * Input Parameters: |
| * none |
| * |
| * Returned Value: |
| * Zero (OK) on success; a negated errno on failure |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_RTC_ALARM |
| int kinetis_rtc_cancelalarm(void) |
| { |
| if (g_alarmcb != NULL) |
| { |
| /* Cancel the global callback function */ |
| |
| g_alarmcb = NULL; |
| |
| /* Unset the alarm */ |
| |
| putreg32(0, KINETIS_RTC_IER); /* Disable alarm interrupt */ |
| return OK; |
| } |
| else |
| { |
| return -ENODATA; |
| } |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Name: kinetis_rtc_rdalarm |
| * |
| * Description: |
| * Query an alarm configured in hardware. |
| * |
| * Input Parameters: |
| * tp - Location to return the timer match register. |
| * |
| * Returned Value: |
| * Zero (OK) on success; a negated errno on failure |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_RTC_ALARM |
| int kinetis_rtc_rdalarm(struct timespec *tp) |
| { |
| DEBUGASSERT(tp != NULL); |
| |
| tp->tv_sec = getreg32(KINETIS_RTC_TAR); |
| tp->tv_nsec = 0; |
| return OK; |
| } |
| #endif |
| |
| #endif /* KINETIS_RTC */ |