| /**************************************************************************** |
| * arch/arm/src/sama5/sam_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 <nuttx/config.h> |
| |
| #include <nuttx/arch.h> |
| #include <nuttx/irq.h> |
| |
| #include <time.h> |
| #include <assert.h> |
| #include <errno.h> |
| #include <debug.h> |
| |
| #include <nuttx/arch.h> |
| #include <nuttx/irq.h> |
| #include <nuttx/wqueue.h> |
| |
| #include <arch/board/board.h> |
| |
| #include "arm_internal.h" |
| #include "sam_rtc.h" |
| |
| #ifdef CONFIG_RTC |
| |
| /**************************************************************************** |
| * Pre-processor Definitions |
| ****************************************************************************/ |
| |
| /* Configuration ************************************************************/ |
| |
| /* This RTC implementation supports only date/time RTC hardware */ |
| |
| #ifndef CONFIG_RTC_DATETIME |
| # error "CONFIG_RTC_DATETIME must be set to use this driver" |
| #endif |
| |
| #ifdef CONFIG_RTC_HIRES |
| # error "CONFIG_RTC_HIRES must NOT be set with this driver" |
| #endif |
| |
| #if defined(CONFIG_RTC_ALARM) && !defined(CONFIG_SCHED_WORKQUEUE) |
| # error CONFIG_RTC_ALARM requires CONFIG_SCHED_WORKQUEUE |
| #endif |
| |
| #define RTC_MAGIC 0xdeadbeef |
| |
| /**************************************************************************** |
| * Private Data |
| ****************************************************************************/ |
| |
| /* Callback to use when the alarm expires */ |
| |
| #ifdef CONFIG_RTC_ALARM |
| static alarmcb_t g_alarmcb; |
| struct work_s g_alarmwork; |
| #endif |
| |
| /**************************************************************************** |
| * Public Data |
| ****************************************************************************/ |
| |
| /* g_rtc_enabled is set true after the RTC has successfully initialized */ |
| |
| 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(" CR: %08x\n", getreg32(SAM_RTC_CR)); |
| rtcinfo(" MR: %08x\n", getreg32(SAM_RTC_MR)); |
| rtcinfo(" TIMR: %08x\n", getreg32(SAM_RTC_TIMR)); |
| rtcinfo(" CALR: %08x\n", getreg32(SAM_RTC_CALR)); |
| rtcinfo(" TIMALR: %08x\n", getreg32(SAM_RTC_TIMALR)); |
| rtcinfo(" CALALR: %08x\n", getreg32(SAM_RTC_CALALR)); |
| rtcinfo(" SR: %08x\n", getreg32(SAM_RTC_SR)); |
| rtcinfo(" IMR: %08x\n", getreg32(SAM_RTC_IMR)); |
| rtcinfo(" VER: %08x\n", getreg32(SAM_RTC_VER)); |
| } |
| #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: rtc_bin2bcd |
| * |
| * Description: |
| * Converts a 2 digit binary to BCD format |
| * |
| * Input Parameters: |
| * value - The byte to be converted. |
| * |
| * Returned Value: |
| * The value in BCD representation |
| * |
| ****************************************************************************/ |
| |
| static uint32_t rtc_bin2bcd(int value) |
| { |
| uint32_t msbcd = 0; |
| |
| while (value >= 10) |
| { |
| msbcd++; |
| value -= 10; |
| } |
| |
| return (msbcd << 4) | value; |
| } |
| |
| /**************************************************************************** |
| * Name: rtc_bin2bcd |
| * |
| * Description: |
| * Convert from 2 digit BCD to binary. |
| * |
| * Input Parameters: |
| * value - The BCD value to be converted. |
| * |
| * Returned Value: |
| * The value in binary representation |
| * |
| ****************************************************************************/ |
| |
| static int rtc_bcd2bin(uint32_t value) |
| { |
| uint32_t tens = (value >> 4) * 10; |
| return (int)(tens + (value & 0x0f)); |
| } |
| |
| /**************************************************************************** |
| * Name: rtc_worker |
| * |
| * Description: |
| * Perform alarm callback |
| * |
| * Input Parameters: |
| * Standard work callbacks |
| * |
| * Returned Value: |
| * Zero (OK) on success; A negated errno value on failure. |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_RTC_ALARM |
| static void rtc_worker(void *arg) |
| { |
| /* Sample once (atomically) */ |
| |
| alarmcb_t alarmcb = g_alarmcb; |
| |
| /* Is there a subscriber to the alarm? */ |
| |
| if (alarmcb) |
| { |
| /* Yes.. perform the callback */ |
| |
| alarmcb(); |
| } |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Name: 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 rtc_interrupt(int irq, void *context, void *arg) |
| { |
| int ret; |
| |
| /* Schedule the callback to occur on the low-priority worker thread */ |
| |
| DEBUGASSERT(work_available(&g_alarmwork)); |
| ret = work_queue(LPWORK, &g_alarmwork, rtc_worker, NULL, 0); |
| if (ret < 0) |
| { |
| rtcerr("ERROR: work_queue failed: %d\n", ret); |
| } |
| |
| /* Disable any further alarm interrupts */ |
| |
| putreg32(RTC_IDR_ALRDIS, SAM_RTC_IDR); |
| |
| /* Clear any pending alarm interrupts */ |
| |
| putreg32(RTC_SCCR_ALRCLR, SAM_RTC_SCCR); |
| return OK; |
| } |
| #endif |
| |
| /**************************************************************************** |
| * 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) |
| { |
| uint32_t ver; |
| |
| rtc_dumpregs("On reset"); |
| |
| /* No clocking setup need be performed. |
| * The Real-time Clock is continuously clocked at 32768 Hz (SCLK). |
| * The Power Management Controller has no effect on RTC behavior. |
| */ |
| |
| /* Set the 24 hour format */ |
| |
| putreg32(0, SAM_RTC_MR); |
| |
| /* Has the RTC been initialized? */ |
| |
| ver = getreg32(SAM_RTC_VER); |
| g_rtc_enabled = ((ver & (RTC_VER_NVTIM | RTC_VER_NVCAL)) == 0); |
| |
| #ifdef CONFIG_RTC_ALARM |
| /* Then attach the ALARM interrupt handler */ |
| |
| irq_attach(SAM_PID_SYS, rtc_interrupt, NULL); |
| |
| /* Should RTC alarm interrupt be enabled at the peripheral? Let's |
| * assume so for now. Let's say yes if the time is valid and a valid |
| * alarm has been programmed. |
| */ |
| |
| if (g_rtc_enabled && (ver & (RTC_VER_NVTIMALR | RTC_VER_NVCALALR)) == 0) |
| { |
| /* Enable the alarm interrupt at the RTC */ |
| |
| putreg32(RTC_IER_ALREN, SAM_RTC_IER); |
| } |
| else |
| { |
| /* Disable the alarm interrupt at the RTC */ |
| |
| putreg32(RTC_IDR_ALRDIS, SAM_RTC_IDR); |
| } |
| |
| /* Enable SYSC interrupts at the AIC in any event */ |
| |
| up_enable_irq(SAM_PID_SYS); |
| |
| #endif |
| |
| rtc_dumpregs("After Initialization"); |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: up_rtc_getdatetime |
| * |
| * Description: |
| * Get the current date and time from the date/time RTC. This interface |
| * is only supported by the date/time RTC hardware implementation. |
| * It is used to replace the system timer. It is only used by the RTOS |
| * during initialization to set up the system time when CONFIG_RTC and |
| * CONFIG_RTC_DATETIME are selected (and CONFIG_RTC_HIRES is not). |
| * |
| * NOTE: |
| * Some date/time RTC hardware is capability of sub-second accuracy. |
| * That sub-second accuracy is lost in this interface. However, since |
| * the system time is reinitialized on each power-up/reset, there will |
| * be no timing inaccuracy in the long run. |
| * |
| * Input Parameters: |
| * tp - The location to return the high resolution time value. |
| * |
| * Returned Value: |
| * Zero (OK) on success; a negated errno on failure |
| * |
| ****************************************************************************/ |
| |
| int up_rtc_getdatetime(struct tm *tp) |
| { |
| uint32_t timr; |
| uint32_t calr; |
| uint32_t cent; |
| uint32_t year; |
| uint32_t tmp; |
| |
| /* Sample the data time registers. |
| * There is a race condition here... If we sample the time just before |
| * midnight on December 31, the date could be wrong because the day rolled |
| * over while were sampling. |
| */ |
| |
| do |
| { |
| calr = getreg32(SAM_RTC_CALR); |
| timr = getreg32(SAM_RTC_TIMR); |
| tmp = getreg32(SAM_RTC_CALR); |
| } |
| while (tmp != calr); |
| |
| rtc_dumpregs("Reading Time"); |
| |
| /* Convert the RTC time register fields to struct tm format. |
| * |
| * struct tm TIMR register |
| * tm_sec 0-61* SEC (0-59) |
| * tm_min 0-59 MIN (0-59) |
| * tm_hour 0-23 HOUR (0-23) |
| * |
| * *To allow for leap seconds. But these never actually happen. |
| */ |
| |
| tmp = (timr & RTC_TIMR_SEC_MASK) >> RTC_TIMR_SEC_SHIFT; |
| tp->tm_sec = rtc_bcd2bin(tmp); |
| |
| tmp = (timr & RTC_TIMR_MIN_MASK) >> RTC_TIMR_MIN_SHIFT; |
| tp->tm_min = rtc_bcd2bin(tmp); |
| |
| tmp = (timr & RTC_TIMR_HOUR_MASK) >> RTC_TIMR_HOUR_SHIFT; |
| tp->tm_hour = rtc_bcd2bin(tmp); |
| |
| /* Convert the RTC date register fields to struct tm format. |
| * |
| * struct tm TIMR register |
| * tm_mday 1-31 DATE (1-31) |
| * tm_wday 0-6 DAY (1-7) ** |
| * tm_mon 0-11 MONTH: (1-12) |
| * tm_year * YEAR (0-99) |
| * CENT (19-20) |
| * |
| * *Years since 1900 |
| * **Day of the week is not supported |
| */ |
| |
| tmp = (calr & RTC_CALR_DATE_MASK) >> RTC_CALR_DATE_SHIFT; |
| tp->tm_mday = rtc_bcd2bin(tmp); |
| |
| tmp = (calr & RTC_CALR_MONTH_MASK) >> RTC_CALR_MONTH_SHIFT; |
| tp->tm_mon = rtc_bcd2bin(tmp) - 1; |
| |
| tmp = (calr & RTC_CALR_CENT_MASK) >> RTC_CALR_CENT_SHIFT; |
| cent = rtc_bcd2bin(tmp); |
| tmp = (calr & RTC_CALR_YEAR_MASK) >> RTC_CALR_YEAR_SHIFT; |
| year = rtc_bcd2bin(tmp); |
| tp->tm_year = cent * 100 + year - 1900; |
| |
| rtc_dumptime(tp, "Returning"); |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * 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) |
| { |
| struct tm newtime; |
| uint32_t regval; |
| uint32_t timr; |
| uint32_t calr; |
| uint32_t cent; |
| uint32_t year; |
| |
| /* Break out the time values |
| * (note that the time is set only to units of seconds) |
| */ |
| |
| gmtime_r(&tp->tv_sec, &newtime); |
| rtc_dumptime(&newtime, "Setting time"); |
| |
| /* Then write the broken out values to the RTC */ |
| |
| /* Convert the struct tm format to RTC time register fields. |
| * |
| * struct tm TIMR register |
| * tm_sec 0-61* SEC (0-59) |
| * tm_min 0-59 MIN (0-59) |
| * tm_hour 0-23 HOUR (0-23) |
| * |
| * *To allow for leap seconds. But these never actually happen. |
| */ |
| |
| timr = (rtc_bin2bcd(newtime.tm_sec) << RTC_TIMR_SEC_SHIFT) & |
| RTC_TIMR_SEC_MASK; |
| timr |= (rtc_bin2bcd(newtime.tm_min) << RTC_TIMR_MIN_SHIFT) & |
| RTC_TIMR_MIN_MASK; |
| timr |= (rtc_bin2bcd(newtime.tm_hour) << RTC_TIMR_HOUR_SHIFT) & |
| RTC_TIMR_HOUR_MASK; |
| |
| /* Convert the struct tm format to RTC date register fields. |
| * |
| * struct tm CALR register |
| * tm_mday 1-31 DATE (1-31) |
| * tm_wday 0-6 DAY (1-7) ** |
| * tm_mon 0-11 MONTH: (1-12) |
| * tm_year * YEAR (0-99) |
| * CENT (19-20) |
| * |
| * *Years since 1900 |
| * **Day of the week is not supported. Set to Monday. |
| */ |
| |
| calr = (rtc_bin2bcd(newtime.tm_mday) << RTC_CALR_DATE_SHIFT) & |
| RTC_CALR_DATE_MASK; |
| calr |= (rtc_bin2bcd(1) << RTC_CALR_DAY_SHIFT) & |
| RTC_CALR_DAY_MASK; |
| calr |= (rtc_bin2bcd(newtime.tm_mon + 1) << RTC_CALR_MONTH_SHIFT) & |
| RTC_CALR_MONTH_MASK; |
| |
| cent = newtime.tm_year / 100 + 19; |
| year = newtime.tm_year % 100; |
| |
| calr |= (rtc_bin2bcd(year) << RTC_CALR_YEAR_SHIFT) & |
| RTC_CALR_YEAR_MASK; |
| calr |= (rtc_bin2bcd(cent) << RTC_CALR_CENT_SHIFT) & |
| RTC_CALR_CENT_MASK; |
| |
| /* Stop RTC time and date counting */ |
| |
| regval = getreg32(SAM_RTC_CR); |
| regval |= (RTC_CR_UPDTIM | RTC_CR_UPDCAL); |
| putreg32(regval, SAM_RTC_CR); |
| |
| /* Wait until the RTC has stopped so that we can update the time */ |
| |
| while ((getreg32(SAM_RTC_SR) & RTC_SR_ACKUPD) != RTC_SR_ACKUPD); |
| |
| /* Clear the ACKUPD bit in the status register */ |
| |
| putreg32(RTC_SCCR_ACKCLR, SAM_RTC_SCCR); |
| |
| /* Set the new date */ |
| |
| putreg32(calr, SAM_RTC_CALR); |
| |
| /* Write the new time */ |
| |
| putreg32(timr, SAM_RTC_TIMR); |
| |
| /* Resume RTC date/time counting */ |
| |
| regval = getreg32(SAM_RTC_CR); |
| regval &= ~(RTC_CR_UPDTIM | RTC_CR_UPDCAL); |
| putreg32(regval, SAM_RTC_CR); |
| |
| /* Clear the SEC status in the SR */ |
| |
| regval = getreg32(SAM_RTC_SCCR); |
| regval = RTC_SCCR_SECCLR; |
| putreg32(regval, SAM_RTC_SCCR); |
| |
| /* The RTC should now be enabled */ |
| |
| g_rtc_enabled = ((getreg32(SAM_RTC_VER) & |
| (RTC_VER_NVTIM | RTC_VER_NVCAL)) == 0); |
| DEBUGASSERT(g_rtc_enabled); |
| |
| rtc_dumpregs("New time setting"); |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: sam_rtc_setalarm |
| * |
| * Description: |
| * Set up an alarm. |
| * Up to two alarms can be supported (ALARM A and ALARM B). |
| * |
| * 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 sam_rtc_setalarm(const struct timespec *tp, alarmcb_t callback) |
| { |
| struct tm newalarm; |
| irqstate_t flags; |
| uint32_t timalr; |
| uint32_t calalr; |
| int ret = -EBUSY; |
| |
| /* Is there already something waiting on the ALARM? */ |
| |
| flags = enter_critical_section(); |
| if (g_alarmcb == NULL) |
| { |
| /* No.. Save the callback function pointer */ |
| |
| g_alarmcb = callback; |
| |
| /* Clear any pending alarm interrupts */ |
| |
| putreg32(RTC_SCCR_ALRCLR, SAM_RTC_SCCR); |
| |
| /* Break out the time values (note that the time is set only to units |
| * of seconds) |
| */ |
| |
| gmtime_r(&tp->tv_sec, &newalarm); |
| rtc_dumptime(&newalarm, "Setting alarm"); |
| |
| /* Then write the broken out values to the RTC */ |
| |
| /* Convert the struct tm format to RTC time register fields. |
| * |
| * struct tm TIMALR register |
| * tm_sec 0-61* SEC (0-59) |
| * tm_min 0-59 MIN (0-59) |
| * tm_hour 0-23 HOUR (0-23) |
| * |
| * *To allow for leap seconds. But these never actually happen. |
| */ |
| |
| timalr = (rtc_bin2bcd(newalarm.tm_sec) << RTC_TIMALR_SEC_SHIFT) & |
| RTC_TIMALR_SEC_MASK; |
| timalr |= (rtc_bin2bcd(newalarm.tm_min) << RTC_TIMALR_MIN_SHIFT) & |
| RTC_TIMALR_MIN_MASK; |
| timalr |= (rtc_bin2bcd(newalarm.tm_hour) << RTC_TIMALR_HOUR_SHIFT) & |
| RTC_TIMALR_HOUR_MASK; |
| timalr |= (RTC_TIMALR_SECEN | RTC_TIMALR_MINEN | RTC_TIMALR_HOUREN); |
| |
| /* Convert the struct tm format to RTC date register fields. |
| * |
| * struct tm CALALR register |
| * tm_mday 1-31 DATE (1-31) |
| * tm_wday 0-6 DAY (1-7) ** |
| * tm_mon 0-11 MONTH: (1-12) |
| * tm_year * YEAR (0-99) |
| * CENT (19-20) |
| * |
| * *Years since 1900 |
| * **Day of the week is not supported |
| */ |
| |
| calalr = (rtc_bin2bcd(newalarm.tm_mday) << |
| RTC_CALALR_DATE_SHIFT) & |
| RTC_CALALR_DATE_MASK; |
| calalr |= (rtc_bin2bcd(newalarm.tm_mon + 1) << |
| RTC_CALALR_MONTH_SHIFT) & |
| RTC_CALALR_MONTH_MASK; |
| calalr |= (RTC_CALALR_MTHEN | RTC_CALALR_DATEEN); |
| |
| /* Set the new date */ |
| |
| putreg32(calalr, SAM_RTC_CALALR); |
| |
| /* Write the new time */ |
| |
| putreg32(timalr, SAM_RTC_TIMALR); |
| |
| DEBUGASSERT((getreg32(SAM_RTC_VER) & RTC_VER_NVTIMALR) == 0); |
| DEBUGASSERT((getreg32(SAM_RTC_VER) & RTC_VER_NVCALALR) == 0); |
| |
| rtc_dumpregs("New alarm setting"); |
| |
| /* Enable alarm interrupts */ |
| |
| putreg32(RTC_IER_ALREN, SAM_RTC_IER); |
| ret = OK; |
| } |
| |
| leave_critical_section(flags); |
| return ret; |
| } |
| #endif |
| |
| #endif /* CONFIG_RTC */ |