Edit online

设计说明

Read time: 6 minute(s)
Edit online

源码说明

Read time: 6 minute(s)

源代码位于:

  • bsp/artinchip/drv/rtc/drv_rtc.c,RTC Driver 层实现

  • bsp/artinchip/hal/rtc/hal_rtc.c,RTC HAL 层实现

  • bsp/artinchip/include/hal/hal_rtc.h,RTC HAL 层接口头文件

Edit online

模块架构

Read time: 6 minute(s)

Linux 提供了一个 RTC 子系统,简称 RTC Core,使得在用户空间可以通过 /dev/watchdogX 来访问 RTC 控制器。为了更方便查看硬件状态和参数设置,本驱动另外扩展了几个 sysfs 节点。 整个软件框架如 图 1 所示:


rtc_sw_system

1. Linux RTC 子系统架构

RTC V1.0 控制器可以适配到通用的 RTC 时间、闹钟接口,其它非标准的特性有:

  • Alarm 的中断输出:

    Alarm 的中断输出是否可用完全由板级电路的设计决定,软件上只需要使能中断信号即可。

    具体操作详见 配置 RTC 自定义参数

  • 时钟校准:

    RTC 控制器支持 ±975 ppm 的校准范围。

    用户需要在实测 32 KHz 晶振的时钟频率后,配置给 menuconfig 中的参数 AIC_RTC_CLK_RATE,详见 配置 RTC 自定义参数

  • SYS_BAK 寄存器:

    RTC 控制器提供了 128 bits 的 NVMEM 寄存器,系统复位时不会丢失数据,目前主要用于记录系统复位状态。 详见 WRI 模块的复位原因管理说明。

  • 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))
Edit online

关键流程设计

Read time: 6 minute(s)
Edit online

初始化流程

Read time: 6 minute(s)

RTC 驱动的初始化接口通过 INIT_BOARD_EXPORT(drv_rtc_init) 完成,主要是通过调用 RTC 子系统的接口 rt_hw_rtc_register() 注册一个 RTC 设备。

RTC 控制器的初始化流程放在 rtc_ops_init() 接口中实现,其中主要步骤有:
  1. 初始化模块的时钟 (clk),确保 RTC 控制器所需的时钟已经正确配置和启用。

  2. 注册中断。

    为 RTC 控制器分配并注册中断处理程序,以便在闹钟触发时能够及时响应。

  3. 设置校准参数。

    根据用户实测的 32 KHz 晶振频率值,计算并设置校准参数,以确保 RTC 的时间精度。

  4. 配置 Alarm IO 的输出信号。

    如果系统配置了 Alarm IO 输出信号,则使能相应的硬件功能,否则可略过。

Edit online

校准算法设计

Read time: 6 minute(s)
校准的算法原理是,将输入的 32 KHz 晶振时钟校准到理想的 32 KHz,公式如下:
(100 * 1024 * 1024 + 100 * calibrate) / (clock-rate / 32) = 1024
=> calibrate = (clock-rate * 32 - 100 * 1024 * 1024) / 100;

其中:

  • clock-rate: 是用户实测 32K 晶振的频率值乘以 100。关于配置说明,详见 打开 RTC 驱动

  • calibrate: 最终要填入 RTC 控制器的校准值,在 hal 层代码中自动处理。

    注:
    校准值 calibrate 分正负:
    • 正:表示 32K 晶振实际偏快。
    • 负:表示 32K 晶振偏慢。
Edit online

数据结构设计

Read time: 6 minute(s)
RTC 数据类型及其结构描述如下:
  • struct aic_rtc_dev:属于 HAL 层接口,记录 RTC 控制器的配置信息:
    struct aic_rtc_dev {
        rtc_callback_t callback;
        u32 clk_rate;
        u32 clk_drv;
        u32 alarm_io;
        u32 cal_fast;
        s32 cal_val;
        s32 inited;
    };
Edit online

接口设计

