[TOC]
drivers/rtc 实时时钟(Real Time Clock) 驱动框架与硬件驱动集合 历史与背景 这项技术是为了解决什么特定问题而诞生的? RTC(Real Time Clock)驱动框架和相关驱动是为了解决计算机系统在**主电源关闭后维持和提供真实世界时间(“wall-clock time”)**这一根本问题而诞生的。
时间持久化 :计算机系统的主处理器和内存是易失性的,一旦断电,所有状态信息都会丢失。然而,系统需要一个持久化的时钟来记录日期和时间,以便在下次启动时能够知道当前的时间,这对于文件系统时间戳、日志记录、证书验证和网络协议至关重要。
低功耗运行 :这个持久化的时钟必须功耗极低,因为它通常仅由一块小型的纽扣电池供电,需要维持数年的运行。
定时唤醒 :除了计时,RTC硬件通常还具备闹钟(Alarm)功能。这允许系统进入深度休眠或关机状态,并由RTC在预设的时间点产生一个硬件中断信号,从而唤醒系统执行预定任务(如定时备份、系统维护等),这是实现高级电源管理的关键。
它的发展经历了哪些重要的里程碑或版本迭代? Linux RTC子系统的发展反映了Linux从单一PC平台向多样化硬件平台演进的过程。
早期PC时代 (rtc-cmos.c) :最初,Linux的RTC支持主要围绕PC架构中的摩托罗拉MC146818兼容芯片。驱动 (rtc-cmos.c) 通过传统的I/O端口 (inb/outb) 直接访问硬件。用户空间接口也比较原始,只有一个固定的 /dev/rtc 设备。
RTC Class框架的诞生 :随着Linux被广泛用于各种嵌入式系统,出现了成百上千种通过I2C、SPI或其他总线连接的RTC芯片。为每种芯片维护一套独立的用户空间接口是不可行的。因此,一个重要的里程碑是**RTC Class框架 (drivers/rtc/class.c)**的建立。它提供了一个统一的、抽象的接口给用户空间(如 /dev/rtc0, /dev/rtc1, …),并定义了一套标准的操作函数集 (struct rtc_class_ops),供底层硬件驱动来实现和注册。
Sysfs接口的引入 :为了方便脚本和简单应用访问,RTC Class框架增加了sysfs接口(位于 /sys/class/rtc/rtcX/)。通过读写这些文件(如 date, time, wakealarm),可以无需复杂的ioctl调用就能获取时间或设置闹钟。
与设备树(Device Tree)的集成 :在现代嵌入式Linux中,RTC硬件的描述(如I2C地址、中断引脚等)都通过设备树来传递给内核,使得硬件驱动本身可以更加通用,无需硬编码平台相关信息。
目前该技术的社区活跃度和主流应用情况如何? RTC子系统是Linux内核中一个非常成熟和稳定的部分。其核心Class框架改动很少,社区的活跃度主要体现在为层出不穷的新SoC或外围芯片添加新的硬件驱动。 该技术无处不在,是所有现代计算设备的基础组件,包括:
PC和服务器 :用于在启动时初始化系统时钟。
智能手机和移动设备 :维持准确时间,并用于定时唤醒以处理通知等。
嵌入式和物联网设备 :在网络连接不可用时提供可靠的时间源,并用于低功耗场景下的定时唤醒。
核心原理与设计 它的核心工作原理是什么? drivers/rtc 目录下的工作原理可以分为两个层面:框架层 和驱动层 。
它的主要优势体现在哪些方面?
抽象与解耦 :用户空间应用程序(如 hwclock 命令)使用完全相同的接口与任何RTC硬件交互,无需关心底层硬件是I2C接口还是SPI接口,是哪家厂商的芯片。
模块化与可扩展性 :支持新的RTC硬件变得非常简单。开发者只需编写一个新的驱动文件来实现 rtc_class_ops,而无需修改任何核心框架或上层应用代码。
标准化 :提供了统一的时间数据结构 (struct rtc_time) 和标准的 ioctl 命令,简化了应用开发。
它存在哪些已知的劣势、局限性或在特定场景下的不适用性?
精度和分辨率有限 :硬件RTC为了追求极低的功耗,通常使用32.768 kHz的晶振。这导致其精度相对较低,每天可能会有数秒的漂移。其分辨率通常也只有1秒,不适合进行高精度的时间测量。
非单调性 :RTC表示的是“墙上时钟”,它可以被用户或NTP服务任意修改,甚至可以向后调整(例如,当时区或夏令时改变时)。因此,它绝不能 用于测量时间间隔。
接口复杂性 :传统的 ioctl 接口虽然功能强大,但对于编程者来说使用起来较为繁琐。
使用场景 在哪些具体的业务或技术场景下,它是首选解决方案?请举例说明。
系统时钟初始化 :这是RTC最核心的场景。系统启动时,内核会读取RTC的时间来初始化系统的“墙上时钟”(CLOCK_REALTIME)。如果没有RTC,系统时钟将从1970年1月1日开始,直到NTP等网络时间服务同步为止。
计划性系统唤醒 :这是RTC的另一个关键功能。rtcwake 命令行工具就是其典型用户。例如,执行 rtcwake -m freeze -s 60 会让系统进入低功耗的冻结状态,并设置RTC闹钟在60秒后唤醒系统。这被广泛用于定时执行维护任务或在物联网设备中定时采集数据。
无网络环境下的时间维持 :在无法接入互联网的嵌入式设备或特定场景中,RTC是维持时间准确性的唯一可靠手段。
是否有不推荐使用该技术的场景?为什么?
高精度时间间隔测量 :当需要测量代码执行时间或事件间隔时,应使用内核的单调时钟(CLOCK_MONOTONIC)。该时钟从系统启动开始单调递增,不受墙上时钟变化的影响,且具有更高的分辨率。
高精度时间同步 :对于需要亚秒级时间精度的应用(如金融交易、科学计算),RTC只能作为初始参考。必须使用NTP(网络时间协议)或PTP(精确时间协议)来持续校准系统时钟,以补偿RTC和本地晶振的漂移。
对比分析 请将其 与 其他相似技术 进行详细对比。 Linux内核中有多种时间来源,RTC是其中特殊的一种。
特性
RTC (Real Time Clock)
System Wall Clock (CLOCK_REALTIME)
Monotonic Clock (CLOCK_MONOTONIC)
硬件高精度定时器 (HPET, TSC)
本质
独立的、电池供电的低功耗硬件。
内核维护的软件时钟,表示“墙上时间”。
内核维护的软件时钟,表示自启动以来的流逝时间。
CPU或主板上的高频硬件计数器。
持久性
断电后保持 。
断电后丢失。
断电后丢失。
断电后丢失。
单调性
否 (可任意设置,可回退)。
否 (可被date命令, NTP等改变)。
是 (保证永不回退)。
是 (通常情况下)。
分辨率
低 (通常为1秒)。
高 (通常为纳秒级)。
高 (通常为纳秒级)。
非常高 (与硬件频率相关)。
主要用途
在系统断电时维持时间;为系统启动提供初始时间;定时唤醒系统。
为应用程序和文件系统提供标准的日期和时间。
测量时间间隔、设置超时。
作为内核时钟子系统的基础,驱动CLOCK_REALTIME和CLOCK_MONOTONIC的运转。
drivers/rtc/dev.c drivers/rtc/class.c RTC 子系统核心:硬件时钟抽象和管理 本代码片段是Linux内核RTC(Real-Time Clock)子系统的核心层。其主要功能是提供一个通用的、硬件无关的框架,用于管理系统中的RTC设备。它负责处理所有RTC设备的共性任务,包括:分配和管理设备ID;通过sysfs和字符设备节点向用户空间暴露标准接口;实现可选的、将硬件时钟同步到系统时钟的功能(hctosys);以及处理系统挂起/恢复期间的时间校准。具体的RTC硬件驱动(如针对STM32H750内部RTC的驱动)会在此框架之上构建,只需实现一组定义好的硬件操作回调函数(rtc_class_ops)。
实现原理分析 该RTC核心框架是一个典型的设备类驱动模型,它将设备驱动的实现与内核的通用接口解耦。
设备类与生命周期管理 :
rtc_init函数注册一个名为rtc的struct class。这会在/sys/class/rtc/下为所有RTC设备提供一个统一的视图。
rtc_allocate_device是RTC设备的“构造函数”。它负责分配rtc_device结构体,并初始化所有内部组件,如互斥锁、自旋锁、定时器队列、工作队列等。这是一个纯软件的初始化,不涉及硬件。
__devm_rtc_register_device是注册的核心。它接收一个已分配并由具体驱动填充了ops的rtc_device,然后执行以下操作: a. 通过ida_alloc从一个全局ID分配器中获取一个唯一的rtcX编号。 b. 创建一个字符设备(cdev_device_add),这使得用户空间可以通过/dev/rtcX设备节点访问该RTC。 c. 在/proc/driver/rtc下创建诊断文件。
rtc_device_release是“析构函数”,在设备的最后一个引用被释放时调用。它负责清理所有定时器、取消工作队列、释放ID、销毁锁并释放内存。
devm_*系列函数(如devm_rtc_allocate_device)是资源管理的封装,它们将设备的分配和释放与驱动程序的生命周期(probe/remove)绑定,极大地简化了驱动的错误处理和清理逻辑。
硬件到系统时钟同步 (hctosys) :
这是一个可选的、在启动时执行的功能,由CONFIG_RTC_HCTOSYS_DEVICE宏控制。
rtc_hctosys函数会读取指定RTC硬件的时间(通过rtc_read_time,它会调用具体驱动的.read_time回调),将RTC的tm格式时间转换为Unix时间戳,然后调用do_settimeofday64来设置系统(wall clock)时间。
它包含一个重要的细节:由于RTC通常只存储整数秒,它在设置系统时间时会加上半秒(NSEC_PER_SEC >> 1),作为对被截断的亚秒部分的一个最佳猜测补偿。
挂起/恢复处理 :
rtc_suspend: 在系统进入休眠前,它会记录下当前的系统时间和RTC时间。它还实现了一个巧妙的“delta-of-deltas”算法来补偿多次休眠/唤醒循环可能引入的微小漂移。
rtc_resume: 在系统从休眠中唤醒后,它再次读取当前的系统时间和RTC时间。通过比较休眠前后的两个时间快照,它可以精确地计算出系统“睡眠”了多长时间(sleep_time)。然后,它调用timekeeping_inject_sleeptime64将这段丢失的时间“注入”回内核的时间保持子系统,从而校准系统时间,防止因休眠导致的时间变慢。
代码分析 rtc-core 设备分配与注册链路:rtc_allocate_device / rtc_device_get_id / devm_rtc_allocate_device / __devm_rtc_register_device / devm_rtc_device_register / rtc_init
rtc_allocate_device:分配并初始化一个 rtc_device(不注册) 作用与关键点
分配 struct rtc_device,初始化其 struct device 基础字段、锁、定时器队列、hrtimer、默认特性位等。
set_offset_nsec 的默认值用于描述“写入 RTC 后,秒翻转相对写入时刻的偏移模型”,它影响校准/对时相关逻辑,属于不直观点。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 #include <linux/bcd.h> #include <linux/export.h> const struct class rtc_class = { .name = "rtc" , .pm = RTC_CLASS_DEV_PM_OPS, }; static struct rtc_device *rtc_allocate_device (void ) { struct rtc_device *rtc ; rtc = kzalloc(sizeof (*rtc), GFP_KERNEL); if (!rtc) return NULL ; device_initialize(&rtc->dev); rtc->set_offset_nsec = NSEC_PER_SEC + 5 * NSEC_PER_MSEC; rtc->irq_freq = 1 ; rtc->max_user_freq = 64 ; rtc->dev.class = &rtc_class; rtc->dev.groups = rtc_get_dev_attribute_groups(); rtc->dev.release = rtc_device_release; mutex_init(&rtc->ops_lock); spin_lock_init(&rtc->irq_lock); init_waitqueue_head(&rtc->irq_queue); timerqueue_init_head(&rtc->timerqueue); INIT_WORK(&rtc->irqwork, rtc_timer_do_work); rtc_timer_init(&rtc->aie_timer, rtc_aie_update_irq, rtc); rtc_timer_init(&rtc->uie_rtctimer, rtc_uie_update_irq, rtc); hrtimer_setup(&rtc->pie_timer, rtc_pie_update_irq, CLOCK_MONOTONIC, HRTIMER_MODE_REL); rtc->pie_enabled = 0 ; set_bit(RTC_FEATURE_ALARM, rtc->features); set_bit(RTC_FEATURE_UPDATE_INTERRUPT, rtc->features); return rtc; }
rtc_device_get_id:分配 rtc 设备号(优先用设备树别名 rtcX) 作用与关键点
如果设备树 /aliases 里存在 rtc0/rtc1...,优先“强制”占用该固定 id,保证用户空间设备节点编号稳定。
用 IDA(DEFINE_IDA(rtc_ida))实现动态/固定范围分配,避免并发冲突与重复。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 static int rtc_device_get_id (struct device *dev) { int of_id = -1 , id = -1 ; if (dev->of_node) of_id = of_alias_get_id(dev->of_node, "rtc" ); else if (dev->parent && dev->parent->of_node) of_id = of_alias_get_id(dev->parent->of_node, "rtc" ); if (of_id >= 0 ) { id = ida_alloc_range(&rtc_ida, of_id, of_id, GFP_KERNEL); if (id < 0 ) dev_warn(dev, "/aliases ID %d not available\n" , of_id); } if (id < 0 ) id = ida_alloc(&rtc_ida, GFP_KERNEL); return id; }
devm_rtc_allocate_device:分配 rtc_device 并绑定到 parent 的 devres 生命周期 作用与关键点
组合调用:rtc_device_get_id() → rtc_allocate_device() → 设定 parent → devm_add_action_or_reset() 注册释放动作 → dev_set_name("rtc%d")。
devm_add_action_or_reset() 是资源托管关键:如果后续 probe 失败,会自动执行释放动作,避免泄漏/半注册。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 struct rtc_device *devm_rtc_allocate_device (struct device *dev) { struct rtc_device *rtc ; int id, err; id = rtc_device_get_id(dev); if (id < 0 ) return ERR_PTR(id); rtc = rtc_allocate_device(); if (!rtc) { ida_free(&rtc_ida, id); return ERR_PTR(-ENOMEM); } rtc->id = id; rtc->dev.parent = dev; err = devm_add_action_or_reset(dev, devm_rtc_release_device, rtc); if (err) return ERR_PTR(err); err = dev_set_name(&rtc->dev, "rtc%d" , id); if (err) return ERR_PTR(err); return rtc; } EXPORT_SYMBOL_GPL(devm_rtc_allocate_device);
__devm_rtc_register_device:注册字符设备 /proc 并根据 ops 修正特性位 作用与关键点
检查 rtc->ops 合法性,并根据 ops 能力位修正 features(例如没有 set_alarm 就清掉 ALARM)。
可能会读取硬件中的 alarm 并初始化(__rtc_read_alarm() + rtc_initialize_alarm()),避免内核与硬件状态不一致。
cdev_device_add() 失败并不算致命:会置 RTC_NO_CDEV,系统仍可通过 sysfs/其他路径使用部分功能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 int __devm_rtc_register_device(struct module *owner, struct rtc_device *rtc){ struct rtc_wkalrm alrm ; int err; if (!rtc->ops) { dev_dbg(&rtc->dev, "no ops set\n" ); return -EINVAL; } if (!rtc->ops->set_alarm) clear_bit(RTC_FEATURE_ALARM, rtc->features); if (rtc->ops->set_offset) set_bit(RTC_FEATURE_CORRECTION, rtc->features); rtc->owner = owner; rtc_device_get_offset(rtc); err = __rtc_read_alarm(rtc, &alrm); if (!err && !rtc_valid_tm(&alrm.time)) rtc_initialize_alarm(rtc, &alrm); rtc_dev_prepare(rtc); err = cdev_device_add(&rtc->char_dev, &rtc->dev); if (err) { set_bit(RTC_NO_CDEV, &rtc->flags); dev_warn(rtc->dev.parent, "failed to add char device %d:%d\n" , MAJOR(rtc->dev.devt), rtc->id); } else { dev_dbg(rtc->dev.parent, "char device (%d:%d)\n" , MAJOR(rtc->dev.devt), rtc->id); } rtc_proc_add_device(rtc); dev_info(rtc->dev.parent, "registered as %s\n" , dev_name(&rtc->dev)); #ifdef CONFIG_RTC_HCTOSYS_DEVICE if (!strcmp (dev_name(&rtc->dev), CONFIG_RTC_HCTOSYS_DEVICE)) rtc_hctosys(rtc); #endif return devm_add_action_or_reset(rtc->dev.parent, devm_rtc_unregister_device, rtc); } EXPORT_SYMBOL_GPL(__devm_rtc_register_device);
devm_rtc_device_register:旧接口封装(已弃用) 作用与关键点
这是历史接口:内部调用 devm_rtc_allocate_device() + 直接设置 rtc->ops + __devm_rtc_register_device()。
新推荐路径是:allocate → 自己填更多字段(range/offset 等)→ register。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 struct rtc_device *devm_rtc_device_register (struct device *dev, const char *name, const struct rtc_class_ops *ops, struct module *owner) { struct rtc_device *rtc ; int err; rtc = devm_rtc_allocate_device(dev); if (IS_ERR(rtc)) return rtc; rtc->ops = ops; err = __devm_rtc_register_device(owner, rtc); if (err) return ERR_PTR(err); return rtc; } EXPORT_SYMBOL_GPL(devm_rtc_device_register);
rtc_init:注册 rtc 类并初始化 rtc_dev 子模块 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 static int __init rtc_init (void ) { int err; err = class_register(&rtc_class); if (err) return err; rtc_dev_init(); return 0 ; } subsys_initcall(rtc_init);
drivers/rtc/interface.c RTC 工具函数集 (rtc_timer_init, rtc_tm_to_ktime, rtc_bound_alarmtime) 本代码片段包含一组 RTC(实时时钟)子系统的核心工具函数。它们提供了基础的数据结构初始化、时间格式转换以及硬件能力适配等功能。rtc_timer_init 用于初始化一个 rtc_timer 结构体,rtc_tm_to_ktime 负责将 RTC 硬件常用的日历时间格式转换为内核统一的 ktime_t 时间格式,而 rtc_bound_alarmtime 则用于将一个通用的闹钟请求时间调整到特定 RTC 硬件所能支持的最大范围内。
实现原理分析
时间基准的转换 (rtc_tm_to_time64) : 该函数的核心是 mktime64,它实现了一个关键的算法:将格里高利历(公历)日期(年月日时分秒)转换为自 Unix 纪元(1970年1月1日 00:00:00 UTC)以来所经过的线性秒数。这个转换涉及到对年月日进行加权计算,并精确处理闰年的情况。函数在调用 mktime64 之前,对 tm->tm_year 加 1900 和 tm->tm_mon 加 1 进行了调整,这是因为 struct rtc_time 遵循 C 标准库 struct tm 的约定,即年份是从1900年起的偏移量,月份是 0-11。
硬件能力适配技巧 (rtc_bound_alarmtime) : 此函数是一个典型的硬件抽象层适配技巧。它允许上层软件(如 alarmtimer_suspend)以统一的方式请求一个任意时长的闹钟,而无需关心底层硬件的具体限制。RTC 驱动在初始化时,可以根据其硬件手册,在 rtc->alarm_offset_max 字段中声明其能支持的最大闹钟偏移时长(以毫秒为单位)。rtc_bound_alarmtime 在设置闹钟前,会检查请求的时长是否超出了这个硬件限制。如果超出,它不会返回错误,而是“优雅降级”,将闹钟时长缩减到硬件所能支持的最大值。这种设计提高了系统的健壮性,确保即使在请求超出硬件能力时,系统仍能尽力设置一个最早可能被唤醒的闹钟。
代码分析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 void rtc_timer_init (struct rtc_timer *timer, void (*f)(struct rtc_device *r), struct rtc_device *rtc) { timerqueue_init(&timer->node); timer->enabled = 0 ; timer->func = f; timer->rtc = rtc; } time64_t rtc_tm_to_time64 (struct rtc_time *tm) { return mktime64(((unsigned int )tm->tm_year + 1900 ), tm->tm_mon + 1 , tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec); } EXPORT_SYMBOL(rtc_tm_to_time64); ktime_t rtc_tm_to_ktime (struct rtc_time tm) { return ktime_set(rtc_tm_to_time64(&tm), 0 ); } EXPORT_SYMBOL_GPL(rtc_tm_to_ktime); static inline ktime_t rtc_bound_alarmtime (struct rtc_device *rtc, ktime_t requested) { if (rtc->alarm_offset_max && rtc->alarm_offset_max * MSEC_PER_SEC < ktime_to_ms(requested)) return ms_to_ktime(rtc->alarm_offset_max * MSEC_PER_SEC); return requested; }
RTC 定时器启动与入队 (rtc_timer_start, rtc_timer_enqueue) 本代码片段展示了 Linux 内核中 RTC(实时时钟)子系统用于启动和管理硬件定时器的核心逻辑。rtc_timer_start 提供了一个高层次的接口用于设置一个基于 RTC 的定时器,而 rtc_timer_enqueue 则是底层的实现函数,负责将定时器高效地加入管理队列,并根据需要对 RTC 硬件闹钟进行编程。
实现原理分析 此实现中包含了若干关键的技术和算法,以确保 RTC 定时器的高效、精确和健壮。
高效的定时器管理 (timerqueue) : rtc_timer_enqueue 函数使用 timerqueue 数据结构来管理所有活动的 RTC 定时器。timerqueue 内部是基于红黑树实现的,这使得添加(timerqueue_add)和删除定时器的操作时间复杂度为 O(log N),而获取下一个即将到期的定时器(timerqueue_getnext)的操作时间复杂度为 O(1)。对于可能需要管理大量定时器的系统,这是一个至关重要的高效设计。
最小化硬件交互的优化 : 在 rtc_timer_enqueue 函数中,if (!next || ktime_before(timer->node.expires, next->expires)) 语句是一个核心优化。它首先检查在添加新定时器 之前 ,队列中最早的定时器是哪一个(next)。只有当新加入的定时器比原来最早的定时器还要早到期时,它才会去调用 __rtc_set_alarm 来重新编程硬件。这种策略极大地减少了对 RTC 硬件寄存器的写操作次数,因为只有在更新“全局最早到期时间”时才需要与硬件交互,从而降低了系统开销和功耗。
过期闹钟的健壮处理 (-ETIME Erorr Handling) : 当 __rtc_set_alarm 返回 -ETIME 错误时,意味着尝试设置的闹钟时间点已经过去。此时,代码并没有简单地返回失败,而是采取了一套恢复机制:
pm_stay_awake(): 调用此函数来创建一个唤醒锁,暂时阻止系统进入睡眠。
schedule_work(&rtc->irqwork): 将 RTC 的中断处理工作调度到一个工作队列(workqueue)中立即执行。
这套机制确保了即使由于调度延迟等原因导致闹钟设置时间点已过,该闹钟事件也不会被丢失,而是会立即通过软件方式进行模拟触发和处理,保证了定时任务的可靠执行。
代码分析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 int rtc_timer_start (struct rtc_device *rtc, struct rtc_timer *timer, ktime_t expires, ktime_t period) { int ret = 0 ; mutex_lock(&rtc->ops_lock); if (timer->enabled) rtc_timer_remove(rtc, timer); timer->node.expires = expires; timer->period = period; ret = rtc_timer_enqueue(rtc, timer); mutex_unlock(&rtc->ops_lock); return ret; } static int rtc_timer_enqueue (struct rtc_device *rtc, struct rtc_timer *timer) { struct timerqueue_node *next = timerqueue_getnext(&rtc->timerqueue); struct rtc_time tm ; ktime_t now; int err; err = __rtc_read_time(rtc, &tm); if (err) return err; timer->enabled = 1 ; now = rtc_tm_to_ktime(tm); while (next) { if (next->expires >= now) break ; next = timerqueue_iterate_next(next); } timerqueue_add(&rtc->timerqueue, &timer->node); trace_rtc_timer_enqueue(timer); if (!next || ktime_before(timer->node.expires, next->expires)) { struct rtc_wkalrm alarm ; alarm.time = rtc_ktime_to_tm(timer->node.expires); alarm.enabled = 1 ; err = __rtc_set_alarm(rtc, &alarm); if (err == -ETIME) { pm_stay_awake(rtc->dev.parent); schedule_work(&rtc->irqwork); } else if (err) { timerqueue_del(&rtc->timerqueue, &timer->node); trace_rtc_timer_dequeue(timer); timer->enabled = 0 ; return err; } } return 0 ; }
RTC 硬件闹钟设置与时间偏移校正 (__rtc_set_alarm, rtc_subtract_offset) 本代码片段揭示了 Linux 内核向 RTC(实时时钟)硬件编程闹钟的核心底层逻辑。__rtc_set_alarm 函数负责执行设置闹钟的完整流程,包括时间校验、防止设置过去时间、以及通过驱动操作硬件。rtc_subtract_offset 则是一个关键的辅助函数,用于处理那些时间表示范围有限的 RTC 硬件,通过软件偏移量实现对整个 time64_t 时间范围的“虚拟化”支持。
实现原理分析 此代码段包含了确保闹钟设置既健壮又兼容多种硬件的关键技术。
过去时间闹钟的预防与竞态窗口 : __rtc_set_alarm 的一个核心设计原则是严禁设置一个已经过去的时间点作为闹钟。它通过以下步骤实现: a. 读取当前 RTC 的精确时间 now。 b. 将请求的闹钟时间 scheduled 与 now 比较。 c. 如果 scheduled <= now,则立即返回 -ETIME 错误。 然而,代码注释(XXX - ... race window ...)明确指出了此机制的一个固有缺陷:在软件检查通过(scheduled > now)到真正通过 rtc->ops->set_alarm 写入硬件寄存器的这段极短时间内,真实时间可能恰好跨过下一秒的边界,使得 scheduled 变为过去时间。这是一个典型的竞态条件,虽然发生概率很低,但它揭示了在非原子操作中检查时间的理论局限性。
硬件时间范围的虚拟化 (rtc_subtract_offset) : 许多(尤其是旧的)RTC 硬件芯片无法表示完整的 Unix 时间戳范围(例如,年份只用两位数表示)。rtc_subtract_offset 和与之配对的 rtc_add_offset 提供了一套精巧的软件解决方案来克服此限制:
问题 : 硬件只能表示例如从 1980 年到 2079 年的时间。
解决方案 : RTC 驱动在初始化时,可以声明其硬件的有效时间范围 [range_min, range_max],并计算出一个大的软件偏移量 offset_secs(例如,1980-01-01 相比 1970-01-01 的秒数)。
写操作 : 当 __rtc_set_alarm 准备向硬件写入一个时间 tm 时,它会调用 rtc_subtract_offset。该函数首先检查 tm 对应的秒数是否已经落在硬件的 [range_min, range_max] 范围内。如果是,则无需任何操作(这是一个优化)。如果超出了范围(例如,请求设置 2090 年的闹钟),函数就会从该时间的秒数中减去 offset_secs,将一个“绝对时间”转换为一个硬件可以理解的“相对时间”,然后再写入硬件。
读操作 : 反之,当从硬件读出时间时,rtc_add_offset 会被调用,将 offset_secs 加回去,从而将硬件的“相对时间”还原为内核使用的“绝对时间”。 这套机制对上层是透明的,使其能够无差别地处理所有 RTC 硬件,无论其物理时间范围有多大。
代码分析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 static int __rtc_set_alarm(struct rtc_device *rtc, struct rtc_wkalrm *alarm){ struct rtc_time tm ; time64_t now, scheduled; int err; err = rtc_valid_tm(&alarm->time); if (err) return err; scheduled = rtc_tm_to_time64(&alarm->time); err = __rtc_read_time(rtc, &tm); if (err) return err; now = rtc_tm_to_time64(&tm); if (scheduled <= now) return -ETIME; rtc_subtract_offset(rtc, &alarm->time); if (!rtc->ops) err = -ENODEV; else if (!test_bit(RTC_FEATURE_ALARM, rtc->features)) err = -EINVAL; else err = rtc->ops->set_alarm(rtc->dev.parent, alarm); trace_rtc_set_alarm(rtc_tm_to_time64(&alarm->time), err); return err; } static void rtc_subtract_offset (struct rtc_device *rtc, struct rtc_time *tm) { time64_t secs; if (!rtc->offset_secs) return ; secs = rtc_tm_to_time64(tm); if (secs >= rtc->range_min && secs <= rtc->range_max) return ; rtc_time64_to_tm(secs - rtc->offset_secs, tm); }
RTC 时间与闹钟的读取 (__rtc_read_time, rtc_read_alarm) 本代码片段包含两个 RTC(实时时钟)子系统的核心读取函数。__rtc_read_time 是一个内部函数,负责通过底层驱动从 RTC 硬件中读取当前的日历时间,并进行有效性校验。rtc_read_alarm 则是一个导出的外部接口,用于从 RTC 子系统的软件状态中查询当前设置的硬件闹钟信息。
实现原理分析 这两个函数展示了 Linux 设备驱动模型中的关键设计原则:硬件抽象和软件状态缓存。
驱动模型与硬件抽象 (__rtc_read_time) : __rtc_read_time 的核心是通过 rtc->ops->read_time 这个函数指针来调用具体硬件驱动提供的功能。rtc->ops 指向一个 rtc_class_ops 结构体,其中包含了所有标准 RTC 操作的函数指针。这种设计将通用的 RTC 核心逻辑与特定硬件的实现完全解耦。RTC 核心不需要知道如何与某个具体的 RTC 芯片通信,它只需要调用这个标准的 read_time 接口。此外,函数在读取硬件数据后,会调用 rtc_valid_tm 进行严格的有效性检查(例如,确保月份在 0-11 之间,小时在 0-23 之间等),这增强了系统的健壮性,防止无效的硬件数据污染整个内核时间系统。
软件状态缓存与性能优化 (rtc_read_alarm) : 与 __rtc_read_time 不同,rtc_read_alarm 并不直接调用硬件驱动的 read_alarm 函数来读取硬件寄存器。相反,它直接从 rtc->aie_timer 这个软件结构体中读取闹钟信息。aie_timer (Alarm Interrupt Enable timer) 是 RTC 核心层维护的一个软件副本,它代表了当前被编程到硬件中的那个闹钟。这种设计的原理是状态缓存 :硬件只在必要时(即当一个新设置的闹钟比所有现有闹钟都更早到期时)才被更新。当外部需要查询闹钟时,直接读取这个软件缓存即可,避免了昂贵的、可能需要加锁的硬件I/O操作。这是一种重要的性能优化策略。
代码分析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 static int __rtc_read_time(struct rtc_device *rtc, struct rtc_time *tm){ int err; if (!rtc->ops) { err = -ENODEV; } else if (!rtc->ops->read_time) { err = -EINVAL; } else { memset (tm, 0 , sizeof (struct rtc_time)); err = rtc->ops->read_time(rtc->dev.parent, tm); if (err < 0 ) { dev_dbg(&rtc->dev, "read_time: fail to read: %d\n" , err); return err; } rtc_add_offset(rtc, tm); err = rtc_valid_tm(tm); if (err < 0 ) dev_dbg(&rtc->dev, "read_time: rtc_time isn't valid\n" ); } return err; } int rtc_read_alarm (struct rtc_device *rtc, struct rtc_wkalrm *alarm) { int err; err = mutex_lock_interruptible(&rtc->ops_lock); if (err) return err; if (!rtc->ops) { err = -ENODEV; } else if (!test_bit(RTC_FEATURE_ALARM, rtc->features)) { err = -EINVAL; } else { memset (alarm, 0 , sizeof (struct rtc_wkalrm)); alarm->enabled = rtc->aie_timer.enabled; alarm->time = rtc_ktime_to_tm(rtc->aie_timer.node.expires); } mutex_unlock(&rtc->ops_lock); trace_rtc_read_alarm(rtc_tm_to_time64(&alarm->time), err); return err; } EXPORT_SYMBOL_GPL(rtc_read_alarm);
RTC 定时器取消与硬件闹钟禁用 (rtc_timer_cancel, rtc_timer_remove) 本代码片段提供了 Linux 内核中用于安全地取消和移除 RTC(实时时钟)定时器的核心功能。rtc_timer_cancel 是一个高层次的外部接口,它通过加锁来保证操作的原子性。其核心逻辑由内部函数 rtc_timer_remove 实现,该函数不仅将指定的定时器从软件管理队列中移除,还智能地判断是否需要重新编程或禁用硬件闹钟,以确保系统状态的正确性。
实现原理分析 此代码段最关键的设计思想在于最小化硬件交互 的优化策略,这对于降低系统功耗和开销至关重要。
条件性硬件重编程 (rtc_timer_remove) : rtc_timer_remove 的核心在于 if (next == &timer->node) 这个判断。在移除一个定时器之前,它首先通过 timerqueue_getnext 获取了当前队列中最早到期的定时器节点 next。如果被移除的定时器 timer 正是这个 next 节点,这意味着被取消的正是当前已编程到硬件中的那个闹钟。只有在这种情况下,代码才会继续执行后续的硬件操作:
查找下一个替代者 : 它会再次调用 timerqueue_getnext 来找出队列中新的、最早的定时器。
更新硬件 : 如果找到了一个新的最早定时器,它就会调用 __rtc_set_alarm 将这个新的时间点编程到硬件中。
禁用硬件 : 如果移除后队列变空(!next),则调用 rtc_alarm_disable 彻底禁用硬件闹钟中断。 如果被移除的定时器不是队列中最早的那个,那么硬件中编程的闹钟(即原先的 next)仍然是正确的,因此无需任何硬件操作,函数直接返回。这个策略避免了每次取消定时器都去访问硬件,极大地提升了效率。
状态一致性管理 : timer->enabled 标志位虽然简单,但对于维护状态一致性至关重要。rtc_timer_cancel 在加锁后首先检查此标志,避免了对一个已经被取消的定时器执行重复的移除操作,防止了可能导致的队列损坏或内核错误。
代码分析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 void rtc_timer_cancel (struct rtc_device *rtc, struct rtc_timer *timer) { mutex_lock(&rtc->ops_lock); if (timer->enabled) rtc_timer_remove(rtc, timer); mutex_unlock(&rtc->ops_lock); } static void rtc_timer_remove (struct rtc_device *rtc, struct rtc_timer *timer) { struct timerqueue_node *next = timerqueue_getnext(&rtc->timerqueue); timerqueue_del(&rtc->timerqueue, &timer->node); trace_rtc_timer_dequeue(timer); timer->enabled = 0 ; if (next == &timer->node) { struct rtc_wkalrm alarm ; int err; next = timerqueue_getnext(&rtc->timerqueue); if (!next) { rtc_alarm_disable(rtc); return ; } alarm.time = rtc_ktime_to_tm(next->expires); alarm.enabled = 1 ; err = __rtc_set_alarm(rtc, &alarm); if (err == -ETIME) { pm_stay_awake(rtc->dev.parent); schedule_work(&rtc->irqwork); } } } static void rtc_alarm_disable (struct rtc_device *rtc) { if (!rtc->ops || !test_bit(RTC_FEATURE_ALARM, rtc->features) || !rtc->ops->alarm_irq_enable) return ; rtc->ops->alarm_irq_enable(rtc->dev.parent, false ); trace_rtc_alarm_irq_enable(0 , 0 ); }
rtc_update_irq 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 void rtc_update_irq (struct rtc_device *rtc, unsigned long num, unsigned long events){ if (IS_ERR_OR_NULL(rtc)) return ; pm_stay_awake(rtc->dev.parent); schedule_work(&rtc->irqwork); } EXPORT_SYMBOL_GPL(rtc_update_irq);
drivers/rtc/lib.c RTC 日期与时间转换及校验工具集 (rtc_time64_to_tm, rtc_valid_tm) 本代码片段是 Linux 内核 RTC 子系统的核心工具库,提供了一系列不依赖于具体硬件的、纯粹的日期和时间计算函数。其主要功能包括:在内核标准的 time64_t(自 Unix 纪元以来的秒数)和 RTC 硬件常用的 struct rtc_time(年月日时分秒的日历格式)之间进行精确转换;校验 struct rtc_time 的有效性;以及提供辅助计算(如计算某月天数)。
实现原理分析 此代码段的核心在于其高效且精确的日历算法,特别是从线性秒数到公历日期的转换。
Neri and Schneider 历法转换算法 (rtc_time64_to_tm) : 此函数没有采用传统的循环或试错法来确定年月日,而是实现了一个基于欧几里得仿射函数的精密算法。
计算历法 : 算法的核心思想是使用一个“计算历法”,其中年份从3月1日开始。这样做在数学上非常便利,因为闰年的额外一天(2月29日)被置于一年的末尾,从而使得在计算一年中的某一天时,无需预先判断当年是否为闰年。
时间基准偏移 : 算法首先将输入的 Unix 时间戳(从1970-01-01起)加上一个 719468 * 86400 秒的偏移量。719468 是从 0000-03-01 到 1970-01-01 的天数,这个操作将时间基准转换到了“计算历法”的起点。
纯算术分解 : 接下来,算法通过一系列精心选择的整数乘法、除法和取模运算,直接从总天数中解析出世纪、世纪中的年份、年内日、月和日。例如,146097 是一个400年周期(公历的完整闰年周期)的总天数 (97*366 + 303*365)。2939745ULL 和 2141 等是根据月份长度分布推导出的“魔术数字”,用于通过乘法和移位来高效地计算出月份和日期。
结果转换 : 在计算历法下得到年月日后,算法最后通过简单的加减法将其转换回标准的公历表示。
查找表优化 : rtc_month_days 和 rtc_year_days 函数使用了静态常量数组(rtc_days_in_month 和 rtc_ydays)作为查找表。这避免了复杂的 if-else 或 switch 分支,使得计算某月天数或某日在一年中的序数成为一次高效的数组索引操作。
纳秒向上取整 (rtc_ktime_to_tm) : 在将 ktime_t 转换为 rtc_time 时,代码中存在 if (ts.tv_nsec) ts.tv_sec++; 的逻辑。这是一个重要的取整策略:只要时间戳中包含任何非零的纳秒部分,就将其归入下一秒。这对于闹钟设置至关重要,它确保了闹钟的触发时间点不会早于请求的时间点,保证了行为的正确性。
代码分析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 static const unsigned char rtc_days_in_month[] = { 31 , 28 , 31 , 30 , 31 , 30 , 31 , 31 , 30 , 31 , 30 , 31 }; static const unsigned short rtc_ydays[2 ][13 ] = { { 0 , 31 , 59 , 90 , 120 , 151 , 181 , 212 , 243 , 273 , 304 , 334 , 365 }, { 0 , 31 , 60 , 91 , 121 , 152 , 182 , 213 , 244 , 274 , 305 , 335 , 366 } }; static inline bool is_leap_year (unsigned int year) { return (!(year % 4 ) && (year % 100 )) || !(year % 400 ); } int rtc_month_days (unsigned int month, unsigned int year) { return rtc_days_in_month[month] + (is_leap_year(year) && month == 1 ); } EXPORT_SYMBOL(rtc_month_days); int rtc_year_days (unsigned int day, unsigned int month, unsigned int year) { return rtc_ydays[is_leap_year(year)][month] + day - 1 ; } EXPORT_SYMBOL(rtc_year_days); void rtc_time64_to_tm (time64_t time, struct rtc_time *tm) { int secs; u64 u64tmp; u32 u32tmp, udays, century, day_of_century, year_of_century, year, day_of_year, month, day; bool is_Jan_or_Feb, is_leap_year; time += (u64)719468 * 86400 ; udays = div_s64_rem(time, 86400 , &secs); tm->tm_wday = (udays + 3 ) % 7 ; u32tmp = 4 * udays + 3 ; century = u32tmp / 146097 ; day_of_century = u32tmp % 146097 / 4 ; u32tmp = 4 * day_of_century + 3 ; u64tmp = 2939745ULL * u32tmp; year_of_century = upper_32_bits(u64tmp); day_of_year = lower_32_bits(u64tmp) / 2939745 / 4 ; year = 100 * century + year_of_century; is_leap_year = year_of_century != 0 ? year_of_century % 4 == 0 : century % 4 == 0 ; u32tmp = 2141 * day_of_year + 132377 ; month = u32tmp >> 16 ; day = ((u16) u32tmp) / 2141 ; is_Jan_or_Feb = day_of_year >= 306 ; year = year + is_Jan_or_Feb; month = is_Jan_or_Feb ? month - 12 : month; day = day + 1 ; day_of_year = is_Jan_or_Feb ? day_of_year - 306 : day_of_year + 31 + 28 + is_leap_year; tm->tm_year = (int ) (year - 1900 ); tm->tm_mon = (int ) month; tm->tm_mday = (int ) day; tm->tm_yday = (int ) day_of_year + 1 ; tm->tm_hour = secs / 3600 ; secs -= tm->tm_hour * 3600 ; tm->tm_min = secs / 60 ; tm->tm_sec = secs - tm->tm_min * 60 ; tm->tm_isdst = 0 ; } EXPORT_SYMBOL(rtc_time64_to_tm); int rtc_valid_tm (struct rtc_time *tm) { if (tm->tm_year < 70 || tm->tm_year > (INT_MAX - 1900 ) || ((unsigned int )tm->tm_mon) >= 12 || tm->tm_mday < 1 || tm->tm_mday > rtc_month_days(tm->tm_mon, ((unsigned int )tm->tm_year + 1900 )) || ((unsigned int )tm->tm_hour) >= 24 || ((unsigned int )tm->tm_min) >= 60 || ((unsigned int )tm->tm_sec) >= 60 ) return -EINVAL; return 0 ; } EXPORT_SYMBOL(rtc_valid_tm); time64_t rtc_tm_to_time64 (struct rtc_time *tm) { return mktime64(((unsigned int )tm->tm_year + 1900 ), tm->tm_mon + 1 , tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec); } EXPORT_SYMBOL(rtc_tm_to_time64); ktime_t rtc_tm_to_ktime (struct rtc_time tm) { return ktime_set(rtc_tm_to_time64(&tm), 0 ); } EXPORT_SYMBOL_GPL(rtc_tm_to_ktime); struct rtc_time rtc_ktime_to_tm (ktime_t kt) { struct timespec64 ts ; struct rtc_time ret ; ts = ktime_to_timespec64(kt); if (ts.tv_nsec) ts.tv_sec++; rtc_time64_to_tm(ts.tv_sec, &ret); return ret; } EXPORT_SYMBOL_GPL(rtc_ktime_to_tm);
drivers/rtc/rtc-stm32.c stm32_rtc_probe / stm32_rtc_init / stm32_rtc_enter_init_mode / stm32_rtc_wait_sync / stm32_rtc_wpr_unlock / stm32_rtc_wpr_lock:STM32 RTC 驱动初始化主路径与日历时钟分频配置
该驱动的并发关注点主要来自中断上下文 与寄存器写保护/初始化模式的硬件状态机 ,与多核无关。
rtc_ck 频率需要通过 PRED_A/PRED_S 分频得到 1Hz 日历时钟 ;这属于硬件约束,配置必须在 RTC 初始化模式下完成。
写保护寄存器 WPR 采用特定 key 序列解锁/上锁;未解锁时对关键寄存器写入会被硬件忽略或拒绝。
作用与实现原理
stm32_rtc_probe() 是平台驱动入口:完成资源映射、时钟使能、备份域写使能(DBP)、可选安全访问检查(RIF)、RTC 分频与格式初始化、RTC 设备注册、告警中断注册、以及 RTC 专用 pinctrl 注册。
stm32_rtc_init() 的核心是计算 (pred_a + 1) * (pred_s + 1) ≈ rate 以产生 1Hz:
若 need_accuracy=true,优先寻找严格整除 的分频组合(得到精确 1Hz)。
否则偏向选择更大的 pred_a(通常意味着更低功耗/更低内部切换频率的配置倾向),并在找不到严格 1Hz 时做降级处理。
stm32_rtc_enter_init_mode()/exit_init_mode()/wait_sync() 实现对 RTC 硬件状态机的进入/退出与同步等待,避免读写日历寄存器时处于未同步状态。
stm32_rtc_wpr_unlock / stm32_rtc_wpr_lock:写保护开关 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 static void stm32_rtc_wpr_unlock (struct stm32_rtc *rtc) { const struct stm32_rtc_registers *regs = &rtc->data->regs; writel_relaxed(RTC_WPR_1ST_KEY, rtc->base + regs->wpr); writel_relaxed(RTC_WPR_2ND_KEY, rtc->base + regs->wpr); } static void stm32_rtc_wpr_lock (struct stm32_rtc *rtc) { const struct stm32_rtc_registers *regs = &rtc->data->regs; writel_relaxed(RTC_WPR_WRONG_KEY, rtc->base + regs->wpr); }
stm32_rtc_enter_init_mode / stm32_rtc_exit_init_mode / stm32_rtc_wait_sync:初始化模式与寄存器同步 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 static int stm32_rtc_enter_init_mode (struct stm32_rtc *rtc) { const struct stm32_rtc_registers *regs = &rtc->data->regs; unsigned int isr = readl_relaxed(rtc->base + regs->isr); if (!(isr & STM32_RTC_ISR_INITF)) { isr |= STM32_RTC_ISR_INIT; writel_relaxed(isr, rtc->base + regs->isr); return readl_relaxed_poll_timeout_atomic(rtc->base + regs->isr, isr, (isr & STM32_RTC_ISR_INITF), 10 , 100000 ); } return 0 ; } static void stm32_rtc_exit_init_mode (struct stm32_rtc *rtc) { const struct stm32_rtc_registers *regs = &rtc->data->regs; unsigned int isr = readl_relaxed(rtc->base + regs->isr); isr &= ~STM32_RTC_ISR_INIT; writel_relaxed(isr, rtc->base + regs->isr); } static int stm32_rtc_wait_sync (struct stm32_rtc *rtc) { const struct stm32_rtc_registers *regs = &rtc->data->regs; unsigned int isr = readl_relaxed(rtc->base + regs->isr); isr &= ~STM32_RTC_ISR_RSF; writel_relaxed(isr, rtc->base + regs->isr); return readl_relaxed_poll_timeout_atomic(rtc->base + regs->isr, isr, (isr & STM32_RTC_ISR_RSF), 10 , 100000 ); }
stm32_rtc_init:计算分频并强制 24 小时制 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 static int stm32_rtc_init (struct platform_device *pdev, struct stm32_rtc *rtc) { const struct stm32_rtc_registers *regs = &rtc->data->regs; unsigned int prer, pred_a, pred_s, pred_a_max, pred_s_max, cr; unsigned int rate; int ret; rate = clk_get_rate(rtc->rtc_ck); pred_a_max = STM32_RTC_PRER_PRED_A >> STM32_RTC_PRER_PRED_A_SHIFT; pred_s_max = STM32_RTC_PRER_PRED_S >> STM32_RTC_PRER_PRED_S_SHIFT; if (rate > (pred_a_max + 1 ) * (pred_s_max + 1 )) { dev_err(&pdev->dev, "rtc_ck rate is too high: %dHz\n" , rate); return -EINVAL; } if (rtc->data->need_accuracy) { for (pred_a = 0 ; pred_a <= pred_a_max; pred_a++) { pred_s = (rate / (pred_a + 1 )) - 1 ; if (pred_s <= pred_s_max && ((pred_s + 1 ) * (pred_a + 1 )) == rate) break ; } } else { for (pred_a = pred_a_max; pred_a + 1 > 0 ; pred_a--) { pred_s = (rate / (pred_a + 1 )) - 1 ; if (((pred_s + 1 ) * (pred_a + 1 )) == rate) break ; } } if (pred_s > pred_s_max || pred_a > pred_a_max) { pred_a = pred_a_max; pred_s = (rate / (pred_a + 1 )) - 1 ; dev_warn(&pdev->dev, "rtc_ck is %s\n" , (rate < ((pred_a + 1 ) * (pred_s + 1 ))) ? "fast" : "slow" ); } cr = readl_relaxed(rtc->base + regs->cr); prer = readl_relaxed(rtc->base + regs->prer); prer &= STM32_RTC_PRER_PRED_S | STM32_RTC_PRER_PRED_A; pred_s = (pred_s << STM32_RTC_PRER_PRED_S_SHIFT) & STM32_RTC_PRER_PRED_S; pred_a = (pred_a << STM32_RTC_PRER_PRED_A_SHIFT) & STM32_RTC_PRER_PRED_A; if ((cr & STM32_RTC_CR_FMT) == 0 && prer == (pred_s | pred_a)) return 0 ; stm32_rtc_wpr_unlock(rtc); ret = stm32_rtc_enter_init_mode(rtc); if (ret) { dev_err(&pdev->dev, "Can't enter in init mode. Prescaler config failed.\n" ); goto end; } writel_relaxed(pred_s, rtc->base + regs->prer); writel_relaxed(pred_a | pred_s, rtc->base + regs->prer); cr &= ~STM32_RTC_CR_FMT; writel_relaxed(cr, rtc->base + regs->cr); stm32_rtc_exit_init_mode(rtc); ret = stm32_rtc_wait_sync(rtc); end: stm32_rtc_wpr_lock(rtc); return ret; }
stm32_rtc_probe:平台驱动入口(资源/时钟/DBP/RIF/注册) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 static int stm32_rtc_probe (struct platform_device *pdev) { struct stm32_rtc *rtc ; const struct stm32_rtc_registers *regs ; struct pinctrl_dev *pctl ; int ret; rtc = devm_kzalloc(&pdev->dev, sizeof (*rtc), GFP_KERNEL); if (!rtc) return -ENOMEM; rtc->base = devm_platform_ioremap_resource(pdev, 0 ); if (IS_ERR(rtc->base)) return PTR_ERR(rtc->base); rtc->data = (struct stm32_rtc_data *)of_device_get_match_data(&pdev->dev); regs = &rtc->data->regs; if (rtc->data->need_dbp) { unsigned int args[2 ]; rtc->dbp = syscon_regmap_lookup_by_phandle_args(pdev->dev.of_node, "st,syscfg" , 2 , args); if (IS_ERR(rtc->dbp)) { dev_err(&pdev->dev, "no st,syscfg\n" ); return PTR_ERR(rtc->dbp); } rtc->dbp_reg = args[0 ]; rtc->dbp_mask = args[1 ]; } if (!rtc->data->has_pclk) { rtc->pclk = NULL ; rtc->rtc_ck = devm_clk_get(&pdev->dev, NULL ); } else { rtc->pclk = devm_clk_get(&pdev->dev, "pclk" ); if (IS_ERR(rtc->pclk)) return dev_err_probe(&pdev->dev, PTR_ERR(rtc->pclk), "no pclk clock" ); rtc->rtc_ck = devm_clk_get(&pdev->dev, "rtc_ck" ); } if (IS_ERR(rtc->rtc_ck)) return dev_err_probe(&pdev->dev, PTR_ERR(rtc->rtc_ck), "no rtc_ck clock" ); if (rtc->data->has_pclk) { ret = clk_prepare_enable(rtc->pclk); if (ret) return ret; } ret = clk_prepare_enable(rtc->rtc_ck); if (ret) goto err_no_rtc_ck; if (rtc->data->need_dbp) regmap_update_bits(rtc->dbp, rtc->dbp_reg, rtc->dbp_mask, rtc->dbp_mask); if (rtc->data->rif_protected) { ret = stm32_rtc_check_rif(rtc, STM32_RTC_RES_INIT); if (!ret) ret = stm32_rtc_check_rif(rtc, STM32_RTC_RES_ALRA); if (ret) { dev_err(&pdev->dev, "Failed to probe RTC due to RIF configuration\n" ); goto err; } } ret = stm32_rtc_init(pdev, rtc); if (ret) goto err; rtc->irq_alarm = platform_get_irq(pdev, 0 ); if (rtc->irq_alarm <= 0 ) { ret = rtc->irq_alarm; goto err; } ret = devm_device_init_wakeup(&pdev->dev); if (ret) goto err; ret = devm_pm_set_wake_irq(&pdev->dev, rtc->irq_alarm); if (ret) goto err; platform_set_drvdata(pdev, rtc); rtc->rtc_dev = devm_rtc_device_register(&pdev->dev, pdev->name, &stm32_rtc_ops, THIS_MODULE); if (IS_ERR(rtc->rtc_dev)) { ret = PTR_ERR(rtc->rtc_dev); dev_err(&pdev->dev, "rtc device registration failed, err=%d\n" , ret); goto err; } ret = devm_request_threaded_irq(&pdev->dev, rtc->irq_alarm, NULL , stm32_rtc_alarm_irq, IRQF_ONESHOT, pdev->name, rtc); if (ret) { dev_err(&pdev->dev, "IRQ%d (alarm interrupt) already claimed\n" , rtc->irq_alarm); goto err; } stm32_rtc_clean_outs(rtc); ret = devm_pinctrl_register_and_init(&pdev->dev, &stm32_rtc_pdesc, rtc, &pctl); if (ret) return dev_err_probe(&pdev->dev, ret, "pinctrl register failed" ); ret = pinctrl_enable(pctl); if (ret) return dev_err_probe(&pdev->dev, ret, "pinctrl enable failed" ); if (!(readl_relaxed(rtc->base + regs->isr) & STM32_RTC_ISR_INITS)) dev_warn(&pdev->dev, "Date/Time must be initialized\n" ); if (regs->verr != UNDEF_REG) { u32 ver = readl_relaxed(rtc->base + regs->verr); dev_info(&pdev->dev, "registered rev:%d.%d\n" , (ver >> STM32_RTC_VERR_MAJREV_SHIFT) & 0xF , (ver >> STM32_RTC_VERR_MINREV_SHIFT) & 0xF ); } return 0 ; err: clk_disable_unprepare(rtc->rtc_ck); err_no_rtc_ck: if (rtc->data->has_pclk) clk_disable_unprepare(rtc->pclk); if (rtc->data->need_dbp) regmap_update_bits(rtc->dbp, rtc->dbp_reg, rtc->dbp_mask, 0 ); return ret; }
stm32_rtc_alarm_irq / stm32_rtc_alarm_irq_enable / stm32_rtc_clear_event_flags / stm32_rtc_clear_events:告警中断处理与使能/清除链路 平台关注:单核、无 MMU、ARMv7-M(STM32H750)
该驱动在 probe() 中用 线程化中断 (devm_request_threaded_irq(..., NULL, stm32_rtc_alarm_irq, IRQF_ONESHOT, ...))注册告警中断,因此 stm32_rtc_alarm_irq() 运行在线程上下文 ,允许使用 rtc_lock() 这类可能睡眠的锁原语。
单核下 IRQF_ONESHOT 的关键意义是:线程处理函数运行期间,内核会保持该 IRQ 线处于屏蔽状态,避免同一告警中断在处理未完成时重入导致重复上报或清旗竞争。
stm32_rtc_alarm_irq:告警中断线程处理函数(确认条件→上报→清旗) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 static irqreturn_t stm32_rtc_alarm_irq (int irq, void *dev_id) { struct stm32_rtc *rtc = (struct stm32_rtc *)dev_id; const struct stm32_rtc_registers *regs = &rtc->data->regs; const struct stm32_rtc_events *evts = &rtc->data->events; unsigned int status, cr; rtc_lock(rtc->rtc_dev); status = readl_relaxed(rtc->base + regs->sr); cr = readl_relaxed(rtc->base + regs->cr); if ((status & evts->alra) && (cr & STM32_RTC_CR_ALRAIE)) { rtc_update_irq(rtc->rtc_dev, 1 , RTC_IRQF | RTC_AF); stm32_rtc_clear_event_flags(rtc, evts->alra); } rtc_unlock(rtc->rtc_dev); return IRQ_HANDLED; }
stm32_rtc_alarm_irq_enable:告警中断开关(同时控制 ALRAE 与 ALRAIE,并清历史标志) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 static int stm32_rtc_alarm_irq_enable (struct device *dev, unsigned int enabled) { struct stm32_rtc *rtc = dev_get_drvdata(dev); const struct stm32_rtc_registers *regs = &rtc->data->regs; const struct stm32_rtc_events *evts = &rtc->data->events; unsigned int cr; cr = readl_relaxed(rtc->base + regs->cr); stm32_rtc_wpr_unlock(rtc); if (enabled) cr |= (STM32_RTC_CR_ALRAIE | STM32_RTC_CR_ALRAE); else cr &= ~(STM32_RTC_CR_ALRAIE | STM32_RTC_CR_ALRAE); writel_relaxed(cr, rtc->base + regs->cr); stm32_rtc_clear_event_flags(rtc, evts->alra); stm32_rtc_wpr_lock(rtc); return 0 ; }
stm32_rtc_clear_event_flags:事件清除的统一入口(变体分发) 1 2 3 4 5 6 7 8 9 10 11 static void stm32_rtc_clear_event_flags (struct stm32_rtc *rtc, unsigned int flags) { rtc->data->clear_events(rtc, flags); }
stm32_rtc_clear_events:部分变体的清旗方式(写回 ISR 清零对应位)
对 STM32H750 典型匹配项 st,stm32h7-rtc,该回调用于清除 RTC_ISR 中的事件位。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 static void stm32_rtc_clear_events (struct stm32_rtc *rtc, unsigned int flags) { const struct stm32_rtc_registers *regs = &rtc->data->regs; writel_relaxed(readl_relaxed(rtc->base + regs->isr) & ~flags, rtc->base + regs->isr); }
stm32mp1_rtc_clear_events:另一类变体的清旗方式(写 SCR 置 1 清除)
该回调展示了“同名事件,不同清除寄存器/语义”的硬件差异:通过写 RTC_SCR 的 1 来清除标志。
1 2 3 4 5 6 7 8 9 10 11 12 13 static void stm32mp1_rtc_clear_events (struct stm32_rtc *rtc, unsigned int flags) { struct stm32_rtc_registers regs = rtc->data->regs; writel_relaxed(flags, rtc->base + regs.scr); }
stm32_rtc_read_time / stm32_rtc_set_time / tm2bcd / bcd2tm:时间读取、设置与 BCD 编码转换链路
读写路径的关键约束来自 RTC 硬件:日历寄存器 TR/DR 的字段以 BCD 存储 ,写入必须在写保护解锁 且通常需要进入初始化模式 (INIT/INITF)后进行,写完还要等待同步标志 RSF 。
单核下主要并发来源仍是中断/线程化上下文,不涉及多核一致性;这里用“进入 init 模式 + 等待同步”来避免读到未同步的日历值。
tm2bcd:把内核 rtc_time(二进制)转换为硬件需要的 BCD 字段 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 static void tm2bcd (struct rtc_time *tm) { tm->tm_sec = bin2bcd(tm->tm_sec); tm->tm_min = bin2bcd(tm->tm_min); tm->tm_hour = bin2bcd(tm->tm_hour); tm->tm_mday = bin2bcd(tm->tm_mday); tm->tm_mon = bin2bcd(tm->tm_mon + 1 ); tm->tm_year = bin2bcd(tm->tm_year - 100 ); tm->tm_wday = (!tm->tm_wday) ? 7 : tm->tm_wday; }
bcd2tm:把硬件寄存器读出的 BCD 字段转换为内核 rtc_time(二进制) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 static void bcd2tm (struct rtc_time *tm) { tm->tm_sec = bcd2bin(tm->tm_sec); tm->tm_min = bcd2bin(tm->tm_min); tm->tm_hour = bcd2bin(tm->tm_hour); tm->tm_mday = bcd2bin(tm->tm_mday); tm->tm_mon = bcd2bin(tm->tm_mon) - 1 ; tm->tm_year = bcd2bin(tm->tm_year) + 100 ; tm->tm_wday %= 7 ; }
stm32_rtc_read_time:从 TR/DR 读取 BCD 字段并转换为内核 rtc_time 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 static int stm32_rtc_read_time (struct device *dev, struct rtc_time *tm) { struct stm32_rtc *rtc = dev_get_drvdata(dev); const struct stm32_rtc_registers *regs = &rtc->data->regs; unsigned int tr, dr; tr = readl_relaxed(rtc->base + regs->tr); dr = readl_relaxed(rtc->base + regs->dr); tm->tm_sec = (tr & STM32_RTC_TR_SEC) >> STM32_RTC_TR_SEC_SHIFT; tm->tm_min = (tr & STM32_RTC_TR_MIN) >> STM32_RTC_TR_MIN_SHIFT; tm->tm_hour = (tr & STM32_RTC_TR_HOUR) >> STM32_RTC_TR_HOUR_SHIFT; tm->tm_mday = (dr & STM32_RTC_DR_DATE) >> STM32_RTC_DR_DATE_SHIFT; tm->tm_mon = (dr & STM32_RTC_DR_MONTH) >> STM32_RTC_DR_MONTH_SHIFT; tm->tm_year = (dr & STM32_RTC_DR_YEAR) >> STM32_RTC_DR_YEAR_SHIFT; tm->tm_wday = (dr & STM32_RTC_DR_WDAY) >> STM32_RTC_DR_WDAY_SHIFT; bcd2tm(tm); return 0 ; }
备注:这段实现没有在读前做“寄存器一致性读”的双读/锁存处理;其正确性依赖于 STM32 RTC 的 TR/DR 读出语义(硬件通常提供内部锁存/同步机制),以及上层在需要时通过同步流程保证读到稳定值。
stm32_rtc_set_time:进入初始化模式写 TR/DR,并等待同步 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 static int stm32_rtc_set_time (struct device *dev, struct rtc_time *tm) { struct stm32_rtc *rtc = dev_get_drvdata(dev); const struct stm32_rtc_registers *regs = &rtc->data->regs; unsigned int tr, dr; int ret = 0 ; tm2bcd(tm); tr = ((tm->tm_sec << STM32_RTC_TR_SEC_SHIFT) & STM32_RTC_TR_SEC) | ((tm->tm_min << STM32_RTC_TR_MIN_SHIFT) & STM32_RTC_TR_MIN) | ((tm->tm_hour << STM32_RTC_TR_HOUR_SHIFT) & STM32_RTC_TR_HOUR); dr = ((tm->tm_mday << STM32_RTC_DR_DATE_SHIFT) & STM32_RTC_DR_DATE) | ((tm->tm_mon << STM32_RTC_DR_MONTH_SHIFT) & STM32_RTC_DR_MONTH) | ((tm->tm_year << STM32_RTC_DR_YEAR_SHIFT) & STM32_RTC_DR_YEAR) | ((tm->tm_wday << STM32_RTC_DR_WDAY_SHIFT) & STM32_RTC_DR_WDAY); stm32_rtc_wpr_unlock(rtc); ret = stm32_rtc_enter_init_mode(rtc); if (ret) { dev_err(dev, "Can't enter in init mode. Set time aborted.\n" ); goto end; } writel_relaxed(tr, rtc->base + regs->tr); writel_relaxed(dr, rtc->base + regs->dr); stm32_rtc_exit_init_mode(rtc); ret = stm32_rtc_wait_sync(rtc); end: stm32_rtc_wpr_lock(rtc); return ret; }
stm32_rtc_read_alarm / stm32_rtc_set_alarm / stm32_rtc_valid_alrm:闹钟时间链路(解析/配置 ALRMAR 与“未来一个月窗口”约束)
stm32_rtc_read_alarm:从 ALRMAR 解码为 rtc_wkalrm(处理 mask/WDSEL/PM) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 static int stm32_rtc_read_alarm (struct device *dev, struct rtc_wkalrm *alrm) { struct stm32_rtc *rtc = dev_get_drvdata(dev); const struct stm32_rtc_registers *regs = &rtc->data->regs; const struct stm32_rtc_events *evts = &rtc->data->events; struct rtc_time *tm = &alrm->time; unsigned int alrmar, cr, status; alrmar = readl_relaxed(rtc->base + regs->alrmar); cr = readl_relaxed(rtc->base + regs->cr); status = readl_relaxed(rtc->base + regs->sr); if (alrmar & STM32_RTC_ALRMXR_DATE_MASK) { tm->tm_mday = -1 ; tm->tm_wday = -1 ; } else { if (alrmar & STM32_RTC_ALRMXR_WDSEL) { tm->tm_mday = -1 ; tm->tm_wday = (alrmar & STM32_RTC_ALRMXR_WDAY) >> STM32_RTC_ALRMXR_WDAY_SHIFT; tm->tm_wday %= 7 ; } else { tm->tm_wday = -1 ; tm->tm_mday = (alrmar & STM32_RTC_ALRMXR_DATE) >> STM32_RTC_ALRMXR_DATE_SHIFT; } } if (alrmar & STM32_RTC_ALRMXR_HOUR_MASK) { tm->tm_hour = -1 ; } else { tm->tm_hour = (alrmar & STM32_RTC_ALRMXR_HOUR) >> STM32_RTC_ALRMXR_HOUR_SHIFT; if (alrmar & STM32_RTC_ALRMXR_PM) tm->tm_hour += 12 ; } if (alrmar & STM32_RTC_ALRMXR_MIN_MASK) { tm->tm_min = -1 ; } else { tm->tm_min = (alrmar & STM32_RTC_ALRMXR_MIN) >> STM32_RTC_ALRMXR_MIN_SHIFT; } if (alrmar & STM32_RTC_ALRMXR_SEC_MASK) { tm->tm_sec = -1 ; } else { tm->tm_sec = (alrmar & STM32_RTC_ALRMXR_SEC) >> STM32_RTC_ALRMXR_SEC_SHIFT; } bcd2tm(tm); alrm->enabled = (cr & STM32_RTC_CR_ALRAE) ? 1 : 0 ; alrm->pending = (status & evts->alra) ? 1 : 0 ; return 0 ; }
stm32_rtc_valid_alrm:为什么限制“只能设到未来一个月窗口” 硬件 Alarm A 不支持“指定月份和年份”。如果允许用户设置一个绝对时间戳,软件无法把它无歧义地映射到硬件比较字段:
例如用户设置“明年 1 月 15 日 08:00”,硬件里没有“年/月”比较,单靠“日=15、时=08:00”会导致每个月 15 日都触发 ,语义被破坏。
因此驱动采用一个可验证的约束:
允许的闹钟时间满足:now < alarm <= now + (最多跨到下个月同日)
实现策略:
先读当前时间 now(通过 stm32_rtc_read_time())
计算“下个月”和“下个月所在年份”
计算从当前日期到“下个月同日(若下个月天数不够,则取下个月最后一天)”的最大前进天数 max_day_forward
把 now 转为时间戳,加上 max_day_forward * SEC_PER_DAY 得到上限,再比较目标 tm
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 static int stm32_rtc_valid_alrm (struct device *dev, struct rtc_time *tm) { static struct rtc_time now ; time64_t max_alarm_time64; int max_day_forward; int next_month; int next_year; stm32_rtc_read_time(dev, &now); next_month = now.tm_mon + 1 ; if (next_month == 12 ) { next_month = 0 ; next_year = now.tm_year + 1 ; } else { next_year = now.tm_year; } max_day_forward = rtc_month_days(now.tm_mon, now.tm_year) - now.tm_mday + min(rtc_month_days(next_month, next_year), now.tm_mday); max_alarm_time64 = rtc_tm_to_time64(&now) + max_day_forward * SEC_PER_DAY; return rtc_tm_to_time64(tm) <= max_alarm_time64 ? 0 : -EINVAL; }
stm32_rtc_set_alarm:写入 ALRMAR(仅写 d/h/m/s),并通过 ALRAWF 确认可写 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 static int stm32_rtc_set_alarm (struct device *dev, struct rtc_wkalrm *alrm) { struct stm32_rtc *rtc = dev_get_drvdata(dev); const struct stm32_rtc_registers *regs = &rtc->data->regs; struct rtc_time *tm = &alrm->time; unsigned int cr, isr, alrmar; int ret = 0 ; if (stm32_rtc_valid_alrm(dev, tm) < 0 ) { dev_err(dev, "Alarm can be set only on upcoming month.\n" ); return -EINVAL; } tm2bcd(tm); alrmar = 0 ; alrmar |= (tm->tm_mday << STM32_RTC_ALRMXR_DATE_SHIFT) & STM32_RTC_ALRMXR_DATE; alrmar &= ~STM32_RTC_ALRMXR_PM; alrmar |= (tm->tm_hour << STM32_RTC_ALRMXR_HOUR_SHIFT) & STM32_RTC_ALRMXR_HOUR; alrmar |= (tm->tm_min << STM32_RTC_ALRMXR_MIN_SHIFT) & STM32_RTC_ALRMXR_MIN; alrmar |= (tm->tm_sec << STM32_RTC_ALRMXR_SEC_SHIFT) & STM32_RTC_ALRMXR_SEC; stm32_rtc_wpr_unlock(rtc); cr = readl_relaxed(rtc->base + regs->cr); cr &= ~STM32_RTC_CR_ALRAE; writel_relaxed(cr, rtc->base + regs->cr); ret = readl_relaxed_poll_timeout_atomic(rtc->base + regs->isr, isr, (isr & STM32_RTC_ISR_ALRAWF), 10 , 100000 ); if (ret) { dev_err(dev, "Alarm update not allowed\n" ); goto end; } writel_relaxed(alrmar, rtc->base + regs->alrmar); stm32_rtc_alarm_irq_enable(dev, alrm->enabled); end: stm32_rtc_wpr_lock(rtc); return ret; }
stm32_rtc_pinmux_action_alarm / stm32_rtc_pinmux_action_lsco / stm32_rtc_pinmux_lsco_available / stm32_rtc_pinmux_set_mux:RTC 输出复用的 pinctrl/pinmux 链路
stm32_rtc_pinctrl_get_*:把“out1/out2/out2_rmp”作为 pin group 暴露 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 static int stm32_rtc_pinctrl_get_groups_count (struct pinctrl_dev *pctldev) { return ARRAY_SIZE(stm32_rtc_pinctrl_pins); } static const char *stm32_rtc_pinctrl_get_group_name (struct pinctrl_dev *pctldev, unsigned int selector) { return stm32_rtc_pinctrl_pins[selector].name; } static int stm32_rtc_pinctrl_get_group_pins (struct pinctrl_dev *pctldev, unsigned int selector, const unsigned int **pins, unsigned int *num_pins) { *pins = &stm32_rtc_pinctrl_pins[selector].number; *num_pins = 1 ; return 0 ; }
stm32_rtc_pinmux_set_mux:pinmux 框架回调,选择功能并执行 action 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 static int stm32_rtc_pinmux_set_mux (struct pinctrl_dev *pctldev, unsigned int selector, unsigned int group) { struct stm32_rtc_pinmux_func selected_func = stm32_rtc_pinmux_functions[selector]; struct pinctrl_pin_desc pin = stm32_rtc_pinctrl_pins[group]; if (selected_func.action) return selected_func.action(pctldev, pin.number); return -EINVAL; }
stm32_rtc_pinmux_action_alarm:把 Alarm A 事件路由到 out1/out2/out2_rmp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 static int stm32_rtc_pinmux_action_alarm (struct pinctrl_dev *pctldev, unsigned int pin) { struct stm32_rtc *rtc = pinctrl_dev_get_drvdata(pctldev); struct stm32_rtc_registers regs = rtc->data->regs; unsigned int cr = readl_relaxed(rtc->base + regs.cr); unsigned int cfgr = readl_relaxed(rtc->base + regs.cfgr); if (!rtc->data->has_alarm_out) return -EPERM; cr &= ~STM32_RTC_CR_OSEL; cr |= STM32_RTC_CR_OSEL_ALARM_A; cr &= ~STM32_RTC_CR_TAMPOE; cr &= ~STM32_RTC_CR_COE; cr &= ~STM32_RTC_CR_TAMPALRM_TYPE; switch (pin) { case OUT1: cr &= ~STM32_RTC_CR_OUT2EN; cfgr &= ~STM32_RTC_CFGR_OUT2_RMP; break ; case OUT2: cr |= STM32_RTC_CR_OUT2EN; cfgr &= ~STM32_RTC_CFGR_OUT2_RMP; break ; case OUT2_RMP: cr |= STM32_RTC_CR_OUT2EN; cfgr |= STM32_RTC_CFGR_OUT2_RMP; break ; default : return -EINVAL; } stm32_rtc_wpr_unlock(rtc); writel_relaxed(cr, rtc->base + regs.cr); writel_relaxed(cfgr, rtc->base + regs.cfgr); stm32_rtc_wpr_lock(rtc); return 0 ; }
stm32_rtc_pinmux_lsco_available:LSCO 输出资源冲突判定(避免与校准/防拆/其它输出重叠) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 static int stm32_rtc_pinmux_lsco_available (struct pinctrl_dev *pctldev, unsigned int pin) { struct stm32_rtc *rtc = pinctrl_dev_get_drvdata(pctldev); struct stm32_rtc_registers regs = rtc->data->regs; unsigned int cr = readl_relaxed(rtc->base + regs.cr); unsigned int cfgr = readl_relaxed(rtc->base + regs.cfgr); unsigned int calib = STM32_RTC_CR_COE; unsigned int tampalrm = STM32_RTC_CR_TAMPOE | STM32_RTC_CR_OSEL; switch (pin) { case OUT1: if ((!(cr & STM32_RTC_CR_OUT2EN) && ((cr & calib) || cr & tampalrm)) || ((cr & calib) && (cr & tampalrm))) return -EBUSY; break ; case OUT2_RMP: if ((cr & STM32_RTC_CR_OUT2EN) && (cfgr & STM32_RTC_CFGR_OUT2_RMP) && ((cr & calib) || (cr & tampalrm))) return -EBUSY; break ; default : return -EINVAL; } if (clk_get_rate(rtc->rtc_ck) != 32768 ) return -ERANGE; return 0 ; }
stm32_rtc_pinmux_action_lsco:选择 LSCO 输出,并注册 gate 时钟供系统引用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 static int stm32_rtc_pinmux_action_lsco (struct pinctrl_dev *pctldev, unsigned int pin) { struct stm32_rtc *rtc = pinctrl_dev_get_drvdata(pctldev); struct stm32_rtc_registers regs = rtc->data->regs; struct device *dev = rtc->rtc_dev->dev.parent; u8 lscoen; int ret; if (!rtc->data->has_lsco) return -EPERM; ret = stm32_rtc_pinmux_lsco_available(pctldev, pin); if (ret) return ret; lscoen = (pin == OUT1) ? STM32_RTC_CFGR_LSCOEN_OUT1 : STM32_RTC_CFGR_LSCOEN_OUT2_RMP; rtc->clk_lsco = clk_register_gate(dev, "rtc_lsco" , __clk_get_name(rtc->rtc_ck), CLK_IGNORE_UNUSED | CLK_IS_CRITICAL, rtc->base + regs.cfgr, lscoen, 0 , NULL ); if (IS_ERR(rtc->clk_lsco)) return PTR_ERR(rtc->clk_lsco); of_clk_add_provider(dev->of_node, of_clk_src_simple_get, rtc->clk_lsco); return 0 ; }
stm32_rtc_clean_outs / stm32_rtc_check_rif:RTC 输出状态清理与 RIF 访问权限检查
stm32_rtc_clean_outs:清理 RTC 输出选择位,避免与后续 pinmux 冲突 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 static void stm32_rtc_clean_outs (struct stm32_rtc *rtc) { struct stm32_rtc_registers regs = rtc->data->regs; unsigned int cr = readl_relaxed(rtc->base + regs.cr); cr &= ~STM32_RTC_CR_OSEL; cr &= ~STM32_RTC_CR_TAMPOE; cr &= ~STM32_RTC_CR_COE; cr &= ~STM32_RTC_CR_TAMPALRM_TYPE; cr &= ~STM32_RTC_CR_OUT2EN; stm32_rtc_wpr_unlock(rtc); writel_relaxed(cr, rtc->base + regs.cr); stm32_rtc_wpr_lock(rtc); if (regs.cfgr != UNDEF_REG) { unsigned int cfgr = readl_relaxed(rtc->base + regs.cfgr); cfgr &= ~STM32_RTC_CFGR_LSCOEN; cfgr &= ~STM32_RTC_CFGR_OUT2_RMP; writel_relaxed(cfgr, rtc->base + regs.cfgr); } }
stm32_rtc_check_rif:检查资源隔离配置(CID 与 secure world) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 static int stm32_rtc_check_rif (struct stm32_rtc *stm32_rtc, struct stm32_rtc_rif_resource res) { u32 rxcidcfgr = readl_relaxed(stm32_rtc->base + STM32_RTC_RXCIDCFGR(res.num)); u32 seccfgr; if ((rxcidcfgr & STM32_RTC_RXCIDCFGR_CFEN) && (FIELD_GET(STM32_RTC_RXCIDCFGR_CID, rxcidcfgr) != STM32_RTC_RXCIDCFGR_CID1)) return -EACCES; seccfgr = readl_relaxed(stm32_rtc->base + STM32_RTC_SECCFGR); if ((seccfgr & STM32_RTC_SECCFGR_SEC) | (seccfgr & res.bit)) return -EACCES; return 0 ; }
stm32_rtc_remove / stm32_rtc_suspend / stm32_rtc_resume:卸载与系统休眠/唤醒链路
stm32_rtc_remove:驱动卸载时释放 LSCO gate、关闭告警中断、关时钟、恢复 DBP 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 static void stm32_rtc_remove (struct platform_device *pdev) { struct stm32_rtc *rtc = platform_get_drvdata(pdev); const struct stm32_rtc_registers *regs = &rtc->data->regs; unsigned int cr; if (!IS_ERR_OR_NULL(rtc->clk_lsco)) clk_unregister_gate(rtc->clk_lsco); stm32_rtc_wpr_unlock(rtc); cr = readl_relaxed(rtc->base + regs->cr); cr &= ~STM32_RTC_CR_ALRAIE; writel_relaxed(cr, rtc->base + regs->cr); stm32_rtc_wpr_lock(rtc); clk_disable_unprepare(rtc->rtc_ck); if (rtc->data->has_pclk) clk_disable_unprepare(rtc->pclk); if (rtc->data->need_dbp) regmap_update_bits(rtc->dbp, rtc->dbp_reg, rtc->dbp_mask, 0 ); }
stm32_rtc_suspend:系统休眠前关闭 pclk(若存在) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 static int stm32_rtc_suspend (struct device *dev) { struct stm32_rtc *rtc = dev_get_drvdata(dev); if (rtc->data->has_pclk) clk_disable_unprepare(rtc->pclk); return 0 ; }
stm32_rtc_resume:系统唤醒后开启 pclk,并等待日历寄存器同步完成 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 static int stm32_rtc_resume (struct device *dev) { struct stm32_rtc *rtc = dev_get_drvdata(dev); int ret = 0 ; if (rtc->data->has_pclk) { ret = clk_prepare_enable(rtc->pclk); if (ret) return ret; } ret = stm32_rtc_wait_sync(rtc); if (ret < 0 ) { if (rtc->data->has_pclk) clk_disable_unprepare(rtc->pclk); return ret; } return ret; }