This article describes how to use a Real-Time Clock (RTC) on Embedded Linux.
Two clocks are important in Linux: a ‘hardware clock’, also known as RTC, CMOS or BIOS clock. This is the battery-backed clock that keeps time even when the system is shut down. The second clock is called the ‘system clock/tick’ or 'kernel clock' and is maintained by the operating system. At boot time, the hardware clock is read and used to set the system clock. From that point onward the system clock is used to track time.
Our images use systemd-timedated and the timedatectl command since V2.1 and sytemd-timesyncd since V2.4 to synchronize time with a remote Network Time Protocol (NTP) server. When using the timedatectl utility, the time will get stored into the hardware clock immediately when setting a new date or time. The system clock will not get stored into the hardware clock on shutdown.
Alternatively, there is the tool hwclock for accessing one of the hardware clocks directly. You can display the current time, set a hardware clock to a specified time, set a hardware clock to the system time, and set the system time from the hardware clock. Traditionally, on power off, the system clock will get stored into the hardware clock by a shutdown script.
Note: To let one of the RTCs retain the time even when no power is applied to the system, put a backup battery in the respective holder on the carrier board.
The timedatectl allows displaying the current time, both, the system clock and the hardware clock. Furthermore, it also displays the currently configured time zone of the system (which is UTC by default in our images).
# timedatectlLocal time: Mon 2014-05-26 11:29:40 UTCUniversal time: Mon 2014-05-26 11:29:40 UTCRTC time: Mon 2014-05-26 11:29:39Timezone: Universal (UTC, +0000)NTP enabled: n/aNTP synchronized: yesRTC in local TZ: noDST active: n/a
If the RTC reports an invalid time, timedatectl reports an error:
# timedatectl statusFailed to query server: Invalid argument
In this case, the RTC needs to be rewritten.
If an Internet connection is provided, the systemd-timesyncd service will automatically synchronize the local system clock with a remote Network Time Protocol server and the systemd-timedated service will make sure the new system clock is synchronized with the hardware clock (RTC) immediately.
If no Internet connection is provided, timedatectl can be used to set the date or time and the systemd-timedated service will make sure the new system clock is synchronized with the hardware clock (RTC) immediately:
timedatectl set-ntp falsetimedatectl set-time "2015-01-31 11:13:54"
For more information refer to the timedatectl manual page at freedesktop.org.
Use these two commands to manipulate the system clock and hardware clock directly.
From http://busybox.net/downloads/BusyBox.html#hwclock.
hwclock -r
(Show hardware clock time)
hwclock -w
(Set hardware clock from system time)
hwclock -s
(Set system time from hardware clock)
-f FILE can be used to specify a particular RTC device (e.g. /dev/rtc2).
To set system time, use the date command:
date -s "2013-11-19 15:11:40"
Setting the system time using the date command does not automatically synchronize the RTCs. Use the hwclock command after entering the date command to synchronize an RTC with the updated system time:
hwclock -w
When in sleep mode, an RTC can be used to wake the system up later. Not all RTC support this wake-up mode, check the availability of the wakealarm file.
To enable wake in 20 seconds from now, one can use this statement:
echo +20 > /sys/class/rtc/rtc2/wakealarm
As can be seen from the kernel boot log there are actually 3 RTC drivers active on Tegra based modules:
[ 4.483598] using rtc device, m41t00, for alarms[ 4.488204] rtc-ds1307 0-0068: rtc core: registered m41t00 as rtc0[ 4.500130] tegra_rtc tegra_rtc: rtc core: registered tegra_rtc as rtc1[ 4.509545] tegra_rtc tegra_rtc: Tegra internal Real Time Clock[ 4.519127] tps6586x-rtc tps6586x-rtc.0: rtc core: registered tps6586x-rtc as rtc2...[ 5.440682] rtc-ds1307 0-0068: setting system clock to 2012-09-28 00:38:03 UTC (1348792683)
The first one is the ultra-low-power RTC available on the carrier board. The second one is Tegra SoC internal and won't keep the time across power cycles in our design. The third one is PMIC integrated usually drawing much more current than the first dedicated one.
As can be seen from the kernel boot log there are actually 3 RTC drivers active on Tegra K1 based modules:
[ 5.062457] as3722-rtc as3722-rtc.1: rtc core: registered as3722 as rtc0[ 5.092070] rtc-ds1307 0-0068: rtc core: registered m41t0 as rtc1[ 5.101134] tegra_rtc tegra_rtc: rtc core: registered tegra_rtc as rtc2[ 5.115821] tegra_rtc tegra_rtc: Tegra internal Real Time Clock
The first one is the PMIC integrated usually drawing much more current than the one from the carrier board. The second one is the ultra-low-power RTC available on the carrier board. The third one is Tegra SoC internal and won't keep the time across power cycles in our design.
As can be seen from the kernel boot log there are actually 2 RTC drivers active on Vybrid based modules:
[ 1.339243] rtc-ds1307 0-0068: rtc core: registered m41t0 as rtc0[ 1.353708] snvs_rtc 400a7000.snvs:snvs-rtc-lp: rtc core: registered 400a7000.snvs:snvs- as rtc1....[ 2.562813] rtc-ds1307 0-0068: setting system clock to 2015-01-31 11:20:53 UTC (1422703253)
The first one is the ultra-low-power RTC available on the carrier board. The second one is Vybrid SoC internal.
As can be seen from the kernel boot log there are 2 RTC drivers active on i.MX6 based modules:
[ 1.798495] rtc-ds1307 2-0068: rtc core: registered m41t00 as rtc0[ 1.818131] snvs_rtc 20cc034.snvs-rtc-lp: rtc core: registered 20cc034.snvs-rtc-lp as rtc1
The first one is the ultra-low-power RTC available on the carrier board. The second one is i.MX6 SoC internal.
As can be seen from the kernel boot log there are 2 RTC drivers active on i.MX6 ULL based modules:
[ 1.673105] rtc-ds1307 0-0068: rtc core: registered m41t0 as rtc0[ 1.685104] snvs_rtc 20cc000.snvs:snvs-rtc-lp: rtc core: registered 20cc000.snvs:snvs-r as rtc1
The first one is the ultra-low-power RTC available on the carrier board. The second one is i.MX6 ULL SoC internal.
As can be seen from the kernel boot log there are 2 RTC drivers active on i.MX7 based modules:
[ 1.419043] rtc-ds1307 3-0068: rtc core: registered m41t0 as rtc0[ 1.428023] snvs_rtc 30370000.snvs:snvs-rtc-lp: rtc core: registered 30370000.snvs:snvs- as rtc1
The first one is the ultra-low-power RTC available on the carrier board. The second one is i.MX7 SoC internal.
As can be seen from the kernel boot log there are 2 RTC drivers active on i.MX 8/8X based modules:
[ 2.689390] rtc-ds1307 17-0068: registered as rtc0
[ 2.698728] imx_sc_rtc rtc: rtc core: registered rtc as rtc1
[ 5.461431] rtc-ds1307 17-0068: setting system clock to 2019-07-30 09:00:25 UTC (1564477225)
The first one is the ultra-low-power RTC available on the carrier board. The second one is SoC internal.
As can be seen from the kernel boot log there are 2 RTC drivers active on i.MX 8M Mini based modules:
[ 2.517752] rtc-ds1307 0-0032: registered as rtc0
[ 2.523736] snvs_rtc 30370000.snvs:snvs-rtc-lp: registered as rtc1
....
[ 3.646315] rtc-ds1307 0-0032: setting system clock to 2020-08-12 02:52:22 UTC (1597200742)
The first one is an internal (on the Verdin module) low power RX8130CE real-time clock from Epson. The second one is i.MX 8M Mini/Plus SoC internal.
A battery is inserted on the carrier board to backup the RTC. The NTP daemon of systemd gets the correct time from the network and sets the system and RTC time. The RTC preserved the time and sets the system clock accordingly.
root@verdin-imx8mp-07817302:~# dmesg | grep ds1307
[ 1.878459] rtc-ds1307 0-0032: registered as rtc0
[ 4.179267] rtc-ds1307 0-0032: setting system clock to 2021-11-24T15:28:46 UTC (1637767726)
To change the default RTC as e.g. used by hwclock create a file called /etc/udev/rules.d/99-rtc1.rules with e.g. the following contents:
KERNEL=="rtc1", SUBSYSTEM=="rtc", DRIVER=="", ATTR{name}=="m41t0", SYMLINK="rtc", MODE="0666"
Note: In former BSPs the board level RTC was called m41t00
instead.