Read time: 6 minute(s)

Driver 层接口设计

以下接口是 RTC 设备驱动框架的标准接口。
struct rt_rtc_ops
{
    rt_err_t (*init)(void);
    rt_err_t (*get_secs)(time_t *sec);
    rt_err_t (*set_secs)(time_t *sec);
    rt_err_t (*get_alarm)(struct rt_rtc_wkalarm *alarm);
    rt_err_t (*set_alarm)(struct rt_rtc_wkalarm *alarm);
    rt_err_t (*get_timeval)(struct timeval *tv);    // 暂未实现
    rt_err_t (*set_timeval)(struct timeval *tv);    // 暂未实现
};
1. rtc_ops_init

函数原型

rt_err_t rtc_ops_init(void)

功能说明

RTC 控制器的初始化

参数定义

返回值

0,成功

注意事项

-

2. rtc_ops_get_secs

函数原型

rt_err_t rtc_ops_get_secs(time_t *sec)

功能说明

获取当前的 RTC 时间

参数定义

sec - 用于保存返回的时间信息,单位:秒

返回值

0,成功

注意事项

-

3. rtc_ops_set_secs

函数原型

rt_err_t rtc_ops_set_secs(time_t *sec)

功能说明

设置当前的 RTC 时间

参数定义

sec - 用于保存要设置的时间信息,单位:秒

返回值

0,成功

注意事项

-

4. rtc_ops_get_alarm

函数原型

rt_err_t rtc_ops_get_alarm(struct rt_rtc_wkalarm *alarm)

功能说明

获取当前的 Alarm 信息

参数定义

alarm - 用于保存返回的 alarm 信息

返回值

0,成功

注意事项

-

5. rtc_ops_set_alarm

函数原型

rt_err_t rtc_ops_set_alarm(struct rt_rtc_wkalarm *alarm)

功能说明

设置 Alarm 信息

参数定义

alarm - 用于保存要设置的 Alarm 信息

返回值

0,成功

注意事项

-

HAL 层接口设计

HAL 层的函数接口声明存放在 hal_rtc.h 中,主要接口有:
s32 hal_rtc_init(void);
s32 hal_rtc_deinit(void);
void hal_rtc_read_time(u32 *sec);
void hal_rtc_set_time(u32 sec);
s32 hal_rtc_read_alarm(u32 *sec);
void hal_rtc_set_alarm(u32 sec);
void hal_rtc_alarm_io_output(void);
void hal_rtc_32k_clk_output(void);
irqreturn_t hal_rtc_irq(int irq, void *arg);

void hal_rtc_cali(s32 clk_rate);

s32 hal_rtc_register_callback(rtc_callback_t callback);
Edit online

Demo

Read time: 6 minute(s)

如果要获取 RTC 时间,调用 RT-Thread 的标准时间接口即可,比如,- time() - clock_gettime() - gettimeofday()

本 Demo 是 test_alarm 的源码 bsp/examples/test-alarm/test_alarm.c,展示了在 RT-Thread 中,设置一个 oneshot 类型的 alarm 并使用标准时间接口获取当前时间的参考示例:
static void test_alarm_callback(rt_alarm_t alarm, time_t timestamp)
{
    pr_info("Test alarm callback function.\n");
}

static void cmd_test_alarm(int argc, char **argv)
{
    struct rt_alarm_setup setup;
    struct rt_alarm *alarm = RT_NULL;
    u32 timeout = 0;
    time_t now;
    struct tm p_tm;

    if (argc != 2) {
        pr_err("Invalid parameter\n");
        return;
    }
    sscanf((char *)argv[1], "%u", &timeout);

    now = time(NULL) + timeout;
    gmtime_r(&now, &p_tm);

    setup.wktime = p_tm;
    alarm = rt_alarm_create(test_alarm_callback, &setup);
    if (alarm) {
        alarm->flag = RT_ALARM_ONESHOT;
        rt_alarm_start(alarm);
    }
}