/**************************************************************************** * arch/arm/src/cxd56xx/cxd56_rtc.c * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include "clock/clock.h" #include "arm_internal.h" #include "cxd56_rtc.h" #include "hardware/cxd5602_topreg.h" #include "hardware/cxd5602_memorymap.h" #include "hardware/cxd5602_backupmem.h" #include "hardware/cxd56_rtc.h" /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ /* Configuration ************************************************************/ #ifdef CONFIG_RTC_HIRES # ifndef CONFIG_RTC_FREQUENCY # define CONFIG_RTC_FREQUENCY 32768 # endif # if CONFIG_RTC_FREQUENCY != 32768 # error "Only lo-res CONFIG_RTC_FREQUENCY of 32.768kHz is supported" # endif #else # ifndef CONFIG_RTC_FREQUENCY # define CONFIG_RTC_FREQUENCY 1 # endif # if CONFIG_RTC_FREQUENCY != 1 # error "Only lo-res CONFIG_RTC_FREQUENCY of 1Hz is supported" # endif #endif /* convert seconds to 64bit counter value running at 32kHz */ #define SEC_TO_CNT(sec) ((uint64_t)(((uint64_t)(sec)) << 15)) /* convert nano-seconds to 32kHz counter less than 1 second */ #define NSEC_TO_PRECNT(nsec) \ (((nsec) / (NSEC_PER_SEC / CONFIG_RTC_FREQUENCY)) & 0x7fff) #define MAGIC_RTC_SAVE (0x12aae190077a80ull) /* RTC clcok stable waiting time (interval x retry) */ #define RTC_CLOCK_CHECK_INTERVAL (200) /* milliseconds */ #define RTC_CLOCK_CHECK_MAX_RETRY (15) /**************************************************************************** * Private Types ****************************************************************************/ #ifdef CONFIG_RTC_ALARM struct alm_cbinfo_s { volatile alm_callback_t ac_cb; /* Client callback function */ volatile void *ac_arg; /* Argument to pass with the callback function */ }; #endif struct rtc_backup_s { uint64_t magic; int64_t reserved0; int64_t offset; /* offset time from RTC HW value */ int64_t reserved1; }; /**************************************************************************** * Private Data ****************************************************************************/ /* Callback to use when the alarm expires */ #ifdef CONFIG_RTC_ALARM static struct alm_cbinfo_s g_alarmcb[RTC_ALARM_LAST]; #endif /* Saved data for persistent RTC time */ static struct rtc_backup_s *g_rtc_save; /**************************************************************************** * Public Data ****************************************************************************/ volatile bool g_rtc_enabled = false; /**************************************************************************** * Private Functions ****************************************************************************/ /**************************************************************************** * Name: rtc_dumptime * * Description: * Dump RTC * * Input Parameters: * None * * Returned Value: * None * ****************************************************************************/ #ifdef CONFIG_DEBUG_RTC static void rtc_dumptime(const struct timespec *tp, const char *msg) { struct tm tm; gmtime_r(&tp->tv_sec, &tm); rtcinfo("%s:\n", msg); rtcinfo("RTC %u.%09u\n", tp->tv_sec, tp->tv_nsec); rtcinfo("%4d/%02d/%02d %02d:%02d:%02d\n", tm.tm_year, tm.tm_mon, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec); } #else # define rtc_dumptime(tp, msg) #endif /**************************************************************************** * Name: cxd56_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. * ****************************************************************************/ #ifdef CONFIG_RTC_ALARM static int cxd56_rtc_interrupt(int irq, void *context, void *arg) { struct alm_cbinfo_s *cbinfo; alm_callback_t cb; void *cb_arg; uint32_t source; uint32_t clear; int id; int ret = OK; /* interrupt clear */ source = getreg32(CXD56_RTC0_ALMFLG); if (source & RTCREG_ALM0_MASK) { id = RTC_ALARM0; clear = source & RTCREG_ALM0_MASK; } else { rtcerr("ERROR: Invalid ALARM\n"); return ret; } putreg32(clear, CXD56_RTC0_ALMCLR); putreg32(0, CXD56_RTC0_ALMOUTEN(id)); cbinfo = &g_alarmcb[id]; if (cbinfo->ac_cb != NULL) { /* Alarm callback */ cb = cbinfo->ac_cb; cb_arg = (void *)cbinfo->ac_arg; cbinfo->ac_cb = NULL; cbinfo->ac_arg = NULL; cb(cb_arg, id); } return 0; } #endif /**************************************************************************** * Name: cxd56_rtc_initialize * * Description: * Actually initialize the hardware RTC. This function is called in the * initialization sequence, thereafter may be called when wdog timer is * expired. * * Input Parameters: * arg: Not used * ****************************************************************************/ static void cxd56_rtc_initialize(wdparm_t arg) { struct timespec ts; #ifdef CONFIG_CXD56_RTC_LATEINIT static struct wdog_s s_wdog; static int s_retry = 0; /* Check whether RTC clock source selects the external RTC and the * synchronization from the external RTC is completed. */ g_rtc_save = (struct rtc_backup_s *)BKUP->rtc_saved_data; if (((getreg32(CXD56_TOPREG_CKSEL_ROOT) & STATUS_RTC_MASK) != STATUS_RTC_SEL) || (g_rtc_save->magic != MAGIC_RTC_SAVE)) { /* Retry until RTC clock is stable */ if (s_retry++ < RTC_CLOCK_CHECK_MAX_RETRY) { rtcinfo("retry count: %d\n", s_retry); if (OK == wd_start(&s_wdog, MSEC2TICK(RTC_CLOCK_CHECK_INTERVAL), cxd56_rtc_initialize, 0)) { /* Again, this function is called recursively */ return; } } rtcerr("ERROR: Use inaccurate RCRTC instead of RTC\n"); } /* RTC clock is stable, or give up using the external RTC */ wd_cancel(&s_wdog); #endif #ifdef CONFIG_RTC_ALARM /* Configure RTC interrupt to catch overflow and alarm interrupts. */ irq_attach(CXD56_IRQ_RTC0_A0, cxd56_rtc_interrupt, NULL); irq_attach(CXD56_IRQ_RTC_INT, cxd56_rtc_interrupt, NULL); up_enable_irq(CXD56_IRQ_RTC0_A0); up_enable_irq(CXD56_IRQ_RTC_INT); #endif /* If saved data is invalid, clear offset information */ if (g_rtc_save->magic != MAGIC_RTC_SAVE) { g_rtc_save->offset = 0; } if (g_rtc_save->offset == 0) { /* Keep the system operating time before RTC is enabled. */ clock_systime_timespec(&ts); } #ifdef CONFIG_RTC_HIRES /* Synchronize the base time to the RTC time */ up_rtc_gettime(&g_basetime); #endif if (g_rtc_save->offset == 0) { /* Reflect the system operating time to RTC offset data. */ g_rtc_save->offset = SEC_TO_CNT(ts.tv_sec) | NSEC_TO_PRECNT(ts.tv_nsec); } /* Make it possible to use the RTC timer functions */ g_rtc_enabled = true; } /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** * 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) { cxd56_rtc_initialize(0); 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) { uint64_t count; count = cxd56_rtc_count(); count += g_rtc_save->offset; count >>= 15; /* convert to 1sec resolution */ return (time_t)count / CONFIG_RTC_FREQUENCY; } #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) { uint64_t count; count = cxd56_rtc_count(); count += g_rtc_save->offset; /* Then we can save the time in seconds and fractional seconds. */ tp->tv_sec = count / CONFIG_RTC_FREQUENCY; tp->tv_nsec = (count % CONFIG_RTC_FREQUENCY) * (NSEC_PER_SEC / CONFIG_RTC_FREQUENCY); rtc_dumptime(tp, "Getting time"); 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; uint64_t count; flags = spin_lock_irqsave(NULL); #ifdef RTC_DIRECT_CONTROL /* wait until previous write request is completed */ while (RTCREG_WREQ_BUSYA_MASK & getreg32(CXD56_RTC0_WRREGREQ)); putreg32(tp->tv_sec, CXD56_RTC0_WRREGPOSTCNT); putreg32(NSEC_TO_PRECNT(tp->tv_nsec), CXD56_RTC0_WRREGPRECNT); putreg32(RTCREG_WREQ_BUSYA_MASK, CXD56_RTC0_WRREGREQ); /* wait until write request reflected */ while (RTCREG_WREQ_BUSYB_MASK & getreg32(CXD56_RTC0_WRREGREQ)); #else /* Only save the difference from HW raw value */ count = SEC_TO_CNT(tp->tv_sec) | NSEC_TO_PRECNT(tp->tv_nsec); g_rtc_save->offset = (int64_t)count - (int64_t)cxd56_rtc_count(); #endif spin_unlock_irqrestore(NULL, flags); rtc_dumptime(tp, "Setting time"); return OK; } /**************************************************************************** * Name: cxd56_rtc_count * * Description: * Get RTC raw counter value * * Returned Value: * 64bit counter value running at 32kHz * ****************************************************************************/ uint64_t cxd56_rtc_count(void) { uint64_t val; irqstate_t flags; /* The pre register is latched with reading the post rtcounter register, * so these registers always have to been read in the below order, * 1st post -> 2nd pre, and should be operated in atomic. */ flags = spin_lock_irqsave(NULL); val = (uint64_t)getreg32(CXD56_RTC0_RTPOSTCNT) << 15; val |= getreg32(CXD56_RTC0_RTPRECNT); spin_unlock_irqrestore(NULL, flags); return val; } /**************************************************************************** * Name: cxd56_rtc_almcount * * Description: * Get RTC raw alarm counter value * * Returned Value: * 64bit alarm counter value running at 32kHz * ****************************************************************************/ #ifdef CONFIG_RTC_ALARM uint64_t cxd56_rtc_almcount(void) { uint64_t val; irqstate_t flags; flags = spin_lock_irqsave(NULL); val = (uint64_t)getreg32(CXD56_RTC0_SETALMPOSTCNT(0)) << 15; val |= (getreg32(CXD56_RTC0_SETALMPRECNT(0)) & 0x7fff); spin_unlock_irqrestore(NULL, flags); return val; } #endif /**************************************************************************** * Name: cxd56_rtc_setalarm * * Description: * Set up an alarm. * * Input Parameters: * alminfo - Information about the alarm configuration. * * Returned Value: * Zero (OK) on success; a negated errno on failure * ****************************************************************************/ #ifdef CONFIG_RTC_ALARM int cxd56_rtc_setalarm(struct alm_setalarm_s *alminfo) { struct alm_cbinfo_s *cbinfo; irqstate_t flags; int ret = -EBUSY; int id; uint64_t count; uint32_t mask; ASSERT(alminfo != NULL); DEBUGASSERT(RTC_ALARM_LAST > alminfo->as_id); /* Set the alarm in hardware and enable interrupts */ id = alminfo->as_id; cbinfo = &g_alarmcb[id]; if (g_rtc_enabled && (cbinfo->ac_cb == NULL)) { /* The set the alarm */ flags = spin_lock_irqsave(NULL); cbinfo->ac_cb = alminfo->as_cb; cbinfo->ac_arg = alminfo->as_arg; count = SEC_TO_CNT(alminfo->as_time.tv_sec) | NSEC_TO_PRECNT(alminfo->as_time.tv_nsec); count -= g_rtc_save->offset; /* clear previsous setting */ mask = RTCREG_ALM0_ERR_FLAG_MASK | RTCREG_ALM0_FLAG_MASK; mask <<= id; putreg32(mask, CXD56_RTC0_ALMCLR); /* wait until previous alarm request is completed */ while (RTCREG_ASET_BUSY_MASK & getreg32(CXD56_RTC0_SETALMPRECNT(id))); putreg32((uint32_t)(count >> 15), CXD56_RTC0_SETALMPOSTCNT(id)); putreg32((uint32_t)(count & 0x7fff), CXD56_RTC0_SETALMPRECNT(id)); while (RTCREG_ALM_BUSY_MASK & getreg32(CXD56_RTC0_ALMOUTEN(id))); putreg32(RTCREG_ALM_EN_MASK | RTCREG_ALM_ERREN_MASK, CXD56_RTC0_ALMOUTEN(id)); while (RTCREG_ALM_BUSY_MASK & getreg32(CXD56_RTC0_ALMOUTEN(id))); spin_unlock_irqrestore(NULL, flags); rtc_dumptime(&alminfo->as_time, "New Alarm time"); ret = OK; } return ret; } #endif /**************************************************************************** * Name: cxd56_rtc_cancelalarm * * Description: * Cancel an alaram. * * Input Parameters: * alarmid - Identifies the alarm to be cancelled * * Returned Value: * Zero (OK) on success; a negated errno on failure * ****************************************************************************/ #ifdef CONFIG_RTC_ALARM int cxd56_rtc_cancelalarm(enum alm_id_e alarmid) { struct alm_cbinfo_s *cbinfo; irqstate_t flags; int ret = -ENODATA; uint32_t mask; DEBUGASSERT(RTC_ALARM_LAST > alarmid); /* Cancel the alarm in hardware and clear interrupts */ cbinfo = &g_alarmcb[alarmid]; if (cbinfo->ac_cb != NULL) { /* Unset the alarm */ flags = spin_lock_irqsave(NULL); cbinfo->ac_cb = NULL; while (RTCREG_ALM_BUSY_MASK & getreg32(CXD56_RTC0_ALMOUTEN(alarmid))); putreg32(0, CXD56_RTC0_ALMOUTEN(alarmid)); while (RTCREG_ALM_BUSY_MASK & getreg32(CXD56_RTC0_ALMOUTEN(alarmid))); /* wait until previous alarm request is completed */ while (RTCREG_ASET_BUSY_MASK & getreg32(CXD56_RTC0_SETALMPRECNT(alarmid))); /* clear the alarm counter */ putreg32(0, CXD56_RTC0_SETALMPOSTCNT(alarmid)); putreg32(0, CXD56_RTC0_SETALMPRECNT(alarmid)); while (RTCREG_ASET_BUSY_MASK & getreg32(CXD56_RTC0_SETALMPRECNT(alarmid))); /* wait until the interrupt flag is clear */ mask = RTCREG_ALM0_ERR_FLAG_MASK | RTCREG_ALM0_FLAG_MASK; mask <<= alarmid; while (mask & getreg32(CXD56_RTC0_ALMFLG)) { putreg32(mask, CXD56_RTC0_ALMCLR); } spin_unlock_irqrestore(NULL, flags); ret = OK; } return ret; } #endif