设计说明
源码说明
源代码位于:
-
RTC V1.0: drivers/rtc/artinchip-rtc.c
-
RTC V0.1: drivers/rtc/artinchip-rtc-v0.1.c
模块架构
Linux 提供了一个 RTC 子系统,简称 RTC Core,使得在用户空间可以通过 /dev/watchdogX 来访问 RTC 控制器。为了更方便查看硬件状态和参数设置,本驱动另外扩展了几个 sysfs 节点。 整个软件框架如 Linux RTC 子系统架构 所示:

RTC V1.0 控制器可以适配到通用的 RTC 时间、闹钟接口,其它非标准的特性有:
- Alarm 的中断输出:
Alarm 的中断输出是否可用完全由板级电路的设计决定,软件上只需要使能中断信号即可。
在 DTS 中提供了一个 boolean 类型的参数方便用户配置
alarm-io-output。 - 时钟校准:
RTC 控制器支持 ±975 ppm 的校准范围。
用户需要配置 DTS 中的参数
clock-rate,详见 配置 DTS 参数 。 - 精准驱动能力
为了节省功耗,可以降低 32K 时钟的驱动能力到刚好够用,扫描方法见 驱动能力扫描。
- 8-bit 寄存器的读写为了减少对代码的干扰,可以将 8-bit 数据的拆解、打包进行封装,示例如下:
#define RTC_WRITEL(val, reg) \ do { \ RTC_WRITE_ENABLE; \ writeb((val) & 0xFF, RTC_BASE + (reg)); \ writeb(((val) >> 8) & 0xFF, RTC_BASE + (reg) + 0x4); \ writeb(((val) >> 16) & 0xFF, RTC_BASE + (reg) + 0x8); \ writeb(((val) >> 24) & 0xFF, RTC_BASE + (reg) + 0xC); \ RTC_WRITE_DISABLE; \ } while (0) #define RTC_READL(reg) (readb(RTC_BASE + reg) \ | (readb((RTC_BASE + reg) + 0x4) << 8) \ | (readb((RTC_BASE + reg) + 0x8) << 16) \ | (readb((RTC_BASE + reg) + 0xC) << 24))
关键流程设计
初始化流程
#define rtc_register_device(device) __rtc_register_device(THIS_MODULE, device)static const struct rtc_class_ops aic_rtc_ops = {
.read_time = aic_rtc_read_time,
.set_time = aic_rtc_set_time,
.read_alarm = aic_rtc_read_alarm,
.set_alarm = aic_rtc_set_alarm,
.alarm_irq_enable = aic_rtc_alarm_irq_enable,
};校准算法设计
(100 * 1024 * 1024 + 100 * calibrate) / (clock-rate / 32) = 1024
=> calibrate = (clock-rate * 32 - 100 * 1024 * 1024) / 100;其中:
-
clock-rate: 是用户实测 32K 晶振的频率值乘以 100。关于配置说明,详见 配置 DTS 参数
-
calibrate: 最终要填入 RTC 控制器的校准值
注:校准值 calibrate 分正负:- 正:表示 32K 晶振实际偏快。
- 负:表示 32K 晶振偏慢。
系统状态的备份功能
RTC 控制器提供了 128-bit 备份寄存器 SYS_BAK,用于掉电时一些重要状态或者参数的保存。RTC 驱动将这几个寄存器封装为对外接口(
EXPORT_SYMBOL_GPL() 的形式),Linux 中其它驱动都可以调用。
Reboot Reason 的设计
将系统状态的备份功能 中 系统备份寄存器 保存不同情况的 Reboot reason,可用于分析终端运行稳定性问题、进入快速启动模式等场景。
SYS_BAK 寄存器需要和 WRI 模块一起配合来完成 reason 的处理:
- WRI
负责记录 硬件可监测 到的 Reboot 原因,如过温保护、看门狗复位、外部输入复位等。
- SYS_BAK
负责记录 软件可监测 到的 Reboot 原因,如 Suspend、Panic、进入烧写模式、正常重启等。
关于 Reboot 原因,梳理分类如下:

