44.Linux RTC
RTC同样也是一个标准的字符设备驱动
linux内核为其抽象出来一个rtc_device来描述rtc设备
1.申请并初始化rtc_device
2.注册进linux内核中
3.编写fops字符设备操作集
此结构体定义在 include/linux/rtc.h 文件中
c
struct rtc_device
{
struct device dev;
struct module *owner;
int id;
char name[RTC_DEVICE_NAME_SIZE];
const struct rtc_class_ops *ops;
struct mutex ops_lock;
struct cdev char_dev;
unsigned long flags;
unsigned long irq_data;
spinlock_t irq_lock;
wait_queue_head_t irq_queue;
struct fasync_struct *async_queue;
struct rtc_task *irq_task;
spinlock_t irq_task_lock;
int irq_freq;
int max_user_freq;
struct timerqueue_head timerqueue;
struct rtc_timer aie_timer;
struct rtc_timer uie_rtctimer;
struct hrtimer pie_timer; /* sub second exp, so needs hrtimer */
int pie_enabled;
struct work_struct irqwork;
/* Some hardware can't support UIE mode */
int uie_unsupported;
#ifdef CONFIG_RTC_INTF_DEV_UIE_EMUL
struct work_struct uie_task;
struct timer_list uie_timer;
/* Those fields are protected by rtc->irq_lock */
unsigned int oldsecs;
unsigned int uie_irq_active:1;
unsigned int stop_uie_polling:1;
unsigned int uie_task_active:1;
unsigned int uie_timer_active:1;
#endif
};
const struct rtc_class_ops *ops;为rtc的字符设备操作集,需要自己实现。
c
struct rtc_class_ops {
int (*open)(struct device *);
void (*release)(struct device *);
int (*ioctl)(struct device *, unsigned int, unsigned long);
int (*read_time)(struct device *, struct rtc_time *);
int (*set_time)(struct device *, struct rtc_time *);
int (*read_alarm)(struct device *, struct rtc_wkalrm *);
int (*set_alarm)(struct device *, struct rtc_wkalrm *);
int (*proc)(struct device *, struct seq_file *);
int (*set_mmss64)(struct device *, time64_t secs);
int (*set_mmss)(struct device *, unsigned long secs);
int (*read_callback)(struct device *, int data);
int (*alarm_irq_enable)(struct device *, unsigned int enabled);
};
通过设备树的rtc相关节点的compatible很容易找到对应的驱动文件。
c
snvs_rtc: snvs-rtc-lp {
compatible = "fsl,sec-v4.0-mon-rtc-lp";
regmap = <&snvs>;
offset = <0x34>;
interrupts = <GIC_SPI 19 IRQ_TYPE_LEVEL_HIGH>, <GIC_SPI 20 IRQ_TYPE_LEVEL_HIGH>;
};
rtc-lp:可以使用纽扣电池和系统主电源进行供电、掉电不丢失
rtc-hp:只使用系统电源供电,掉电后数据丢失。
驱动文件:drivers/rtc/rtc-snvs.c
c
static const struct of_device_id snvs_dt_ids[] = {
{ .compatible = "fsl,sec-v4.0-mon-rtc-lp", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, snvs_dt_ids);
static struct platform_driver snvs_rtc_driver = {
.driver = {
.name = "snvs_rtc",
.pm = SNVS_RTC_PM_OPS,
.of_match_table = snvs_dt_ids,
},
.probe = snvs_rtc_probe,
};
module_platform_driver(snvs_rtc_driver);
MODULE_AUTHOR("Freescale Semiconductor, Inc.");
MODULE_DESCRIPTION("Freescale SNVS RTC Driver");
MODULE_LICENSE("GPL");
是一个标准的platform框架
NXP对rtc_device进一步封装成了snvs_rtc_data
c
struct snvs_rtc_data *data;
struct snvs_rtc_data {
struct rtc_device *rtc;
struct regmap *regmap;
int offset;
int irq;
struct clk *clk;
};
匹配后snvs_rtc_probe函数会执行
主要工作是:
①、寻找设备树定义的rtc寄存器地址
②、将设备树相关信息来初始化snvs_rtc_data的成员变量
主要的重点就是snvs_rtc_data.rtc_device.rtc_class_ops中的函数
c
static const struct rtc_class_ops snvs_rtc_ops = {
.read_time = snvs_rtc_read_time,
.set_time = snvs_rtc_set_time,
.read_alarm = snvs_rtc_read_alarm,
.set_alarm = snvs_rtc_set_alarm,
.alarm_irq_enable = snvs_rtc_alarm_irq_enable,
};
③、中断设置
④、操作寄存器 基地址加上offset就是rtc_lp的寄存器地址。详细看6ul的参考手册
⑤、申请中断 -》 闹钟
⑥、注册设备 rtc_device_register (设备、名字、操作集、THIS_MODULE)
c
struct rtc_device *rtc_device_register(const char *name,
struct device *dev,
const struct rtc_class_ops *ops,
struct module *owner)
name:设备名字。
dev: 设备。
ops: RTC 底层驱动函数集。
owner:驱动模块拥有者。
返回值: 注册成功的话就返回 rtc_device,错误的话会返回一个负值。
当卸载 RTC 驱动的时候需要调用 rtc_device_unregister 函数来注销注册的 rtc_device,函数原型如下:
c
void rtc_device_unregister(struct rtc_device *rtc)
rtc:要删除的 rtc_device。返回值: 无。

当应用通过ioctl读取RTC的时间的时候,RTC的核心层rtc_dev_ioctl会执行,通过cmd来决定具体操作,具体操作实例如下:
系统调用字符设备操作集
file_operations.rtc_dev_ioctl
->rtc_read_time
调用驱动设备操作集合rtc_class_ops 中的函数
->rtc_device *rtc -> ops -> read_time
-> snvs_rtc_read_time
->rtc_read_lp_counter 获取rtc秒数
->rtc_time_to_tm 将获取到的rtc描述转化成时间
rtc_read_lp_counter 具体实现
c
static u32 rtc_read_lp_counter(struct snvs_rtc_data *data)
{
u64 read1, read2;
u32 val;
do {
regmap_read(data->regmap, data->offset + SNVS_LPSRTCMR, &val);
read1 = val;
read1 <<= 32;
regmap_read(data->regmap, data->offset + SNVS_LPSRTCLR, &val);
read1 |= val;
regmap_read(data->regmap, data->offset + SNVS_LPSRTCMR, &val);
read2 = val;
read2 <<= 32;
regmap_read(data->regmap, data->offset + SNVS_LPSRTCLR, &val);
read2 |= val;
/*
* when CPU/BUS are running at low speed, there is chance that
* we never get same value during two consecutive read, so here
* we only compare the second value.
*/
} while ((read1 >> CNTR_TO_SECS_SH) != (read2 >> CNTR_TO_SECS_SH));
/* Convert 47-bit counter to 32-bit raw second count */
return (u32) (read1 >> CNTR_TO_SECS_SH);
}

read2同理,这里读取了两次 RTC 计数值,因为要读取两个寄存器,因此可能存在读取第二个寄存器的时候时间数据更新了,导致时间不匹配,因此这里连续读两次,如果两次的时间值相等那么就表示时间数据有效。
c
while ((read1 >> CNTR_TO_SECS_SH) != (read2 >> CNTR_TO_SECS_SH));
这里只比较前17位,即相同的秒速就认为时间有效
最后返回秒数值


c
snvs_rtc 20cc000.snvs:snvs-rtc-lp: rtc core: registered 20cc000.snvs:snvs-r as rtc0
shell
//查看RTC时间
date
//设置当前时间
date -s "2025-9-3 10:29:00"
大家注意我们使用" date -s"命令仅仅是将当前系统时间设置了,此时间还没有写入到I.MX6U 内部 RTC 里面或其他的 RTC 芯片里面,因此系统重启以后时间又会丢失。我们需要将当前的时间写入到 RTC 里面,这里要用到 hwclock 命令,输入如下命令将系统时间写入到 RTC里面
c
hwclock -w //将当前系统时间写入到 RTC 里面
时间写入到 RTC 里面以后就不怕系统重启以后时间丢失了,如果 I.MX6U-ALPHA 开发板底板接了纽扣电池,那么开发板即使断电了时间也不会丢失。