RTC
RTC
在哪里更新?中断?- 如何从
rtc_fops
(open
/read
/ioctl
)->rk808_rtc_ops
(底层硬件操作接口)
/sys/class/rtc/rtc0/hctosys
是什么?
在 Linux 系统中,/sys/class/rtc/rtc0/hctosys
是一个内核暴露的虚拟文件 (属于 sysfs 文件系统),其核心作用是控制 "是否将 RTC 硬件时钟同步到系统时钟",而非存储实际时间数据。要理解它,需要先明确 RTC、系统时钟的区别,以及 sysfs 文件系统的定位。

cat /sys/class/rtc/rtc0/date
cat /sys/class/rtc/rtc0/time

核心图

do_initcalls
使能顺序 ->rtc_init
c
start_kernel()
kernel_init()
kernel_init_freeable()
do_basic_setup()
driver_init // 创建 `/sys/devices/platform` 文件夹
do_initcalls() // init/main.c
do_initcall_level(2) // postcore_initcall(spi_init/i2c_init)注册总线
do_initcall_level(3s) // 对应 of_platform_default_populate_init解析dev
do_initcall_level(4) // subsys_initcall(rtc_init)
do_initcall_level(6) // 对应 module_platform_driver = module_init + platform_driver_register module_spi_driver
c
subsys_initcall(rtc_init)
rtc_class = class_create(THIS_MODULE, "rtc") //创建 /sys/class/rtc
rtc_class->pm = RTC_CLASS_DEV_PM_OPS
rtc_dev_init()
alloc_chrdev_region(&rtc_devt, 0, RTC_DEV_MAX, "rtc")//动态申请字符设备号范围
//cat /proc/devices | grep rtc可见
.compatible = "rockchip,rk809
->rk808_of_match
c
rk809: pmic@20 {
compatible = "rockchip,rk809";
reg = <0x20>;
interrupt-parent = <&gpio0>;
interrupts = <3 IRQ_TYPE_LEVEL_LOW>;
pinctrl-names = "default", "pmic-sleep",
"pmic-power-off", "pmic-reset";
pinctrl-0 = <&pmic_int>;
pinctrl-1 = <&soc_slppin_slp>, <&rk817_slppin_slp>;
pinctrl-2 = <&soc_slppin_gpio>, <&rk817_slppin_pwrdn>;
pinctrl-3 = <&soc_slppin_gpio>, <&rk817_slppin_rst>;
rockchip,system-power-controller;
wakeup-source;
#clock-cells = <1>;
clock-output-names = "rk808-clkout1", "rk808-clkout2";
//fb-inner-reg-idxs = <2>;
/* 1: rst regs (default in codes), 0: rst the pmic */
pmic-reset-func = <0>;
/* not save the PMIC_POWER_EN register in uboot */
not-save-power-en = <1>;
vcc1-supply = <&vcc3v3_sys>;
vcc2-supply = <&vcc3v3_sys>;
vcc3-supply = <&vcc3v3_sys>;
vcc4-supply = <&vcc3v3_sys>;
vcc5-supply = <&vcc3v3_sys>;
vcc6-supply = <&vcc3v3_sys>;
vcc7-supply = <&vcc3v3_sys>;
vcc8-supply = <&vcc3v3_sys>;
vcc9-supply = <&vcc3v3_sys>;
c
static const struct of_device_id rk808_of_match[] = {
{ .compatible = "rockchip,rk805" },
{ .compatible = "rockchip,rk808" },
{ .compatible = "rockchip,rk809" },
{ .compatible = "rockchip,rk816" },
{ .compatible = "rockchip,rk817" },
{ .compatible = "rockchip,rk818" },
{ },
};
MODULE_DEVICE_TABLE(of, rk808_of_match);
c
static struct i2c_driver rk808_i2c_driver = {
.driver = {
.name = "rk808",
.of_match_table = rk808_of_match,
.pm = &rk8xx_pm_ops,
},
.probe = rk808_probe,
.remove = rk808_remove,
};
module_i2c_driver(rk808_i2c_driver);
rk808_i2c_driver
->rk808_probe
c
rk808_probe
i2c_smbus_read_byte_data
i2c_smbus_read_byte_data
switch (rk808->variant)
case RK809_ID:
case RK817_ID:
rk808->regmap_cfg = &rk817_regmap_config;
rk808->regmap_irq_chip = &rk817_irq_chip;
pre_init_reg = rk817_pre_init_reg;
nr_pre_init_regs = ARRAY_SIZE(rk817_pre_init_reg);
cells = rk817s;
nr_cells = ARRAY_SIZE(rk817s);
on_source = RK817_ON_SOURCE_REG;
off_source = RK817_OFF_SOURCE_REG;
rk808->pm_pwroff_prep_fn = rk817_shutdown_prepare;
of_property_prepare_fn = rk817_of_property_prepare;
pinctrl_init = rk817_pinctrl_init;
break;
i2c_set_clientdata(client, rk808)
rk808->regmap = devm_regmap_init_i2c(client, rk808->regmap_cfg)
regmap_add_irq_chip
ret = devm_mfd_add_devices(&client->dev, PLATFORM_DEVID_NONE,
cells, nr_cells, NULL, 0,
regmap_irq_get_domain(rk808->irq_data));
mfd_add_devices
mfd_add_device
for_each_child_of_node(parent->of_node, np) //device_node
of_device_is_compatible(np, cell->of_compatible)//与 rk817s 做匹配
rk808_probe
->cells = rk817s;
c
static const struct mfd_cell rk817s[] = {
{ .name = "rk808-clkout",},
{ .name = "rk808-regulator",},
{ .name = "rk817-battery", .of_compatible = "rk817,battery", },
{ .name = "rk817-charger", .of_compatible = "rk817,charger", },
{
.name = "rk805-pwrkey",
.num_resources = ARRAY_SIZE(rk817_pwrkey_resources),
.resources = &rk817_pwrkey_resources[0],
},
{
.name = "rk808-rtc",
.num_resources = ARRAY_SIZE(rk817_rtc_resources),
.resources = &rk817_rtc_resources[0],
},
{
.name = "rk817-codec",
.of_compatible = "rockchip,rk817-codec",
},
};
rk808_probe
->of_device_is_compatible
->rk808_rtc_driver
->rk808_rtc_probe
rk808_rtc_driver
->rk808_rtc_driver
.name = "rk808-rtc"
c
static struct platform_driver rk808_rtc_driver = {
.probe = rk808_rtc_probe,
.driver = {
.name = "rk808-rtc",
.pm = &rk808_rtc_pm_ops,
},
};
module_platform_driver(rk808_rtc_driver);
c
rk808_rtc_probe
dev_get_drvdata
switch (rk808->variant)
case RK809_ID:
case RK817_ID:
kr808_rtc->creg = &rk817_creg;
break;
rk808_rtc->rtc = devm_rtc_allocate_device(&pdev->dev)
rk808_rtc->rtc->ops = &rk808_rtc_ops
rk808_rtc->irq = platform_get_irq(pdev, 0)
ret = devm_request_threaded_irq(&pdev->dev, rk808_rtc->irq, NULL,
rk808_alarm_irq, 0,
"RTC alarm", rk808_rtc);
rtc_register_device(rk808_rtc->rtc)
__rtc_register_device
rtc_initialize_alarm
rtc_dev_prepare
cdev_init(&rtc->char_dev, &rtc_dev_fops)
cdev_device_add
rtc_proc_add_device
c
static const struct file_operations rtc_dev_fops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.read = rtc_dev_read,
.poll = rtc_dev_poll,
.unlocked_ioctl = rtc_dev_ioctl,
.open = rtc_dev_open,
.release = rtc_dev_release,
.fasync = rtc_dev_fasync,
};
什么是
rk808_rtc
? 包含rtc_device
rk808_rtc_probe
->rk808_rtc
->rtc_device
c
struct rk808_rtc {
struct rk808 *rk808;
struct rtc_device *rtc;
struct rk_rtc_compat_reg *creg;
int irq;
unsigned int flag;
};
rtc_device
->rtc_class_ops
c
struct rtc_device {
struct device dev;
const struct rtc_class_ops *ops;
rk808_rtc_probe
->rk817_creg
c
static struct rk_rtc_compat_reg rk817_creg = {
.ctrl_reg = RK817_RTC_CTRL_REG,
.status_reg = RK817_RTC_STATUS_REG,
.alarm_seconds_reg = RK817_ALARM_SECONDS_REG,
.int_reg = RK817_RTC_INT_REG,
.seconds_reg = RK817_SECONDS_REG,
};
rk808_rtc_probe
->rk808_rtc_ops
c
static const struct rtc_class_ops rk808_rtc_ops = {
.read_time = rk808_rtc_readtime,
.set_time = rk808_rtc_set_time,
.read_alarm = rk808_rtc_readalarm,
.set_alarm = rk808_rtc_setalarm,
.alarm_irq_enable = rk808_rtc_alarm_irq_enable,
};
rk808_rtc_ops
->rk808_rtc_readtime
c
rk808_rtc_readtime
dev_get_drvdata
ret = regmap_update_bits(rk808->regmap, rk808_rtc->creg->ctrl_reg,
BIT_RTC_CTRL_REG_RTC_GET_TIME,
BIT_RTC_CTRL_REG_RTC_GET_TIME);
ret = regmap_update_bits(rk808->regmap, rk808_rtc->creg->ctrl_reg,
BIT_RTC_CTRL_REG_RTC_GET_TIME,
0);
ret = regmap_bulk_read(rk808->regmap, rk808_rtc->creg->seconds_reg,
rtc_data, NUM_TIME_REGS);
tm->tm_sec = bcd2bin(rtc_data[0] & SECONDS_REG_MSK);
tm->tm_min = bcd2bin(rtc_data[1] & MINUTES_REG_MAK);
......
dev_dbg
问题:
rtc_fops
(open
/read
/ioctl
)->rk808_rtc_ops
(底层硬件操作接口)如何做到的?
c
rtc_register_device(rk808_rtc->rtc)
__rtc_register_device
rtc_initialize_alarm
rtc_dev_prepare
cdev_init(&rtc->char_dev, &rtc_dev_fops)
cdev_device_add
rtc_proc_add_device
read
是如何工作的?
c
read("/dev/rtc0", &data, sizeof(data))
rtc_dev_read
add_wait_queue(&rtc->irq_queue, &wait)// 将当前进程加入 RTC 事件等待队列
schedule();// 调度其他进程,当前进程睡眠等待
put_user//将内核空间的数据安全地拷贝到用户空间的底层工具,主要用于驱动程序中向用户空间传递数据(如 read 或 ioctl 操作的返回数据)
rk808_rtc_probe
->devm_rtc_allocate_device
->rtc_timer_init
->rtc_handle_legacy_irq
->
wake_up_interruptible
->
c
rk808_rtc_probe
dev_get_drvdata
switch (rk808->variant)
case RK809_ID:
case RK817_ID:
kr808_rtc->creg = &rk817_creg;
break;
rk808_rtc->rtc = devm_rtc_allocate_device(&pdev->dev)
rk808_rtc->rtc->ops = &rk808_rtc_ops
rk808_rtc->irq = platform_get_irq(pdev, 0)
ret = devm_request_threaded_irq(&pdev->dev, rk808_rtc->irq, NULL,
rk808_alarm_irq, 0,
"RTC alarm", rk808_rtc);
rtc_register_device(rk808_rtc->rtc)
__rtc_register_device
rtc_initialize_alarm
rtc_dev_prepare
cdev_init(&rtc->char_dev, &rtc_dev_fops)
cdev_device_add
rtc_proc_add_device
用于支持 RTC 的闹钟(AIE)、更新结束(UIE)、周期性(PIE)等中断功能的软件模拟或硬件辅助
UIE
:Update Interrupt Enable(更新结束中断使能),通常用于每秒同步 RTC 时间(对应硬件的 "秒更新" 中断)
c
rk808_rtc_probe
devm_rtc_allocate_device
rtc_allocate_device
/* Init timerqueue */
timerqueue_init_head(&rtc->timerqueue);
INIT_WORK(&rtc->irqwork, rtc_timer_do_work);
/* Init aie timer */
rtc_timer_init(&rtc->aie_timer, rtc_aie_update_irq, (void *)rtc);
/* Init uie timer */
rtc_timer_init(&rtc->uie_rtctimer, rtc_uie_update_irq, (void *)rtc);
/* Init pie timer */
hrtimer_init(&rtc->pie_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
rtc->pie_timer.function = rtc_pie_update_irq;
rtc_handle_legacy_irq
->wake_up_interruptible
唤醒
- 周期更新
rtc_uie_update_irq
c
rtc_aie_update_irq
rtc_handle_legacy_irq(rtc, 1, RTC_AF)
rtc_uie_update_irq
rtc_handle_legacy_irq(rtc, 1, RTC_UF)
//更新中断事件数据:累加计数 + 设置事件标志
rtc->irq_data = (rtc->irq_data + (num << 8)) | (RTC_IRQF|mode)
//低 8 位:用于存储中断类型标志(如 RTC_IRQF 表示有中断、RTC_AF 表示闹钟中断、RTC_PF 表示周期性中断等)高 24 位:用于存储中断累计次数(记录发生了多少次中断)
rtc_pie_update_irq
rtc_handle_legacy_irq(rtc, count, RTC_PF)
c
rtc_handle_legacy_irq
wake_up_interruptible(&rtc->irq_queue)//唤醒在 irq_queue 等待队列中阻塞的进程(如 rtc_dev_read 中的 read 调用)
kill_fasync(&rtc->async_queue, SIGIO, POLL_IN)//向注册了异步通知的进程发送 SIGIO 信号(支持异步 I/O)
rtc_read_time
rtc_set_time
在哪里调用?
rtc_dev_ioctl
->rtc_read_time
rtc_set_time
c
rtc_dev_ioctl
rtc_read_time
rtc_set_time
rtc_device_register
->rtc_read_time
- 核心功能是将硬件 RTC(实时时钟)的时间同步到系统时钟(xtime)
- 通常在系统启动时执行,确保系统初始化时能从硬件时钟获取正确的初始时间
c
rtc_device_register
rtc_hctosys
rtc_class_open
rtc_read_time // 获取硬件 RTC(实时时钟)
do_settimeofday64(&tv64); // 将 tv64 设为系统时间
DECLARE_DELAYED_WORK
->sync_hw_clock
c
//是 Linux 内核中用于定义一个延迟执行的工作队列项的宏,核心作用是将 sync_hw_clock 函数(硬件时钟同步逻辑)注册为 "延迟工作",以便在未来某个时间点(或延迟指定时长后)由内核工作队列调度执行
static DECLARE_DELAYED_WORK(sync_work, sync_hw_clock)
DECLARE_DELAYED_WORK
是怎么运作的?
c
#define DECLARE_DELAYED_WORK(n, f) \
struct delayed_work n = __DELAYED_WORK_INITIALIZER(n, f, 0)
#define __DELAYED_WORK_INITIALIZER(n, f, tflags) { \
.work = __WORK_INITIALIZER((n).work, (f)), \
.timer = __TIMER_INITIALIZER(delayed_work_timer_fn,\
(tflags) | TIMER_IRQSAFE), \
}
// __TIMER_INITIALIZER 宏的核心作用是标准化定时器的静态初始化流程,确保所有定时器都包含必要的回调函数、标志位和调试信息
#define __TIMER_INITIALIZER(_function, _flags) { \
.entry = { .next = TIMER_ENTRY_STATIC }, \ // 表示该定时器是静态初始化的
.function = (_function), \ //定时器超时后要执行的回调函数
.flags = (_flags), \ //定时器的属性标志位,控制定时器的行为
__TIMER_LOCKDEP_MAP_INITIALIZER( \ //初始化锁依赖跟踪(`lockdep`)相关的调试信息,用于内核的静态锁检查机制
__FILE__ ":" __stringify(__LINE__)) \
}
void delayed_work_timer_fn(struct timer_list *t)
{
struct delayed_work *dwork = from_timer(dwork, t, timer);
/* should have been called from irqsafe timer with irq already off */
__queue_work(dwork->cpu, dwork->wq, &dwork->work);
}
DECLARE_DELAYED_WORK
->sync_hw_clock
->rtc_set_ntp_time
|queue_delayed_work
- Linux 时钟已通过外部方式实现同步(如网络时间协议等),需每约 11 分钟相应更新 RTC 时钟
c
sync_hw_clock
sync_rtc_clock
ktime_get_real_ts64(&now) // 获取当前系统的实时时间(包含秒和纳秒精度)
adjust.tv_sec -= (sys_tz.tz_minuteswest * 60)//本地时区相对于 UTC 的西偏分钟数
rc = rtc_set_ntp_time(adjust, &target_nsec)//用于将调整后的时间(`adjust`)写入硬件 RTC
sched_sync_hw_clock(now, target_nsec, rc)//调度下一次同步
ktime_get_real_ts64 // 获取当前真实时间(从系统实时时钟获取,包含纳秒精度)
next.tv_sec = 659; // 11 min
next.tv_nsec = target_nsec - next.tv_nsec;// 计算达到 target_nsec 所需的纳秒延迟
queue_delayed_work(system_power_efficient_wq, &sync_work,
timespec64_to_jiffies(&next))// 将sync_work 工作项加入低功耗工作队列,延迟指定时间后执行
c
CONFIG_RTC_HCTOSYS=y 允许RTC时间设置到系统时间
CONFIG_RTC_HCTOSYS_DEVICE="rtc0" 默认同步时间的RTC设备
CONFIG_RTC_SYSTOHC=y 允许系统时间设置到RTC
CONFIG_RTC_SYSTOHC_DEVICE="rtc0" 默认同步时间的RTC设备