其中“外部 IO 复位”指常用的 Reset 按键。
enum aic_reboot_reason {
REBOOT_REASON_COLD = 0,
REBOOT_REASON_CMD_REBOOT = 1,
REBOOT_REASON_CMD_SHUTDOWN = 2,
REBOOT_REASON_SUSPEND = 3,
REBOOT_REASON_UPGRADE = 4,
REBOOT_REASON_FASTBOOT = 5,
/* Some software exception reason */
REBOOT_REASON_SW_LOCKUP = 8,
REBOOT_REASON_HW_LOCKUP = 9,
REBOOT_REASON_PANIC = 10,
REBOOT_REASON_RAMDUMP = 11,
/* Some hardware exception reason */
REBOOT_REASON_RTC = 17,
REBOOT_REASON_EXTEND = 18,
REBOOT_REASON_DM = 19,
REBOOT_REASON_OTP = 20,
REBOOT_REASON_UNDER_VOL = 21,
};针对不同场景,SYS_BAK0 寄存器中的 reason 和 WRI 中的 FLAG 值对应如下:
|
场景 |
触发行为 |
WRI 状态 |
SYS_BAK 状态值 |
|---|---|---|---|
|
正常重启 |
按 Reset 按键 |
EXT_RST |
COLD |
|
shell 中执行 reboot 命令 |
WDOG_RST |
CMD_REBOOT |
|
|
shell 执行 aicupg 命令进入烧写 |
WDOG_RST |
UPGRADE |
|
|
正常关机 |
长按 PowerOn 按键 |
SYS_POR |
COLD |
|
shell 中执行 poweroff 命令 |
SYS_POR |
CMD_SHUTDOWN |
|
|
进入深度休眠状态 |
SYS_POR |
SUSPEND |
|
|
异常重启 |
过温保护 |
OTP |
COLD |
|
通过 Jtag 执行 reset 命令 |
DM_RST |
COLD |
|
|
RTC 模块断电 |
RTC_POR |
COLD |
|
|
内核中发生 SW Lock |
WDOG_RST |
SW_LOCKUP |
|
|
内核中发生 HW Lock |
WDOG_RST |
HW_LOCKUP |
|
|
内核中发生 Panic |
WDOG_RST |
PANIC |
|
|
内核中触发进入 Ramdump |
WDOG_RST |
RAMDUMP |
|
|
电源电压不稳定 |
CMP_RST |
COLD |
其中按 Reset 按键的情况,因为软件来不及设置 BAK,所以是初始值 0 (COLD)。
数据结构设计
- struct aic_rtc_dev:记录 RTC
控制器的配置信息:
struct aic_rtc_dev { void __iomem *base; struct rtc_device *rtc_dev; struct attribute_group attrs; struct clk *clk; u32 clk_rate; u32 clk_drv; bool alarm_io; bool cal_fast; s32 cal_val; struct completion complete; };
接口设计
以下接口是 Linux RTC 子系统需要的标准接口。
外部接口
#define RTC_AIE_ON _IO('p', 0x01) /* Alarm int. enable on */
#define RTC_AIE_OFF _IO('p', 0x02) /* ... off */
#define RTC_UIE_ON _IO('p', 0x03) /* Update int. enable on */
#define RTC_UIE_OFF _IO('p', 0x04) /* ... off */
#define RTC_PIE_ON _IO('p', 0x05) /* Periodic int. enable on */
#define RTC_PIE_OFF _IO('p', 0x06) /* ... off */
#define RTC_WIE_ON _IO('p', 0x0f) /* Watchdog int. enable on */
#define RTC_WIE_OFF _IO('p', 0x10) /* ... off */
#define RTC_ALM_SET _IOW('p', 0x07, struct rtc_time) /* Set alarm time */
#define RTC_ALM_READ _IOR('p', 0x08, struct rtc_time) /* Read alarm time */
#define RTC_RD_TIME _IOR('p', 0x09, struct rtc_time) /* Read RTC time */
#define RTC_SET_TIME _IOW('p', 0x0a, struct rtc_time) /* Set RTC time */
#define RTC_IRQP_READ _IOR('p', 0x0b, unsigned long) /* Read IRQ rate */
#define RTC_IRQP_SET _IOW('p', 0x0c, unsigned long) /* Set IRQ rate */
#define RTC_EPOCH_READ _IOR('p', 0x0d, unsigned long) /* Read epoch */
#define RTC_EPOCH_SET _IOW('p', 0x0e, unsigned long) /* Set epoch */
#define RTC_WKALM_SET _IOW('p', 0x0f, struct rtc_wkalrm)/* Set wakeup alarm*/
#define RTC_WKALM_RD _IOR('p', 0x10, struct rtc_wkalrm)/* Get wakeup alarm*/
#define RTC_PLL_GET _IOR('p', 0x11, struct rtc_pll_info) /* Get PLL correction */
#define RTC_PLL_SET _IOW('p', 0x12, struct rtc_pll_info) /* Set PLL correction */Demo 就是调用的这些接口完成 alarm 配置,以及 hwclock 工具也是调用上述接口。
RTC 相关的内部接口
函数原型 |
static int aic_rtc_read_time(struct device *dev, struct rtc_time *tm) |
|---|---|
功能说明 |
读取当前的 RTC 时间 |
参数定义 |
|
返回值 |
0,成功 |
注意事项 |
- |
函数原型 |
static int aic_rtc_set_time(struct device *dev, struct rtc_time *tm) |
|---|---|
功能说明 |
设置 RTC 时间 |
参数定义 |
|
返回值 |
0,成功 |
注意事项 |
更新 RTC 控制器的秒数,需要先暂停 RTC 计数,设置完秒数,再使能 RTC。 |
函数原型 |
static int aic_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alarm) |
|---|---|
功能说明 |
读取当前的 Alarm 状态信息 |
参数定义 |
|
返回值 |
0,成功 |
注意事项 |
- |
函数原型 |
static int aic_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alarm) |
|---|---|
功能说明 |
设置一个 Alarm,并使能 Alarm 中断 |
参数定义 |
|
返回值 |
0,成功 |
注意事项 |
- |
函数原型 |
static int aic_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled) |
|---|---|
功能说明 |
开关 Alarm 中断 |
参数定义 |
|
返回值 |
0,成功 |
注意事项 |
- |
系统状态备份相关的内部接口
函数原型 |
void aic_rtc_set_bak(u32 offset, u32 mask, u32 shift, u32 val) |
|---|---|
功能说明 |
设置 BAK 寄存器中某几个 (连续的)bit |
参数定义 |
|
返回值 |
无 |
注意事项 |
设置过程:先将 val 左移,然后再做掩码处理 |
函数原型 |
u32 aic_rtc_get_bak(u32 offset, u32 mask, u32 shift) |
|---|---|
功能说明 |
读取 BAK 寄存器中某几个 (连续的)bit |
参数定义 |
|
返回值 |
实际读取到的寄存器值 |
注意事项 |
读取过程:先将读取到的寄存器当前值做掩码处理,然后再右移 |
函数原型 |
void aic_set_software_reboot_reason(enum aic_reboot_reason reason) |
|---|---|
功能说明 |
设置 reason 到 SYS_BAK 寄存器 |
参数定义 |
reason - aic_reboot_reason 类型的启动原因 |
返回值 |
无 |
注意事项 |
aic_reboot_reason 详见 Reboot Reason 的设计 |
函数原型 |
enum aic_reboot_reason aic_get_software_reboot_reason(void) |
|---|---|
功能说明 |
从 BAK 寄存器中读取上一次系统的 Reboot reason 类型 |
参数定义 |
无 |
返回值 |
aic_reboot_reason 类型的启动原因 |
注意事项 |
aic_reboot_reason 详见 Reboot Reason 的设计 |
Demo
#include "base.h"
#include <sys/time.h>
#include <linux/rtc.h>
/* Global macro and variables */
#define ALARM_MAX_DELAY (60 * 60)
#define ALARM_MIN_DELAY 1
static const char sopts[] = "d:u";
static const struct option lopts[] = {
{"delay", required_argument, NULL, 'd'},
{"usage", no_argument, NULL, 'u'},
{0, 0, 0, 0}
};
/* Functions */
void usage(char *program)
{
printf("Usage: %s will start a timer of given seconds, and wait it\n",
program);
printf("\t -d, --delay\trange: [%d, %d]\n", ALARM_MIN_DELAY,
ALARM_MAX_DELAY);
printf("\t -u, --usage \n");
printf("\n");
printf("Example: %s -d 12\n\n", program);
}
/* Open a device file to be needed. */
int device_open(char *_fname, int _flag)
{
s32 fd = -1;
fd = open(_fname, _flag);
if (fd < 0) {
ERR("Failed to open %s errno: %d[%s]\n",
_fname, errno, strerror(errno));
exit(0);
}
return fd;
}
int main(int argc, char **argv)
{
int c, ret;
int delay = 0;
int rtc_fd = -1;
time_t tmp = 0;
struct rtc_time start = {0};
struct rtc_time end = {0};
struct rtc_wkalrm alrm_set = {0};
struct rtc_wkalrm alrm_get = {0};
DBG("Compile time: %s\n", __TIME__);
while ((c = getopt_long(argc, argv, sopts, lopts, NULL)) != -1) {
switch (c) {
case 'd':
delay = str2int(optarg);
continue;
case 'u':
usage(argv[0]);
return 0;
default:
break;
}
}
if ((delay < ALARM_MIN_DELAY) || (delay > ALARM_MAX_DELAY)) {
ERR("Invalid delay: %d\n", delay);
return -1;
}
rtc_fd = open("/dev/rtc0", O_RDWR);
if (rtc_fd < 0) {
ERR("Failed to open RTC device!\n");
return -1;
}
DBG("ioctl(%#x)\n", RTC_RD_TIME);
ret = ioctl(rtc_fd, RTC_RD_TIME, &start);
if (ret < 0) {
ERR("Failed to read RTC time!\n");
goto err;
}
DBG("Current time: %04d-%02d-%02d %02d:%02d:%02d\n",
start.tm_year, start.tm_mon, start.tm_mday,
start.tm_hour, start.tm_min, start.tm_sec);
alrm_set.enabled = 1;
tmp = mktime((struct tm *)&start) + delay;
memcpy(&alrm_set.time, gmtime(&tmp), sizeof(struct rtc_time));
DBG("ioctl(%#x)\n", RTC_WKALM_SET);
ret = ioctl(rtc_fd, RTC_WKALM_SET, &alrm_set);
if (ret < 0) {
ERR("Failed to set alarm! [%d]: %s\n", errno, strerror(errno));
goto err;
}
DBG("Set a alarm to: %04d-%02d-%02d %02d:%02d:%02d\n",
alrm_set.time.tm_year, alrm_set.time.tm_mon,
alrm_set.time.tm_mday, alrm_set.time.tm_hour,
alrm_set.time.tm_min, alrm_set.time.tm_sec);
do {
memset(&alrm_get, 0, sizeof(struct rtc_wkalrm));
DBG("ioctl(%#x)\n", RTC_WKALM_RD);
ret = ioctl(rtc_fd, RTC_WKALM_RD, &alrm_get);
if (ret < 0) {
ERR("Failed to read alarm!\n");
goto err;
}
if (alrm_get.pending)
break;
printf("Waiting ...\n");
usleep(200000); // 200ms
} while (1);
DBG("ioctl(%#x)\n", RTC_RD_TIME);
ret = ioctl(rtc_fd, RTC_RD_TIME, &end);
if (ret < 0) {
ERR("Failed to read RTC time!\n");
goto err;
}
DBG("Current time: %04d-%02d-%02d %02d:%02d:%02d\n",
end.tm_year, end.tm_mon, end.tm_mday,
end.tm_hour, end.tm_min, end.tm_sec);
tmp = mktime((struct tm *)&end) - mktime((struct tm *)&start);
DBG("Start a timer of %d, actualy is %ld ...\n", delay, tmp);
if (ret != delay) {
ERR("The timer is not accurate!\n");
ret = -1;
}
else {
DBG("The timer is good!\n");
ret = 0;
}
err:
if (rtc_fd > 0)
close(rtc_fd);
return ret;
}
