2. Cvitek RTC 具体操作指南

2.1. 模块介绍

RTC(real time clock)实时时钟,是处理器内一个独立供电的恒电模块,为 Linux 系统提供时间。由于RTC 由电池供电,当处理器处于下电关机或休眠状态时,RTC 仍会维持工作状态时间不丢失。Linux 内核将 RTC 作为时间与日期维护器。亦可作为内核睡眠时唤醒内核的闹钟。

应用程序可以用 RTC 提供的周期中断做周期性任务。

2.1.1. 计数时钟频率

RTC 的计数时钟采用 32.768KHz 时钟,运行基于一个 32-bit 加法计数器提供秒计数,计数最大时间为 :

2^32秒 = 49710 天 = 136 年

2.2. 操作准备

2.2.1. 使用 RTC(默认)

  • 使用 SDK 发布的 U-boot 与 Kernel 即可,无需修改 DTS。

  • 设备节点:/dev/rtc0

2.2.2. 移除 RTC

若要在板级禁用 RTC,在板级 DTS(或 overlay)里用 delete-node 删除 RTC 节点。

本 SDK 中 RTC 节点名为 rtc (在 cv184x_base.dtsi / cv1835_asic.dtsi 中定义),删除示例如下:

/ {
    /delete-node/ rtc;
};

若文档或其它 BSP 中节点名为 cvitek-rtc@3005000,则使用:

/delete-node/ cvitek-rtc@3005000;

同文件中删除其它节点示例(仅作参考):

/delete-node/ i2s@04120000;
/delete-node/ sound_ext1;
/delete-node/ sound_ext2;
/delete-node/ sound_PDM;
/delete-node/ rtc;                    /* 删除 RTC */

aliases {
    /delete-property/ ethernet1;
};

2.3. 应用层使用方式

2.3.1. 设备节点与头文件

  • 设备节点:/dev/rtc0

  • 头文件:#include <linux/rtc.h>#include <sys/ioctl.h>

2.3.2. 常用 ioctl 指令

指令

描述

RTC_RD_TIME

读取当前时间

RTC_SET_TIME

设置当前时间

RTC_ALM_READ

读取闹钟时间

RTC_ALM_SET

设置闹钟时间

RTC_AIE_ON

使能闹钟中断

RTC_AIE_OFF

禁止闹钟中断

RTC_UIE_ON

使能更新中断

RTC_UIE_OFF

禁止更新中断

RTC_PIE_ON

开周期中断

RTC_PIE_OFF

关周期中断

RTC_IRQP_SET

设置周期中断频率

2.3.3. 结构体 rtc_time 说明

struct rtc_time {
    int tm_sec;   /* 秒 [0,59] */
    int tm_min;   /* 分 [0,59] */
    int tm_hour;  /* 时 [0,23] */
    int tm_mday;  /* 日 [1,31] */
    int tm_mon;   /* 月 [0,11],0=1月 */
    int tm_year;  /* 年,实际年 = tm_year + 1900 */
    int tm_wday;  /* 星期几 [0,6],0=周日,1=周一 */
    int tm_yday;  /* 一年中第几天 [0,365],0=1月1日 */
    int tm_isdst; /* 夏令时:1=是,0=否 */
};

注意:

  • tm_mon:0 表示 1 月,11 表示 12 月。

  • tm_year:存的是“年份 − 1900”,如 2025 年填 125。

  • tm_wday:0=周日,1=周一,依此类推。

  • tm_yday:0=1月1日,1=1月2日。

2.4. 示例代码

2.4.1. 用户态操作示例(rtc_example.c)

该示例将“读取时间 / 设置时间 / 设置闹钟”整合为一个程序,通过参数选择具体操作。

编译

arm-none-linux-uclibcgnueabihf-gcc -o rtc_example rtc_example.c -static

运行

./rtc_example [read|set|alarm]
  • read:读取并打印当前 RTC 时间。

  • set:将 RTC 设置为示例时间(2025-03-18 12:00:00),并打印设置后的时间。

  • alarm:将闹钟设为当前时间 + 5 秒,并开启闹钟中断(部分 RTC 可能不支持闹钟中断,会返回错误)。

/*
 * Cvitek RTC 使用示例:读取时间、设置时间、设置闹钟
 * 编译: arm-none-linux-uclibcgnueabihf-gcc -o rtc_example rtc_example.c -static
 * 运行: ./rtc_example [read|set|alarm]
 */
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <linux/rtc.h>
#include <sys/ioctl.h>
#include <unistd.h>

#define RTC_DEV "/dev/rtc0"

static void print_rtc_time(const struct rtc_time *t)
{
    printf("RTC 时间: %04d-%02d-%02d %02d:%02d:%02d 星期%d\n",
           t->tm_year + 1900, t->tm_mon + 1, t->tm_mday,
           t->tm_hour, t->tm_min, t->tm_sec, t->tm_wday);
}

/* 读取并打印当前 RTC 时间 */
static int do_read(int fd)
{
    struct rtc_time rtc_tm;
    if (ioctl(fd, RTC_RD_TIME, &rtc_tm) < 0) {
        perror("RTC_RD_TIME");
        return -1;
    }
    print_rtc_time(&rtc_tm);
    return 0;
}

/* 设置 RTC 时间(示例:2025-03-18 12:00:00) */
static int do_set(int fd)
{
    struct rtc_time rtc_tm = {
        .tm_sec  = 0,
        .tm_min  = 0,
        .tm_hour = 12,
        .tm_mday = 18,
        .tm_mon  = 2,   /* 3 月 */
        .tm_year = 125, /* 2025 */
        .tm_wday = 2,
        .tm_isdst = 0
    };
    if (ioctl(fd, RTC_SET_TIME, &rtc_tm) < 0) {
        perror("RTC_SET_TIME");
        return -1;
    }
    printf("已设置 RTC,当前为: ");
    return do_read(fd);
}

