RTC中rtc_fops到rk808_rtc_ops流控

RTC

  • RTC在哪里更新?中断?
  • 如何从rtc_fopsopen/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_fopsopen/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设备

文档链接说明