设计说明
源码说明
源代码位于:
-
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; }