/* 设置闹钟为当前时间 + 5 秒,并开启闹钟中断 */
static int do_alarm(int fd)
{
    struct rtc_time rtc_tm;
    if (ioctl(fd, RTC_RD_TIME, &rtc_tm) < 0) {
        perror("RTC_RD_TIME");
        return -1;
    }
    rtc_tm.tm_sec += 5;
    if (rtc_tm.tm_sec >= 60) {
        rtc_tm.tm_sec %= 60;
        rtc_tm.tm_min++;
    }
    if (rtc_tm.tm_min >= 60) {
        rtc_tm.tm_min = 0;
        rtc_tm.tm_hour++;
    }
    if (rtc_tm.tm_hour >= 24)
        rtc_tm.tm_hour = 0;

    if (ioctl(fd, RTC_ALM_SET, &rtc_tm) < 0) {
        if (errno == EINVAL)
            fprintf(stderr, "此 RTC 不支持闹钟中断\n");
        else
            perror("RTC_ALM_SET");
        return -1;
    }
    ioctl(fd, RTC_AIE_ON, 0);
    printf("闹钟已设为 5 秒后,已开启闹钟中断\n");
    return 0;
}

int main(int argc, char **argv)
{
    const char *cmd = (argc > 1) ? argv[1] : "read";
    int fd = open(RTC_DEV, O_RDONLY);
    if (fd < 0) {
        perror("open " RTC_DEV);
        return 1;
    }

    if (strcmp(cmd, "read") == 0)
        do_read(fd);
    else if (strcmp(cmd, "set") == 0)
        do_set(fd);
    else if (strcmp(cmd, "alarm") == 0)
        do_alarm(fd);
    else {
        fprintf(stderr, "用法: %s [read|set|alarm]\n", argv[0]);
        close(fd);
        return 1;
    }

    close(fd);
    return 0;
}

2.5. 小结

操作

做法

使用 RTC

不删节点,应用直接操作 /dev/rtc0

移除 RTC

在板级 DTS 中加 /delete-node/ rtc; (本 SDK 节点名为 rtc

读时间

ioctl(fd, RTC_RD_TIME, &rtc_tm)

设时间

填充 struct rtc_time,再 ioctl(fd, RTC_SET_TIME, &rtc_tm)

设闹钟

填充时间后 ioctl(fd, RTC_ALM_SET, &rtc_tm),再用 RTC_AIE_ON 使能

2.6. RTC 中断定时重启(软重启)

软重启指不断电、不拉闸,仅让 CPU/SoC 重新执行启动流程(内核重新跑一遍),电源保持供电。通过 RTC 闹钟到点触发,可实现定时软重启。

2.6.1. 执行方式

  1. 在设备上查找 auto_restart_after 节点路径:

find /sys/devices/ -name auto_restart_after
  1. 向该节点写入秒数即可在指定秒数后触发软重启。下面示例为 30 秒后重启,路径需替换为上一步 find 得到的实际节点(通常形如 /sys/devices/platform/5026000.rtc/auto_restart_after):

echo 30 > /sys/devices/platform/5026000.rtc/auto_restart_after

2.6.2. 从 RTC 闹钟到 SoC 复位的流程

  1. 设置 RTC 闹钟:用户通过 sysfs 写入属性 auto_restart_after ,例如命令 echo 30 ,表示约 30 秒后重启。驱动读取 RTC 当前秒数,将「当前秒数 + 设定秒数」写入 RTC 闹钟寄存器并使能闹钟。

  2. RTC 闹钟中断:到点后 RTC 硬件比较秒计数与闹钟时间,拉高中断。内核 RTC 驱动在中断处理函数中清除中断、通过 rtc_update_irq() 通知用户态 /dev/rtc0,并提交一个延迟工作项。

  3. 工作队列中触发重启:在工作函数中,若此前已通过 auto_restart_after 设定了定时重启,则调用 orderly_reboot();若超时未重启则再调用 kernel_restart(NULL)

  4. orderly_reboot:先尝试执行 /sbin/reboot,给用户态做收尾(sync、脚本等);若执行失败则直接调用 kernel_restart(NULL)

  5. kernel_restart:进入重启准备阶段(通知链、设备 shutdown、迁移到重启 CPU、系统核心关闭、内核日志 dump),最后调用架构相关的 machine_restart()

  6. machine_restart(ARM64):关中断、停止其它 CPU,再调用 do_kernel_restart()

  7. do_kernel_restart:调用所有通过 register_restart_handler() 注册的重启回调。

  8. Cvitek 重启回调:在 cvi-reboot.c 中注册的 cvi_restart_handler() 写 RTC 域寄存器请求温复位(RTC_EN_WARM_RST_REQ),等待状态就绪后写 RTC_CTRL0 触发 SoC 内部 warm reset。CPU 从 BootROM/固件重新启动,整机不断电,完成软重启。

2.6.3. 关键点小结

  • RTC 闹钟:提供“到点触发”,用 RTC 当前秒数 + N 秒设闹钟,与系统时间解耦。

  • orderly_reboot:先执行 /sbin/reboot 做用户态收尾,失败则直接 kernel_restart

  • kernel_restart:关设备、迁 CPU、dump 日志,再交给架构的 machine_restart

  • cvi_restart_handler:Cvitek SoC 的软复位实现,通过写 RTC 域 WARM_RST_REQ 由硬件做 CPU/SoC 温复位,电源保持供电。