/************************************************************************************ * arch/arm/src/imxrt/imxrt_lpsrtc.c * * Copyright (C) 2018 Gregory Nutt. All rights reserved. * Author: Gregory Nutt * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * 3. Neither the name NuttX nor the names of its contributors may be * used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * ************************************************************************************/ /************************************************************************************ * Included Files ************************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include "arm_arch.h" #include "hardware/imxrt_snvs.h" #include "imxrt_periphclks.h" #include "imxrt_lpsrtc.h" #include "imxrt_hprtc.h" #ifdef CONFIG_IMXRT_SNVS_HPRTC /************************************************************************************ * Private Data ************************************************************************************/ /* Callback to use when the alarm expires */ #if defined(CONFIG_RTC_ALARM) && defined(CONFIG_RTC_DRIVER) static hprtc_alarm_callback_t g_hprtc_alarmcb; #endif /************************************************************************************ * Public Data ************************************************************************************/ /* Variable determines the state of the RTC module. * * After initialization value is set to 'true' if RTC starts successfully. * The value can be changed to false also during operation if RTC for * some reason fails. */ volatile bool g_rtc_enabled; #if !defined(CONFIG_IMXRT_SNVS_LPSRTC) && defined(CONFIG_RTC_DRIVER) bool g_hprtc_timset; /* True: time has been set since power up */ #endif /************************************************************************************ * Private Functions ************************************************************************************/ /************************************************************************************ * Name: imxrt_snvs_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) && defined(CONFIG_RTC_DRIVER) static int imxrt_snvs_interrupt(int irq, void *context, FAR void *arg) { hprtc_alarm_callback_t cb; /* The alarm is the only interrupt enabled */ DEBUGASSERT((getreg32(IMXRT_SNVS_HPSR) & SNVS_HPSR_HPTA) != 0); /* Sample and clear the callback */ cb = g_hprtc_alarmcb; g_hprtc_alarmcb = NULL; /* Disable the alarm, alarm interrupts, clear pending alarm interrupt status */ imxrt_hprtc_alarmdisable(); /* Perform the callback */ cb(); return 0; } #endif /************************************************************************************ * Name: imxrt_hprtc_enable * * Description: * Enable/start the HPRTC time counter. * * Input Parameters: * None * * Returned Value: * None * ************************************************************************************/ static void imxrt_hprtc_enable(void) { uint32_t regval; /* Enable the HPRTC */ regval = getreg32(IMXRT_SNVS_HPCR); regval |= SNVS_HPCR_RTCEN; putreg32(regval, IMXRT_SNVS_HPCR); while ((getreg32(IMXRT_SNVS_HPCR) & SNVS_HPCR_RTCEN) == 0) { } } /************************************************************************************ * Name: imxrt_hprtc_alarmenable * * Description: * Enable alarm interrupts. This is currently only used internally at the time * that alarm interrupts are enabled. * * Input Parameters: * None * * Returned Value: * None * ************************************************************************************/ #if defined(CONFIG_RTC_ALARM) && defined(CONFIG_RTC_DRIVER) static void imxrt_hprtc_alarmenable(void) { uint32_t regval; /* Enable the alarm */ regval = getreg32(IMXRT_SNVS_HPCR); regval |= SNVS_HPCR_HPTAEN; putreg32(regval, IMXRT_SNVS_HPCR); /* Enable alarm interrupts at the NVIC */ up_enable_irq(IMXRT_IRQ_SNVS); } #endif /************************************************************************************ * Public Functions ************************************************************************************/ /************************************************************************************ * Functions used only for HPRTC ************************************************************************************/ #ifndef CONFIG_IMXRT_SNVS_LPSRTC /************************************************************************************ * 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 * ************************************************************************************/ time_t up_rtc_time(void) { /* Delegate to imxrt_hprtc_time() */ return imxrt_hprtc_time(); } /************************************************************************************ * 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(FAR const struct timespec *ts) { uint32_t regval; DEBUGASSERT(ts != NULL); /* Disable the HPRTC */ regval = getreg32(IMXRT_SNVS_HPCR); regval &= ~SNVS_HPCR_RTCEN; putreg32(regval, IMXRT_SNVS_HPCR); while ((getreg32(IMXRT_SNVS_HPCR) & SNVS_HPCR_RTCEN) != 0) { } /* Set HPRTC time in seconds. We could do better by accounting for the * ts->tv_nsec unused residual. * * IMXRT_SNVS_HPTAMR Bits 9-14 = 15-bit MSB of alarm setting. * IMXRT_SNVS_HPTALR 32-bit LSB of alarm setting. */ putreg32((uint32_t)ts->tv_sec >> 17, IMXRT_SNVS_HPRTCMR); putreg32((uint32_t)ts->tv_sec << 15, IMXRT_SNVS_HPRTCLR); #ifdef CONFIG_RTC_DRIVER /* The time has been set */ g_hprtc_timset = true; #endif /* Unconditionally re-enable the HPRTC */ imxrt_hprtc_enable(); return OK; } #endif /* !CONFIG_IMXRT_SNVS_LPSRTC */ /************************************************************************************ * Logic Common to LPSRTC and HPRTC ************************************************************************************/ /************************************************************************************ * 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) { int ret; #ifdef CONFIG_IMXRT_SNVS_LPSRTC /* Perform LPSRTC initialization */ ret = imxrt_lpsrtc_initialize(); if (ret < 0) { rtcerr("ERROR: imxrt_lpsrtc_initialize failed: %d\n", ret); return ret; } #else /* Initialize the HPRTC */ ret = imxrt_hprtc_initialize(); if (ret < 0) { rtcerr("ERROR: imxrt_hprtc_initialize failed: %d\n", ret); return ret; } /* Start the HPRTC */ imxrt_hprtc_enable(); #endif #if defined(CONFIG_IMXRT_SNVS_HPRTC) && defined(CONFIG_RTC_ALARM) && \ defined(CONFIG_RTC_DRIVER) /* Attach the alarm interrupt handler */ ret = irq_attach(IMXRT_IRQ_SNVS, imxrt_snvs_interrupt, NULL); if (ret < 0) { rtcerr("ERROR: Failed to attach to IRQ%d\n", IMXRT_IRQ_SNVS); return ret; } #endif g_rtc_enabled = true; return OK; } /************************************************************************************ * Name: imxrt_hprtc_initialize * * Description: * Initialize the LPSRTC per the selected configuration. This function is called * via up_rtc_initialize (see imxrt_hprtc.c). * * Input Parameters: * None * * Returned Value: * Zero (OK) on success; a negated errno on failure * ************************************************************************************/ int imxrt_hprtc_initialize(void) { uint32_t regval; /* Enable clocking to the the SNVS HP module. * Clock is on in run mode, but off in WAIT and STOP modes. */ imxrt_clockrun_snvs_hp(); /* Enable non-privileged access */ regval = getreg32(IMXRT_SNVS_HPCOMR); regval |= (SNVS_HPCOMR_NPSWAEN | SNVS_HPCOMR_SWSV); putreg32(regval, IMXRT_SNVS_HPCOMR); /* TODO: Set the periodic interrupt frequency */ putreg32(SNVS_HPCR_PIFREQ(0), IMXRT_SNVS_HPCR); #ifdef CONFIG_IMXRTC_HPRTC_CALENABLE /* Set the HPRTC calibration value */ regval = getreg32(IMXRT_SNVS_HPCR); regval &= ~SNVS_HPCR_HPCALBVAL_MASK; regval |= SNVS_HPCR_HPCALBVAL(CONFIG_IMXRTC_HPRTC_CALVALUE); regval |= SNVS_HPCR_HPCALBEN; putreg32(regval, IMXRT_SNVS_HPCR); #endif return OK; } /************************************************************************************ * Name: imxrt_hprtc_synchronize * * Description: * Synchronize the HPRTC to the LPSRTC and enable the HPRTC timer. * * Input Parameters: * None * * Returned Value: * None * ************************************************************************************/ #ifdef CONFIG_IMXRT_SNVS_LPSRTC void imxrt_hprtc_synchronize(void) { uint32_t regval; uint32_t hpcr; /* Make sure that the RTC is disabled (it should be at this point in the LPSRTC * initialization sequence). */ hpcr = getreg32(IMXRT_SNVS_HPCR); regval = hpcr; regval &= ~SNVS_HPCR_RTCEN; putreg32(regval, IMXRT_SNVS_HPCR); while ((getreg32(IMXRT_SNVS_HPCR) & SNVS_HPCR_RTCEN) != 0) { } /* Synchronize to the LPSRTC */ regval = getreg32(IMXRT_SNVS_HPCR); regval |= SNVS_HPCR_HPTS; putreg32(regval, IMXRT_SNVS_HPCR); /* Unconditionally enable the HPRTC */ imxrt_hprtc_enable(); } #endif /************************************************************************************ * Name: imxrt_hprtc_time * * Description: * Get the current time in seconds. This is the underlying implementation of the * up_rtc_time() function that is used by the RTOS during initialization to set up * the system time. * * Input Parameters: * None * * Returned Value: * The current time in seconds * ************************************************************************************/ uint32_t imxrt_hprtc_time(void) { uint32_t seconds; uint32_t verify = 0; /* Do consecutive reads until value is correct */ do { /* IMXRT_SNVS_HPRTCMR: Bits 9-14 = 15-bit MSB of counter. * IMXRT_SNVS_HPRTCLR: 32-bit LSB of counter. * * REVISIT: This could be modified to support CONFIG_RTC_HI_RES */ seconds = verify; verify = (getreg32(IMXRT_SNVS_HPRTCMR) << 17) | (getreg32(IMXRT_SNVS_HPRTCLR) >> 15); } while (verify != seconds); return seconds; } /************************************************************************************ * Name: imxrt_hprtc_getalarm * * Description: * Get the current alarm setting in seconds. This is only used by the lower half * RTC driver. * * Input Parameters: * None * * Returned Value: * The current alarm setting in seconds * ************************************************************************************/ #if defined(CONFIG_RTC_ALARM) && defined(CONFIG_RTC_DRIVER) uint32_t imxrt_hprtc_getalarm(void) { /* Return the alarm setting in seconds * * IMXRT_SNVS_HPTAMR Bits 9-14 = 15-bit MSB of alarm setting. * IMXRT_SNVS_HPTALR 32-bit LSB of alarm setting. */ return (getreg32(IMXRT_SNVS_HPTAMR) << 17) | (getreg32(IMXRT_SNVS_HPTALR) >> 15); } #endif /************************************************************************************ * Name: imxrt_hprtc_setalarm * * Description: * Set the alarm (in seconds) and enable alarm interrupts. This is only used by * the lower half RTC driver. * * Input Parameters: * None * * Returned Value: * The current alarm setting in seconds * ************************************************************************************/ #if defined(CONFIG_RTC_ALARM) && defined(CONFIG_RTC_DRIVER) int imxrt_hprtc_setalarm(FAR struct timespec *ts, hprtc_alarm_callback_t cb) { irqstate_t flags; uint32_t regval; uint32_t now; DEBUGASSERT(ts != NULL && cb != NULL); /* Disable interrupts so that the following sequence of events will not be * interrupted or preempted. */ flags = spin_lock_irqsave(NULL); now = imxrt_hprtc_time(); /* Return error if the alarm time has passed. * NOTES: (1) This will fail, of course, when the number of seconds since * epoch wraps. (2) We could do better by accounting for the ts->tv_nsec * unused residual. */ if ((uint32_t)ts->tv_sec <= now) { rtcwarn("WARNING: time is in the past\n"); return -EINVAL; } /* Disable the RTC alarm interrupt */ regval = getreg32(IMXRT_SNVS_HPCR); regval &= ~SNVS_HPCR_HPTAEN; putreg32(regval, IMXRT_SNVS_HPCR); while ((getreg32(IMXRT_SNVS_HPCR) & SNVS_HPCR_HPTAEN) != 0) { } /* Save the alarm callback */ g_hprtc_alarmcb = cb; /* Set alarm in seconds * * IMXRT_SNVS_HPTAMR Bits 9-14 = 15-bit MSB of alarm setting. * IMXRT_SNVS_HPTALR 32-bit LSB of alarm setting. */ putreg32((uint32_t)ts->tv_sec >> 17, IMXRT_SNVS_HPTAMR); putreg32((uint32_t)ts->tv_sec << 15, IMXRT_SNVS_HPTALR); /* Unconditionally enable the RTC alarm interrupt */ imxrt_hprtc_alarmenable(); spin_unlock_irqrestore(NULL, flags); return OK; } #endif /************************************************************************************ * Name: imxrt_hprtc_alarmdisable * * Description: * Disable alarm interrupts. Used internally after the receipt of the alarm * interrupt. Also called by the lower-half RTC driver in order to cancel an * alarm. * * Input Parameters: * None * * Returned Value: * None * ************************************************************************************/ #if defined(CONFIG_RTC_ALARM) && defined(CONFIG_RTC_DRIVER) void imxrt_hprtc_alarmdisable(void) { uint32_t regval; /* Disable alarm interrupts at the NVIC */ up_disable_irq(IMXRT_IRQ_SNVS); /* Disable the alarm function */ regval = getreg32(IMXRT_SNVS_HPCR); regval &= ~SNVS_HPCR_HPTAEN; putreg32(regval, IMXRT_SNVS_HPCR); /* Clear any pending alarm interrupts */ putreg32(SNVS_HPSR_HPTA, IMXRT_SNVS_HPSR); } #endif #endif /* CONFIG_IMXRT_SNVS_HPRTC */