blob: 2652c03cfc255d3360ba51180f36911ef226248f [file] [log] [blame]
/****************************************************************************
* arch/arm/src/lc823450/lc823450_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 <nuttx/timers/rtc.h>
#ifdef CONFIG_RTC_ALARM
# include <nuttx/alarm.h>
#endif
#include <nuttx/power/pm.h>
#include <time.h>
#include <errno.h>
#include <debug.h>
#include <string.h>
#include "lc823450_syscontrol.h"
#include "arm_internal.h"
#include "clock/clock.h"
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
#define RTC_REGBASE 0x4008e000
#define RTC_SEC (RTC_REGBASE + 0x000)
#define RTC_MIN (RTC_REGBASE + 0x004)
#define RTC_HOUR (RTC_REGBASE + 0x008)
#define RTC_WEEK (RTC_REGBASE + 0x00c)
#define RTC_DAY (RTC_REGBASE + 0x010)
#define RTC_MONTH (RTC_REGBASE + 0x014)
#define RTC_YEAR (RTC_REGBASE + 0x018)
#define RTC_CENT (RTC_REGBASE + 0x01c)
#define RTC_SECALM (RTC_REGBASE + 0x020)
#define RTC_MINALM (RTC_REGBASE + 0x024)
#define RTC_HOURALM (RTC_REGBASE + 0x028)
#define RTC_WEEKALM (RTC_REGBASE + 0x02c)
#define RTC_DAYALM (RTC_REGBASE + 0x030)
#define RTC_MONTHALM (RTC_REGBASE + 0x034)
#define RTC_PRSEL (RTC_REGBASE + 0x038)
#define RTC_RTCINT (RTC_REGBASE + 0x03c)
#define RTC_RTCINT_AIE 5
#define RTC_RTCINT_SET 7
#define RTC_DST (RTC_REGBASE + 0x040)
#define RTC_RTCSTAT (RTC_REGBASE + 0x044)
#define RTC_RTCSTAT_UIP 0
#define RTC_RTCSTAT_UF 4
#define RTC_RTCSTAT_PCLR 7
#define RTC_TEST (RTC_REGBASE + 0x048)
#define RTC_PTN32BIT0 (RTC_REGBASE + 0x04c)
#define RTC_PTN32BIT1 (RTC_REGBASE + 0x050)
#define RTC_PTN32BIT2 (RTC_REGBASE + 0x054)
#define RTC_PTN32BIT3 (RTC_REGBASE + 0x058)
#define RTC_PTN32BIT0_VAL 0x12
#define RTC_PTN32BIT1_VAL 0x34
#define RTC_PTN32BIT2_VAL 0x56
#define RTC_PTN32BIT3_VAL 0x78
#define RTC_VPTN32BIT0 (RTC_REGBASE + 0x05c)
#define RTC_VPTN32BIT1 (RTC_REGBASE + 0x060)
#define RTC_VPTN32BIT2 (RTC_REGBASE + 0x064)
#define RTC_VPTN32BIT3 (RTC_REGBASE + 0x068)
#define RTC_VPTN32BIT0_VAL 0xa7
#define RTC_VPTN32BIT1_VAL 0x58
#define RTC_VPTN32BIT2_VAL 0x3c
#define RTC_VPTN32BIT3_VAL 0xf1
#define RTC_VDET (RTC_REGBASE + 0x06c)
#define RTC_VDET_VDET 0x01
#define RTC_RTCINTCNT (RTC_REGBASE + 0x070)
#ifndef timespec_sub
#define timespec_sub(a, b, c) /* c = a - b */ \
do \
{\
(c)->tv_nsec = (a)->tv_nsec - (b)->tv_nsec; \
(c)->tv_sec = (a)->tv_sec - (b)->tv_sec; \
if ((c)->tv_nsec < 0) \
{\
(c)->tv_nsec += (1000 * 1000 * 1000); \
(c)->tv_sec--; \
}\
}\
while (0)
#endif /* timespec_sub */
/****************************************************************************
* Private Types
****************************************************************************/
#ifdef CONFIG_RTC_SAVE_DEFAULT
struct rtc_default
{
int sig;
#define RTC_DEFAULT_SIGNATURE 0x12345678
struct tm tm;
};
#endif
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
#ifdef CONFIG_RTC_SAVE_DEFAULT
static void rtc_pmnotify(struct pm_callback_s *cb, enum pm_state_e pmstate);
#endif /* CONFIG_RTC_SAVE_DEFAULT */
/****************************************************************************
* Private Data
****************************************************************************/
#ifdef CONFIG_RTC_ALARM
static alarmcb_t g_alarmcb;
#endif
#ifdef CONFIG_RTC_SAVE_DEFAULT
static struct pm_callback_s pm_cb =
{
.notify = rtc_pmnotify,
};
#endif /* CONFIG_RTC_SAVE_DEFAULT */
#ifdef CONFIG_RTC_DIV
static int cboot = 1;
#endif /* CONFIG_RTC_DIV */
static struct timespec lastupdate_mono;
static struct timespec lastupdate_rtc;
/****************************************************************************
* Public Data
****************************************************************************/
volatile bool g_rtc_enabled = false;
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: tm_divider
****************************************************************************/
#ifdef CONFIG_RTC_DIV
static void tm_divider(struct tm *tm, int divn, int divm)
{
time_t tt;
tt = timegm(tm);
tt = (time_t) ((uint64_t)tt * divn / divm);
gmtime_r(&tt, tm);
}
#endif /* CONFIG_RTC_DIV */
/****************************************************************************
* Name: rtc_pmnotify
****************************************************************************/
#ifdef CONFIG_RTC_SAVE_DEFAULT
static void rtc_pmnotify(struct pm_callback_s *cb, enum pm_state_e pmstate)
{
struct tm tm;
switch (pmstate)
{
case PM_SLEEP:
case PM_SHUTDOWN:
/* RTC is not set yet. */
if (getreg8(RTC_PTN32BIT0) != RTC_PTN32BIT0_VAL ||
getreg8(RTC_PTN32BIT1) != RTC_PTN32BIT1_VAL ||
getreg8(RTC_PTN32BIT2) != RTC_PTN32BIT2_VAL ||
getreg8(RTC_PTN32BIT3) != RTC_PTN32BIT3_VAL)
{
break;
}
up_rtc_getdatetime(&tm);
up_rtc_set_default_datetime(&tm);
break;
default:
break;
}
}
#endif /* CONFIG_RTC_SAVE_DEFAULT */
/****************************************************************************
* 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)
{
struct tm tm;
up_rtc_getdatetime(&tm);
rtcinfo("RTCSTAT = 0x%02x (%04d/%02d/%02d %02d:%02d:%02d)\n",
getreg8(RTC_RTCSTAT),
tm.tm_year + 1900,
tm.tm_mon + 1,
tm.tm_mday,
tm.tm_hour,
tm.tm_min,
tm.tm_sec);
/* Disable IRQ */
putreg8(0, RTC_RTCINT);
/* Clear interrupt status */
putreg8(0, RTC_RTCSTAT);
putreg8(1 << RTC_RTCSTAT_PCLR, RTC_RTCSTAT);
if (g_alarmcb)
{
g_alarmcb();
}
return OK;
}
#endif /* CONFIG_RTC_ALARM */
/****************************************************************************
* 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
*
****************************************************************************/
static int up_rtc_getdatetime_main(struct tm *tp)
{
#ifdef CONFIG_RTC_DIV
tp->tm_sec = getreg8(RTC_SEC);
tp->tm_min = getreg8(RTC_MIN);
tp->tm_hour = getreg8(RTC_HOUR);
tp->tm_mday = getreg8(RTC_DAY);
/* this RTC count month from 1 */
tp->tm_mon = getreg8(RTC_MONTH) - 1;
/* int tm_year: years since 1900 */
tp->tm_year = getreg8(RTC_YEAR) + (getreg8(RTC_CENT) - 19) * 100;
tm_divider(tp, CONFIG_RTC_DIV_N, CONFIG_RTC_DIV_M);
#else /* CONFIG_RTC_DIV */
if (getreg8(RTC_PTN32BIT0) != RTC_PTN32BIT0_VAL ||
getreg8(RTC_PTN32BIT1) != RTC_PTN32BIT1_VAL ||
getreg8(RTC_PTN32BIT2) != RTC_PTN32BIT2_VAL ||
getreg8(RTC_PTN32BIT3) != RTC_PTN32BIT3_VAL)
{
tp->tm_sec = 0;
tp->tm_min = 0;
tp->tm_hour = 0;
tp->tm_mday = CONFIG_START_DAY; /* 1 to 31 */
tp->tm_mon = CONFIG_START_MONTH - 1; /* 0 to 11 */
tp->tm_year = (CONFIG_START_YEAR - 1900); /* since 1900 */
}
else
{
tp->tm_sec = getreg8(RTC_SEC);
tp->tm_min = getreg8(RTC_MIN);
tp->tm_hour = getreg8(RTC_HOUR);
tp->tm_mday = getreg8(RTC_DAY);
tp->tm_mon = getreg8(RTC_MONTH) - 1; /* this RTC count month from 1 */
/* int tm_year: years since 1900 */
tp->tm_year = getreg8(RTC_YEAR) + (getreg8(RTC_CENT) - 19) * 100;
}
#endif /* CONFIG_RTC_DIV */
return OK;
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: up_rtcinitialize
*
* 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)
{
/* RTC clock / reset */
modifyreg32(MCLKCNTAPB, 0, MCLKCNTAPB_RTC_CLKEN);
modifyreg32(MRSTCNTAPB, 0, MRSTCNTAPB_RTC_RSTB);
putreg8(0xa7, RTC_VPTN32BIT0);
putreg8(0x58, RTC_VPTN32BIT1);
putreg8(0x3c, RTC_VPTN32BIT2);
putreg8(0xf1, RTC_VPTN32BIT3);
#ifdef CONFIG_RTC_DIV
struct tm tm;
/* Stop rtc update */
putreg8(1 << RTC_RTCINT_SET, RTC_RTCINT);
tm.tm_sec = 0;
tm.tm_min = 0;
tm.tm_hour = 0;
tm.tm_mday = CONFIG_START_DAY;
tm.tm_mon = CONFIG_START_MONTH - 1;
tm.tm_year = CONFIG_START_YEAR - 1900;
tm_divider(&tm, CONFIG_RTC_DIV_M, CONFIG_RTC_DIV_N);
putreg8(tm.tm_sec, RTC_SEC);
putreg8(tm.tm_min, RTC_MIN);
putreg8(tm.tm_hour, RTC_HOUR);
putreg8(tm.tm_mday, RTC_DAY);
putreg8(tm.tm_mon + 1, RTC_MONTH);
putreg8((tm.tm_year + 1900) / 100, RTC_CENT);
putreg8(tm.tm_year % 100, RTC_YEAR);
#else /* CONFIG_RTC_DIV */
if (!(getreg8(RTC_VDET) & RTC_VDET_VDET))
{
rtcinfo("VDET Detect\n");
putreg8(0, RTC_PTN32BIT0);
putreg8(0, RTC_PTN32BIT1);
putreg8(0, RTC_PTN32BIT2);
putreg8(0, RTC_PTN32BIT3);
/* VDET decahtter */
putreg8(RTC_VPTN32BIT0_VAL, RTC_VPTN32BIT0);
putreg8(RTC_VPTN32BIT1_VAL, RTC_VPTN32BIT1);
putreg8(RTC_VPTN32BIT2_VAL, RTC_VPTN32BIT2);
putreg8(RTC_VPTN32BIT3_VAL, RTC_VPTN32BIT3);
/* Reset VDET */
putreg32(RTC_VDET_VDET, RTC_VDET);
/* 32.768kHz * 2 clock */
up_udelay(62);
putreg32(0, RTC_VDET);
}
if (getreg8(RTC_PTN32BIT0) != RTC_PTN32BIT0_VAL ||
getreg8(RTC_PTN32BIT1) != RTC_PTN32BIT1_VAL ||
getreg8(RTC_PTN32BIT2) != RTC_PTN32BIT2_VAL ||
getreg8(RTC_PTN32BIT3) != RTC_PTN32BIT3_VAL)
{
/* DETECT RTC reset. Set temporary timevalue for RTC alarm */
/* Stop rtc update */
putreg8(1 << RTC_RTCINT_SET, RTC_RTCINT);
putreg8(0, RTC_SEC);
putreg8(0, RTC_MIN);
putreg8(0, RTC_HOUR);
putreg8(CONFIG_START_DAY, RTC_DAY);
putreg8(CONFIG_START_MONTH, RTC_MONTH);
putreg8(CONFIG_START_YEAR % 100, RTC_YEAR);
putreg8(CONFIG_START_YEAR / 100, RTC_CENT);
}
#endif /* CONFIG_RTC_DIV */
/* RTC start, if not run */
putreg8(0, RTC_RTCINT);
putreg8(0, RTC_RTCSTAT);
putreg8(1 << RTC_RTCSTAT_PCLR, RTC_RTCSTAT);
#ifdef CONFIG_RTC_ALARM
irq_attach(LC823450_IRQ_RTC, rtc_interrupt, NULL);
up_enable_irq(LC823450_IRQ_RTC);
#endif
#ifdef CONFIG_RTC_SAVE_DEFAULT
pm_register(&pm_cb);
#endif /* CONFIG_RTC_SAVE_DEFAULT */
g_rtc_enabled = true;
return OK;
}
int up_rtc_getdatetime(struct tm *tp)
{
struct tm tm1;
struct tm tm2;
#ifdef CONFIG_RTC_DIV
/* WA: time registers cannot be read within one second(from set) */
if (cboot)
{
tp->tm_sec = 0;
tp->tm_min = 0;
tp->tm_hour = 0;
tp->tm_mday = CONFIG_START_DAY;
tp->tm_mon = CONFIG_START_MONTH - 1;
tp->tm_year = CONFIG_START_YEAR - 1900;
cboot = 0;
return OK;
}
#endif /* CONFIG_RTC_DIV */
up_rtc_getdatetime_main(&tm1);
up_rtc_getdatetime_main(&tm2);
if (tm1.tm_sec == tm2.tm_sec)
{
/* if tm1 == tm2, could read registers atomically */
*tp = tm1;
}
else
{
/* if tm1 != tm2, can read registers atomically in 1 sec */
up_rtc_getdatetime_main(tp);
}
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 *ts)
{
struct tm *tp;
#ifdef CONFIG_ALARM_DEV
alarm_before_rtc_change();
#endif /* CONFIG_ALARM_DEV */
tp = gmtime(&ts->tv_sec);
#ifdef CONFIG_RTC_DIV
tm_divider(tp, CONFIG_RTC_DIV_M, CONFIG_RTC_DIV_N);
#endif /* CONFIG_RTC_DIV */
/* Stop rtc update */
putreg8(1 << RTC_RTCINT_SET, RTC_RTCINT);
putreg8(tp->tm_sec, RTC_SEC);
putreg8(tp->tm_min, RTC_MIN);
putreg8(tp->tm_hour, RTC_HOUR);
putreg8(tp->tm_mday, RTC_DAY);
putreg8(tp->tm_mon + 1, RTC_MONTH); /* this RTC count month from 1 */
/* int tm_year; years since 1900 */
putreg8((tp->tm_year + 1900) / 100, RTC_CENT);
putreg8(tp->tm_year % 100, RTC_YEAR);
/* mark RTC has valid timevalue */
putreg8(RTC_PTN32BIT0_VAL, RTC_PTN32BIT0);
putreg8(RTC_PTN32BIT1_VAL, RTC_PTN32BIT1);
putreg8(RTC_PTN32BIT2_VAL, RTC_PTN32BIT2);
putreg8(RTC_PTN32BIT3_VAL, RTC_PTN32BIT3);
#ifdef CONFIG_RTC_SAVE_DEFAULT
up_rtc_set_default_datetime(tp);
#endif /* CONFIG_RTC_SAVE_DEFAULT */
clock_systime_timespec(&lastupdate_mono);
lastupdate_rtc = *ts;
/* Start rtc update */
putreg8(0, RTC_RTCINT);
#ifdef CONFIG_ALARM_DEV
alarm_after_rtc_change();
#endif /* CONFIG_ALARM_DEV */
return OK;
}
/****************************************************************************
* Name: up_rtc_setalarm
*
* Description:
* Set up an alarm.
* NOTE: Only one alarm is supported.
*
* 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 up_rtc_setalarm(const struct timespec *ts, alarmcb_t callback)
{
struct tm *tp;
if (g_alarmcb)
{
return -EBUSY;
}
tp = gmtime(&ts->tv_sec);
#ifdef CONFIG_RTC_DIV
tm_divider(tp, CONFIG_RTC_DIV_M, CONFIG_RTC_DIV_N);
#endif /* CONFIG_RTC_DIV */
g_alarmcb = callback;
#if 0
llinfo("SETALARM (%04d/%02d/%02d %02d:%02d:%02d)\n",
tp->tm_year + 1900,
tp->tm_mon + 1,
tp->tm_mday,
tp->tm_hour,
tp->tm_min,
tp->tm_sec);
#endif
/* Stop rtc update */
putreg8(1 << RTC_RTCINT_SET, RTC_RTCINT);
putreg8(tp->tm_sec, RTC_SECALM);
putreg8(tp->tm_min, RTC_MINALM);
putreg8(tp->tm_hour, RTC_HOURALM);
putreg8(0, RTC_WEEKALM); /* 0 == not use */
putreg8(tp->tm_mday, RTC_DAYALM);
putreg8(tp->tm_mon + 1, RTC_MONTHALM); /* this RTC count month from 1 */
/* tp->tm_year : this rtc don't support. */
putreg8(0, RTC_RTCSTAT);
putreg8(1 << RTC_RTCSTAT_PCLR, RTC_RTCSTAT);
/* Start rtc update & interrupt enable */
modifyreg8(RTC_RTCINT, 1 << RTC_RTCINT_SET, 1 << RTC_RTCINT_AIE);
return OK;
}
/****************************************************************************
* Name: up_rtc_cancelalarm
****************************************************************************/
int up_rtc_cancelalarm(void)
{
irqstate_t flags;
flags = enter_critical_section();
g_alarmcb = NULL;
/* Disable IRQ */
putreg8(0, RTC_RTCINT);
leave_critical_section(flags);
return 0;
}
#endif
/****************************************************************************
* Name: up_rtc_getrawtime
*
* Description:
* This function is used to get timespec of raw hardware value.
*
****************************************************************************/
int up_rtc_getrawtime(struct timespec *ts)
{
struct tm tm;
struct timespec now;
struct timespec diff;
clock_systime_timespec(&now);
timespec_sub(&now, &lastupdate_mono, &diff);
if (lastupdate_rtc.tv_sec != 0 && diff.tv_sec < 1)
{
/* Can not read RTC value until the end of first count (<1s) */
*ts = lastupdate_rtc;
return 0;
}
tm.tm_sec = getreg8(RTC_SEC);
tm.tm_min = getreg8(RTC_MIN);
tm.tm_hour = getreg8(RTC_HOUR);
tm.tm_mday = getreg8(RTC_DAY);
tm.tm_mon = getreg8(RTC_MONTH) - 1; /* this RTC count month from 1 */
tm.tm_year = getreg8(RTC_YEAR) + (getreg8(RTC_CENT) - 19) * 100;
#ifdef CONFIG_RTC_DIV
tm_divider(&tm, CONFIG_RTC_DIV_N, CONFIG_RTC_DIV_M);
#endif /* CONFIG_RTC_DIV */
ts->tv_nsec = 0;
ts->tv_sec = timegm(&tm);
return 0;
}
#ifdef CONFIG_RTC_SAVE_DEFAULT
/****************************************************************************
* Name: up_rtc_set_default_datetime
****************************************************************************/
void up_rtc_set_default_datetime(struct tm *tp)
{
int ret;
void *handle;
struct rtc_default rtc_def;
rtc_def.tm = *tp;
rtc_def.sig = RTC_DEFAULT_SIGNATURE;
ret = bchlib_setup(CONFIG_RTC_SAVE_BLOCKNAME, true, &handle);
if (ret)
{
llerr("error: %d\n");
return;
}
bchlib_write(handle, (void *)&rtc_def,
CONFIG_RTC_SAVE_SECTOR_OFFSET * 512, sizeof(rtc_def));
bchlib_teardown(handle);
}
/****************************************************************************
* Name: up_rtc_get_default_datetime
****************************************************************************/
int up_rtc_get_default_datetime(struct tm *tp)
{
void *handle;
struct rtc_default rtc_def;
int ret;
ret = bchlib_setup(CONFIG_RTC_SAVE_BLOCKNAME, true, &handle);
if (ret)
{
llerr("error: %d\n");
return -1;
}
bchlib_read(handle, (void *)&rtc_def,
CONFIG_RTC_SAVE_SECTOR_OFFSET * 512, sizeof(rtc_def));
bchlib_teardown(handle);
if (rtc_def.sig != RTC_DEFAULT_SIGNATURE)
{
return -ENOENT;
}
*tp = rtc_def.tm;
return 0;
}
/****************************************************************************
* Name: up_rtc_clear_default
****************************************************************************/
void up_rtc_clear_default(void)
{
int ret;
struct rtc_default rtc_def;
void *handle;
ret = bchlib_setup(CONFIG_RTC_SAVE_BLOCKNAME, true, &handle);
if (ret)
{
llerr("error: %d\n");
return;
}
memset(&rtc_def, 0, sizeof(rtc_def));
bchlib_write(handle, (void *)&rtc_def,
CONFIG_RTC_SAVE_SECTOR_OFFSET * 512, sizeof(rtc_def));
bchlib_teardown(handle);
}
#endif /* CONFIG_RTC_SAVE_DEFAULT */